Comprendre la possibilité de valeur Null
Si vous êtes un développeur .NET, vous avez probablement déjà rencontré l’exception System.NullReferenceException. Cela se produit au moment de l’exécution lorsqu’une variable null
est déréférencée, c’est-à-dire quand une variable est évaluée au moment de l’exécution, mais qu’elle référence null
. Cette exception est de loin l’exception la plus fréquente au sein de l’écosystème .NET. Le créateur de null
, Sir Tony Hoare, fait référence à null
comme « l’erreur à un milliard de dollars ».
Dans l’exemple suivant, la variable FooBar
est attribuée à null
et immédiatement déréférencée, ce qui présente un problème :
// Declare variable and assign it as null.
FooBar fooBar = null;
// Dereference variable by calling ToString.
// This will throw a NullReferenceException.
_ = fooBar.ToString();
// The FooBar type definition.
record FooBar(int Id, string Name);
ce problème devient bien plus difficile à repérer pour les développeurs lorsque les applications évoluent en taille et en complexité. La détection des erreurs potentielles comme celle-ci est une tâche à laisser aux outils, et le compilateur C# est là pour vous aider.
Définition de la sécurité des valeurs Null
Le terme sécurité des valeurs Null définit un ensemble de fonctionnalités spécifiques aux types Nullable qui permettent de réduire le nombre d’occurrences possibles de NullReferenceException
.
Dans l’exemple FooBar
précédent, vous auriez pu éviter l’exception NullReferenceException
en vérifiant que la variable fooBar
était null
avant de la déréférencer :
// Declare variable and assign it as null.
FooBar fooBar = null;
// Check for null
if (fooBar is not null)
{
_ = fooBar.ToString();
}
// The FooBar type definition for example.
record FooBar(int Id, string Name);
Pour faciliter l’identification de scénarios comme celui-ci, le compilateur peut déduire l’intention de votre code et appliquer le comportement souhaité. Toutefois, cela se fait uniquement lorsqu’un contexte pouvant accepter la valeur Null est activé. Avant d’aborder le contexte pouvant accepter la valeur Null, nous allons décrire les types Nullable possibles.
Types Nullable
Avant C# 2.0, seuls les types référence pouvaient accepter la valeur Null. La paire types/valeur comme int
ou DateTime
ne pouvaient pas être null
. Initialisés sans valeur, ces types reviennent à leur valeur default
. Dans le cas d’un int
, il s’agit de 0
. Pour un DateTime
, il s’agit de DateTime.MinValue
.
Les types référence instanciés sans valeurs initiales fonctionnent différemment. La valeur default
pour tous les types référence est null
.
Prenons l’extrait de code C# suivant :
string first; // first is null
string second = string.Empty // second is not null, instead it's an empty string ""
int third; // third is 0 because int is a value type
DateTime date; // date is DateTime.MinValue
Dans l’exemple précédent :
first
estnull
en raison du fait que le type référencestring
a été déclaré, mais qu’aucune affectation n’a été effectuée.second
se voit attribuerstring.Empty
lorsqu’une déclaration a été faite. L’objet n’a jamais eu d’affectationnull
.third
est0
même s’il n’y a pas eu d’attribution. Il s’agit d’unstruct
(type valeur) qui a la valeurdefault
de0
.date
n’est pas initialisé, mais sa valeurdefault
est System.DateTime.MinValue.
À compter de la version C# 2.0, vous pouvez définir des types valeur pouvant accepter la valeur Null à l’aide de Nullable<T>
(ou T?
si vous souhaitez utiliser un raccourci). Cela permet aux types valeur d’accepter la valeur Null. Prenons l’extrait de code C# suivant :
int? first; // first is implicitly null (uninitialized)
int? second = null; // second is explicitly null
int? third = default; // third is null as the default value for Nullable<Int32> is null
int? fourth = new(); // fourth is 0, since new calls the nullable constructor
Dans l’exemple précédent :
first
estnull
, car le type valeur Nullable n’a pas été initialisé.second
se voit attribuernull
lorsqu’une déclaration a été faite.third
estnull
, car la valeurdefault
pourNullable<int>
estnull
.fourth
est0
, car l’expressionnew()
appelle le constructeurNullable<int>
etint
est0
par défaut.
C# 8.0 a introduit les types référence pouvant accepter les valeurs Null, dans lesquels vous pouvez exprimer votre intention qu’un type référence soitnull
ou toujours non-null
. Vous vous dites peut-être ceci : « Je pensais que tous les types référence acceptaient la valeur Null ! ». Et vous avez raison. Cette fonctionnalité vous permet d’exprimer votre intention, que le compilateur tentera ensuite d’appliquer. La même syntaxe T?
exprime qu’un type référence est destiné à accepter la valeur Null.
Prenons l’extrait de code C# suivant :
#nullable enable
string first = string.Empty;
string second;
string? third;
À partir de l’exemple précédent, le compilateur déduit votre intention comme suit :
first
n’est jamaisnull
, car il a été attribué de manière définitive.second
ne doit jamais êtrenull
, même si au départ il s’agissait denull
. Évaluersecond
avant l’attribution d’une valeur entraîne l’avertissement du compilateur, car il n’a pas été initialisé.third
peut êtrenull
. Par exemple, il peut pointer vers unSystem.String
, mais il peut aussi pointer versnull
. L’une ou l’autre de ces variantes est acceptable. Le compilateur vous aide en vous avertissant si vous déréférencezthird
sans vérifier d’abord qu’il n’est pas Null.
Important
Pour pouvoir utiliser la fonctionnalité de types référence pouvant accepter la valeur Null comme indiqué ci-dessus, elle doit se trouver dans un contexte pouvant accepter la valeur Null. Cela est détaillé dans la section suivante.
Contexte pouvant accepter la valeur Null
Les contextes nullables permettent de contrôler précisément comment le compilateur interprète les variables de type référence. Il existe quatre contextes possibles pouvant accepter la valeur Null :
disable
: le compilateur se comporte de la même façon que C# 7.3 et versions antérieures.enable
: le compilateur active toutes les analyses de référence Null et toutes les fonctionnalités de langage.warnings
: le compilateur effectue toutes les analyses de valeurs Null et émet des avertissements quand le code peut déréférencernull
.annotations
: le compilateur n’effectue pas d’analyse des valeurs Null et n’émet pas d’avertissements quand le code peut déréférencernull
, mais vous pouvez toujours annoter votre code à l’aide des types référence?
pouvant accepter la valeur Null et des opérateurs null-indulgent (!
).
Ce module concerne les contextes disable
ou enable
qui peuvent accepter la valeur Null. Pour plus d’informations, consultez Types référence pouvant accepter la valeur Null : contextes pouvant accepter la valeur Null.
Activer des types référence pouvant accepter la valeur Null
Dans le fichier projet C# (.csproj), ajoutez un nœud enfant <Nullable>
à l’élément <Project>
(ou ajoutez à un <PropertyGroup>
existant). Cela permet d’appliquer le contexte enable
pouvant accepter la valeur Null à l’ensemble du projet.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<!-- Omitted for brevity -->
</Project>
Vous pouvez également étendre le contexte pouvant accepter la valeur Null à un fichier C# à l’aide d’une directive de compilateur.
#nullable enable
La directive du compilateur C# précédente fonctionne comme la configuration du projet, à la différence qu’elle s’applique au fichier dans lequel elle réside. Pour plus d’informations, consultez types référence pouvant accepter la valeur Null : contextes pouvant accepter la valeur Null (documentation)
Important
Le contexte pouvant accepter la valeur Null est activé dans le fichier .csproj par défaut dans tous les modèles de projet C# à partir de .NET 6.0 et versions ultérieures.
Lorsque le contexte pouvant accepter la valeur Null est activé, vous obtenez de nouveaux avertissements. Prenons l’exemple FooBar
précédent, qui affiche deux avertissements quand il est analysé dans un contexte pouvant accepter la valeur Null :
La ligne
FooBar fooBar = null;
contient un avertissement sur l’affectationnull
: Avertissement C# CS8600 : conversion d’un littéral ayant une valeur Null ou d’une potentielle valeur Null en type non-nullable.La ligne
_ = fooBar.ToString();
contient également un avertissement. Cette fois-ci, le compilateur vous informe que la valeurfooBar
est peut être Null : Avertissement C# CS8602 : déréférencement d’une référence éventuellement Null.
Important
Il n’y a pas de sécurité des valeurs Null garantie, même si vous réagissez et éliminez tous les avertissements. Dans certains scénarios limités, l’analyse du compilateur aboutira tout de même à une exception NullReferenceException
de runtime.
Résumé
Dans cette leçon, vous avez appris à activer un contexte pouvant accepter la valeur Null en C# pour vous protéger de l’exception NullReferenceException
. Dans la leçon suivante, vous en apprendrez davantage sur l’expression explicite de votre intention dans un contexte pouvant accepter la valeur Null.