En regardant cette vidéo sur les nouveautés de C# 6, j'ai découvert qu'arrivait l'opérateur "null conditional" (?.), très pratique pour gérer les références nulles. Je me suis dit que c'était l'occasion de regarder un peu tout ça.

Commençons par le commencement

En .net, nous avons des types valeurs, et des types référence. Une variable de type valeur enregistre directement son contenu en mémoire (c'est le cas des types int ou DateTime par exemple), alors qu'un type référence enregistre une adresse mémoire, où est stockée la valeur voulue.

Si un type valeur est forcément défini (une valeur 0 pour un type int représente bien 0), ce n'est pas forcément le cas pour un type référence : une valeur 0 ne représente aucune adresse mémoire, et donc aucun objet. C'est la valeur spéciale null.

Comment gérer les variables null

Le problème de null, c'est qu'on ne peut pas appeler ses propriétés, au risque de se retrouver avec la fameuse NullReferenceException, on se retrouve donc à vérifier systématiquement nos variables.

public int GetStringLength(String value)
{
    if (value == null)
    {
        return 0;
    }
    return value.Length;
}

Le code peut donc être parfois assez lourd, pour finalement pas grand chose. On a plusieurs opérateurs C# qui vont nous permettre d'alléger notre code. On a par exemple l'opérateur ternaire (condition ? valeur si vrai : valeur si faux).

public int GetStringLength(String value)
{
    return value != null ? value.Length : 0;
}

Un autre opérateur utile est l'opérateur de fusion ??. Cet opérateur permet de récupérer la variable si elle est différente de null, ou une valeur par défaut.

public int GetStringLength(String value)
{
    var valueOrDefault = value ?? string.Empty;
    return valueOrDefault.Length;
}

Malgré tout, on peut se retrouver avec des conditions assez lourdes si on veut chainer les appels aux propriétés d'un objet.

public int GetSubpropertyLength(CustomViewModel model)
{
    if (model != null
        && model.Data != null
        && model.Data.Title != null)
    {
        return model.Data.Title.Length;
    }

    return 0;
}

Le nouvel opérateur ?.

Avec C# 6, un nouvel opérateur arrive, qui va nous permettre de gérer ce dernier cas plus facilement : l'opérateur "null-conditional" ?.

Au lieu d'écrire model.Data, on va utiliser model?.Data. La différence : si model est null, l'accès à model.Data lèvera une exception, alors que model?.Data renverra null.

Et on peut le chainer sans problème

String title = model?.Data?.Title;

Si model est null, on renvoie null ; si model.Data est null, on renvoie null ; sinon on renvoie model.Data.Title (qui peut être null ou non).

Si la propriété cible est un type valeur, l'expression ne pourra pas renvoyer null pour ce type, le type sera donc un Nullable. Donc, pour récupérer la propriété Length, le type sera Nullable<int> (ou int?) :

int? length = model?.Data?.Title?.Length;

Avec C# 6, on peut réécrire la méthode GetSubpropertyLength de la manière suivante :

public static int? GetSubpropertyLength(CustomViewModel model)
{
    return model?.Data?.Title?.Length;            
}

Ou, si on souhaite définir une valeur par défaut comme à l'origine (et renvoyer un int, au lieu d'un int?) :

public static int GetSubpropertyLength(CustomViewModel model)
{
    return model?.Data?.Title?.Length ?? 0;            
}

Et pour les tableaux ?

J'ai parlé de l'opérateur ?. pour accéder aux propriétés d'un objet, mais il est aussi possible d'accéder aux éléments d'un dictionnaire de la même manière :

public static int GetFirstElementLength(CustomViewModel model)
{
    return model?.Elements?[0]?.Length ?? 0;
}

On vérifie ici dans l'ordre si model est null, si le tableau model.Elements est null, si le premier élément du tableau est null, et si on trouve un élément, on retourne sa longueur.

Et pareil si Elements un dictionnaire :

public static int GetFirstElementLength(CustomViewModel model)
{
    return model?.Elements?["key"]?.Length ?? 0;
}

Attention, ici on ne gère que le cas d'objets null, si vous utilisez une clé non existante dans le tableau, l'exception KeyNotFoundException sera levée.

N'oublions pas Visual Basic

J'ai pris l'exemple de C# tout le long de cet article, mais la version 14 de Visual Basic apporte les mêmes nouveautés. En VB, l'équivalent du null est le mot clé Nothing, à une différence près : en C# la valeur null n'est valable que pour les types référence, en VB Nothing est valide pour les types valeur, et représente la valeur par défaut1.

Pour accéder aux propriétés, on pourra écrire la fonction GetStringLength de cette manière :

Function GetStringLength(ByRef model As CustomViewModel) As Nullable(Of Integer)
    Return model?.Data?.Title?.Length
End Function

Ou utiliser l'opérateur If(a,b) (équivalent du ?? de C#) pour définir la valeur 0 par défaut :

Function GetStringLength(ByRef model As CustomViewModel) As Integer
    Return If(model?.Data?.Title?.Length, 0)
End Function

Et pour un tableau ou un dictionnaire on a la notation model?.Elements?(0) :

Function GetFirstElementLength(ByRef model As CustomViewModel) As Integer
    Return If(model?.Elements?(0)?.Length, 0)
End Function

[1] Merci à Patrice pour la correction ;-)