[C#] Connaissez-vous l’opérateur de Null-coalescence ?

Commençons par un opérateur plus simple

Bon, dit comme ça, c’est sûr, ça rebute un peu son développeur. On va commencer par l’opérateur ternaire. Vous avez certainement quelque part dans votre code (ou dans le code que vous maintenez) des tas d’instructions comme celles-ci :

if (Valeur != null)
    Resultat = Valeur;
else
    Resultat = string.Empty;

Vous savez certainement que l’opérateur ternaire vous permet d’écrire ceci de manière plus concise :

Resultat = Valeur != null ? Valeur : string.Empty;

La condition peut bien sûr porter sur n’importe quelle autre variable, et même dans le cas où on traite la variable Valeur, on pourrait tester qu’elle contient une chaîne ou qu’on peut la parser pour obtenir un autre type. Mais il est très courant tout de même qu’on teste sa nullité comme dans l’exemple ci-dessus, et dans ce cas, on peut utiliser une grammaire encore plus simplifiée, à savoir :

Resultat = Valeur ?? string.Empty;

L’opérateur de null-coalescence

?? est l’opérateur de coalescence à null, c’est-à-dire qu’il va renvoyer la variable sur laquelle il s’applique si celle-ci est différente de null, et sinon la valeur par défaut qui est passée ensuite.

S’il y a un cas où ceci s’applique souvent, c’est bien sur la gestion des types Nullable. Voici un exemple de variable Nullable<int> qu’on va convertir en entier grâce à l’opérateur de null-coalescence :

int? Test = 25;
int TestEntier = Test ?? 0;

Lors des premières utilisations, la grammaire peut sembler difficile à lire, mais on s’y habitue vite. Et surtout, cette écriture a un énorme avantage, en ce sens qu’elle permet de ne plus avoir à se poser la question du code le plus expressif entre les deux versions suivantes :

Resultat = Valeur != null ? Valeur : string.Empty;
Resultat = Valeur == null ? string.Empty : Valeur;

Avec l’opérateur ??, la valeur par défaut est toujours à la fin, et on s’habitue au fur et à mesure à chercher toujours l’opérateur par défaut à la fin. De même, si on chaîne cet opérateur, on obtient une instruction très expressive :

Resultat = Valeur1 ?? Valeur2 ?? Valeur3 ?? string.Empty;

Ce code est l’équivalent de ceci :

if (Valeur1 != null)
    Resultat = Valeur1;
else if (Valeur2 != null)
    Resultat = Valeur2;
else if (Valeur3 != null)
    Resultat = Valeur3;
else
    Resultat = string.Empty

Il est indéniable que la première grammaire est beaucoup plus simple à lire : en parcourant de gauche à droite, le résultat prendra la première valeur non nulle, et sinon la toute dernière valeur.

Un dernier bout de code pour faire voir l’apport important de l’opérateur ??. Si vous utilisez cet opérateur pour supprimer le caractère Nullable d’un type, comme on l’a vu dans l’exemple plus haut, ce n’est pas seulement le code suivant que vous remplacez :

if (Test != null)
    TestEntier = Test;
else
    TestEntier = 0;

Et ce pour une bonne raison : le code précédent ne compile pas ! Il faut en effet écrire :

if (Test != null)
    TestEntier = (int)Test;
else
    TestEntier = 0;

Et oui, le cast est obligatoire, et l’opérateur de null-coalescence vous en dispense. Bref, ce n’est pas qu’un raccourci d’écriture, mais réellement un opérateur à part entière.

Digression sur les Nullable

Tant qu’on en est à parler des Nullable, vous vous êtes déjà demandés ce que renvoyait ce code ?

int? Test = 25;
Console.WriteLine(Test.GetType().FullName);

Il renvoie “System.Int32”, alors qu’on s’attendait à quelque chose comme “System.Nullable”, non ? Le code ci-dessous, par exemple, renvoie “System.Collections.Generic.List`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]” :

List<string> Liste = new List<string>();
Console.WriteLine(Liste.GetType().FullName);

Et du coup, il faut appeler le code ci-dessous pour obtenir le type sur lequel se base la collection générique, à savoir “System.String” dans notre exemple :

Console.WriteLine(Liste.GetType().GetGenericArguments()[0].FullName);

Les Nullable<> ne fonctionnent pas de la même manière. Si vous appelez GetType() sur une variable, vous aurez toujours le type valeur. Si vous voulez retrouver l’information comme quoi le type de la variable “Test” est Nullable, le seul moyen est de partir du type lui-même :

Console.WriteLine(typeof(int?).IsGenericType + Environment.NewLine);
Console.WriteLine(typeof(int?).FullName + Environment.NewLine);
Console.WriteLine(typeof(int?).GetGenericTypeDefinition().FullName + Environment.NewLine);
Console.WriteLine(typeof(int?).GetGenericArguments()[0].FullName + Environment.NewLine);

Ce code renverra les valeurs ci-dessous :

image

Tout ceci n’est évidemment pas sans lien avec l’inutilité du cast dont je parlais plus haut, ou avec le fait que le mot clé is fonctionne sur un Nullable<int> avec un opérateur int. Ou de manière plus technique, le code ci-dessous affichera true :

Console.WriteLine(Test is int);

Voilà une petite astuce sur quelques lignes, et qui finit par faire un article complet.

[Edit]

Je rajoute à la main les commentaires, suite migration de mon ancienne plateforme de blog à celle-ci.

Paslatek :

Très pratique ce p’tit opérateur ! Merci pour l’astuce/article.
Juste histoire de chipoter sur le dernier code avant la partie sur les nullables j’aurais plutôt fait ça pour eviter le cast :
if (Test.HasValue)
    TestEntier = Test.Value;
else
    TestEntier = 0;
mais le ?? me semble bien plus pratique effectivement !

About JP Gouigoux

Jean-Philippe Gouigoux est Architecte Logiciel, MVP Connected Systems Developer. Il intervient régulièrement à l'Université de Bretagne Sud ainsi qu'à l'Agile Tour. Plus de détails sur la page "Curriculum Vitae" de ce blog.
This entry was posted in .NET, C# and tagged . Bookmark the permalink.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha Captcha Reload