Cet article fait partie d’une série d’articles sur les apports fonctionnels de C# 8.0.
Référence nullable vs référence non-nullable
Activer l’option de configuration Nullable
Se prémunir contre les valeurs nulles
Opérateur !. (null-forgiving)
Autres opérateurs contre les NullReferenceException
Opérateur ?. (null-conditional)
Opérateur ?[] (null-conditional)
Opérateur ?? (null-coalescing)
Opérateur ??=
Cette fonctionnalité fait partie des fonctionnalités les plus importantes de C# 8.0. Elle vise à éviter que la référence d’un objet soit nulle par inadvertance.
En C#, les objets de type référence sont manipulés en utilisant une référence permettant d’atteindre l’objet stocké dans le tas managé. Les opérations les plus courantes sur une référence sont:
- L’initialisation d’une nouvelle référence faite par copie d’une autre référence (la référence est copiée mais pas l’objet en mémoire) ou en utilisant l’opérateur
new
pour instancier un nouvel objet:Circle circle = new Circle();
La classe
Circle
est:public class Circle { public int Radius; public void UpdateRadius(int newRadius) { this.radius = newRadius; } }
- Le passage d’une référence en argument d’une fonction: lors de l’appel de la fonction, une copie de la référence est effectuée si elle est passée en argument (sauf si on utilise le mot-clé
ref
). - Le déférencement: cette opération permet de déférencer une référence pour utiliser l’objet dont elle fait référence en mémoire. Le déférencement permet, par exemple, d’appeler une méthode de l’objet ou d’accéder à une propriété:
Circle circle = new Circle(); circle.UpdateRadius(3); // déférencement pour appeler une fonction circle.Radius = 5; // déférencement pour accéder à une propriété
En C#, l’initialisation, l’affectation, le passage de référence sont des opérations réalisables avec une référence nulle. Le déférencement ne l’est pas, la référence nulle ne pointant sur aucun objet en mémoire. C’est ce dernier cas qui provoque le plus d’erreurs d’inadvertance puisqu’elles provoquent des erreurs de type NullReferenceException
. Tony Hoare, qui est le scientifique lauréat du prix Turing à l’origine des références nulles avait qualifié son invention d’erreur à un milliard de dollars à cause de toutes les erreurs que les références nulles ont pu provoquer.
Les références nullables est une fonctionnalité de C# 8.0 visant à empêcher l’utilisation de références nulles en générant des warnings à la compilation lorsque des références potentiellement nulles sont détectées dans le code. Une référence est potentiellement nulle si elle n’est pas initialisée avec une valeur différente de null
ou si elle est affectée par la valeur null
.
Les warnings générés par le compilateur à la suite d’utilisation de référence nulles ne sont pas bloquants. Il est possible de les rendre bloquant en transformant ces warnings en erreurs en activant l’option TreatWarningsAsErrors
. Pour activer cette option:
- Dans un projet .NET Core: il faut éditer le fichier
.csproj
du projet et ajouter le nœud<TreatWarningsAsErrors>
:<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp3.1</TargetFramework> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> </PropertyGroup> </Project>
- Dans Visual Studio, il faut éditer les options du projet:
Dans les propriétés du projet ⇒ Onglet “Build” ⇒ dans la partie “TreatWarningsAsErrors” ⇒ sélectionner “All”.
Cette option permettra de générer des erreurs bloquantes à la compilation au lieu des warnings.
Référence nullable vs référence non-nullable
Par défaut, le comportement du compilateur est le même que pour les versions précédents C# 8.0 c’est-à-dire:
- Le compilateur n’affiche pas de warnings à la compilation dans le cas où une référence est assignée avec
null
ou si un déférencement est effectué pour une référence potentiellement nulle. - Les références nullables ne sont pas possibles: les objets de type valeur nullables (i.e. nullable value types) sont apparus en C# 2.0. Ils sont notés avec la syntaxe
<type>?
par exempleint?
pour un entier nullable. Cette fonctionnalité n’était valable que pour les objets de type valeur et non les objets de type référence (puisqu’une référence peut être nulle).
A partir de C# 8.0, il est possible de créer des références nullables de la même façon que les objets de type valeur avec la notation <type>?
, par exemple:
Circle? Circle = null;
Le compilateur considère ainsi 2 types de références:
- Les références non-nullables: ce sont les références normales, elles sont appelées “non-nullables” toutefois, par défaut sans modification de la configuration, elles peuvent être assignées avec la valeur nulle.
- Les références nullables: elles sont déclarées avec la notation
<type>?
et elles sont nullables comme les références non-nullables toutefois, par défaut, elles génèrent des warnings si l’optionNullable
n’est pas activée:Warning CS8632: The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
Ces 2 types de référence prennent leur sens si on active l’option Nullable
.
Activer l’option de configuration Nullable
Cette option permet de changer le comportement du compilateur vis-à-vis des références nullables et non-nullables. Il existe plusieurs niveaux de d’activation de cette option. Pour l’activer, il faut ajouter enable
, warnings
ou annotations
dans un nœud Nullable
dans le fichier .csproj
du projet:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Les différents niveaux d’activation de cette option peuvent être résumés de cette façon:
Niveau | Comportement général | Référence nullable | Référence non-nullable |
---|---|---|---|
enable |
Les références nullables sont utilisables et des warnings sont générés si des références sont potentiellement nulles. |
|
|
warnings |
Les références nullables génèrent des warnings et des warnings sont générés si des références sont potentiellement nulles. |
|
|
annotations |
Les références nullables sont utilisables et les warnings ne sont pas générés si des références sont potentiellement nulles |
Pas de warnings | Pas de warnings |
disable (valeur par défaut) |
Les références nullables génèrent un warning et des warnings ne sont pas générés si des références sont potentiellement nulles. |
|
Contexte nullable
Le contexte nullable correspond au contexte dans lequel le code est compilé. Ce contexte permet au compilateur de savoir:
- quelles sont les règles à appliquer pour vérifier la syntaxe,
- si des références sont nullables et
- si des références peuvent contenir une valeur nulle et sont déférencées.
Le contexte nullable comporte 2 contextes sous-jacents:
- Le contexte d’annotation nullable dans lequel l’utilisation des références nullables est possible et ne provoque pas de warnings.
- Le contexte des warnings pour les références nulles dans lequel des warnings sont générés dans le cas où:
- Une référence non-nullable est affectée avec la valeur
null
. - Une référence non-nullable est potentiellement nulle et est déférencée.
- Une référence non-nullable est affectée avec la valeur
Il existe 2 méthodes pour indiquer ces contextes:
- Par configuration: en indiquant le paramètre
Nullable
au niveau du fichier.csproj
. De cette façon, on indique le contexte nullable dans tout le projet.Si on reprend le tableau plus haut, on obtient:
Configuration Contexte d’annotation nullable Contexte des warnings pour les références nulles Référence nullable Référence non-nullable enable
Activé Activé OK
Warning en cas de déférencement d’une valeur nulleWarnings si nulle warnings
Désactivé Non autorisé (provoque un warning) annotations
Activé Désactivé OK
Pas de warnings en cas de déférencement d’une valeur nullePas de warnings si nulle disable
(valeur par défaut)Désactivé Non autorisé (provoque un warning) - Par code: la configuration permet d’indiquer un paramétrage pour tout le projet, ensuite il est possible d’affiner au niveau du code pour indiquer un contexte sur une portion de code. Dans le code, on peut indiquer un contexte sur une portion avec les annotations suivantes:
Annotation Contexte d’annotation nullable Contexte des warnings pour les références nulles #nullable enable
Activé Activé #nullable disable
Désactivé Désactivé #nullable restore
Les contextes sont ceux indiqués dans la configuration du projet #nullable enable warnings
Pas de changement Activé #nullable disable warnings
Désactivé #nullable restore warnings
Contexte indiqué dans la configuration du projet #nullable enable annotations
Activé Pas de changement #nullable disable annotations
Désactivé #nullable restore annotations
Contexte indiqué dans la configuration du projet
Se prémunir contre les valeurs nulles
La syntaxe C# prévoit quelques opérateurs pour se prémunir contre les valeurs nulles.
Opérateur !. (null-forgiving)
Cet opérateur utilisable à partir de C# 8.0, est autorisé dans un contexte d’annotation nullable (c’est-à-dire quand il est possible d’utiliser des références nullables <nom variable>?
). Il vise à éviter d’avoir le warning correspondant au déférencement d’une référence nullable potentiellement nulle et quand le compilateur ne peut pas déterminer si la référence est nulle ou non. Il n’a pas d’incidence sur l’exécution du code.
Par exemple, si on exécute le code suivant avec les warnings pour les références nulles activés:
int intValue = 5;
Circle? nullableRef = null;
if (intValue > 4)
nullableRef = new Circle{ Radius = 3 };
Console.WriteLine(nullableRef.Radius); // Warning
A la compilation, ce code produit le warning suivant indiquant un déférencement de la référence nullableRef
qui pourrait être nulle:
warning CS8602: Deference of a possible null reference.
Pour éviter ce warning, on peut:
- Désactiver les warnings pour les références nulles en modifiant le code de cette façon:
#nullable disable warnings Console.WriteLine(nullableRef.Radius); // Pas de warning #nullable enable warnings
- Utiliser l’opérateur null-forgiving:
Console.WriteLine(nullableRef!.Radius);
Comme indiqué plus haut l’opérateur null-forgiving ne protège pas d’erreurs en cas de déférencement d’une référence nullable effectivement nulle:
Circle? nullableRef = null;
Console.WriteLine(nullableRef!.Radius); // NullReferenceException
D’autres opérateurs permettent d’éviter des erreurs provenant de références nulles.
Autres opérateurs contre les NullReferenceException
Pour se prémunir des NullReferenceException
, on peut utiliser les opérateurs (la plupart de ces opérateurs existent avant C# 8.0):
Opérateur ?. (null-conditional)
L’opérateur null-conditional ?.
(à partir de C# 6) s’utilise avec la syntaxe circle?.Radius
.
Le comportement est:
- si
circle
contientnull
alors la valeur decircle?.Radius
estnull
. - si
circle
est différent denull
alorscircle?.Radius
correspond à la valeur deRadius
.
Opérateur ?[] (null-conditional)
L’opérateur null-conditional ?[]
(à partir de C# 6) s’utilise avec des objets contenant des index.
Par exemple dans le cas d’un tableau:
Circle[] circles = new Circle[] { new Circle(), null };
Console.WriteLine(circles?[1].Radius);
Le comportement est:
- si
circles[1]
contientnull
alors la valeur decircles?[1].Radius
estnull
. - si
circles[1]
est différent denull
alorscircles?[1].Radius
correspond à la valeur deRadius
.
Opérateur ?? (null-coalescing)
L’opérateur ??
permet d’évaluer si une variable est nulle.
Cet opérateur s’utilise de cette façon:
<variable à évaluer> ?? <expression si la variable est nulle>
Ainsi:
- Si
<variable à évaluer>
contientnull
alors l’expression retournée est<expression si la variable est nulle>
. - Si
<variable à évaluer>
est différent denull
alors l’expression retournée est la valeur de<variable à évaluer>
.
Par exemple:
Circle firstCircle = new Circle{ Radius = 1};
Circle secondCircle = null;
var radius = (secondCircle ?? firstCircle).Radius;
Console.WriteLine(radius); // 1 c'est-à-dire la valeur de firstCircle.Radius
secondCircle = new Circle{ Radius = 2};
radius = (secondCircle ?? firstCircle).Radius;
Console.WriteLine(radius); // 2 c'est-à-dire la valeur de secondCircle.Radius
A partir de C# 8.0, pour utiliser l’opérateur ??
la variable à évaluer ne peut pas être un objet de type valeur non nullable.
Opérateur ??=
L’opérateur ??=
permet d’affecter le résultat d’une expression à une variable si cette variable contient null
(à partir de C# 8.0).
La syntaxe est du type <variable à évaluer> ??= <expression à affecter si variable null>
.
Par exemple:
Circle firstCircle = new Circle{ Radius = 1 };
Circle? secondCircle = null;
secondCircle ??= firstCircle;
Console.WriteLine(secondCircle.Radius); // 1
Dans ce code secondCircle ??= firstCircle
est équivalent à:
if (secondCircle == null)
secondCircle = firstCircle;
- What’s new in C# 8.0: https://docs.microsoft.com/fr-fr/dotnet/csharp/whats-new/csharp-8#nullable-reference-types
- Migrate existing code with nullable reference types: https://docs.microsoft.com/fr-fr/dotnet/csharp/tutorials/upgrade-to-nullable-references
- Nullable reference types: https://docs.microsoft.com/fr-fr/dotnet/csharp/nullable-references
- Member access operators and expressions: https://docs.microsoft.com/fr-fr/dotnet/csharp/language-reference/operators/member-access-operators
- Null-conditional (?. and ?[]) and null-coalescing (??) operators: https://csharp.today/c-6-features-null-conditional-and-and-null-coalescing-operators/
- ?? and ??= operators: https://docs.microsoft.com/fr-fr/dotnet/csharp/language-reference/operators/null-coalescing-operator