null signifie « pas de valeur ». Les types référence (classes, chaînes, tableaux) ont toujours pu le contenir. Les types valeur (int, decimal, bool, DateTime, structs) ne le peuvent pas, sauf si vous les rendez nullables avec ?. C# fournit alors un petit ensemble d'opérateurs pour travailler avec des valeurs qui peuvent manquer : ??, ??= et ?..
Types valeur nullables : int?
Ajoutez ? à un type valeur pour autoriser null. int? est un raccourci pour la struct Nullable<int>, qui contient une valeur plus un indicateur de présence de cette valeur :
Sortie :
False
True
True
5.50
0
3
Rated 4 stars
4
Les sources typiques de types valeur nullables sont les champs de formulaire facultatifs, les paramètres de requête et les colonnes NULL d'une base de données : un Order avec un DateTime? ShippedAt n'est pas expédié tant que la valeur est null. L'alternative, une valeur magique comme -1 ou DateTime.MinValue, ressemble à une vraie donnée et finit tôt ou tard par être utilisée comme telle.
Un int? ne se convertit en int qu'explicitement, car la conversion peut échouer. Ce qui nous amène à la seule exception que lèvent les types valeur nullables.
« Nullable object must have a value »
Lire .Value quand il n'y a pas de valeur, ou caster un int? null en int, lève InvalidOperationException :
Sortie :
InvalidOperationException: Nullable object must have a value.
0
0
not stocked
stock is int count est un pattern : il teste la présence d'une valeur et la déballe dans count en une seule étape, ce qui se lit mieux que HasValue suivi de Value. La page sur le pattern matching présente d'autres formes.
Arithmétique et comparaisons avec null
Les opérateurs sur les types valeur nullables sont relevés (lifted) : si l'un des opérandes est null, le résultat est null. Les comparaisons d'ordre (<, >, <=, >=) sont l'exception : elles renvoient false plutôt que null. (L'autre exception est bool? avec & et |, où null & false vaut false.) Par exemple :
Sortie :
True
[]
False
False
True
True
a > 5 et a <= 5 sont tous deux faux, donc !(a > 5) n'équivaut pas à a <= 5 quand a est null. Du code comme if (!(age >= 18)) Deny(); traite un âge manquant comme mineur, ce qui correspond ou non à votre intention. Décidez d'abord ce que null doit signifier, en général avec ??, puis comparez.
L'opérateur de fusion null ??
a ?? b vaut a quand a n'est pas null, et b sinon. Il fonctionne sur les types valeur nullables et sur les types référence, et s'enchaîne de gauche à droite :
Sortie :
dark
16px
20
ANONYMOUS
La partie droite n'est évaluée que si nécessaire, donc cache ?? LoadFromDatabase() n'interroge pas la base de données quand le cache a une valeur. ?? accepte aussi une expression throw, ce qui donne une garde compacte (voir throw) :
this.repository = repository ?? throw new ArgumentNullException(nameof(repository));
??= (C# 8)
x ??= y affecte y à x seulement si x est null. C'est l'idiome de l'initialisation paresseuse :
private List<string> tags;
public void AddTag(string tag)
{
tags ??= new List<string>(); // C# 8 and later
tags.Add(tag);
}
Avant C# 8, écrivez tags = tags ?? new List<string>(); ou if (tags == null) tags = new List<string>();.
L'opérateur conditionnel null ?.
a?.Member n'évalue Member que si a n'est pas null ; sinon toute l'expression vaut null. Il transforme une chaîne de tests de null en une seule expression :
Sortie :
Braga
(no city)
(no customer)
0
True
handler: saved
Détails à connaître :
- L'opérateur court-circuite le reste de la chaîne : dans
nobody?.Address?.City, dès quenobodyest null, rien de ce qui est à droite ne s'exécute. - Quand le membre renvoie un type valeur, le résultat devient nullable :
Orders?.Countest unint?, c'est pourquoi il faut?? 0pour retrouver unint. ?.ne peut pas être la cible d'une affectation avant C# 14 :customer?.Name = "x"ne compile pas dans les versions antérieures.handler?.Invoke(...)est la façon standard de déclencher un événement ou d'appeler un callback facultatif.
Utilisez ?. là où null est un état légitime. En parsemer le code pour éviter les exceptions cache le bug qui a produit le null inattendu, et le programme continue avec des données manquantes au lieu d'échouer là où se trouve le problème.
Tester null
if (customer == null) { ... } // classic
if (customer is null) { ... } // C# 7 constant pattern
if (customer != null) { ... }
if (customer is not null) { ... } // C# 9
if (customer is { } c) { ... } // C# 8 property pattern: not null, and named
is null effectue toujours un vrai test de null, alors que == appelle la surcharge d'opérateur du type s'il en a une, si bien qu'une surcharge mal écrite peut faire mentir x == null. Pour les arguments qui ne doivent pas être null, échouez tôt :
public void Ship(Order order)
{
ArgumentNullException.ThrowIfNull(order); // .NET 6+
// ...
}
Types référence nullables (C# 8)
Ajouter ? à un type valeur change le type. Ajouter ? à un type référence, depuis C# 8, ne change que ce que vérifie le compilateur. Avec la fonctionnalité activée (<Nullable>enable</Nullable> dans le fichier projet, la valeur par défaut des nouveaux projets depuis .NET 6), string signifie « ne devrait jamais être null » et string? signifie « peut être null », et le compilateur avertit quand le code ne correspond pas :
#nullable enable
public class User
{
public string Email { get; set; } // warning CS8618: Non-nullable property 'Email'
// must contain a non-null value when exiting constructor.
public string? Nickname { get; set; } // allowed to be null
public int NicknameLength() => Nickname.Length;
// warning CS8602: Dereference of a possibly null reference.
public int SafeLength() => Nickname?.Length ?? 0; // no warning
}
string? input = Console.ReadLine();
string name = input; // warning CS8600: Converting null literal or possible
// null value to non-nullable type.
string checkedName = input ?? "guest"; // no warning
int len = input!.Length; // ! tells the compiler "trust me": no warning
Le compilateur suit vos vérifications à travers la méthode : après if (input != null), input compte comme non null. L'opérateur ! (null-forgiving) fait taire un avertissement quand vous en savez plus que l'analyse ; chacun est une affirmation que le compilateur ne peut pas vérifier, donc gardez-les rares.
Deux faits dissipent l'essentiel de la confusion. D'abord, ce ne sont que des avertissements : le programme s'exécute de la même façon, et une string peut toujours contenir null à l'exécution si elle vient de code sans annotations. Ensuite, string? n'est pas Nullable<string> ; il n'a pas de HasValue.
Erreurs courantes
.Valuesans vérification. LèveInvalidOperationExceptionquand la valeur est null. Utilisez??,GetValueOrDefault()ouis int n.- Supposer que
!(x > 5)signifiex <= 5. Pas quandxest null : toute comparaison d'ordre avec null est fausse. - Couvrir chaque null avec
?.. Cela fait taire le symptôme et éloigne l'échec de sa cause. - Traiter les avertissements de référence nullable comme du bruit. Chacun signale une ligne qui peut lever
NullReferenceException. Corrigez le flux ou déclarez l'intention avec?. - Des valeurs magiques au lieu de null.
-1,0etDateTime.MinValuepour « inconnu » ressemblent à de vraies données. Utilisezint?etDateTime?.
Questions fréquentes
Que signifie int? en C# ?
int? est un raccourci pour Nullable<int> : un int qui peut aussi valoir null. Les types valeur comme int, decimal, bool et DateTime contiennent normalement toujours une valeur ; ajouter ? leur donne un état « pas de valeur », ce dont a besoin un champ de formulaire absent ou une colonne NULL de base de données. Vérifiez-le avec HasValue ou != null et lisez-le avec Value ou GetValueOrDefault().
Que fait ?? en C# ?
a ?? b est l'opérateur de fusion null : il vaut a si a n'est pas null, sinon b. string name = input ?? "guest"; fournit une valeur de repli en une seule expression. Il s'enchaîne (a ?? b ?? c prend la première valeur non nulle) et b n'est évalué que si nécessaire. ??= (C# 8) n'affecte la partie droite que lorsque la variable est null.
Que fait ?. en C# ?
a?.B est l'opérateur conditionnel null : si a est null, toute l'expression vaut null et B n'est jamais évalué, donc pas de NullReferenceException. Il s'enchaîne (order?.Customer?.Address?.City) et fonctionne pour les appels de méthode (logger?.Log(...)), les événements (Changed?.Invoke(...)) et les indexeurs (items?[0]). Combinez-le avec ?? pour une valeur par défaut : order?.Customer?.Name ?? "unknown".
Qu'est-ce qui provoque « Nullable object must have a value » ?
Lire .Value d'un type valeur nullable qui vaut null, ou le caster vers le type non nullable ((int)maybe), lève InvalidOperationException avec ce message. Vérifiez d'abord HasValue, ou utilisez GetValueOrDefault(), ?? fallback ou un pattern : if (maybe is int n) { ... }.
Comment tester null en C# ?
x == null et x is null fonctionnent tous deux ; is null (C# 7) ne peut pas être perturbé par un type qui surcharge ==, c'est pourquoi beaucoup de bases de code le préfèrent. La négation est x != null, ou x is not null en C# 9. Pour rejeter les arguments null, utilisez ArgumentNullException.ThrowIfNull(x) (.NET 6) ou _ = x ?? throw new ArgumentNullException(nameof(x)); dans du code plus ancien.
Que sont les types référence nullables en C# ?
Une fonctionnalité du compilateur de C# 8, activée avec <Nullable>enable</Nullable> (la valeur par défaut des nouveaux projets .NET 6+). Quand elle est active, string signifie « jamais null » et string? signifie « peut être null », et le compilateur avertit quand vous risquez de déréférencer null (CS8602) ou d'affecter null à une variable non nullable (CS8600). Elle ne change que les avertissements, pas le comportement à l'exécution.