Les types de référence Nullable sont apparus en C # il y a trois ans. A cette époque, ils ont trouvé leur public. Mais même ceux qui travaillent avec cette «bête» peuvent ne pas connaître toutes ses capacités. Voyons comment travailler avec ces types plus efficacement.
Introduction
Les types de référence Nullable sont conçus pour aider à créer une architecture d’application meilleure et plus sûre. Au stade de l’écriture du code, il faut comprendre si telle ou telle variable de référence peut être null
ou non, si la méthode peut retourner null
etc.
Il est sûr de dire que chaque développeur a rencontré NRE (NullReferenceException
). Étant donné que cette exception peut être générée au stade du développement, c’est un bon scénario car vous pouvez résoudre le problème immédiatement. C’est bien pire lorsque l’utilisateur trouve le problème lorsqu’il travaille avec le produit. Les types de référence Nullable aident à protéger contre NRE.
Dans cet article, je parlerai d’un certain nombre de fonctionnalités non évidentes liées aux types de référence nullables. Mais cela vaut la peine de commencer par une brève description de ces types.
Référence Nullable
En termes de logique d’exécution de programme, un type de référence nullable n’est pas différent d’un type de référence. La différence entre eux réside uniquement dans l’annotation spécifique du premier. L’annotation permet au compilateur de déterminer si une variable ou une expression particulière peut être null
. Pour utiliser des types de référence nullables, vous devez vous assurer que le contexte nullable est activé pour le projet ou le fichier (je décrirai plus tard comment procéder).
Pour déclarer une variable de référence nullable, ajoutez ‘?’ à la fin du nom du type.
Exemple:
Maintenant la variable str
peut être null
, et le compilateur n’émettra pas d’avertissement pour ce code. Si vous n’ajoutez pas ‘?’ lors de la déclaration d’une variable et de son affectation avec null
un avertissement sera émis.
Il est possible de supprimer les avertissements du compilateur concernant une éventuelle écriture null
à une variable de référence qui n’est pas marquée comme nullable.
Exemple:
object? GetPotentialNull(bool flag)
{
return flag ? null : new object();
}
void Foo()
{
object obj = GetPotentialNull(false);
}
Les obj
la variable ne sera jamais affectée avec null
, mais le compilateur ne le comprend pas toujours. Vous pouvez supprimer l’avertissement comme suit :
object obj = GetPotentialNull(false)!;
En utilisant le ‘!’ opérateur, nous « indiquons » au compilateur que la méthode ne retournera certainement pas null
. Par conséquent, il n’y aura aucun avertissement pour ce fragment de code.
La fonctionnalité disponible lorsque vous travaillez avec des types de référence nullables ne se limite pas à déclarer des variables de ce type (à l’aide de ‘?’) et à supprimer les avertissements avec ‘!.’ Ci-dessous, j’examinerai les fonctionnalités les plus intéressantes lorsque vous travaillez avec des types de référence nullables.
Travailler avec un contexte Nullable
Il existe un certain nombre de mécanismes pour un travail plus flexible avec des types de référence nullables. Regardons certains d’entre eux.
Travailler avec des attributs
Les attributs peuvent être utilisés pour indiquer au compilateur l’état nul de divers éléments. Voyons les plus intéressants.
Note: vérifier Attributs pour l’analyse statique à l’état nul interprétés par le compilateur C# pour trouver la liste complète des attributs.
Pour faciliter les choses, introduisons le terme d’état nul. L’état nul est une information indiquant si une variable ou une expression peut être null
à un moment donné.
Permettre null
Regardons comment fonctionne l’attribut. Voici un exemple:
public string Name
{
get => _name;
set => _name = value ?? "defaultName";
}
private string _name;
Si vous écrivez le null
valeur à la Name
propriété, le compilateur émettra un avertissement : « Impossible de convertir un littéral nul en un type de référence non nullable ». Mais vous pouvez voir à partir de l’implémentation de la propriété qu’elle peut être null
. Dans ce cas, le defaultName
la chaîne est affectée au _name
domaine.
Si vous ajoutez ‘?’ au type de propriété, le compilateur supposera que :
- L’accesseur d’ensemble peut accepter
null
(c’est correct). - L’accesseur get peut retourner
null
(c’est une erreur).
Pour une mise en œuvre correcte, il convient d’ajouter le AllowNull
attribut à la propriété :
[AllowNull]
public string Name
Après cela, le compilateur supposera que Name
peut être affecté avec null
, bien que le type de la propriété ne soit pas marqué comme nullable. Si vous affectez la valeur de cette propriété à une variable qui ne doit jamais être null
alors il n’y aura pas d’avertissements.
NonNullQuand
Supposons que nous ayons une méthode qui vérifie une variable pour null
. En fonction du résultat de cette vérification, la méthode renvoie une valeur de bool
taper. Cette méthode nous informe sur l’état nul de la variable.
Voici un exemple de code synthétique :
bool CheckNotNull(object? obj)
{
return obj != null;
}
Cette méthode vérifie la obj
paramètre pour null
et renvoie une valeur de bool
type en fonction du résultat de la vérification.
Utilisons le résultat de cette méthode dans la condition :
public void Foo(object? obj1)
{
object obj2 = new object();
if (CheckNotNull(obj1))
obj2 = obj1;
}
Le compilateur émettra un avertissement au code ci-dessus : « Conversion d’un littéral nul ou éventuellement d’une valeur nulle en type non nullable ». Mais un tel scénario est impossible, puisque la condition garantit que obj1
n’est pas null
dans la branche d’alors. Le problème est que le compilateur ne comprend pas cela, nous devons donc l’aider.
Changeons la signature du CheckNotNull
méthode en ajoutant le NotNullWhen
attribut:
bool CheckNotNull([NotNullWhen(true)]object? obj)
Cet attribut prend une valeur de bool
type comme premier argument. Avec NotNullWhen
, nous lions l’état nul de l’argument avec la valeur de retour de la méthode. Dans ce cas, nous « disons » au compilateur que si la méthode renvoie true
l’argument a une valeur autre que null
.
Il y a une particularité associée à cet attribut.
Voici quelques exemples:
En utilisant le en dehors modificateur:
bool GetValidOrDefaultName([NotNullWhen(true)] out string? validOrDefaultName,
string name)
{
if (name == null)
{
validOrDefaultName = name;
return true;
}
else
{
validOrDefaultName = "defaultName";
return false;
}
}
Ici, le compilateur émettra un avertissement : « Paramètre validOrDefaultName
doit avoir une valeur non nulle lors de la sortie avec true
.” C’est tout à fait raisonnable, puisque ‘==’ est utilisé dans la condition au lieu de l’opérateur ‘!=’. Dans cette implémentation, la méthode renvoie true
lorsque validOrDefaultName
est null
.
En utilisant le réf modificateur:
bool SetDefaultIfNotValid([NotNullWhen(true)] ref string? name)
{
if (name == null)
return true;
name = "defaultName";
return false;
}
Nous recevrons également un avertissement pour ce fragment de code : « Paramètre name
doit avoir une valeur non nulle lors de la sortie avec true
.” Comme dans l’exemple précédent, l’avertissement est raisonnable. ‘==’ est utilisé à la place de l’opérateur ‘!=’.
Sans utiliser de modificateur :
bool CheckingForNull([NotNullWhen(true)] string? name)
{
if (name == null)
return true;
Console.WriteLine("name is null");
return false;
}
La situation ici est similaire aux cas précédents. Si name
équivaut à null
la méthode renvoie true
. Suivant la logique des exemples précédents, un avertissement doit également être émis ici : « Paramètre name
doit avoir une valeur non nulle lors de la sortie avec true
.” Cependant, il n’y a pas d’avertissement. Il est difficile de dire ce qui a causé cela, mais cela semble étrange.
NonNullSiNonNull
Cet attribut permet d’établir une relation entre l’argument et la valeur de retour de la méthode. Si l’argument n’est pas null
la valeur de retour n’est pas non plus null
et vice versa.
Exemple:
public string? GetString(object? obj)
{
return obj == null ? null : string.Empty;
}
Les GetString
la méthode renvoie null
ou une chaîne vide, selon l’état nul de l’argument.
Utilisation de cette méthode :
public void Foo(object? obj)
{
string str = string.Empty;
if(obj != null)
str = GetString(obj);
}
Avertissement du compilateur pour ce code : « Conversion d’un littéral null ou éventuellement d’une valeur null en un type non nullable. » Dans ce cas, le compilateur ment. L’affectation est effectuée dans le corps de if
dont la condition garantit que GetString
ne reviendra pas null
. Pour aider le compilateur, ajoutons le NotNullIfNotNull
attribut pour la valeur de retour de la méthode :
[return: NotNullIfNotNull("obj")]
public string? GetString(object? obj)
Note: À partir de C#11, vous pouvez obtenir le nom du paramètre à l’aide de la nameof
expression. Dans ce cas, ce serait nameof(obj)
.
Les NotNullIfNotNull
L’attribut prend la valeur du type de chaîne comme premier argument – le nom du paramètre, sur la base duquel l’état nul de la valeur de retour est défini. Maintenant, le compilateur a des informations sur la relation entre obj
et la valeur de retour de la méthode : si obj
n’est pas null
la valeur de retour de la méthode ne sera pas null
, et vice versa.
MemberNotNull
Commençons par un exemple :
class Person
{
private string _name;
public Person()
{
SetDefaultName();
}
private void SetDefaultName()
{
_name = "Bob";
}
}
Le compilateur émettra un avertissement sur ce fragment de code : « Champ non nul _name
doit contenir une valeur non nulle lors de la sortie du constructeur. Envisagez de déclarer le champ comme nullable. Cependant, le SetDefaultName
La méthode est appelée dans le corps du constructeur, qui initialise le seul champ de la classe. Cela signifie que le message du compilateur est faux. Les MemberNotNull
L’attribut vous permet de résoudre le problème :
[MemberNotNull(nameof(_name))]
private void SetDefaultName()
Cet attribut prend un argument de la string[]
tapez avec le ‘paramètres’ mot-clé. Les chaînes doivent correspondre aux noms des membres initialisés dans la méthode.
Ainsi, nous indiquons que la valeur de la _name
le champ ne sera pas null
après l’appel de cette méthode. Maintenant le…