Was ist der Unterschied zwischen "x ist null" und "x == null"?

277

In C # 7 können wir verwenden

if (x is null) return;

anstatt

if (x == null) return;

Gibt es irgendwelche Vorteile bei der Verwendung des neuen Weges (früheres Beispiel) gegenüber dem alten Weg?

Ist die Semantik anders?

Ist nur Geschmackssache? Wenn nicht, wann sollte ich eins über das andere verwenden?

Referenz: Neues in C # 7.0 ist .

Maniero
quelle
4
Das ist der Link, den ich gerade gesehen habe, aber er gibt Ihnen nicht viele Informationen, weshalb ich denke, dass das OP die Frage stellt. Der wichtigste Teil der Seite ist, dass dieser Test Operator ist. Mit dem Operator "is" wird überprüft, ob der Laufzeittyp eines Objekts mit einem bestimmten Typ kompatibel ist oder nicht. Mit anderen Worten, wir verwenden den Operator "is", um zu überprüfen, ob der Typ eines Objekts dem entspricht, was wir erwarten. Schauen wir uns die Syntax an:
Simon Price
2
@SimonPrice Hier geht es um die aktuelle Version von C #: C # 6. Bei dieser Frage geht es um C # 7 mit Mustervergleich .
Patrick Hofman
@bigown welche Art von Detail suchst du?
Patrick Hofman
@PatrickHofman die Art von svick antwortete mit gutem Beispiel
Maniero

Antworten:

231

Update: Der Roslyn-Compiler wurde aktualisiert, um das Verhalten der beiden Operatoren zu ändern, wenn kein überladener Gleichheitsoperator vorhanden ist . Bitte beachten Sie den Code in den aktuellen Compiler-Ergebnissen ( M1und M2im Code), der zeigt, was passiert, wenn kein überladener Gleichheitsvergleich vorhanden ist. Beide haben jetzt das leistungsfähigere ==Verhalten. Wenn es einen überladenen Gleichheitsvergleich gibt, unterscheidet sich der Code immer noch .

Siehe für ältere Versionen des Roslyn-Compilers die folgende Analyse.


Denn nulles gibt keinen Unterschied zu dem, was wir mit C # 6 gewohnt sind. Wenn Sie jedoch nullzu einer anderen Konstante wechseln, werden die Dinge jedoch interessant .

Nehmen Sie zum Beispiel:

Test(1);

public void Test(object o)
{
    if (o is 1) Console.WriteLine("a");
    else Console.WriteLine("b");
}

Der Test ergibt a. Wenn Sie das mit dem vergleichen, o == (object)1was Sie normalerweise geschrieben hätten, macht es einen verdammt großen Unterschied. isberücksichtigt den Typ auf der anderen Seite des Vergleichs. Das ist cool!

Ich denke, das == nullvs. is nullkonstante Muster ist nur etwas, das "aus Versehen" sehr vertraut ist, wobei die Syntax des isOperators und des Operators gleich dem gleichen Ergebnis liefert.


Wie svick kommentierte, is nullruft System.Object::Equals(object, object)wo ==Anrufe anceq .

IL für 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 für ==:

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

Da es sich um einen Unterschied handeltnull , gibt es keinen Unterschied, da dies nur bei Instanzen einen Unterschied macht . Dies kann sich ändern, wenn Sie den Gleichheitsoperator überladen haben.

Patrick Hofman
quelle
15
@PatrickHofman Es sieht aus wie isAufrufe object.Equals(x, null), während ==kompiliert als ceq. Aber das Ergebnis sollte das gleiche sein, wie Sie sagten.
Svick
16
Denken Sie immer daran, dass ==es sich um einen überlastbaren Bediener handelt. Sie können damit jedes gewünschte Verhalten haben. Zum Beispiel sagt Ihnen diese seltsam implementierte== nicht, ob Ihre Instanz wirklich null ist. is nullAuf der anderen Seite wird immer true für echte Nullreferenzen zurückgegeben :) Wenn Sie ReferenceEqualsin Ihrem Code haben, schlagen VS 2017-Glühbirnen vor is null, nicht == null(korrekt) zu ändern .
Nawfal
2
@PatrickHofman @svick Die beiden Nullprüfungen werden jetzt auf dieselbe Weise kompiliert, sodass isbei der Überprüfung auf Null nicht mehr der Aufwand eines Funktionsaufrufs anfällt . Zum Beweis siehe den von @svick geposteten Link in den Kommentaren.
AndreasHassing
1
@ AndreasBjørnHassingNielsen Meine Antwort wurde aktualisiert.
Patrick Hofman
2
@PatrickHofman sollten ILs nicht umgekehrt sein? == ruft System.Object :: Equals (Objekt, Objekt) auf und ist null ruft ceq
Zbigniew Ledwoń
67

Überladen ist gleich Operator

Tatsächlich gibt es einen Unterschied in der Semantik zwischen den beiden Vergleichen, wenn Sie nullmit einem Typ vergleichen, der den ==Operator überlastet hat . foo is nullverwendet einen direkten Referenzvergleich, um das Ergebnis zu bestimmen, während foo == nullder überladene ==Operator natürlich ausgeführt wird , falls vorhanden.

In diesem Beispiel habe ich einen "Fehler" im überladenen ==Operator eingeführt, der dazu führt, dass immer eine Ausnahme ausgelöst wird, wenn das zweite Argument lautet 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);
    }

    // ...
}

Der IL-Code für foo is nullverwendet die ceqAnweisung, um einen direkten Referenzvergleich durchzuführen:

IL_0003:  ldloc.0     // foo
IL_0004:  ldnull      
IL_0005:  ceq

Der IL-Code für foo == nullverwendet einen Aufruf an den überladenen Operator:

IL_0016:  ldloc.0     // foo
IL_0017:  ldnull      
IL_0018:  call        UserQuery+Foo.op_Equality

Der Unterschied besteht also darin, dass Sie bei Verwendung ==das Risiko eingehen, Benutzercode auszuführen (was möglicherweise zu unerwartetem Verhalten oder Leistungsproblemen führen kann).

Beschränkung auf Generika

Durch die Verwendung des is nullKonstrukts wird der Typ auf einen Referenztyp beschränkt. Der Compiler stellt dies sicher, was bedeutet, dass Sie nicht is nullfür einen Werttyp verwenden können. Wenn Sie über eine generische Methode verfügen, können Sie diese nur verwenden, is nullwenn der generische Typ auf einen Referenztyp beschränkt ist.

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

Vielen Dank an David Augusto Villa für diesen Hinweis.

Thorkil Holm-Jacobsen
quelle
2
Außerdem erfordert note (x ist null) eine Klassenbeschränkung, wenn x ein generischer Typ ist, während (x == null) und object.ReferenceEquals (x, null) dies nicht tun.
David Augusto Villa