Types références Nullables

Avant C# 8,0, tous les types référence étaient Nullable. Les types de référence Nullable font référence à un groupe de fonctionnalités introduites dans C# 8,0 que vous pouvez utiliser pour réduire la probabilité que votre code provoque la levée du runtime System.NullReferenceException . Les types de référence Nullable incluent trois fonctionnalités qui vous permettent d’éviter ces exceptions, notamment la possibilité de marquer explicitement un type de référence comme Nullable:

  • Amélioration de l’analyse de workflow statique qui détermine si une variable peut être avant d’être null déréférencée.
  • Attributs qui annotent des API pour que l’analyse de Flow détermine l' État null.
  • Annotations de variable que les développeurs utilisent pour déclarer explicitement l' État null prévu pour une variable.

L’analyse d’État null et les annotations de variable sont désactivées par défaut pour les projets existants — , ce qui signifie que tous les types de référence continuent à être Nullable. À compter de .NET 6, elles sont activées par défaut pour les nouveaux projets. Pour plus d’informations sur l’activation de ces fonctionnalités en déclarant un contexte d’annotation Nullable, consultez contextes Nullable.

Le reste de cet article décrit comment ces trois fonctionnalités fonctionnent pour générer des avertissements lorsque votre code peut déréférencer une null valeur. Si vous déréférencez une variable, vous pouvez accéder à l’un de ses membres à l’aide de l' . opérateur (point), comme indiqué dans l’exemple suivant :

string message = "Hello, World!";
int length = message.Length; // dereferencing "message"

Lorsque vous déréférencez une variable dont la valeur est null , le runtime lève une exception System.NullReferenceException .

Analyse d’État null

*Analyse d’État null: effectue le suivi de l' _null-état * des références. Cette analyse statique émet des avertissements lorsque votre code peut être déréférencé null . Vous pouvez résoudre ces avertissements pour réduire les incidences lorsque le runtime lève une exception System.NullReferenceException . Le compilateur utilise l’analyse statique pour déterminer l' État null d’une variable. Une variable n’a pas la valeur null ou a peut-être la valeur null. Le compilateur détermine qu’une variable n’est pas null de deux manières :

  1. La variable a été assignée à une valeur qui est connue comme non null.
  2. La variable a été vérifiée null et n’a pas été modifiée depuis cette vérification.

Toute variable que le compilateur n’a pas déterminée comme not-null est considérée comme peut-être null. L’analyse fournit des avertissements dans les situations où vous pouvez déréférencer accidentellement une null valeur. Le compilateur génère des avertissements en fonction de l' État null.

  • Quand une variable n’a pas la valeur null, cette variable peut être déréférencée en toute sécurité.
  • Quand une variable est peut-être null, cette variable doit être vérifiée pour s’assurer qu’elle ne doit pas être null déréférencée.

Prenez l’exemple suivant :

string message = null;

// warning: dereference null.
Console.WriteLine($"The length of the message is {message.Length}");

var originalMessage = message;
message = "Hello, World!";

// No warning. Analysis determined "message" is not null.
Console.WriteLine($"The length of the message is {message.Length}");

// warning!
Console.WriteLine(originalMessage.Length);

Dans l’exemple précédent, le compilateur détermine que message est peut-être null lorsque le premier message est imprimé. Il n’y a pas d’avertissement pour le deuxième message. La dernière ligne de code génère un avertissement, car originalMessage peut avoir la valeur null. L’exemple suivant montre une utilisation plus pratique pour parcourir une arborescence de nœuds vers la racine, en traitant chaque nœud pendant la traversée :

void FindRoot(Node node, Action<Node> processNode)
{
    for (var current = node; current != null; current = current.Parent)
    {
        processNode(current);
    }
}

Le code précédent ne génère aucun avertissement pour déréférencer la variable current . L’analyse statique détermine que current ne peut jamais être déréférencé lorsqu’il s’agit d' une valeur null. La variable current est vérifiée par rapport à null avant que current.Parent ne soit accessible, et avant current de passer à l' ProcessNode action. Les exemples précédents montrent comment le compilateur détermine l' État null pour les variables locales lors de l’initialisation, de l’assignation ou de la comparaison à null .

Notes

Un certain nombre d’améliorations ont été apportées à l’assignation définie et à l’analyse d’État null en C# 10. Lorsque vous effectuez une mise à niveau vers C# 10, vous pouvez trouver moins d’avertissements Nullable qui sont des faux positifs. Pour plus d’informations sur les améliorations apportées à la spécification des fonctionnalités, consultez améliorations de l’attribution définie.

Attributs sur les signatures d’API

L’analyse d’État null a besoin d’indications de la part des développeurs pour comprendre la sémantique des API. Certaines API fournissent des vérifications de valeur null et doivent remplacer l' État null d’une variable de la valeur de peut-null par not-null. D’autres API retournent des expressions qui ne sont pas null ou peuvent éventuellement-null selon l' État null des arguments d’entrée. Par exemple, considérez le code suivant qui affiche un message :

public void PrintMessage(string message)
{
    if (!string.IsNullOrWhiteSpace(message))
    {
        Console.WriteLine($"{DateTime.Now}: {message}");
    }
}

En se basant sur l’inspection, n’importe quel développeur peut considérer ce code comme sécurisé et ne doit pas générer d’avertissements. Le compilateur ne sait pas que IsNullOrWhiteSpace fournit une vérification de valeur null. Vous appliquez des attributs pour informer le compilateur qui message n’est pas null si et uniquement si IsNullOrWhiteSpace retourne false . Dans l’exemple précédent, la signature comprend le NotNullWhen pour indiquer l’État null de message :

public static bool IsNullOrWhiteSpace([NotNullWhen(false)] string message);

Les attributs fournissent des informations détaillées sur l’État null des arguments, les valeurs de retour et les membres de l’instance d’objet utilisée pour appeler un membre. Pour plus d’informations sur chaque attribut, consultez l’article référence du langage sur les attributs de référence Nullable. Les API du Runtime .NET ont toutes été annotées dans .NET 5. Vous améliorez l’analyse statique en annotant vos API pour fournir des informations sémantiques sur l' État null des arguments et les valeurs de retour.

Annotations de variable Nullable

L’analyse d' État null fournit une analyse fiable pour la plupart des variables. Le compilateur a besoin d’informations supplémentaires pour les variables membres. Le compilateur ne peut pas faire d’hypothèses sur l’ordre dans lequel les membres publics sont accessibles. N’importe quel membre public est accessible dans n’importe quel ordre. N’importe quel constructeur accessible peut être utilisé pour initialiser l’objet. Si un champ membre peut avoir null la valeur, le compilateur doit supposer que son État null est peut-être null au début de chaque méthode.

Vous utilisez des annotations qui peuvent déclarer si une variable est un type de référence Nullable ou un type de référence non Nullable. Ces annotations constituent des instructions importantes sur l' État null pour les variables :

  • Une référence ne doit pas être null. L’État par défaut d’une variable de référence non NULL n’est pas null. Le compilateur applique des règles qui garantissent qu’il est possible de déréférencer ces variables sans avoir d’abord vérifié qu’elles n’ont pas la valeur NULL :
    • La variable doit être initialisée avec une valeur non null.
    • La variable ne peut jamais se voir attribuer la valeur null. Le compilateur émet un avertissement lorsque le code assigne une expression peut-être null à une variable qui ne doit pas avoir la valeur null.
  • Une référence peut être null. L’État par défaut d’une variable de référence Nullable est peut-être null. Le compilateur applique des règles pour vérifier que vous avez correctement vérifié une null référence :
    • La variable ne peut être déréférencée que lorsque le compilateur peut garantir que la valeur n’est pas null .
    • Ces variables peuvent être initialisées avec la valeur null par défaut et peuvent se voir attribuer la valeur null dans un autre code.
    • Le compilateur n’émet pas d’avertissements quand le code assigne une expression peut -être null à une variable qui peut être null.

Toute variable de référence qui n’est pas censée avoir null un État null de type not-null. Toute variable de référence ayant null initialement l' État null a peut - être la valeur null.

Un type de référence nullable est inidqué avec la même syntaxe que celle des types valeur nullable : un ? est ajouté au type de la variable. Par exemple, la déclaration de variable suivante représente une variable de chaîne nullable, name :

string? name;

Toute variable où ? n’est pas ajouté au nom de type est un type de référence non Nullable. Cela comprend toutes les variables de type référence dans le code existant lorsque vous avez activé cette fonctionnalité. Toutefois, toutes les variables locales implicitement typées (déclarées à l’aide de var ) sont des types référence Nullable. Comme le montrent les sections précédentes, l’analyse statique détermine l' État null des variables locales pour déterminer si elles sont peut-être nulles.

Parfois, vous devez remplacer un avertissement lorsque vous savez qu’une variable n’est pas null, mais que le compilateur détermine que son État null est peut-être null. Vous utilisez l' opérateur null-indulgent avec à ! la suite d’un nom de variable pour forcer le null-État à être non null. Par exemple, si vous savez que la name variable n’est pas null mais que le compilateur émet un avertissement, vous pouvez écrire le code suivant pour remplacer l’analyse du compilateur :

name!.Length;

Les types référence Nullable et les types valeur Nullable fournissent un concept sémantique similaire : une variable peut représenter une valeur ou un objet, ou cette variable peut être null . Toutefois, les types référence Nullable et les types valeur Nullable sont implémentés différemment : les types valeur Nullable sont implémentés à l’aide de System.Nullable<T> , et les types référence Nullable sont implémentés par les attributs lus par le compilateur. Par exemple, string? et string sont tous les deux représentés par le même type : System.String . Toutefois, int? et int sont représentés par System.Nullable<System.Int32> et System.Int32 , respectivement.

Génériques

Les génériques requièrent des règles détaillées à gérer T? pour tous les paramètres de type T . Les règles sont nécessairement détaillées en raison de l’historique et de l’implémentation différente pour un type valeur Nullable et un type référence Nullable. Les types valeur Nullable sont implémentés à l’aide du System.Nullable<T> struct. Les types de référence Nullable sont implémentés en tant qu’annotations de type qui fournissent des règles sémantiques au compilateur.

En C# 8,0, l’utilisation de T? sans contraindre T à être un struct ou un class n’a pas été compilée. Cela permettait au compilateur d’interpréter T? clairement. Cette restriction a été supprimée dans C# 9,0, en définissant les règles suivantes pour un paramètre de type sans contrainte T :

  • Si l’argument de type de T est un type référence, T? fait référence au type de référence Nullable correspondant. Par exemple, si T est un string , T? est un string? .
  • Si l’argument de type de T est un type valeur, T? référence le même type valeur, T . Par exemple, si T est un int , T? est également un int .
  • Si l’argument de type pour T est un type de référence Nullable, T? référence ce même type de référence Nullable. Par exemple, si T est un string? , T? est également un string? .
  • Si l’argument de type pour T est un type valeur Nullable, T? référence ce même type de valeur Nullable. Par exemple, si T est un int? , T? est également un int? .

Pour les valeurs de retour, équivaut T? à [MaybeNull]T ; pour les valeurs d’argument, T? est équivalent à [AllowNull]T . Pour plus d’informations, consultez l’article sur les attributs pour l’analyse d’État null dans la référence du langage.

Vous pouvez spécifier un comportement différent à l’aide de contraintes:

  • La class contrainte signifie que T doit être un type de référence n’acceptant pas les valeurs null (par exemple string ). Le compilateur génère un avertissement si vous utilisez un type de référence Nullable, comme string? pour T .
  • La class? contrainte signifie que T doit être un type référence, qui n’accepte pas les valeurs null ( string ) ou un type de référence Nullable (par exemple string? ). Lorsque le paramètre de type est un type de référence Nullable, tel que string? , une expression de référence T? ce même type de référence Nullable, tel que string? .
  • La notnull contrainte signifie qu’il T doit s’agir d’un type de référence non Nullable ou d’un type valeur n’acceptant pas les valeurs NULL. Si vous utilisez un type de référence Nullable ou un type de valeur Nullable pour le paramètre de type, le compilateur génère un avertissement. En outre, quand T est un type valeur, la valeur de retour est ce type valeur, et non le type valeur Nullable correspondant.

Ces contraintes aident à fournir plus d’informations au compilateur sur la manière dont T sera utilisé. Cela aide quand les développeurs choisissent le type pour T et fournit une meilleure analyse d' État null lorsqu’une instance du type générique est utilisée.

Contextes nullables

Les nouvelles fonctionnalités qui protègent contre la levée d’une System.NullReferenceException peuvent être perturbatrices 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 de référence non Nullable.
  • La signification de la class contrainte dans les génériques a changé pour signifier un type de référence qui n’accepte pas les valeurs NULL.
  • De nouveaux avertissements sont générés en raison de ces nouvelles règles.

Vous devez explicitement accepter d’utiliser ces fonctionnalités dans vos projets existants. Qui fournit un chemin de migration et préserve la compatibilité descendante. Les contextes nullables permettent de contrôler précisément comment le compilateur interprète les variables de type référence. Le contexte d’annotation Nullable détermine le comportement du compilateur. Il y a quatre valeurs pour le contexte d’annotation Nullable:

  • désactivé: le compilateur se comporte de la même façon que C# 7,3 et versions antérieures :
    • Les avertissements Nullable sont désactivés.
    • Toutes les variables de type référence sont des types référence Nullable.
    • Vous ne pouvez pas déclarer une variable comme type de référence Nullable à l’aide du ? suffixe sur le type.
    • Vous pouvez utiliser l’opérateur null indulgent avec, ! , mais il n’a aucun effet.
  • activé: le compilateur active toutes les analyses de référence null et toutes les fonctionnalités de langage.
    • Tous les nouveaux avertissements Nullable sont activés.
    • Vous pouvez utiliser le ? suffixe pour déclarer un type de référence Nullable.
    • Toutes les autres variables de type référence sont des types de référence n’acceptant pas les valeurs NULL.
    • L’opérateur null indulgent avec supprime les avertissements pour une assignation possible à null .
  • avertissements: le compilateur effectue toutes les analyses null et émet des avertissements quand le code peut être déréférencé null .
    • Tous les nouveaux avertissements Nullable sont activés.
    • L’utilisation du ? suffixe pour déclarer un type référence Nullable produit un avertissement.
    • Toutes les variables de type référence peuvent avoir la valeur null. Toutefois, les membres ont l' État null de not-null à l’accolade ouvrante de toutes les méthodes, sauf s’ils sont déclarés avec le ? suffixe.
    • Vous pouvez utiliser l’opérateur indulgent avec NULL, ! .
  • Annotations: le compilateur n’effectue pas d’analyse null ou n’émet pas d’avertissements quand le code peut être déréférencé null .
    • Tous les nouveaux avertissements Nullable sont désactivés.
    • Vous pouvez utiliser le ? suffixe pour déclarer un type de référence Nullable.
    • Toutes les autres variables de type référence sont des types de référence n’acceptant pas les valeurs NULL.
    • Vous pouvez utiliser l’opérateur null indulgent avec, ! , mais il n’a aucun effet.

Le contexte d’annotation Nullable et le contexte d’avertissement Nullable peuvent être définis pour un projet à l’aide de l' <Nullable> élément dans votre fichier . csproj . Cet élément configure la façon dont le compilateur interprète la possibilité de valeur null des types et les avertissements émis. Le tableau suivant présente les valeurs autorisées et résume les contextes qu’elles spécifient.

Context Avertissements de déréférence Avertissements d’affectation Types référence ? suffixe ! and
disabled Désactivé Désactivé Toutes les valeurs null Ne peut pas être utilisé N’a aucun effet
enabled activé activé N’accepte pas les valeurs NULL, sauf si elles sont déclarées avec ? Déclare un type Nullable Supprime les avertissements pour une null assignation possible
warnings activé Non applicable All acceptent la valeur null , mais les membres ne sont pas considérés comme null à l’accolade ouvrante des méthodes Génère un avertissement Supprime les avertissements pour une null assignation possible
annotations Désactivé Désactivé N’accepte pas les valeurs NULL, sauf si elles sont déclarées avec ? Déclare un type Nullable N’a aucun effet

Les variables de type référence dans le code compilé avant C# 8, ou dans un contexte désactivé sont Nullable-oublie. Vous pouvez assigner un null littéral ou une variable peut-être null à une variable qui accepte les valeurs NULL oublie. Toutefois, l’État par défaut d’une variable Nullable-oublie n’est pas null.

Vous pouvez choisir le paramètre le mieux adapté à votre projet :

  • Choisissez désactivé pour les projets hérités que vous ne souhaitez pas mettre à jour en fonction des diagnostics ou des nouvelles fonctionnalités.
  • Choisissez avertissements pour déterminer l’emplacement où votre code peut lever des System.NullReferenceException s. Vous pouvez résoudre ces avertissements avant de modifier le code pour activer les types de référence non Nullable.
  • Choisissez Annotations pour exprimer votre intention de conception avant d’activer les avertissements.
  • Choisissez activé pour les nouveaux projets et projets actifs dans 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 contextes n’importe où dans votre code source. Ils sont particulièrement utiles lorsque vous migrez un code base volumineux.

  • #nullable enable: Définit le contexte d’annotation Nullable et le contexte d’avertissement Nullable sur activé.
  • #nullable disable: Définit le contexte d’annotation Nullable et le contexte d’avertissement Nullable sur désactivé.
  • #nullable restore: Restaure le contexte d’annotation Nullable et le contexte d’avertissement Nullable aux paramètres du projet.
  • #nullable disable warnings: Affectez la valeur désactivé au contexte d’avertissement Nullable.
  • #nullable enable warnings: Affectez la valeur activé au contexte d’avertissement Nullable.
  • #nullable restore warnings: Restaure le contexte d’avertissement Nullable aux paramètres du projet.
  • #nullable disable annotations: Affectez la valeur désactivé au contexte d’annotation Nullable.
  • #nullable enable annotations: Affectez la valeur activé au contexte d’annotation Nullable.
  • #nullable restore annotations: Restaure le contexte d’avertissement d’annotation aux paramètres du projet.

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

Contexte d’avertissement Contexte d’annotation Utilisation
valeur par défaut du projet valeur par défaut du projet Default
enabled disabled Corriger les avertissements d’analyse
enabled valeur par défaut du projet Corriger les avertissements d’analyse
valeur par défaut du projet enabled Ajouter des annotations de type
enabled enabled Code déjà migré
disabled enabled Annoter le code avant de résoudre les avertissements
disabled disabled Ajout de code hérité à un projet migré
valeur par défaut du projet disabled Exceptionnel
disabled valeur par défaut du projet Exceptionnel

Ces neuf combinaisons vous offrent un contrôle affiné sur les diagnostics émis par le compilateur pour votre code. Vous pouvez activer plus 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 à résoudre.

Important

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 Nullable est désactivé pour tout fichier source marqué comme généré. Cela signifie que toutes les API dans les fichiers générés ne sont pas annotées. Un fichier est marqué comme généré de quatre façons :

  1. Dans le fichier. 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. Démarrer le nom de fichier avec TemporaryGeneratedFile_
  4. Terminez le nom de fichier avec . Designer. cs, . generated. cs, . g. cs ou . g. i. cs.

Les générateurs peuvent s’abonner à l’aide de la #nullable directive de préprocesseur.

Par défaut, l’annotation Nullable et les contextes d’avertissement sont désactivés. Cela signifie que votre code existant est compilé sans modification et sans générer de nouveaux avertissements. À compter de .NET 6, les nouveaux projets incluent l' <Nullable>enable</Nullable> élément dans tous les modèles de projet.

Ces options fournissent deux stratégies distinctes pour mettre à jour une base de code existante afin d’utiliser des types de référence Nullable.

Pièges connus

Les tableaux et les structs qui contiennent des types référence sont des pièges connus dans les références Nullable et l’analyse statique qui détermine la sécurité des valeurs NULL. Dans les deux cas, une référence qui n’accepte pas les valeurs NULL peut être initialisée à null , sans générer d’avertissement.

Structures

Un struct qui contient des types référence n’acceptant pas les valeurs NULL permet de l’assigner default sans avertissement. Prenez l’exemple suivant :

using System;

#nullable enable

public struct Student
{
    public string FirstName;
    public string? MiddleName;
    public string LastName;
}

public static class Program
{
    public static void PrintStudent(Student student)
    {
        Console.WriteLine($"First name: {student.FirstName.ToUpper()}");
        Console.WriteLine($"Middle name: {student.MiddleName?.ToUpper()}");
        Console.WriteLine($"Last name: {student.LastName.ToUpper()}");
    }

    public static void Main() => PrintStudent(default);
}

Dans l’exemple précédent, aucun avertissement n’est présent dans PrintStudent(default) les types référence non Nullable FirstName et LastName est null.

Un autre cas plus courant est lorsque vous traitez des structs génériques. Prenez l’exemple suivant :

#nullable enable

public struct Foo<T>
{
    public T Bar { get; set; }
}

public static class Program
{
    public static void Main()
    {
        string s = default(Foo<string>).Bar;
    }
}

Dans l’exemple précédent, la propriété Bar va être null au moment de l’exécution et elle est assignée à une chaîne non Nullable sans avertissement.

Tableaux

Les tableaux sont également un piège connu dans les types de référence Nullable. Prenons l’exemple suivant qui ne génère pas d’avertissements :

using System;

#nullable enable

public static class Program
{
    public static void Main()
    {
        string[] values = new string[10];
        string s = values[0];
        Console.WriteLine(s.ToUpper());
    }
}

Dans l’exemple précédent, la déclaration du tableau indique qu’elle contient des chaînes qui n’acceptent pas les valeurs NULL, tandis que ses éléments sont tous initialisés à null . Ensuite, s une valeur est assignée à la variable null (le premier élément du tableau). Enfin, la variable s est déréférencée et provoque une exception Runtime.

Voir aussi