Exprimer votre intention

Effectué

Au cours de la leçon précédente, vous avez appris comment le compilateur C# pouvait effectuer une analyse statique pour vous aider à vous protéger de l’exception NullReferenceException. Vous avez également appris à activer un contexte pouvant accepter la valeur Null. Dans cette leçon, vous en apprendrez davantage sur l’expression explicite de votre intention dans un contexte pouvant accepter la valeur Null.

Déclaration de variables

Avec un contexte compatible avec la valeur Null, vous bénéficiez d’une plus grande visibilité sur la façon dont le compilateur voit votre code. Vous pouvez agir sur les avertissements générés dans un contexte acceptant les valeurs Null et par là-même définir explicitement vos intentions. Par exemple, poursuivons l’examen du code FooBar et examinons la déclaration et l’affectation :

// Define as nullable
FooBar? fooBar = null;

Notez le ? ajouté à FooBar. Cela indique au compilateur que vous souhaitez explicitement autoriser fooBar à avoir une valeur Null. Si vous n’avez pas l’intention d’utiliser fooBar avec une valeur Null, mais que vous souhaitez tout de même éviter l’avertissement, tenez compte des points suivants :

// Define as non-nullable, but tell compiler to ignore warning
// Same as FooBar fooBar = default!;
FooBar fooBar = null!;

Cet exemple ajoute l’opérateur null-indulgent (!) à null, qui indique au compilateur que vous initialisez explicitement cette variable en tant que valeur Null. Le compilateur n’émettra pas d’avertissements sur la valeur Null pour cette référence.

Une bonne pratique consiste à attribuer à vos variables non-nullable les valeurs null lorsqu’elles sont déclarées, si possible :

// Define as non-nullable, assign using 'new' keyword
FooBar fooBar = new(Id: 1, Name: "Foo");

Opérateurs

Comme indiqué dans la leçon précédente, C# définit plusieurs opérateurs pour exprimer votre intention autour des types référence pouvant accepter la valeur Null.

Opérateur null-indulgent (!)

L’opérateur null-indulgent (!) a été mentionné dans la section précédente. Il indique au compilateur d’ignorer l’avertissement CS8600. Il s’agit d’un moyen d’indiquer au compilateur que vous savez ce que vous faites, mais il faut que vous sachiez réellement ce que vous faites !

Quand vous initialisez des types non-nullable alors qu’un contexte pouvant accepter la valeur Null est activé, vous pouvez avoir besoin de demander explicitement au compilateur d’utiliser l’indulgence. Considérons par exemple le code suivant :

#nullable enable

using System.Collections.Generic;

var fooList = new List<FooBar>
{
    new(Id: 1, Name: "Foo"),
    new(Id: 2, Name: "Bar")
};

FooBar fooBar = fooList.Find(f => f.Name == "Bar");

// The FooBar type definition for example.
record FooBar(int Id, string Name);

Dans l’exemple précédent, FooBar fooBar = fooList.Find(f => f.Name == "Bar"); génère un avertissement CS8600, car Find peut retourner null. Ce null potentiel peut être attribué à fooBar,qui a une valeur non-nullable dans ce contexte. Toutefois, dans cet exemple fictif, nous savons que Find ne retournera jamais null comme écrit. Vous pouvez exprimer cette intention au compilateur en utilisant l’opérateur null-indulgent :

FooBar fooBar = fooList.Find(f => f.Name == "Bar")!;

Notez le ! à la fin de fooList.Find(f => f.Name == "Bar"). Cela indique au compilateur que vous savez que l’objet retourné par la méthode Find peut être null, et que c’est correct.

Vous pouvez appliquer l’opérateur null-forgiving sur un objet inlined avant un appel de méthode ou avant une évaluation de propriété. Prenons un autre exemple fictif :

List<FooBar>? fooList = FooListFactory.GetFooList();

// Declare variable and assign it as null.
FooBar fooBar = fooList.Find(f => f.Name == "Bar")!; // generates warning

static class FooListFactory
{
    public static List<FooBar>? GetFooList() =>
        new List<FooBar>
        {
            new(Id: 1, Name: "Foo"),
            new(Id: 2, Name: "Bar")
        };
}

// The FooBar type definition for example.
record FooBar(int Id, string Name);

Dans l’exemple précédent :

  • GetFooList est une méthode statique qui retourne un type Nullable, List<FooBar>?.
  • fooList se voit attribuer la valeur retournée par GetFooList.
  • Le compilateur génère un avertissement sur fooList.Find(f => f.Name == "Bar");, car la valeur attribuée à fooList peut être null.
  • Si fooList n’est pas null, Find peut retourner null, mais nous savons que ce ne sera pas le cas, ainsi l’opérateur null-indulgent est appliqué.

Vous pouvez appliquer l’opérateur null-indulgent à fooList pour désactiver l’avertissement :

FooBar fooBar = fooList!.Find(f => f.Name == "Bar")!;

Notes

Vous devez utiliser l’opérateur null-forgiving judicieusement. L’utiliser simplement pour ignorer un avertissement signifie que vous indiquez au compilateur de ne pas vous aider à découvrir les éventuels incidents sur les valeurs Null. Utilisez-le avec modération et uniquement lorsque vous êtes sûr de vous.

Pour plus d’informations, consultez Opérateur ! (null-forgiving) (Informations de référence sur C#).

Opérateur de coalescence nulle (??)

Lorsque vous utilisez des types Nullable, vous serez peut-être amené à évaluer s’il s’agit actuellement de null et s’ils effectuent certaines actions. Par exemple, lorsqu’un type Nullable s’est vu affecté null ou qu’il n’a pas été initialisé, vous devrez peut-être lui attribuer une valeur non-null. C’est là que l’opérateur de coalescence nulle (??) est utile.

Prenons l’exemple suivant :

public void CalculateSalesTax(IStateSalesTax? salesTax = null)
{
    salesTax ??= DefaultStateSalesTax.Value;

    // Safely use salesTax object.
}

Dans le code C# précédent :

  • Le paramètre salesTax est défini comme pouvant accepter la valeur Null IStateSalesTax.
  • Dans le corps de méthode, le salesTax est attribué de manière conditionnelle à l’aide de l’opérateur de coalescence nulle.
    • Cela garantit que si salesTax a été transmis en tant que null, il aura une valeur.

Conseil

Cela fonctionne de la même manière que le code C# suivant :

public void CalculateSalesTax(IStateSalesTax? salesTax = null)
{
    if (salesTax is null)
    {
        salesTax = DefaultStateSalesTax.Value;
    }

    // Safely use salesTax object.
}

Voici l’exemple d’un autre idiome C# courant où l’opérateur de coalescence nulle peut être utile :

public sealed class Wrapper<T> where T : new()
{
    private T _source;

    // If given a source, wrap it. Otherwise, wrap a new source:
    public Wrapper(T source = null) => _source = source ?? new T();
}

Le code C# précédent :

  • Définit une classe wrapper générique, où le paramètre de type générique est restreint à new().
  • Le constructeur accepte un paramètre T source dont la valeur par défaut est null.
  • Le _source inclus dans un wrapper est initialisé de manière conditionnelle dans un new T().

Pour plus d’informations, consultez Opérateurs ?? et ??= (Informations de référence sur C#).

Opérateur conditionnel Null (?.)

Lorsque vous travaillez avec des types Nullable, vous êtes parfois appelé à effectuer des actions conditionnelles en fonction de l’état d’un objet null. Par exemple, dans l’unité précédente, l’enregistrement FooBar avait été utilisé pour illustrer l’exception NullReferenceException par le déréférencement de null. Cela était dû à l’appel de ToString. Gardons le même exemple, mais maintenant en appliquant l’opérateur conditionnel Null :

using System;

// Declare variable and assign it as null.
FooBar fooBar = null;

// Conditionally dereference variable.
var str = fooBar?.ToString();
Console.Write(str);

// The FooBar type definition.
record FooBar(int Id, string Name);

Le code C# précédent :

  • Déréférencez fooBar de manière conditionnelle, en assignant le résultat de ToString à la variable str.
    • La variable str est de type string? (chaîne pouvant accepter la valeur Null).
  • Il écrit la valeur de str dans une sortie standard, qui n’est rien.
  • L’appel de Console.Write(null) est valide, donc il n’y a pas d’avertissement.
  • Vous auriez reçu un avertissement si vous aviez appelé Console.Write(str.Length), car vous auriez risqué de déréférencer la valeur Null.

Conseil

Cela fonctionne de la même manière que le code C# suivant :

using System;

// Declare variable and assign it as null.
FooBar fooBar = null;

// Conditionally dereference variable.
string str = (fooBar is not null) ? fooBar.ToString() : default;
Console.Write(str);

// The FooBar type definition.
record FooBar(int Id, string Name);

Vous pouvez combiner les opérateurs pour affiner davantage votre intention. Par exemple, vous pouvez rattacher les opérateurs ?. et ?? :

FooBar fooBar = null;
var str = fooBar?.ToString() ?? "unknown";
Console.Write(str); // output: unknown

Pour plus d’informations, consultez Opérateurs ?. et ?[] (null-conditional).

Résumé

Dans cette leçon, vous avez appris à exprimer votre intention de possibilité de valeur Null dans le code. Dans la prochaine leçon, vous allez appliquer ce que vous avez appris à un projet existant.

Vérifiez vos connaissances

1.

Quelle est la valeur default du type référence string ?

2.

Quel est le comportement attendu du déréférencement de null ?

3.

Que se passe-t-il quand ce code C# throw null; est exécuté ?

4.

Parmi les affirmations suivantes, laquelle est la plus précise en ce qui concerne les types référence pouvant accepter la valeur Null ?