Tutoriel : Exprimer plus clairement une intention de conception avec les types référence Nullable et non NullableTutorial: Express your design intent more clearly with nullable and non-nullable reference types

C#8,0 introduit les types de référence Nullable, qui complètent les types référence de la même façon que les types valeur Nullable complètent les types valeur.C# 8.0 introduces nullable reference types, which complement reference types the same way nullable value types complement value types. Pour déclarer une variable comme étant un type référence Nullable, on ajoute ? au type.You declare a variable to be a nullable reference type by appending a ? to the type. Par exemple, string? représente une string Nullable.For example, string? represents a nullable string. Vous pouvez utiliser ces nouveaux types pour exprimer plus clairement votre intention de conception : certaines variables doivent toujours avoir une valeur, d’autres peuvent ne pas en avoir.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.

Dans ce tutoriel, vous allez apprendre à :In this tutorial, you'll learn how to:

  • incorporer des types référence Nullable et non Nullable dans vos conceptions ;Incorporate nullable and non-nullable reference types into your designs
  • activer les contrôles de type référence Nullable dans l’ensemble de votre code ;Enable nullable reference type checks throughout your code.
  • écrire du code permettant au compilateur d’appliquer ces décisions de conception ;Write code where the compiler enforces those design decisions.
  • utiliser la fonctionnalité de référence Nullable dans vos propres conceptions.Use the nullable reference feature in your own designs

Configuration requisePrerequisites

Vous devez configurer votre ordinateur pour exécuter .NET Core, y compris le C# compilateur 8,0.You'll need to set up your machine to run .NET Core, including the C# 8.0 compiler. Le C# compilateur 8,0 est disponible avec Visual Studio 2019ou .net Core 3,0.The C# 8.0 compiler is available with Visual Studio 2019, or .NET Core 3.0.

Ce tutoriel suppose de connaître C# et .NET, y compris Visual Studio ou l’interface CLI .NET Core.This tutorial assumes you're familiar with C# and .NET, including either Visual Studio or the .NET Core CLI.

Incorporer des types référence Nullable dans des conceptionsIncorporate nullable reference types into your designs

Dans ce tutoriel, vous allez générer une bibliothèque qui modélise la réalisation d’une enquête.In this tutorial, you'll build a library that models running a survey. Le code utilise des types référence Nullable et non Nullable pour représenter les concepts du monde réel.The code uses both nullable reference types and non-nullable reference types to represent the real-world concepts. Les questions de l’enquête ne peuvent jamais être Null.The survey questions can never be null. Si la personne interrogée ne souhaite pas répondre à une question,A respondent might prefer not to answer a question. Les réponses peuvent être null dans ce cas.The responses might be null in this case.

Le code que vous allez écrire pour cet exemple exprime cette intention, que le compilateur se charge d’appliquer.The code you'll write for this sample expresses that intent, and the compiler enforces that intent.

Créer l’application et activer les types référence NullableCreate the application and enable nullable reference types

Créez une application console dans Visual Studio ou en ligne de commande avec dotnet new console.Create a new console application either in Visual Studio or from the command line using dotnet new console. Nommez l'application NullableIntroduction.Name the application NullableIntroduction. Une fois que vous avez créé l’application, vous devez spécifier que l’intégralité du projet compile dans un contexte d' annotation Nullableactivé.Once you've created the application, you'll need to specify that the entire project compiles in an enabled nullable annotation context. Ouvrez le fichier . csproj et ajoutez un élément Nullable à l’élément PropertyGroup.Open the .csproj file and add a Nullable element to the PropertyGroup element. Définissez sa valeur sur enable.Set its value to enable. Vous devez opter pour la fonctionnalité des types de référence Nullable , C# même dans les projets 8,0.You must opt into the nullable reference types feature, even in C# 8.0 projects. En effet, une fois la fonctionnalité activée, les déclarations de variables référence existantes deviennent des types référence non Nullable.That's because once the feature is turned on, existing reference variable declarations become non-nullable reference types. Bien que cette décision permette de détecter les problèmes dans lesquels le code existant peut ne pas avoir de contrôles nuls corrects, il risque de ne pas refléter précisément votre intention de conception d’origine :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>

Concevoir les types de l’applicationDesign the types for the application

Cette application d’enquête implique la création d’un certain nombre de classes :This survey application requires creating a number of classes:

  • une classe qui modélise la liste des questions ;A class that models the list of questions.
  • une classe qui modélise la liste des personnes contactées pour l’enquête ;A class that models a list of people contacted for the survey.
  • une classe qui modélise les réponses d’une personne ayant répondu à l’enquête.A class that models the answers from a person that took the survey.

Ces types utilisent des types référence Nullable et non Nullable pour indiquer quels membres sont requis et lesquels sont facultatifs.These types will make use of both nullable and non-nullable reference types to express which members are required and which members are optional. Les types référence Nullable communiquent clairement cette intention de conception :Nullable reference types communicate that design intent clearly:

  • Les questions qui font partie de l’enquête ne peuvent jamais être Null : poser une question vide n’aurait pas de sens.The questions that are part of the survey can never be null: It makes no sense to ask an empty question.
  • Les personnes interrogées ne peuvent jamais être Null.The respondents can never be null. Vous souhaitez faire un suivi des personnes que vous avez contactées, même de celles qui ont refusé de participer.You'll want to track people you contacted, even respondents that declined to participate.
  • La réponse à une question peut être Null.Any response to a question may be null. Les personnes interrogées peuvent refuser de répondre à certaines questions ou à la totalité d’entre elles.Respondents can decline to answer some or all questions.

Si vous avez programmé dans C#, vous serez peut-être habitué à référencer des types qui autorisent des valeurs denullque vous avez peut-être oublié d’autres possibilités de déclarer des instances non Nullable :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:

  • La collection de questions doit être non Nullable.The collection of questions should be non-nullable.
  • La collection de personnes interrogées doit être non Nullable.The collection of respondents should be non-nullable.

À mesure que vous écrivez le code, vous verrez qu’un type de référence qui n’accepte pas les valeurs NULL comme valeur par défaut pour les références évite les erreurs courantes susceptibles d’aboutir à 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. L’une des leçons de ce didacticiel est que vous avez pris des décisions concernant les variables qui pouvaient ou non être null.One lesson from this tutorial is that you made decisions about which variables could or could not be null. Avant, le langage ne fournissait pas de syntaxe permettant d’exprimer ces décisions ;The language didn't provide syntax to express those decisions. maintenant, si.Now it does.

L’application que vous allez générer effectue les étapes suivantes :The app you'll build does the following steps:

  1. Crée une enquête et y ajoute des questions.Creates a survey and adds questions to it.
  2. Crée un ensemble Pseudo-aléatoire de personnes interrogées pour l’enquête.Creates a pseudo-random set of respondents for the survey.
  3. Contacte les répondants jusqu’à ce que la taille de l’enquête terminée atteigne le numéro d’objectif.Contacts respondents until the completed survey size reaches the goal number.
  4. Écrit des statistiques importantes sur les réponses de l’enquête.Writes out important statistics on the survey responses.

Créer l’enquête avec des types Nullable et non NullableBuild the survey with nullable and non-nullable types

La première partie du code crée l’enquête.The first code you'll write creates the survey. Vous allez écrire des classes pour modéliser une question et une enquête.You'll write classes to model a survey question and a survey run. L’enquête comporte trois types de questions, en fonction du format de la réponse : réponses par oui ou non, réponses chiffrées et réponses textuelles.Your survey has three types of questions, distinguished by the format of the answer: Yes/No answers, number answers, and text answers. Créez une classe public SurveyQuestion :Create a public SurveyQuestion class:

namespace NullableIntroduction
{
    public class SurveyQuestion
    {
    }
}

Le compilateur interprète chaque déclaration de variable de type référence comme un type de référence non Nullable pour le code dans un contexte d’annotation Nullable activé.The compiler interprets every reference type variable declaration as a non-nullable reference type for code in an enabled nullable annotation context. Un premier avertissement apparaît lorsque l’on ajoute des propriétés pour le texte et le type de la question avec le code suivant :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; }
    }
}

Comme QuestionText n’est pas initialisé, le compilateur émet l’avertissement selon lequel une propriété non Nullable n’a pas été initialisée.Because you haven't initialized QuestionText, the compiler issues a warning that a non-nullable property hasn't been initialized. Or, la conception exige que le texte de la question soit non Null. Ajoutez un constructeur pour l’initialiser, ainsi que la valeur QuestionType.Your design requires the question text to be non-null, so you add a constructor to initialize it and the QuestionType value as well. Une fois terminée, la définition de classe se présente ainsi :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);
    }
}

Le fait d’ajouter le constructeur supprime l’avertissement.Adding the constructor removes the warning. L’argument du constructeur étant également un type référence non Nullable, le compilateur ne génère pas d’avertissements.The constructor argument is also a non-nullable reference type, so the compiler doesn't issue any warnings.

Ensuite, créez une classe public nommée SurveyRun.Next, create a public class named SurveyRun. Cette classe contient une liste d’objets SurveyQuestion et de méthodes permettant d’ajouter des questions à l’enquête, comme l’illustre le code suivant :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);
    }
}

Comme tout à l’heure, il faut initialiser l’objet de liste sur une valeur non Null pour éviter que le compilateur n’émette un avertissement.As before, you must initialize the list object to a non-null value or the compiler issues a warning. Il n’y a pas de contrôle de type Null dans la deuxième surcharge de AddQuestion, car ce n’est pas nécessaire : vous avez déclaré cette variable comme étant non Nullable.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. Sa valeur ne peut pas être null.Its value can't be null.

Basculez vers Program.cs dans votre éditeur et remplacez le contenu de Main par les lignes de code suivantes :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?");

Étant donné que le projet entier est dans un contexte d’annotation Nullable activé, vous obtenez des avertissements quand vous transmettez null à une méthode qui attend un type de référence non Nullable.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. Regardez le résultat en ajoutant la ligne suivante à Main :Try it by adding the following line to Main:

surveyRun.AddQuestion(QuestionType.Text, default);

Créer des personnes interrogées et obtenir des réponses à l’enquêteCreate respondents and get answers to the survey

Ensuite, écrivez le code qui génère des réponses à l’enquête.Next, write the code that generates answers to the survey. Ce processus implique plusieurs petites tâches :This process involves several small tasks:

  1. Créer une méthode qui génère des objets personne interrogée,Build a method that generates respondent objects. représentant les personnes à qui il a été demandé de répondre à l’enquête.These represent people asked to fill out the survey.
  2. Créer une logique pour simuler les questions posées à une personne interrogée et les réponses obtenues, ou noter qu’une personne interrogée n’a pas répondu.Build logic to simulate asking the questions to a respondent and collecting answers or noting that a respondent didn't answer.
  3. Répéter le processus jusqu'à ce que les personnes interrogées soient en nombre suffisant.Repeat until enough respondents have answered the survey.

Il vous faut une classe qui représente les réponse à l’enquête. Ajoutez-la maintenant.You'll need a class to represent a survey response, so add that now. Activez la prise en charge Nullable.Enable nullable support. Ajoutez une propriété Id et un constructeur qui l’initialise, comme dans le code suivant :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;
    }
}

Ensuite, ajoutez une méthode static pour créer de nouveaux participants en générant un ID aléatoire :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());

La responsabilité principale de cette classe consiste à générer les réponses d’un participant aux questions de l’enquête,The main responsibility of this class is to generate the responses for a participant to the questions in the survey. ce qui se décompose en plusieurs étapes :This responsibility has a few steps:

  1. Demander à participer à l’enquête.Ask for participation in the survey. Si la personne refuse, renvoyer une réponse manquante (Null).If the person doesn't consent, return a missing (or null) response.
  2. Poser chaque question et enregistrer la réponse.Ask each question and record the answer. Chacune des réponses peut également être manquante (Null).Each answer may also be missing (or null).

Ajoutez le code suivant à la 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!";
    }
}

Les réponses à l’enquête sont stockées dans un Dictionary<int, string>?, qui peut donc être Null.The storage for the survey answers is a Dictionary<int, string>?, indicating that it may be null. Vous utilisez la nouvelle fonctionnalité du langage pour déclarer votre intention de conception, à la fois au compilateur et à toute personne qui lira votre code.You're using the new language feature to declare your design intent, both to the compiler and to anyone reading your code later. Si vous déréférencez surveyResponses sans vérifier la valeur null en premier, vous obtenez un avertissement du compilateur.If you ever dereference surveyResponses without checking for the null value first, you'll get a compiler warning. La méthode AnswerSurvey ne génère pas d’avertissement, car le compilateur peut déterminer que la variable surveyResponses a été définie avant sur une valeur non Null.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.

L’utilisation de null pour les réponses manquantes met en évidence un point important pour travailler avec les types référence nullable : votre objectif n’est pas de supprimer toutes les valeurs null à partir de votre programme.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. Au lieu de cela, votre objectif est de vous assurer que le code que vous écrivez exprime l’intention de votre conception.Rather, your goal is to ensure that the code you write expresses the intent of your design. Les valeurs manquantes sont un concept nécessaire à exprimer dans votre code.Missing values are a necessary concept to express in your code. La valeur null est une méthode évidente pour exprimer les valeurs manquantes.The null value is a clear way to express those missing values. Essayer de supprimer toutes les valeurs null mène uniquement à la définition d’une autre façon d’exprimer ces valeurs manquantes sans null.Trying to remove all null values only leads to defining some other way to express those missing values without null.

Ensuite, il reste à écrire la méthode PerformSurvey dans la classe SurveyRun.Next, you need to write the PerformSurvey method in the SurveyRun class. Ajoutez le code suivant à la 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);
    }
}

Là encore, le choix d’une List<SurveyResponse>? Nullable indique que la réponse peut être Null,Here again, your choice of a nullable List<SurveyResponse>? indicates the response may be null. et que l’enquête n’a pour le moment été menée auprès de personne.That indicates the survey hasn't been given to any respondents yet. Des personnes interrogées sont ajoutées jusqu'à ce qu’elles soient suffisamment nombreuses à avoir accepté.Notice that respondents are added until enough have consented.

La dernière étape consiste à ajouter un appel pour effectuer l’enquête à la fin de la méthode 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);

Examiner les réponses à l’enquêteExamine survey responses

La dernière étape consiste à afficher les résultats de l’enquête,The last step is to display survey results. ce qui suppose d’ajouter du code à la plupart des classes déjà écrites.You'll add code to many of the classes you've written. Ce code montre l’intérêt de distinguer les types référence Nullable et non Nullable.This code demonstrates the value of distinguishing nullable and non-nullable reference types. Commencez par ajouter les deux membres expression-bodied suivants à la 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";

Étant donné que surveyResponses est un type référence Nullable, les vérifications de valeur NULL sont nécessaires avant de le déréférencer.Because surveyResponses is a nullable reference type, null checks are necessary before de-referencing it. La méthode Answer retourne une chaîne qui n’accepte pas les valeurs NULL. nous devons donc aborder le cas d’une réponse manquante à l’aide de l’opérateur de fusion Null.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.

Ensuite, ajoutez ces trois membres expression-bodied à la 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];

Le membre AllParticipants doit prendre en compte le fait que la variable respondents peut être Null, mais pas la valeur de retour.The AllParticipants member must take into account that the respondents variable might be null, but the return value can't be null. Si vous modifiez cette expression en supprimant ?? et la séquence vide qui suit, le compilateur émet un avertissement, indiquant que la méthode peut retourner null et sa signature de retour un type non Nullable.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.

Enfin, ajoutez la boucle suivante en bas de la méthode 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).QuestionText} : {answer}");
        }
    }
    else
    {
        Console.WriteLine("\tNo responses");
    }
}

Aucun contrôle null n’est nécessaire dans ce code, car vous avez conçu les interfaces sous-jacentes de sorte qu’elles retournent toutes des types référence non Nullable.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.

Obtenir le codeGet the code

Pour obtenir le code du tutoriel complet, consultez notre dépôt samples dans le dossier csharp/NullableIntroduction.You can get the code for the finished tutorial from our samples repository in the csharp/NullableIntroduction folder.

Faites des essais en modifiant les déclarations de type entre les types référence Nullable et non Nullable.Experiment by changing the type declarations between nullable and non-nullable reference types. Examinez les différents avertissements générés, qui visent à empêcher de déréférencer accidentellement un null.See how that generates different warnings to ensure you don't accidentally dereference a null.

Étapes suivantesNext steps

En savoir plus en migrant une application existante pour utiliser des types de référence nullable :Learn more by migrating an existing application to use nullable reference types: