Tutorial: Expressar sua intenção de design mais claramente com tipos de referência que permitem valor nulo e tipos de referência que não permitem valor nuloTutorial: Express your design intent more clearly with nullable and non-nullable reference types

C#8,0 apresenta tipos de referência anuláveis, que complementam os tipos de referência da mesma maneira que os tipos de valores de valor nulo complementam tipos Value.C# 8.0 introduces nullable reference types, which complement reference types the same way nullable value types complement value types. Para declarar que uma variável é um tipo de referência que permite valor nulo, anexe um ? ao tipo.You declare a variable to be a nullable reference type by appending a ? to the type. Por exemplo, string? representa uma string que permite valor nulo.For example, string? represents a nullable string. Você pode usar esses novos tipos para expressar mais claramente sua intenção de design: algumas variáveis devem sempre ter um valor, outras podem ter um valor ausente.You can use these new types to more clearly express your design intent: some variables must always have a value, others may be missing a value.

Neste tutorial, você aprenderá a:In this tutorial, you'll learn how to:

  • Incorporar tipos de referência que permitem valores nulos e tipos de referência que não permitem valores nulos aos designsIncorporate nullable and non-nullable reference types into your designs
  • Habilitar verificações de tipo de referência que permitem valor nulo em todo o código.Enable nullable reference type checks throughout your code.
  • Gravar código em locais onde o compilador imponha essas decisões de design.Write code where the compiler enforces those design decisions.
  • Usar o recurso de referência que permite valor nulo em seus próprios designsUse the nullable reference feature in your own designs

Pré-requisitosPrerequisites

Você precisará configurar seu computador para executar o .NET Core, incluindo o C# compilador 8,0.You'll need to set up your machine to run .NET Core, including the C# 8.0 compiler. O C# compilador 8,0 está disponível com o Visual Studio 2019ou o .NET Core 3,0.The C# 8.0 compiler is available with Visual Studio 2019, or .NET Core 3.0.

Este tutorial pressupõe que você esteja familiarizado com o C# e .NET, incluindo o Visual Studio ou a CLI do .NET Core.This tutorial assumes you're familiar with C# and .NET, including either Visual Studio or the .NET Core CLI.

Incorporar tipos de referência que permitem valor nulo aos designsIncorporate nullable reference types into your designs

Neste tutorial, você criará uma biblioteca para modelar a executar uma pesquisa.In this tutorial, you'll build a library that models running a survey. O código usa tipos de referência que permitem valores nulos e tipos de referência que não permitem valores nulos para representar os conceitos do mundo real.The code uses both nullable reference types and non-nullable reference types to represent the real-world concepts. As perguntas da pesquisa nunca podem ser um valor nulo.The survey questions can never be null. Um entrevistado pode optar por não responder a uma pergunta.A respondent might prefer not to answer a question. As respostas podem ser null nesse caso.The responses might be null in this case.

O código que você gravará para este exemplo expressa essa intenção e o compilador a aplica.The code you'll write for this sample expresses that intent, and the compiler enforces that intent.

Criar o aplicativo e habilitar os tipos de referência que permitem valor nuloCreate the application and enable nullable reference types

Crie um novo aplicativo de console no Visual Studio ou na linha de comando usando dotnet new console.Create a new console application either in Visual Studio or from the command line using dotnet new console. Dê o nome NullableIntroduction ao aplicativo.Name the application NullableIntroduction. Depois de criar o aplicativo, você precisará especificar que todo o projeto será compilado em um contexto de anotação anulávelhabilitado.Once you've created the application, you'll need to specify that the entire project compiles in an enabled nullable annotation context. Abra o arquivo . csproj e adicione um elemento Nullable ao elemento PropertyGroup.Open the .csproj file and add a Nullable element to the PropertyGroup element. Defina seu valor como enable.Set its value to enable. Você deve optar pelo recurso tipos de referência anulável , mesmo em C# projetos de 8,0.You must opt into the nullable reference types feature, even in C# 8.0 projects. Isso porque, quando o recurso é ativado, as declarações de variáveis de referência existentes tornam-se tipos de referência que não permitem valor nulo.That's because once the feature is turned on, existing reference variable declarations become non-nullable reference types. Embora essa decisão ajude a encontrar problemas em que o código existente pode não ter verificações nulas adequadas, ele pode não refletir com precisão sua intenção de design original:While that decision will help find issues where existing code may not have proper null-checks, it may not accurately reflect your original design intent:

<Nullable>enable</Nullable>

Criar os tipos para o aplicativoDesign the types for the application

Este aplicativo de pesquisa requer a criação de várias classes:This survey application requires creating a number of classes:

  • Uma classe que modela a lista de perguntas.A class that models the list of questions.
  • Uma classe que modela uma lista de pessoas contatadas para a pesquisa.A class that models a list of people contacted for the survey.
  • Uma classe que modela as respostas de uma pessoa que participou da pesquisa.A class that models the answers from a person that took the survey.

Esses tipos usarão os tipos de referência que permitem valor nulo e tipos de referência que não permitem valor nulo para expressar quais membros são obrigatórios e quais são opcionais.These types will make use of both nullable and non-nullable reference types to express which members are required and which members are optional. Os tipos de referência que permitem valor nulo informam claramente essa intenção de design:Nullable reference types communicate that design intent clearly:

  • As perguntas que fazem parte da pesquisa nunca podem ser nulas: Não faz sentido fazer uma pergunta vazia.The questions that are part of the survey can never be null: It makes no sense to ask an empty question.
  • Os entrevistados nunca poderão ser nulos.The respondents can never be null. Convém controlar as pessoas contatadas, mesmo os entrevistados que se recusaram a participar.You'll want to track people you contacted, even respondents that declined to participate.
  • Qualquer resposta a uma pergunta pode ser um valor nulo.Any response to a question may be null. Os entrevistados podem se recusar a responder a algumas ou a todas as perguntas.Respondents can decline to answer some or all questions.

Se você já tiver programado C#o no, talvez esteja acostumado a se acostumar com tipos de referência que permitem valores null que você pode ter perdido outras oportunidades para declarar instâncias não anuláveis:If you've programmed in C#, you may be so accustomed to reference types that allow null values that you may have missed other opportunities to declare non-nullable instances:

  • O conjunto de perguntas não deve permitir um valor nulo.The collection of questions should be non-nullable.
  • O conjunto de entrevistados não deve permitir um valor nulo.The collection of respondents should be non-nullable.

Ao escrever o código, você verá que um tipo de referência não anulável como o padrão para referências evita erros comuns que poderiam levar a NullReferenceExceptions.As you write the code, you'll see that a non-nullable reference type as the default for references avoids common mistakes that could lead to NullReferenceExceptions. Uma lição deste tutorial é que você tomou decisões sobre quais variáveis poderiam ou não ser null.One lesson from this tutorial is that you made decisions about which variables could or could not be null. O idioma não forneceu sintaxe para expressar essas decisões.The language didn't provide syntax to express those decisions. Agora ele já fornece.Now it does.

O aplicativo que você criará faz as seguintes etapas:The app you'll build does the following steps:

  1. Cria uma pesquisa e adiciona perguntas a ela.Creates a survey and adds questions to it.
  2. Cria um conjunto pseudo aleatório de entrevistados para a pesquisa.Creates a pseudo-random set of respondents for the survey.
  3. Os participantes do contato até que o tamanho da pesquisa concluída atinja o número da meta.Contacts respondents until the completed survey size reaches the goal number.
  4. Grava estatísticas importantes nas respostas da pesquisa.Writes out important statistics on the survey responses.

Criar a pesquisa com tipos que permitem valor nulo e tipos que não permitem valor nuloBuild the survey with nullable and non-nullable types

O primeiro código gravado criará a pesquisa.The first code you'll write creates the survey. Você escreverá classes para modelar uma pergunta da pesquisa e uma execução da pesquisa.You'll write classes to model a survey question and a survey run. A pesquisa tem três tipos de perguntas, diferenciadas pelo formato da resposta: Respostas Sim/Não, respostas de números e respostas de texto.Your survey has three types of questions, distinguished by the format of the answer: Yes/No answers, number answers, and text answers. Criar uma classe public SurveyQuestion:Create a public SurveyQuestion class:

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 o código em um contexto de anotação anulável habilitado.The compiler interprets every reference type variable declaration as a non-nullable reference type for code in an enabled nullable annotation context. Para ver seu primeiro aviso, adicione propriedades ao texto da pergunta e tipo de pergunta, conforme mostrado no código a seguir:You can see your first warning by adding properties for the question text and the type of question, as shown in the following code:

namespace NullableIntroduction
{
    public enum QuestionType
    {
        YesNo,
        Number,
        Text
    }

    public class SurveyQuestion
    {
        public string QuestionText { get; }
        public QuestionType TypeOfQuestion { get; }
    }
}

Como você não inicializou QuestionText, o compilador emitirá um aviso informando que uma propriedade que não permite valor nulo não foi inicializada.Because you haven't initialized QuestionText, the compiler issues a warning that a non-nullable property hasn't been initialized. Seu design exige que o texto da pergunta não seja um valor nulo, portanto, você inclui um construtor para inicializá-lo e o valor QuestionType também.Your design requires the question text to be non-null, so you add a constructor to initialize it and the QuestionType value as well. A definição da classe concluída se parece com o código a seguir:The finished class definition looks like the following code:

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);
    }
}

A adição do construtor removerá o aviso.Adding the constructor removes the warning. O argumento do construtor também é um tipo de referência que não permite valor nulo, portanto, o compilador não emite avisos.The constructor argument is also a non-nullable reference type, so the compiler doesn't issue any warnings.

Em seguida, crie uma classe public chamada SurveyRun.Next, create a public class named SurveyRun. Esta classe contém uma lista de métodos e objetos SurveyQuestion para adicionar perguntas à pesquisa, conforme mostrado no código a seguir:This class contains a list of SurveyQuestion objects and methods to add questions to the survey, as shown in the following code:

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 foi feito anteriormente, você deve inicializar o objeto de lista com um valor não nulo ou o compilador emitirá um aviso.As before, you must initialize the list object to a non-null value or the compiler issues a warning. Não há verificações de nulo na segunda sobrecarga de AddQuestion porque elas não são necessárias: Você declarou essa variável como não permitindo valor nulo.There are no null checks in the second overload of AddQuestion because they aren't needed: You've declared that variable to be non-nullable. Seu valor não pode ser null.Its value can't be null.

Alterne para Program.cs no seu editor e substitua o conteúdo de Main pelas seguintes linhas de código:Switch to Program.cs in your editor and replace the contents of Main with the following lines of code:

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 o projeto inteiro está em um contexto de anotação anulável habilitado, você receberá avisos quando passar null para qualquer método que espera um tipo de referência não anulável.Because the entire project is in an enabled nullable annotation context, you'll get warnings when you pass null to any method expecting a non-nullable reference type. Experimente adicionar a seguinte linha a Main:Try it by adding the following line to Main:

surveyRun.AddQuestion(QuestionType.Text, default);

Criar entrevistados e obter respostas para a pesquisaCreate respondents and get answers to the survey

Em seguida, grave o código que gerará respostas para a pesquisa.Next, write the code that generates answers to the survey. Esse processo envolve várias tarefas pequenas:This process involves several small tasks:

  1. Criar um método para gerar objetos dos entrevistados.Build a method that generates respondent objects. Eles representam pessoas solicitadas a preencher a pesquisa.These represent people asked to fill out the survey.
  2. Criar lógica para simular a realização de perguntas para um pesquisado e coletar respostas ou perceber que um pesquisado não respondeu.Build logic to simulate asking the questions to a respondent and collecting answers or noting that a respondent didn't answer.
  3. Repetir até que entrevistados suficientes tenham respondido à pesquisa.Repeat until enough respondents have answered the survey.

Será necessária uma classe para representar uma resposta da pesquisa. Adicione-a agora.You'll need a class to represent a survey response, so add that now. Habilitar o suporte para tipos que permitem valor nulo.Enable nullable support. Adicione uma propriedade Id e um construtor para inicializá-la, conforme mostrado no código a seguir:Add an Id property and a constructor that initializes it, as shown in the following code:

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 ao gerar uma ID aleatória:Next, add a static method to create new participants by generating a random ID:

private static readonly Random randomGenerator = new Random();
public static SurveyResponse GetRandomId() => new SurveyResponse(randomGenerator.Next());

A principal responsabilidade dessa classe é gerar as respostas de um participante para as perguntas da pesquisa.The main responsibility of this class is to generate the responses for a participant to the questions in the survey. Essa responsabilidade conta com algumas etapas:This responsibility has a few steps:

  1. Peça para participar da pesquisa.Ask for participation in the survey. Se a pessoa não consentir, retorne uma resposta de ausente (ou de valor nulo).If the person doesn't consent, return a missing (or null) response.
  2. Faça as perguntas e registre a resposta.Ask each question and record the answer. As respostas também pode ser ausentes (ou de valor nulo).Each answer may also be missing (or null).

Adicione o seguinte código à classe SurveyResponse:Add the following code to your SurveyResponse class:

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 da pesquisa é um Dictionary<int, string>?, indicando que ele pode ser um valor nulo.The storage for the survey answers is a Dictionary<int, string>?, indicating that it may be null. Você está usando o novo recurso de idioma para declarar sua intenção de design, tanto para o compilador quanto para qualquer pessoa que leia seu código posteriormente.You're using the new language feature to declare your design intent, both to the compiler and to anyone reading your code later. Se você já desreferenciar surveyResponses sem verificar o valor de null primeiro, obterá um aviso do compilador.If you ever dereference surveyResponses without checking for the null value first, you'll get a compiler warning. Você não receberá um aviso no método AnswerSurvey porque o compilador pode determinar que a variável surveyResponses foi definida como um valor não nulo acima.You don't get a warning in the AnswerSurvey method because the compiler can determine the surveyResponses variable was set to a non-null value above.

O uso de null para respostas ausentes destaca um ponto importante para trabalhar com tipos de referência anuláveis: seu objetivo não é remover todos os valores null de seu programa.Using null for missing answers highlights a key point for working with nullable reference types: your goal isn't to remove all null values from your program. Em vez disso, sua meta é garantir que o código escrito expresse a intenção do seu design.Rather, your goal is to ensure that the code you write expresses the intent of your design. Os valores ausentes representam um conceito que precisa ser expresso em seu código.Missing values are a necessary concept to express in your code. O valor null é uma forma clara de expressar esses valores ausentes.The null value is a clear way to express those missing values. Tentar remover todos os valores null leva somente à definição de alguma outra maneira de expressar esses valores ausentes sem null.Trying to remove all null values only leads to defining some other way to express those missing values without null.

Em seguida, é necessário gravar o método PerformSurvey na classe SurveyRun.Next, you need to write the PerformSurvey method in the SurveyRun class. Adicione o seguinte código à classe SurveyRun:Add the following code in the SurveyRun class:

private List<SurveyResponse>? respondents;
public void PerformSurvey(int numberOfRespondents)
{
    int repondentsConsenting = 0;
    respondents = new List<SurveyResponse>();
    while (repondentsConsenting < numberOfRespondents)
    {
        var respondent = SurveyResponse.GetRandomId();
        if (respondent.AnswerSurvey(surveyQuestions))
            repondentsConsenting++;
        respondents.Add(respondent);
    }
}

Aqui, novamente, sua opção por uma List<SurveyResponse>? que permite valor nulo indica que a resposta pode ser um valor nulo.Here again, your choice of a nullable List<SurveyResponse>? indicates the response may be null. Isso indica que a pesquisa ainda não foi entregue a nenhum pesquisado.That indicates the survey hasn't been given to any respondents yet. Observe que os entrevistados são adicionados até que um suficiente de pessoas tiver consentido.Notice that respondents are added until enough have consented.

A última etapa para executar a pesquisa é adicionar uma chamada para executar a pesquisa no final do método Main:The last step to run the survey is to add a call to perform the survey at the end of the Main method:

surveyRun.PerformSurvey(50);

Examinar as respostas da pesquisaExamine survey responses

A última etapa é exibir os resultados da pesquisa.The last step is to display survey results. Você adicionará código a várias classes gravadas.You'll add code to many of the classes you've written. Este código demonstra o valor da distinção dos tipos de referência que permitem valor nulo e tipos de referência que não permitem valor nulo.This code demonstrates the value of distinguishing nullable and non-nullable reference types. Comece adicionando os dois membros com corpo de expressão à classe SurveyResponse:Start by adding the following two expression-bodied members to the SurveyResponse class:

public bool AnsweredSurvey => surveyResponses != null;
public string Answer(int index) => surveyResponses?.GetValueOrDefault(index) ?? "No answer";

Como surveyResponses é um tipo de referência anulável, são necessárias verificações nulas antes de fazer referência a ela.Because surveyResponses is a nullable reference type, null checks are necessary before de-referencing it. O método Answer retorna uma cadeia de caracteres não anulável, portanto, precisamos abordar o caso de uma resposta ausente usando o operador de União nula.The Answer method returns a non-nullable string, so we have to cover the case of a missing answer by using the null-coalescing operator.

Em seguida, adicione esses três membros com corpo de expressão à classe SurveyRun:Next, add these three expression-bodied members to the SurveyRun class:

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 um valor nulo, mas o valor de retorno não pode ser nulo.The AllParticipants member must take into account that the respondents variable might be null, but the return value can't be null. Se você alterar essa expressão removendo ?? e a sequência vazia que se segue, o compilador avisará que o método poderá retornar null e sua assinatura de retorno retornará um tipo que não permite valor nulo.If you change that expression by removing the ?? and the empty sequence that follows, the compiler warns you the method might return null and its return signature returns a non-nullable type.

Por fim, adicione o seguinte loop à parte inferior do método Main:Finally, add the following loop at the bottom of the Main method:

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)} : {answer}");
        }
    }
    else
        Console.WriteLine("\tNo responses");
}

Você não precisa de verificações de null neste código porque criou as interfaces subjacentes para que todas elas retornem tipos de referência que não permitem valor nulo.You don't need any null checks in this code because you've designed the underlying interfaces so that they all return non-nullable reference types.

Obter o códigoGet the code

Obtenha o código do tutorial concluído em nosso repositório de amostras na pasta csharp/NullableIntroduction.You can get the code for the finished tutorial from our samples repository in the csharp/NullableIntroduction folder.

Experimente alterar as declarações de tipo entre os tipos de referência que permitem valor nulo e tipos de referência que não permitem valor nulo.Experiment by changing the type declarations between nullable and non-nullable reference types. Veja como isso gera avisos diferentes para garantir que um null não será acidentalmente cancelado.See how that generates different warnings to ensure you don't accidentally dereference a null.

Próximas etapasNext steps

Saiba mais migrando um aplicativo existente para usar tipos de referência anuláveis:Learn more by migrating an existing application to use nullable reference types: