Jaka jest różnica między „x jest zerowy” a „x == null”?

276

W C # 7 możemy użyć

if (x is null) return;

zamiast

if (x == null) return;

Czy są jakieś zalety korzystania z nowego sposobu (poprzedni przykład) w stosunku do starego?

Czy semantyka jest inna?

Czy to tylko kwestia gustu? Jeśli nie, to kiedy powinienem stosować jeden na drugim?

Odniesienie: Co nowego w C # 7.0 .

Maniero
źródło
4
to jest właśnie link, na który właśnie patrzyłem, jednak nie zawiera on wielu informacji, dlatego chyba OP zadaje to pytanie. Najważniejszą częścią strony jest ten test. Operator „Operator” służy do sprawdzenia, czy typ wykonania obiektu jest zgodny z danym typem, czy nie. Innymi słowy, używamy operatora „jest”, aby sprawdzić, czy typ obiektu jest zgodny z oczekiwaniami. Spójrzmy na jego składnię:
Simon Price
2
@ SimonPrice To jest o bieżącej wersji C #: C # 6. To pytanie dotyczy C # 7, który ma dopasowanie wzorca .
Patrick Hofman,
@bigown, jakiego rodzaju szczegółów szukasz?
Patrick Hofman
@PatrickHofman rodzaj svick odpowiedział, przykładowo
Maniero

Odpowiedzi:

231

Aktualizacja: Kompilator Roslyn został zaktualizowany, aby zachowanie dwóch operatorów było takie samo, gdy nie ma przeciążonego operatora równości . Zobacz kod w bieżących wynikach kompilatora ( M1i M2w kodzie), który pokazuje, co się dzieje, gdy nie ma przeciążonego modułu porównującego równość. Obaj mają teraz lepsze wyniki ==. Jeśli istnieje przeciążony moduł porównujący równość, kod nadal się różni .

Zobacz starsze wersje kompilatora Roslyn poniższą analizę.


Ponieważ nullnie ma różnicy w stosunku do tego, do czego jesteśmy przyzwyczajeni w C # 6. Jednak rzeczy stają się interesujące, kiedy zmieniasz nullna inną stałą.

Weźmy na przykład:

Test(1);

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

Test daje wynik a. Jeśli porównasz to z o == (object)1tym, co napisałbyś normalnie, robi to cholerną różnicę. isbierze pod uwagę typ po drugiej stronie porównania. To jest fajne!

Myślę, że wzorzec == nullvs. is nullstały jest po prostu czymś bardzo znanym „przypadkiem”, gdzie składnia isoperatora i operatora równości daje ten sam wynik.


Jak komentował svick , is nullpołączenia są System.Object::Equals(object, object)tam==ceq , gdzie są połączenia .

IL dla 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 dla ==:

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

Ponieważ mówimy o tym null, nie ma różnicy, ponieważ wpływa to tylko na instancje . Może się to zmienić, gdy przeciążono operatora równości.

Patrick Hofman
źródło
15
@PatrickHofman Wygląda jak iswywołania object.Equals(x, null), podczas gdy ==kompiluje jako ceq. Ale wynik powinien być taki sam, jak powiedziałeś.
svick,
16
Zawsze należy pamiętać, że ==operator jest przeciążalny. Możesz mieć z nim dowolne zachowanie. Na przykład dla tego dziwnie zaimplementowane== nie powie ci, czy twoja instancja jest naprawdę zerowa. is nullz drugiej strony zawsze zwróci true dla prawdziwych referencji zerowych :) Ponadto, jeśli masz ReferenceEqualsw swoim kodzie, żarówki VS 2017 zasugerują zmianę na is null, nie == null(poprawnie).
nawfal
2
@PatrickHofman @svick dwa sprawdzenia zerowe są teraz kompilowane do tej samej rzeczy, więc isnie ma już narzutu wywołania funkcji, gdy jest używany do sprawdzania wartości zerowej. Aby uzyskać dowód, zobacz link opublikowany przez @svick w komentarzach.
AndreasHassing
1
@ AndreasBjørnHassingNielsen Zaktualizowałem moją odpowiedź.
Patrick Hofman,
2
@PatrickHofman nie powinien być odwrotnie? == wywołuje System.Object :: Equals (object, object) i jest null wywołuje ceq
Zbigniew Ledwoń
67

Przeciążony jest równy operatorowi

W rzeczywistości istnieje różnica w semantyce między dwoma porównaniami, gdy porównujesz nullz typem, który przeciążył ==operatora. foo is nullużyje bezpośredniego porównania referencyjnego w celu ustalenia wyniku, podczas gdy foo == nulloczywiście uruchomi przeciążone== operatora, jeśli taki istnieje.

W tym przykładzie wprowadziłem „błąd” w przeciążonym ==operatorze, powodując, że zawsze zgłasza wyjątek, jeśli drugi argument to 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);
    }

    // ...
}

Kod IL foo is nullużywa ceqinstrukcji do bezpośredniego porównania odniesienia:

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

Kod IL dla foo == nullużywa połączenia do przeciążonego operatora:

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

Różnica polega na tym, że jeśli użyjesz == , ryzykujesz uruchomieniem kodu użytkownika (który może mieć nieoczekiwane problemy z zachowaniem lub wydajnością).

Ograniczenia dotyczące leków generycznych

Użycie is nullkonstrukcji ogranicza typ do typu odwołania. Kompilator to zapewnia, co oznacza, że ​​nie można użyć is nulltypu wartości. Jeśli masz ogólną metodę, nie będziesz w stanie jej użyćis null chyba że typ ogólny jest ograniczony do typu referencyjnego.

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

Dzięki Davidowi Augusto Villa za zwrócenie na to uwagi.

Thorkil Holm-Jacobsen
źródło
2
Ponadto notatka (x jest zerowa) wymaga ograniczenia klasy, jeśli x jest typem ogólnym, podczas gdy (x == null) i object.ReferenceEquals (x, null) nie.
David Augusto Villa