En C # 7 on peut utiliser
if (x is null) return;
au lieu de
if (x == null) return;
Y a-t-il des avantages à utiliser la nouvelle méthode (exemple précédent) par rapport à l'ancienne?
La sémantique est-elle différente?
Est-ce juste une question de goût? Si non, quand devrais-je utiliser l'un sur l'autre?
Référence: Nouveautés de C # 7. .
Mise à jour: Le compilateur Roslyn a été mis à jour pour rendre le comportement des deux opérateurs identique lorsqu'il n'y a pas d'opérateur d'égalité surchargé. . Veuillez voir le code dans les résultats actuels du compilateur (_M1
_ et _M2
_ dans le code) qui montre ce qui se passe lorsqu'il n'y a pas de comparateur d'égalité surchargé. Ils ont tous les deux maintenant le comportement le plus performant _==
_. S'il existe un comparateur d'égalité surchargé, le code est toujours différent .
Voir pour les anciennes versions du compilateur Roslyn l'analyse ci-dessous.
Pour null
, il n'y a pas de différence avec ce à quoi nous sommes habitués avec C # 6. Cependant, les choses deviennent intéressantes lorsque vous changez null
en une autre constante.
Prenons ceci par exemple:
_Test(1);
public void Test(object o)
{
if (o is 1) Console.WriteLine("a");
else Console.WriteLine("b");
}
_
Le test donne a
. Si vous comparez cela à o == (object)1
ce que vous auriez écrit normalement, cela fait toute une différence. is
prend en compte le type de l'autre côté de la comparaison. C'est cool!
Je pense que le modèle constant _== null
_ et _is null
_ est juste quelque chose de très familier "par accident", où la syntaxe de l'opérateur is
et de l'opérateur égal aboutit au même résultat.
Comme svick commenté, is null
_ APPELLE System.Object::Equals(object, object)
OÙ _==
_ APPELLE ceq
.
IL pour is
:
_IL_0000: ldarg.1 // Load argument 1 onto the stack
IL_0001: ldnull // Push a null reference on the stack
IL_0002: call bool [mscorlib]System.Object::Equals(object, object) // Call method indicated on the stack with arguments
IL_0007: ret // Return from method, possibly with a value
_
IL pour _==
_:
_IL_0000: ldarg.1 // Load argument 1 onto the stack
IL_0001: ldnull // Push a null reference on the stack
IL_0002: ceq // Push 1 (of type int32) if value1 equals value2, else Push 0
IL_0004: ret // Return from method, possibly with a value
_
Puisque nous parlons de null
, il n'y a pas de différence puisque this ne fait de différence que sur les instances . Cela pourrait changer si vous avez surchargé l'opérateur d'égalité.
Il existe en fait une différence de sémantique entre les deux comparaisons. Le cas Edge se présente lorsque vous comparez null
à un type qui a surchargé l'opérateur ==
.
foo is null
utilisera la comparaison de référence directe pour déterminer le résultat, alors que foo == null
exécutera bien sûr l'opérateur surchargé ==
s'il existe.
Dans cet exemple, j'ai introduit un "bogue" dans l'opérateur surchargé ==
, le forçant à toujours lever une exception si le deuxième argument est null
:
void Main()
{
Foo foo = null;
if (foo is null) Console.WriteLine("foo is null"); // This condition is met
if (foo == null) Console.WriteLine("foo == null"); // This will throw an exception
}
public class Foo
{
public static bool operator ==(Foo foo1, Foo foo2)
{
if (object.Equals(foo2, null)) throw new Exception("oops");
return object.Equals(foo1, foo2);
}
// ...
}
Le code IL pour foo is null
utilise l'instruction ceq
pour effectuer une comparaison de référence directe:
IL_0003: ldloc.0 // foo
IL_0004: ldnull
IL_0005: ceq
Le code IL pour foo == null
utilise un appel à l'opérateur surchargé:
IL_0016: ldloc.0 // foo
IL_0017: ldnull
IL_0018: call UserQuery+Foo.op_Equality
La différence est donc que si vous utilisez ==
, vous risquez d'exécuter un code utilisateur (qui peut potentiellement avoir un comportement inattendu ou des problèmes de performances).