Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Os tipos de referência anuláveis complementam os tipos de referência da mesma forma que os tipos de valor anuláveis complementam os tipos de valor. Você declara uma variável como um tipo de referência anulável anexando um ? ao tipo. Por exemplo, string? representa um stringanulável . Pode usar estes novos tipos para expressar de forma mais clara a sua intenção de design: algumas variáveis têm sempre de ter um valor enquanto outras podem estar em falta de um valor.
Neste tutorial, aprenderás como:
- Incorpore tipos de referência anuláveis e não anuláveis em seus designs
- Ative as verificações de tipos de referência nulos em todo o código.
- Escreva código onde o compilador impõe essas decisões de design.
- Use o recurso de referência anulável em seus próprios designs
Pré-requisitos
- O mais recente SDK do .NET
- Editor de código do Visual Studio
- O Kit de Desenvolvimento C#
Este tutorial pressupõe que você esteja familiarizado com C# e .NET, incluindo o Visual Studio ou a CLI do .NET.
Incorpore tipos de referência anuláveis em seus designs
Neste tutorial, você criará uma biblioteca que modela a execução de uma pesquisa. O código usa tipos de referência anuláveis e tipos de referência não anuláveis para representar os conceitos do mundo real. As perguntas da pesquisa nunca podem ser nulas. Um entrevistado pode preferir não responder a uma pergunta. Neste caso, as respostas podem ser null.
O código que escreves para este exemplo expressa essa intenção, e o compilador faz cumprir essa intenção.
Criar o aplicativo e habilitar tipos de referência anuláveis
Crie um novo aplicativo de console no Visual Studio ou na linha de comando usando dotnet new console. Nomeie o aplicativo NullableIntroduction. Depois de criar a aplicação, precisa de especificar que todo o projeto compila num contexto de anotação anulável ativado. Abra o arquivo de .csproj do e adicione um elemento Nullable ao elemento PropertyGroup. Defina seu valor como enable. Deve optar pela funcionalidade de tipos de referência anuláveis em projetos criados antes de C# 11 / .NET 7. Uma vez ativada a funcionalidade, as declarações existentes de variáveis de referência tornam-se tipos de referência não anuláveis. Embora essa decisão ajude a identificar problemas onde o código existente pode não ter verificações nulas adequadas, pode não refletir com precisão a sua intenção original de design:
<Nullable>enable</Nullable>
Projetar os tipos para o aplicativo
Esta aplicação de inquérito exige a criação destas classes:
- Uma classe que modela a lista de perguntas.
- Uma classe que modela uma lista de pessoas contatadas para a pesquisa.
- Uma classe que modela as respostas de uma pessoa que respondeu ao questionário.
Estes tipos utilizam tanto tipos de referência anuláveis como não anuláveis para expressar quais os membros necessários e quais os opcionais. Os tipos de referência anuláveis comunicam claramente essa intenção de design:
- As perguntas que fazem parte do inquérito nunca podem ser nulas: não faz sentido fazer uma pergunta vazia.
- Os entrevistados nunca podem ser nulos. Quer acompanhar as pessoas que contactou, até os respondentes que recusaram participar.
- Qualquer resposta a uma pergunta pode ser nula. Os entrevistados podem recusar-se a responder a algumas ou a todas as perguntas.
Pode estar tão habituado a tipos de referência que permitem valores null que pode não notar outras oportunidades de declarar instâncias não anuláveis:
- A recolha de perguntas não deve ser anulável.
- A recolha de inquiridos não deve ser anulável.
À medida que escreves o código, vês que definir um tipo de referência não anulável como o padrão para referências evita erros comuns que podem levar a NullReferenceExceptions. Uma lição deste tutorial é que tomaste decisões sobre quais variáveis podiam ou não ser null. A linguagem não fornecia sintaxe para expressar essas decisões. Agora sim.
A aplicação que desenvolves segue os seguintes passos:
- Cria uma pesquisa e adiciona perguntas a ela.
- Cria um conjunto pseudoaleatório de respondentes para a pesquisa.
- Entra em contato com os respondentes até que o tamanho da pesquisa concluída atinja o número da meta.
- Escreve estatísticas importantes sobre as respostas ao inquérito.
Construa a pesquisa com tipos de referência anuláveis e não anuláveis
O primeiro código que escreves cria o inquérito. Escreves classes para modelar uma pergunta de inquérito e uma execução de inquérito. O questionário tem três tipos de perguntas, distinguidas pelo formato da resposta: respostas Sim/Não, respostas numéricas e respostas em texto. Crie uma classe public SurveyQuestion:
namespace NullableIntroduction
{
public class SurveyQuestion
{
}
}
O compilador interpreta cada declaração de variável de tipo de referência como um tipo de referência não anulável para código em um contexto de anotação anulável habilitado. Você pode ver seu primeiro aviso adicionando propriedades para o texto da pergunta e o tipo de pergunta, conforme mostrado no código a seguir:
namespace NullableIntroduction
{
public enum QuestionType
{
YesNo,
Number,
Text
}
public class SurveyQuestion
{
public string QuestionText { get; }
public QuestionType TypeOfQuestion { get; }
}
}
Como não inicializou QuestionText, o compilador emite um aviso de que uma propriedade não anulável não foi inicializada. Seu design requer que o texto da pergunta não seja nulo, então você adiciona um construtor para inicializá-lo e o valor QuestionType também. A definição de classe concluída se parece com o seguinte código:
namespace NullableIntroduction;
public enum QuestionType
{
YesNo,
Number,
Text
}
public class SurveyQuestion
{
public string QuestionText { get; }
public QuestionType TypeOfQuestion { get; }
public SurveyQuestion(QuestionType typeOfQuestion, string text) =>
(TypeOfQuestion, QuestionText) = (typeOfQuestion, text);
}
Ao adicionar o construtor, o aviso é removido. O argumento do construtor também é um tipo de referência não anulável, portanto, o compilador não emite nenhum aviso.
Em seguida, crie uma classe public chamada SurveyRun. Essa classe contém uma lista de SurveyQuestion objetos e métodos para adicionar perguntas à pesquisa, conforme mostrado no código a seguir:
using System.Collections.Generic;
namespace NullableIntroduction
{
public class SurveyRun
{
private List<SurveyQuestion> surveyQuestions = new List<SurveyQuestion>();
public void AddQuestion(QuestionType type, string question) =>
AddQuestion(new SurveyQuestion(type, question));
public void AddQuestion(SurveyQuestion surveyQuestion) => surveyQuestions.Add(surveyQuestion);
}
}
Como antes, você deve inicializar o objeto list para um valor não nulo ou o compilador emite um aviso. Não há verificações nulas na segunda sobrecarga de AddQuestion porque o compilador ajuda a impor o contrato não anulável: declaraste essa variável como não anulável. Enquanto o compilador avisa sobre possíveis atribuições nulas, os valores nulos de tempo de execução ainda são possíveis. Para APIs públicas, considere adicionar validação de argumento mesmo para tipos de referência não anuláveis, uma vez que o código do cliente pode não ter tipos de referência anuláveis habilitados ou pode passar intencionalmente nulo.
Mude para Program.cs no editor e substitua o conteúdo do Main pelas seguintes linhas de código:
var surveyRun = new SurveyRun();
surveyRun.AddQuestion(QuestionType.YesNo, "Has your code ever thrown a NullReferenceException?");
surveyRun.AddQuestion(new SurveyQuestion(QuestionType.Number, "How many times (to the nearest 100) has that happened?"));
surveyRun.AddQuestion(QuestionType.Text, "What is your favorite color?");
Como todo o projeto está num contexto de anotação de nulidade ativado, receberá avisos quando passar null para qualquer método que espera um tipo de referência não anulável. Experimente adicionando a seguinte linha ao Main:
surveyRun.AddQuestion(QuestionType.Text, default);
Crie respondentes e obtenha respostas para a pesquisa
Em seguida, escreva o código que gera as respostas para a pesquisa. Este processo envolve várias pequenas tarefas:
- Crie um método que gere objetos de respondente. Estes objetos representam pessoas a quem pediram para preencher o inquérito.
- Crie lógica para simular fazer as perguntas a um entrevistado e coletar respostas ou notar que um entrevistado não respondeu.
- Repita até que um número suficiente de respondentes respondam ao inquérito.
Precisas de uma classe para representar uma resposta a um inquérito, por isso adiciona-a agora. Ativar o suporte anulável. Adicione uma propriedade Id e um construtor que a inicializa, conforme mostrado no código a seguir:
namespace NullableIntroduction
{
public class SurveyResponse
{
public int Id { get; }
public SurveyResponse(int id) => Id = id;
}
}
Em seguida, adicione um método static para criar novos participantes gerando uma ID aleatória:
private static readonly Random randomGenerator = new Random();
public static SurveyResponse GetRandomId() => new SurveyResponse(randomGenerator.Next());
A principal responsabilidade desta aula é gerar as respostas para um participante às perguntas da pesquisa. Esta responsabilidade tem alguns passos:
- Peça a participação no inquérito. Se a pessoa não consentir, devolva uma resposta ausente (ou nula).
- Faça cada pergunta e registre a resposta. Cada resposta pode também estar em falta (ou ser nula).
Adicione o seguinte código à sua classe SurveyResponse:
private Dictionary<int, string>? surveyResponses;
public bool AnswerSurvey(IEnumerable<SurveyQuestion> questions)
{
if (ConsentToSurvey())
{
surveyResponses = new Dictionary<int, string>();
int index = 0;
foreach (var question in questions)
{
var answer = GenerateAnswer(question);
if (answer != null)
{
surveyResponses.Add(index, answer);
}
index++;
}
}
return surveyResponses != null;
}
private bool ConsentToSurvey() => randomGenerator.Next(0, 2) == 1;
private string? GenerateAnswer(SurveyQuestion question)
{
switch (question.TypeOfQuestion)
{
case QuestionType.YesNo:
int n = randomGenerator.Next(-1, 2);
return (n == -1) ? default : (n == 0) ? "No" : "Yes";
case QuestionType.Number:
n = randomGenerator.Next(-30, 101);
return (n < 0) ? default : n.ToString();
case QuestionType.Text:
default:
switch (randomGenerator.Next(0, 5))
{
case 0:
return default;
case 1:
return "Red";
case 2:
return "Green";
case 3:
return "Blue";
}
return "Red. No, Green. Wait.. Blue... AAARGGGGGHHH!";
}
}
O armazenamento das respostas do inquérito é um Dictionary<int, string>?, indicando que pode ser nulo. Você está usando o novo recurso de linguagem para declarar sua intenção de design, tanto para o compilador quanto para qualquer pessoa que leia seu código mais tarde. Se alguma vez desreferenciares surveyResponses sem verificar primeiro o null valor, recebes um aviso do compilador. Não recebes aviso no AnswerSurvey método porque o compilador pode determinar que a surveyResponses variável foi definida para um valor não nulo no código anterior.
Usar null para respostas ausentes destaca um ponto-chave para trabalhar com tipos de referência anuláveis: seu objetivo não é remover todos os valores null do seu programa. Em vez disso, seu objetivo é garantir que o código que você escreve expresse a intenção do seu design. Valores ausentes são um conceito necessário para expressar em seu código. O valor null é uma maneira clara de expressar esses valores ausentes. Tentar remover todos os valores null leva a definir uma outra forma de expressar os valores em falta sem null.
Em seguida, você precisa escrever o método PerformSurvey na classe SurveyRun. Adicione o seguinte código na classe SurveyRun:
private List<SurveyResponse>? respondents;
public void PerformSurvey(int numberOfRespondents)
{
int respondentsConsenting = 0;
respondents = new List<SurveyResponse>();
while (respondentsConsenting < numberOfRespondents)
{
var respondent = SurveyResponse.GetRandomId();
if (respondent.AnswerSurvey(surveyQuestions))
respondentsConsenting++;
respondents.Add(respondent);
}
}
Mais uma vez, a sua escolha de um anulável List<SurveyResponse>? indica que a resposta pode ser nula. Isso indica que a pesquisa ainda não foi dada a nenhum entrevistado. Note que os respondentes são adicionados até haver consentimento suficiente.
A última etapa para executar a pesquisa é adicionar uma chamada para executar a pesquisa no final do método Main:
surveyRun.PerformSurvey(50);
Examinar as respostas da pesquisa
A última etapa é exibir os resultados da pesquisa. Adicionas código a muitas das classes que escreveste. Este código demonstra o valor de distinguir tipos de referência anuláveis e não anuláveis. Comece adicionando os seguintes dois membros com corpo de expressão à classe SurveyResponse:
public bool AnsweredSurvey => surveyResponses != null;
public string Answer(int index) => surveyResponses?.GetValueOrDefault(index) ?? "No answer";
Como surveyResponses é um tipo de referência anulável, verificações nulas são necessárias antes de desreferenciar isso. O método Answer retorna uma cadeia de caracteres não anulável, portanto, temos que cobrir o caso de uma resposta ausente usando o operador null-coalescing.
Em seguida, adicione estes três membros com corpo de expressão à classe SurveyRun:
public IEnumerable<SurveyResponse> AllParticipants => (respondents ?? Enumerable.Empty<SurveyResponse>());
public ICollection<SurveyQuestion> Questions => surveyQuestions;
public SurveyQuestion GetQuestion(int index) => surveyQuestions[index];
O membro AllParticipants deve levar em conta que a variável respondents pode ser nula, mas o valor de retorno não pode ser nulo. Se você alterar essa expressão removendo o ?? e a sequência vazia a seguir, o compilador avisa que o método pode retornar null e sua assinatura de retorno retorna um tipo não anulável.
Finalmente, adicione o seguinte loop na parte inferior do método Main:
foreach (var participant in surveyRun.AllParticipants)
{
Console.WriteLine($"Participant: {participant.Id}:");
if (participant.AnsweredSurvey)
{
for (int i = 0; i < surveyRun.Questions.Count; i++)
{
var answer = participant.Answer(i);
Console.WriteLine($"\t{surveyRun.GetQuestion(i).QuestionText} : {answer}");
}
}
else
{
Console.WriteLine("\tNo responses");
}
}
Não precisas de verificações null neste código porque desenhaste as interfaces subjacentes para que todas retornem tipos de referência não anuláveis. A análise estática do compilador ajuda a garantir que esses contratos de design sejam seguidos.
Obter o código
Você pode obter o código para o tutorial concluído do nosso repositório de exemplos na pasta csharp/NullableIntroduction.
Experimente alterando as declarações de tipo entre tipos de referência anuláveis e não anuláveis. Veja como isso gera diferentes avisos para garantir que não desreferencies acidentalmente um null.
Próximos passos
Saiba como usar o tipo de referência anulável ao usar o Entity Framework: