Quelle est la différence entre «x est nul» et «x == nul»?

277

En C # 7, nous pouvons utiliser

if (x is null) return;

au lieu de

if (x == null) return;

Y a-t-il des avantages à utiliser la nouvelle méthode (ancien exemple) par rapport à l'ancienne?

La sémantique est-elle différente?

Est-ce juste une question de goût? Sinon, quand devrais-je utiliser l'un sur l'autre?

Référence: Nouveautés de C # 7.0 .

Maniero
la source
4
c'est le lien que je regardais, mais il ne vous donne pas beaucoup d'informations, c'est pourquoi je suppose que le PO pose la question. La partie la plus importante de la page est ce test est Opérateur L'opérateur "est" est utilisé pour vérifier si le type d'exécution d'un objet est compatible avec un type donné ou non. En d'autres termes, nous utilisons l'opérateur "is" pour vérifier que le type d'un objet correspond à ce que nous attendons de lui. Regardons sa syntaxe:
Simon Price
2
@SimonPrice Il s'agit de la version actuelle de C #: C # 6. Cette question concerne C # 7, qui a une correspondance de modèle .
Patrick Hofman
@bigown quel genre de détails recherchez-vous?
Patrick Hofman
@PatrickHofman le genre de svick a répondu, par exemple
Maniero

Réponses:

233

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 du compilateur actuel ( M1et M2dans le code) qui montre ce qui se passe quand il n'y a pas de comparateur d'égalité surchargé. Ils ont maintenant tous deux le comportement le plus performant ==. S'il existe un comparateur d'égalité surchargé, le code diffère toujours .

Voir pour les anciennes versions du compilateur Roslyn l'analyse ci-dessous.


Car nullil 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 passez nullà une autre constante.

Prenez 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)1ce que vous auriez écrit normalement, cela fait une énorme différence. isprend en considération le type de l'autre côté de la comparaison. C'est cool!

Je pense que le modèle == nullvs is nullconstant est juste quelque chose de très familier «par accident», où la syntaxe de l' isopérateur et de l'opérateur égal donne le même résultat.


Comme svick a commenté, les is nullappels System.Object::Equals(object, object)où les ==appelsceq .

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 null, il n'y a pas de différence car cela ne fait qu'une différence sur les instances . Cela peut changer lorsque vous avez surchargé l'opérateur d'égalité.

Patrick Hofman
la source
16
@PatrickHofman Il ressemble à des isappels object.Equals(x, null), tandis que se ==compile comme ceq. Mais le résultat devrait être le même, comme vous l'avez dit.
svick
18
Gardez toujours à l'esprit qu'il ==s'agit d'un opérateur surchargeable. Vous pouvez avoir n'importe quel comportement que vous souhaitez avec. Par exemple, cette implémentation bizarre== ne vous dira pas si votre instance est vraiment nulle. is nulld'autre part, retournera toujours vrai pour les vraies références nulles :) De plus, si vous avez ReferenceEqualsdans votre code, les ampoules VS 2017 suggéreront de passer à is null, pas == null(correctement).
nawfal
2
@PatrickHofman @svick les deux vérifications nulles se compilent maintenant pour la même chose, donc isn'a plus la surcharge d'un appel de fonction lorsqu'il est utilisé pour vérifier la nullité. Pour preuve, voir le lien posté par @svick dans les commentaires.
AndreasHassing
1
@ AndreasBjørnHassingNielsen Mise à jour de ma réponse.
Patrick Hofman
2
@PatrickHofman ne devrait-il pas être l'inverse? == appelle System.Object :: Equals (objet, objet) et est nul appelle ceq
Zbigniew Ledwoń
68

Surchargé est égal à l'opérateur

Il existe en fait une différence de sémantique entre les deux comparaisons lorsque vous comparez nullavec un type qui a surchargé l' ==opérateur. foo is nullutilisera la comparaison de référence directe pour déterminer le résultat, tandis que foo == null, bien sûr, exécutera l' ==opérateur surchargé s'il existe.

Dans cet exemple, j'ai introduit un "bogue" dans l' ==opérateur surchargé , ce qui lui fait 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 nullutilise l' ceqinstruction 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 == nullutilise 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 du code utilisateur (qui peut potentiellement avoir des problèmes de comportement ou de performance inattendus).

Restriction sur les génériques

L'utilisation de la is nullconstruction limite le type à un type de référence. Le compilateur le garantit, ce qui signifie que vous ne pouvez pas l'utiliser is nullsur un type de valeur. Si vous avez une méthode générique, vous ne pourrez l'utiliser is nullque si le type générique est contraint à être un type de référence.

bool IsNull<T>(T item) => item is null;                  // Compile error: CS0403
bool IsNull<T>(T item) => item == null;                  // Works
bool IsNull<T>(T item) where T : class => item is null;  // Works

Merci à David Augusto Villa de l' avoir signalé.

Thorkil Holm-Jacobsen
la source
2
De plus, notez (x est nul) requiert une contrainte de classe si x est un type générique, contrairement à (x == null) et object.ReferenceEquals (x, null).
David Augusto Villa