Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Gorjeta
Não conhece tipos de referência anuláveis? Leia primeiro tipos de referência anuláveis. Este tutorial pressupõe que você entenda a diferença entre tipos de referência não anuláveis e anuláveis e como o compilador rastreia o estado nulo.
Vindo de outro idioma? Se você já usou os tipos anuláveis do Kotlin, o strictNullChecks do TypeScript ou os opcionais do Swift, o modelo conceitual se aplica diretamente. O exercício aqui é sobre expressar a intenção de design, não aprender a sintaxe.
Neste tutorial, você criará uma pequena biblioteca que modela a execução de uma pesquisa. Os dados têm dois padrões distintos que permitem distinguir os tipos de referência anuláveis:
- Uma pergunta de pesquisa deve estar sempre presente. A lista de perguntas e o texto de cada pergunta nunca podem ser
null. - Uma resposta a uma pergunta pode estar ausente. Os entrevistados podem se recusar a responder algumas ou todas as perguntas, e o modelo deve tornar isso explícito.
Você declara essas regras com tipos de referência anuláveis e não anuláveis. Em seguida, o compilador avisa sempre que o comportamento do código não corresponde ao design.
Neste tutorial, você:
- Crie o aplicativo.
- Crie as perguntas da pesquisa.
- Crie uma pesquisa de perguntas.
- Teste o requisito não nulo.
- Criar tipos de resposta.
- Criar participantes.
- Gere uma resposta de pesquisa.
- Crie um conjunto de respostas de questionário.
- Examine os resultados da pesquisa.
Três classes modelam a pesquisa:
-
SurveyQuestion: uma pergunta. O texto e o tipo de pergunta são obrigatórios. -
SurveyRun: a coleção de perguntas mais a lista de entrevistados. -
SurveyResponse: respostas de um entrevistado, que podem estar ausentes.
Cada tipo usa tipos de referência não anuláveis para valores necessários e tipos de referência anuláveis para valores ausentes.
Prerequisites
- O SDK .NET mais recente
- editor Visual Studio Code
- O DevKit C#
Este tutorial pressupõe que você esteja familiarizado com C# e Visual Studio ou com a CLI do .NET.
Criar o aplicativo e habilitar tipos de referência anuláveis
Crie um novo aplicativo de console chamado NullableIntroduction:
dotnet new console -n NullableIntroduction
cd NullableIntroduction
Criar as perguntas da pesquisa
Adicione um novo arquivo nomeado SurveyQuestion.cs ao projeto e substitua seu conteúdo pelo código a seguir. O texto e o tipo de pergunta são não anuláveis, portanto, o construtor deve inicializar ambos:
namespace NullableIntroduction;
public enum QuestionType
{
YesNo,
Number,
Text
}
public class SurveyQuestion(QuestionType typeOfQuestion, string text)
{
public string QuestionText { get; } = text;
public QuestionType TypeOfQuestion { get; } = typeOfQuestion;
}
Os parâmetros do construtor são tipos de referência não anuláveis, portanto, o compilador avisa o chamador se um dos argumentos puder ser null.
Criar uma pesquisa de perguntas
Em seguida, adicione um novo arquivo nomeado SurveyRun.cs ao projeto e defina uma SurveyRun classe para manter a lista de perguntas:
namespace NullableIntroduction;
public class SurveyRun
{
private List<SurveyQuestion> surveyQuestions = [];
public void AddQuestion(QuestionType type, string question) =>
AddQuestion(new SurveyQuestion(type, question));
public void AddQuestion(SurveyQuestion surveyQuestion) =>
surveyQuestions.Add(surveyQuestion);
}
O surveyQuestions campo é um não anulável List<SurveyQuestion>. Ele usa uma expressão de coleção para inicializar uma lista vazia. Ambas as AddQuestion sobrecargas aceitam parâmetros não anuláveis, portanto, o compilador impõe que os chamadores não passem null.
In Program.cs, crie uma SurveyRun e adicione três perguntas:
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?");
Testar o requisito não nulo
Para ver como o compilador impõe parâmetros não anuláveis, tente adicionar a seguinte linha e recompilar:
surveyRun.AddQuestion(QuestionType.Text, default);
O compilador emite o aviso CS8625 porque default é avaliado como null para um tipo de referência, e AddQuestion espera um(a) string não anulável. Remova a linha antes de continuar.
Tipos de resposta de build
Os entrevistados podem recusar a fazer a pesquisa e, mesmo quando participam, podem ignorar perguntas individuais. Ambas as formas de "ausente" são resultados válidos e o sistema de tipos deve torná-las visíveis. Ambas as formas são expressas com null.
Adicione um novo arquivo nomeado SurveyResponse.cs ao projeto e defina uma SurveyResponse classe. Use um construtor primário (parâmetros declarados no próprio tipo, disponíveis em todo o corpo) para capturar o sempre necessário Id:
namespace NullableIntroduction;
public class SurveyResponse(int id)
{
public int Id { get; } = id;
}
Criar respondentes
Adicione um método de fábrica estático (um static método que cria e retorna uma nova instância do tipo, uma alternativa para chamar o construtor diretamente) que cria respondentes com uma ID aleatória:
private static readonly Random randomGenerator = new Random();
public static SurveyResponse GetRandomId() => new SurveyResponse(randomGenerator.Next());
Gerar uma resposta de pesquisa
Em seguida, adicione o método que solicita a pesquisa a um entrevistado. Armazene as respostas em um dicionário anulável para que o próprio tipo comunique que o respondente pode recusar:
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 surveyResponses campo é Dictionary<int, string>?. Se você desreferenciar o campo sem primeiro verificar null, o compilador emitirá um aviso. No interior AnswerSurvey, o compilador rastreia que surveyResponsesnão é nulo imediatamente após a new expressão, portanto, o corpo do loop não precisa de nenhuma verificação extra.
Criar um conjunto de respostas de pesquisa
Adicione um método em SurveyRun que monte uma lista de participantes até haver consentimento suficiente para participar:
private List<SurveyResponse>? respondents;
public void PerformSurvey(int numberOfRespondents)
{
int respondentsConsenting = 0;
respondents = [];
while (respondentsConsenting < numberOfRespondents)
{
var respondent = SurveyResponse.GetRandomId();
if (respondent.AnswerSurvey(surveyQuestions))
respondentsConsenting++;
respondents.Add(respondent);
}
}
O campo respondents é List<SurveyResponse>? — é null até que a pesquisa seja executada.
Chamada PerformSurvey de Main:
surveyRun.PerformSurvey(50);
Examinar os resultados da pesquisa
Para informar os resultados, exponha algumas funções auxiliares em SurveyResponse e SurveyRun. Em SurveyResponse, adicione membros com corpo de expressão (membros definidos com => e uma única expressão em vez de um bloco { ... }) que lidam com o dicionário anulável:
public bool AnsweredSurvey => surveyResponses != null;
public string Answer(int index) => surveyResponses?.GetValueOrDefault(index) ?? "No answer";
AnsweredSurvey verifica o campo em relação a null.
Answer usa o ?. operador condicional nulo (que é avaliado como null quando o lado esquerdo é null, em vez de gerar uma exceção) para desreferenciar com segurança, e o ?? operador de coalescência nula (que substitui o operando à direita quando o operando à esquerda é null) para fornecer uma alternativa não nula. O tipo de retorno do método é não anulável string, portanto, os chamadores não precisam de verificações nulas.
Em SurveyRun, adicione membros com corpo de expressão que exponham a lista de participantes e perguntas:
public IEnumerable<SurveyResponse> AllParticipants => (respondents ?? Enumerable.Empty<SurveyResponse>());
public ICollection<SurveyQuestion> Questions => surveyQuestions;
public SurveyQuestion GetQuestion(int index) => surveyQuestions[index];
AllParticipants retorna uma sequência não anulável mesmo que respondents possa ser null. O ?? operador substitui Enumerable.Empty<SurveyResponse>() quando o campo ainda não está preenchido. Se você remover a ?? cláusula, o compilador avisará que o método poderá retornar null apesar de um tipo de retorno não anulável.
Por fim, escreva o relatório na parte inferior de 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");
}
}
Observe que nenhuma verificação nula é necessária para participant, surveyRun.Questionsou surveyRun.GetQuestion(i). Os tipos declaram esses valores como não anuláveis, de modo que o compilador os trata como não nulos em todo o loop.
Execute o aplicativo:
dotnet run
A saída é diferente em cada execução porque os respondentes são gerados aleatoriamente, mas cada linha informa as respostas de um participante ou indica que ele se recusou a responder.
Conclusion
O exemplo concluído está na pasta csharp/NullableIntroduction do repositório dotnet/samples . Experimente alterando tipos entre anuláveis e não anuláveis. A remoção de um ? local em que o design permite valores ausentes produz avisos do compilador que apontam para cada lugar em que o valor ausente importa.