Types référence nullables (Référence C#)

Note

Cet article traite des types référence nullables. Vous pouvez également déclarer des types valeur nullables.

Utilisez des types de référence nullables dans le code qui se trouve dans un contexte prenant en compte null. Les types référence nullables, les avertissements d’analyse statique null et l’opérateur null-forgiving sont des fonctionnalités de langage facultatives. Tous sont désactivés par défaut. Vous contrôlez un contexte nullable au niveau du projet à l’aide des paramètres de build ou du code à l’aide de pragmas.

Le langage C# documente la version la plus récente de la langue C#. Il contient également la documentation initiale des fonctionnalités dans les préversions publiques pour la prochaine version du langage.

La documentation identifie toute fonctionnalité introduite en premier dans les trois dernières versions de la langue ou dans les préversions publiques actuelles.

Conseil / Astuce

Pour savoir quand une fonctionnalité a été introduite en C#, consultez l’article sur l’historique des versions du langage C#.

Importante

Tous les modèles de projet activent par défaut le contexte nullable pour le projet. Les projets créés avec des modèles antérieurs n’incluent pas cet élément, et ces fonctionnalités sont désactivées, sauf si vous les activez dans le fichier projet ou si vous utilisez des pragmas.

Dans un contexte prenant en charge les types nullables :

  • Vous devez initialiser une variable d’un type T référence avec une valeur non null et vous ne pouvez jamais affecter une valeur qui peut être null.
  • Vous pouvez initialiser une variable d’un type T? référence avec null ou affecter null, mais vous devez la vérifier null avant de déreferencing.
  • Lorsque vous appliquez l’opérateur null-forgiving à une variable m de type T?, comme dans m!, la variable est considérée comme non null.

Le compilateur applique les distinctions entre un type T de référence non nullable et un type T? de référence nullable à l’aide des règles précédentes. Une variable de type T et une variable de type T? sont le même type .NET. L’exemple suivant déclare une chaîne non nullable et une chaîne nullable, puis utilise l’opérateur null-forgiving pour affecter une valeur à une chaîne non nullable :

string notNull = "Hello";
string? nullable = default;
notNull = nullable!; // null forgiveness

Les variables notNull et nullable les deux utilisent le String type. Étant donné que les types non nullables et nullables utilisent tous les deux le même type, vous ne pouvez pas utiliser de type référence nullable à plusieurs emplacements. En général, vous ne pouvez pas utiliser de type référence Nullable comme classe de base ou interface implémentée. Vous ne pouvez pas utiliser de type référence Nullable dans une expression de création ou de test de type d’objet. Vous ne pouvez pas utiliser de type référence nullable comme type d’expression d’accès aux membres. Les exemples suivants illustrent ces constructions :

public MyClass : System.Object? // not allowed
{
}

var nullEmpty = System.String?.Empty; // Not allowed
var maybeObject = new object?(); // Not allowed
try
{
    if (thing is string? nullableString) // not allowed
        Console.WriteLine(nullableString);
} catch (Exception? e) // Not Allowed
{
    Console.WriteLine("error");
}

Références nullables et analyse statique

Les exemples de la section précédente illustrent la nature des types référence nullables. Les types référence nullables ne sont pas de nouveaux types de classes, mais plutôt des annotations sur des types référence existants. Le compilateur utilise ces annotations pour vous aider à trouver des erreurs de référence null potentielles dans votre code. Il n’existe aucune différence de runtime entre un type référence non nullable et un type référence nullable. Le compilateur n’ajoute aucune vérification de runtime pour les types référence non nullables. Les avantages se trouvent dans l’analyse au moment de la compilation. Le compilateur génère des avertissements qui vous aident à rechercher et à corriger les erreurs null potentielles dans votre code. Vous déclarez votre intention et le compilateur vous avertit lorsque votre code contrevient à cette intention.

Importante

Les annotations de référence nullables n’introduisent pas de modifications de comportement, mais d’autres bibliothèques peuvent utiliser la réflexion pour produire différents comportements d’exécution pour les types de référence nullables et non nullables. Entity Framework Core lit notamment les attributs nullables. Il interprète une référence nullable comme une valeur facultative et une référence non nullable comme valeur obligatoire.

Dans un contexte prenant en charge les types nullables, le compilateur effectue une analyse statique sur des variables de n’importe quel type référence, nullable et non nullable. Le compilateur effectue le suivi de l’état null de chaque variable de référence comme not-null ou maybe-null. L’état par défaut d’une référence non nullable est not-null. L’état par défaut d’une référence nullable est maybe-null.

Les types référence non nullables doivent toujours pouvoir être déréférencés en toute sécurité, car leur état null est not-null. Pour appliquer cette règle, le compilateur émet des avertissements si un type référence non nullable n’est pas initialisé en valeur non-null. Vous devez affecter des variables locales où vous les déclarez. Chaque champ doit se voir attribuer une valeur not-null, dans un initialiseur de champ ou dans chaque constructeur. Le compilateur émet des avertissements quand une référence non nullable est affectée à une référence dont l’état est maybe-null. En règle générale, une référence non nullable n’est pas null et aucun avertissement n’est émis lorsque vous déférez ces variables.

Note

Si vous affectez une expression maybe-null à un type de référence non nullable, le compilateur génère un avertissement. Le compilateur génère ensuite des avertissements pour cette variable jusqu’à ce qu’elle soit affectée à une expression not-null.

Vous pouvez initialiser ou affecter null des types de référence nullables. Par conséquent, l’analyse statique doit déterminer qu’une variable est not-null avant d’être déréférencée. Si une référence nullable est déterminée comme étant peut-être null, l’affectation à une variable de référence non nullable génère un avertissement du compilateur. La classe suivante montre des exemples de ces avertissements :

public class ProductDescription
{
    private string shortDescription;
    private string? detailedDescription;

    public ProductDescription() // Warning! shortDescription not initialized.
    {
    }

    public ProductDescription(string productDescription) =>
        this.shortDescription = productDescription;

    public void SetDescriptions(string productDescription, string? details=null)
    {
        shortDescription = productDescription;
        detailedDescription = details;
    }

    public string GetDescription()
    {
        if (detailedDescription.Length == 0) // Warning! dereference possible null
        {
            return shortDescription;
        }
        else
        {
            return $"{shortDescription}\n{detailedDescription}";
        }
    }

    public string FullDescription()
    {
        if (detailedDescription == null)
        {
            return shortDescription;
        }
        else if (detailedDescription.Length > 0) // OK, detailedDescription can't be null.
        {
            return $"{shortDescription}\n{detailedDescription}";
        }
        return shortDescription;
    }
}

L’extrait de code suivant montre où le compilateur émet des avertissements lors de l’utilisation de cette classe :

string shortDescription = default; // Warning! non-nullable set to null;
var product = new ProductDescription(shortDescription); // Warning! static analysis knows shortDescription maybe null.

string description = "widget";
var item = new ProductDescription(description);

item.SetDescriptions(description, "These widgets will do everything.");

Les exemples précédents montrent comment l’analyse statique du compilateur détermine l’état null des variables de référence. Le compilateur applique des règles de langage pour les contrôles de valeur null et les affectations afin de guider son analyse. Le compilateur ne peut pas faire d’hypothèses sur la sémantique des méthodes ni des propriétés. Si vous appelez des méthodes qui effectuent des contrôles de valeur null, le compilateur ne peut pas savoir que ces méthodes affectent l’état null d’une variable. Vous pouvez ajouter des attributs à vos API pour informer le compilateur de la sémantique des arguments et des valeurs de retour. De nombreuses API courantes dans les bibliothèques .NET ont ces attributs. Par exemple, le compilateur interprète correctement IsNullOrEmpty comme une vérification null. Pour plus d’informations sur les attributs qui s’appliquent à l’analyse statique d’état null, consultez l’article sur les attributs nullables.

Contexte pouvant accepter la valeur Null

Le contexte nullable détermine la façon dont le compilateur gère les annotations de type référence nullable et les avertissements qu’il génère lors de l’analyse d’état null statique. Le contexte nullable contient deux indicateurs : le paramètre d’annotation et le paramètre d’avertissement .

Les paramètres d'annotation et d’avertissement sont désactivés par défaut pour les projets existants. À compter de .NET 6 (C# 10), les deux indicateurs sont activés par défaut pour les projets new. La raison d'avoir deux indicateurs distincts pour le contexte nullable est de faciliter la migration des grands projets antérieurs à l’introduction des types de référence nullable.

Pour les petits projets, vous pouvez activer les types références nullables, corriger les avertissements et continuer. Toutefois, pour les projets plus volumineux et les solutions multi-projets, ce processus peut générer un grand nombre d’avertissements. Vous pouvez utiliser des pragmas pour activer les types références nullables fichier par fichier lorsque vous commencez à utiliser des types références nullables. Les nouvelles fonctionnalités qui protègent contre la levée d’une System.NullReferenceException peuvent être perturbantes lorsqu’elles sont activées dans une base de code existante.

  • Toutes les variables de référence explicitement typées sont interprétées comme des types référence non nullables.
  • La signification de la contrainte class dans les génériques a changé pour désigner un type référence non nullable.
  • De nouveaux avertissements sont générés en raison de ces nouvelles règles.

Le contexte d’annotation nullable détermine le comportement du compilateur. Il existe quatre combinaisons pour les paramètres du contexte nullable :

  • tous deux désactivés : Le code est nullable-oblivieux. Désactiver correspond au comportement avant que les types références nullables n’aient été activés, sauf que la nouvelle syntaxe génère des avertissements au lieu d’erreurs.
    • Les avertissements de nullabilité sont désactivés.
    • Toutes les variables de type référence sont des références nullables.
    • L’utilisation du suffixe ? pour déclarer une référence nullable produit un avertissement.
    • Vous pouvez utiliser l’opérateur null forgiving, !, mais il n’a aucun effet.
  • deux activés : Le compilateur active toutes les analyses de références nulles et toutes les fonctionnalités du langage.
    • Tous les nouveaux avertissements nullables sont activés.
    • Vous pouvez utiliser le suffixe ? pour déclarer un type de référence nullable.
    • Les variables de type référence sans le suffixe ? sont des types références non-nullables.
    • L’opérateur d'annulation de nullité supprime les avertissements pour un déréférencement possible de null.
  • avertissement active : Le compilateur effectue toutes les analyses de nullité et émet des avertissements lorsque le code risque de déréférencer null.
    • Tous les nouveaux avertissements nullables sont activés.
    • L’utilisation du suffixe ? pour déclarer une référence nullable produit un avertissement.
    • Toutes les variables de type référence sont autorisées à être null. Toutefois, les membres ont l'état null de not-null au début de toutes les méthodes, sauf s'ils sont déclarés avec le suffixe ?.
    • Vous pouvez utiliser le null forgiving operator, !.
  • activées : Le compilateur n'émet pas d'avertissement lorsque le code risque de déréférencer null, ou lorsque vous affectez une expression maybe-null à une variable non-nullable.
    • Les nouveaux avertissements nullables sont désactivés.
    • Vous pouvez utiliser le suffixe ? pour déclarer un type de référence nullable.
    • Les variables de type référence sans le suffixe ? sont des types références non-nullables.
    • Vous pouvez utiliser l’opérateur null forgiving, !, mais il n’a aucun effet.

Vous pouvez définir le contexte d’annotation nullable et le contexte d’avertissement nullable pour un projet à l’aide de l’élément<Nullable> de votre fichier .csproj. Cet élément configure la façon dont le compilateur interprète la nullabilité des types et les avertissements qu’il émet. Le tableau suivant présente les valeurs autorisées et résume les contextes qu’elles spécifient.

Contexte Avertissements de déréférencement Avertissements d’assignation Types de référence Suffixe ? ! Opérateur
disable Disabled Disabled Tous sont annulables Génère un avertissement N'a aucun effet
enable Enabled Enabled Non-nullable, sauf s'il est déclaré avec ? Déclare un type nullable Supprime les avertissements en cas d’affectation null possible
warnings Enabled Sans objet Tous acceptent la valeur Null, mais les membres sont considérés not-null au moment de l’ouverture des méthodes Génère un avertissement Supprime les avertissements en cas d’affectation null possible
annotations Disabled Disabled Non-nullable, sauf s'il est déclaré avec ? Déclare un type nullable N'a aucun effet

Les variables de type référence dans le code compilé dans un contexte désactivé sont nullable-oblivious. Vous pouvez assigner un littéral null ou une variable maybe-null à une variable qui peut avoir la valeur nullable-oblivious. Toutefois, l’état par défaut d’une variable nullable-oblivious estnot-null.

Choisissez le paramètre qui correspond le mieux à votre projet :

  • Choisissez disable pour les projets hérités que vous ne souhaitez pas mettre à jour en fonction des diagnostics ou de nouvelles fonctionnalités.
  • Choisissez warnings pour déterminer où votre code peut lever des System.NullReferenceException. Vous pouvez résoudre ces avertissements avant de modifier le code pour activer les types référence non nullables.
  • Choisissez annotations pour exprimer votre intention de conception avant d’activer les avertissements.
  • Choisissez enable pour les nouveaux projets et les projets actifs pour lesquels vous souhaitez vous protéger contre les exceptions de référence Null.

Exemple :

<Nullable>enable</Nullable>

Vous pouvez également utiliser des directives pour définir ces mêmes indicateurs n’importe où dans votre code source. Ces directives sont particulièrement utiles lorsque vous migrez un codebase volumineux.

  • #nullable enable : Définit l'activation des indicateurs d'annotation et d'avertissement.
  • #nullable disable : Définit la désactivation des indicateurs d'annotation et d'avertissement.
  • #nullable restore: restaure l’indicateur d’annotation et l’indicateur d’avertissement dans les paramètres du projet.
  • #nullable disable warnings: définit l’indicateur d’avertissement à désactiver.
  • #nullable enable warnings: définit l’indicateur d’avertissement à activer.
  • #nullable restore warnings: restaure l’indicateur d’avertissement dans les paramètres du projet.
  • #nullable disable annotations: définit l’indicateur d’annotation à désactiver.
  • #nullable enable annotations: définit l’indicateur d’annotation à activer.
  • #nullable restore annotations: restaure l’indicateur d’annotation dans les paramètres du projet.

Pour n’importe quelle ligne de code, vous pouvez définir l’une des combinaisons suivantes :

Indicateur d’avertissement Indicateur d’annotation Utiliser
paramètres par défaut du projet paramètres par défaut du projet Default
Activer disable Corriger les avertissements liés à l'analyse
Activer paramètres par défaut du projet Corriger les avertissements liés à l'analyse
paramètres par défaut du projet Activer Ajouter des annotations de type
Activer Activer Code déjà migré
disable Activer Annoter le code avant de corriger les avertissements
disable disable Ajout du code hérité au projet migré
paramètres par défaut du projet disable Rarement
disable paramètres par défaut du projet Rarement

Ces neuf combinaisons vous donnent un contrôle précis sur les diagnostics émis par le compilateur pour votre code. Vous pouvez activer davantage de fonctionnalités dans n’importe quelle zone que vous mettez à jour, sans voir d’autres avertissements que vous n’êtes pas encore prêt à traiter.

Importante

Le contexte nullable global ne s’applique pas aux fichiers de code générés. Dans l’une ou l’autre stratégie, le contexte pouvant accepter la valeur Null est désactivé pour tout fichier source marqué comme généré. Cette condition signifie que le compilateur n’annote aucune API dans les fichiers générés. Le compilateur ne produit pas d’avertissements nullables pour les fichiers générés. Un fichier est marqué comme généré de l’une des quatre manières suivantes :

  1. Dans .editorconfig, spécifiez generated_code = true dans une section qui s’applique à ce fichier.
  2. Placez <auto-generated> ou <auto-generated/> dans un commentaire en haut du fichier. Il peut se trouver sur n’importe quelle ligne de ce commentaire, mais le bloc de commentaires doit être le premier élément du fichier.
  3. Le nom du fichier doit commencer par TemporaryGeneratedFile_.
  4. Le nom du fichier doit se terminer par .designer.cs, .generated.cs, .g.cs ou .g.i.cs.

Les générateurs peuvent opter pour l’utilisation de la #nullable directive de préprocesseur.

Par défaut, les indicateurs d'annotation et d'avertissement nullables sont désactivés. Cette valeur par défaut signifie que votre code existant se compile sans modification et sans générer de nouveaux avertissements. À compter de .NET 6, les nouveaux projets incluent l’élément <Nullable>enable</Nullable> dans tous les modèles de projet, en définissant ces indicateurs sur enabled.

Ces options fournissent deux stratégies distinctes pour mettre à jour un codebase existant afin d’utiliser des types référence pouvant accepter la valeur Null.

Définition du contexte nullable

Vous pouvez contrôler le contexte nullable de deux façons. Au niveau du projet, ajoutez le paramètre du <Nullable>enable</Nullable> projet. Dans un seul fichier source C#, ajoutez le #nullable enable pragma pour activer le contexte nullable. Pour plus d’informations, consultez la définition d’une stratégie nullable. Avant .NET 6, les nouveaux projets utilisent la valeur par défaut, <Nullable>disable</Nullable>. À partir de .NET 6, les nouveaux projets incluent l’élément <Nullable>enable</Nullable> dans le fichier projet.

Génériques

Lorsque vous utilisez un paramètre de type, comme Téquivalent nullable, T?l’argument de type réel détermine la façon dont l’interprétation ? est interprétée. Considérez la déclaration générique suivante :

public class Box<T>
{
    public T Contents { get; set; }
}

Étant donné qu’un paramètre de type peut représenter un type référence ou un type valeur, la signification de dépend de T? l’argument de type que l’appelant fournit. Les règles suivantes décrivent ce qui T? se résout en l’absence T de contraintes :

  • L’argument de type est un type référence non Nullable. Pour Box<string>, T est et T? est string?string : le type de référence nullable correspondant.
  • L’argument de type est un type valeur. Pour Box<int>, T est int et T? est également int— le même type de valeur. L’annotation n’a aucun effet sur les types valeur, sauf si le paramètre de type a la struct contrainte, auquel cas T? signifie Nullable<T> (int?).
  • L’argument de type est déjà nullable. Pour Box<string?>, T est string? et T? est encore string?. Vous n’obtenez pas de type « doublement nullable ».

Les contraintes limitent les arguments de type autorisés. Ils permettent également au compilateur de savoir comment T utiliser :

  • where T : class nécessite un type de référence non nullable. Box<string> est autorisé ; Box<string?> génère un avertissement.
  • where T : class? autorise un type de référence nullable ou non nullable. Les deux Box<string> et Box<string?> sont autorisés.
  • where T : struct nécessite un type valeur non Nullable. Box<int> est autorisé ; Box<int?> Ne. Avec cette contrainte, T? à l’intérieur des moyens Nullable<T>génériques , pour Box<int>, T? est int?.
  • where T : notnull nécessite une référence ou un type valeur non nullable. Box<string> et Box<int> sont autorisés ; Box<string?> génère un avertissement.
  • where T : BaseType nécessite un type de référence non nullable qui dérive de BaseType. Ajoutez ? (where T : BaseType?) pour autoriser également les types dérivés nullables.

Les contraintes aident le compilateur à comprendre comment un paramètre de type générique est utilisé :

public static T? FirstOrDefault<T>(IEnumerable<T> source)
{
    foreach (T item in source)
    {
        return item;
    }
    return default;
}

public static void RequireNotNull<T>(T value) where T : notnull
{
    ArgumentNullException.ThrowIfNull(value);
}

public static void Generics()
{
    string? first = FirstOrDefault<string>([]);
    Console.WriteLine(first ?? "<empty>");

    RequireNotNull("not null");
}

spécification du langage C#

Pour plus d’informations, consultez la section Types de référence Nullable de la spécification du langage C#.

Voir aussi