Jak sprawdzić wartości null w przeciążeniu operatora „==” bez nieskończonej rekursji?


113

Poniższe działania spowodują nieskończoną rekursję w metodzie przeciążenia operatora ==

    Foo foo1 = null;
    Foo foo2 = new Foo();
    Assert.IsFalse(foo1 == foo2);

    public static bool operator ==(Foo foo1, Foo foo2) {
        if (foo1 == null) return foo2 == null;
        return foo1.Equals(foo2);
    }

Jak sprawdzić wartości null?

Odpowiedzi:


138

Zastosowanie ReferenceEquals:

Foo foo1 = null;
Foo foo2 = new Foo();
Assert.IsFalse(foo1 == foo2);

public static bool operator ==(Foo foo1, Foo foo2) {
    if (object.ReferenceEquals(null, foo1))
        return object.ReferenceEquals(null, foo2);
    return foo1.Equals(foo2);
}

To rozwiązanie nie działa w przypadkuAssert.IsFalse(foo2 == foo1);
FIL

A co to foo1.Equals(foo2)znaczy, jeśli na przykład chcę foo1 == foo2tylko wtedy foo1.x == foo2.x && foo1.y == foo2.y? Czy to nie odpowiada ignorując przypadek, gdzie, foo1 != nullale foo2 == null?
Daniel

Uwaga: to samo rozwiązanie z prostszą składnią:if (foo1 is null) return foo2 is null;
Rem

20

Rzutowanie na obiekt w metodzie przeciążenia:

public static bool operator ==(Foo foo1, Foo foo2) {
    if ((object) foo1 == null) return (object) foo2 == null;
    return foo1.Equals(foo2);
}

1
Dokładnie. Oba (object)foo1 == nulllub foo1 == (object)nullprzejdą do wbudowanego przeciążenia, ==(object, object)a nie do przeciążenia zdefiniowanego przez użytkownika ==(Foo, Foo). To jest tak, jak w przypadku rozwiązywania przeciążeń metod.
Jeppe Stig Nielsen

2
Dla przyszłych gości - akceptowaną odpowiedzią jest funkcja, która wykonuje == obiektu. Jest to w zasadzie to samo, co przyjęta odpowiedź, z jedną wadą: wymaga obsady. Tak więc przyjęta odpowiedź jest lepsza.
Mafii,

1
@Mafii Obsada jest wyłącznie operacją kompilacji. Ponieważ kompilator wie, że rzutowanie nie może się nie powieść, nie musi niczego sprawdzać w czasie wykonywania. Różnice między metodami są całkowicie estetyczne.
Servy

8

Użyj ReferenceEquals. Z forów MSDN :

public static bool operator ==(Foo foo1, Foo foo2) {
    if (ReferenceEquals(foo1, null)) return ReferenceEquals(foo2, null);
    if (ReferenceEquals(foo2, null)) return false;
    return foo1.field1 == foo2.field2;
}

4

Próbować Object.ReferenceEquals(foo1, null)

W każdym razie nie polecałbym przeciążania ==operatora; Powinien być używany do porównywania odniesień i Equalsdo porównań „semantycznych”.


4

Jeśli nadpisałem bool Equals(object obj)i chcę, aby operator ==i Foo.Equals(object obj)zwracał tę samą wartość, zwykle implementuję !=operator w ten sposób:

public static bool operator ==(Foo foo1, Foo foo2) {
  return object.Equals(foo1, foo2);
}
public static bool operator !=(Foo foo1, Foo foo2) {
  return !object.Equals(foo1, foo2);
}

Operator ==po wykonaniu wszystkich sprawdzeń zerowych zakończy się dla mnie wywołaniem foo1.Equals(foo2), że przesłoniłem, aby wykonać faktyczne sprawdzenie, czy oba są równe.


To wydaje się bardzo odpowiednie; patrząc na implementację Object.Equals(Object, Object)obok siebie Object.ReferenceEquals(Object, Object), jest całkiem jasne, że Object.Equals(Object, Object)robi wszystko, co sugerowano w innych odpowiedziach po wyjęciu z pudełka. Dlaczego tego nie używać?
tne

@tne Ponieważ nie ma sensu przeciążać ==operatora, jeśli chcesz tylko domyślnego zachowania. Należy przeciążać tylko wtedy, gdy trzeba zaimplementować niestandardową logikę porównania, czyli coś więcej niż sprawdzanie równości odwołań.
Dan Bechard

@Dan Jestem pewien, że źle zrozumiałeś moją uwagę; w kontekście, w którym zostało już ustalone, że przeciążanie ==jest pożądane (pytanie to sugeruje), po prostu popieram tę odpowiedź, sugerując, Object.Equals(Object, Object)że inne sztuczki, takie jak używanie ReferenceEqualslub jawne rzucanie, są niepotrzebne (a więc „dlaczego nie użyć tego?”, „to”) istnienie Equals(Object, Object)). Nawet jeśli niepowiązany twój punkt widzenia jest również poprawny i poszedłbym dalej: przeciążenie tylko ==obiektów, które możemy sklasyfikować jako „obiekty wartościowe”.
tne

@tne Główną różnicą jest to, że Object.Equals(Object, Object)z kolei wywołuje Object.Equals (Object), który jest metodą wirtualną, którą Foo prawdopodobnie zastępuje. Fakt, że wprowadziłeś wirtualne wywołanie do sprawdzania równości, może wpłynąć na zdolność kompilatora do optymalizacji (np. Inline) tych wywołań. W większości przypadków jest to prawdopodobnie nieistotne, ale w niektórych przypadkach niewielki koszt operatora równości może oznaczać ogromny koszt pętli lub posortowanych struktur danych.
Dan Bechard

@tne Aby uzyskać więcej informacji na temat zawiłości związanych z optymalizacją wywołań metod wirtualnych, odwiedź stronę stackoverflow.com/questions/530799/… .
Dan Bechard,

3

Jeśli używasz języka C # 7 lub nowszego, możesz użyć stałego dopasowania wzorca o wartości null:

public static bool operator==(Foo foo1, Foo foo2)
{
    if (foo1 is null)
        return foo2 is null;
    return foo1.Equals(foo2);
}

Daje to nieco schludniejszy kod niż ten wywołujący obiekt.ReferenceEquals (foo1, null)


2
lubpublic static bool operator==( Foo foo1, Foo foo2 ) => foo1?.Equals( foo2 ) ?? foo2 is null;
Danko Durbić,

3

W nulltym przypadku istnieje prostszy sposób sprawdzenia :

if (foo is null)

Otóż ​​to!

Ta funkcja została wprowadzona w języku C # 7


1

Moje podejście jest do zrobienia

(object)item == null

na którym polegam na objectwłasnym operatorze równości, który nie może się nie udać. Lub niestandardowa metoda rozszerzenia (i przeciążenie):

public static bool IsNull<T>(this T obj) where T : class
{
    return (object)obj == null;
}

public static bool IsNull<T>(this T? obj) where T : struct
{
    return !obj.HasValue;
}

lub aby obsłużyć więcej spraw, może być:

public static bool IsNull<T>(this T obj) where T : class
{
    return (object)obj == null || obj == DBNull.Value;
}

Ograniczenie zapobiega IsNulltypom wartości. Teraz jest tak słodki jak dzwonienie

object obj = new object();
Guid? guid = null; 
bool b = obj.IsNull(); // false
b = guid.IsNull(); // true
2.IsNull(); // error

co oznacza, że ​​mam jeden spójny / nie podatny na błędy styl sprawdzania wartości null w całym tekście. Stwierdziłem również, że (object)item == nulljest bardzo, bardzo, trochę szybszy niżObject.ReferenceEquals(item, null) , ale tylko wtedy, gdy ma to znaczenie (obecnie pracuję nad czymś, w którym muszę wszystko mikro-optymalizować!).

Aby zobaczyć kompletny przewodnik dotyczący wdrażania sprawdzania równości, zobacz „ Co to jest„ Najlepsza praktyka ”porównywania dwóch wystąpień typu odwołania?


Nitpick: Czytelnicy powinni obserwować swoje zależności, zanim przejdą do funkcji takich jak porównywanie DbNull, IMO przypadki, w których nie generowałoby to problemów związanych z SRP, są dość rzadkie. Jednak samo wskazanie zapachu kodu może być bardzo odpowiednie.
tne

0

Statyczne Equals(Object, Object)metody wskazuje, czy dwa obiekty, objAa objBsą równe. Umożliwia także testowanie obiektów, których wartość dotyczy nullrówności. Porównuje objAi objBdla równości w następujący sposób:

  • Określa, czy te dwa obiekty reprezentują to samo odniesienie do obiektu. Jeśli tak, metoda zwraca true. Ten test jest równoważny wywołaniu ReferenceEqualsmetody. Ponadto, jeśli oba objAi objBnull, metoda zwraca true.
  • Określa, czy jest albo objAczy objBjest null. Jeśli tak, to wraca false. Jeśli te dwa obiekty nie reprezentują tego samego odwołania do obiektu i żadna z nich nie jest null, wywołuje objA.Equals(objB)i zwraca wynik. Oznacza to, że jeśli objAprzesłania Object.Equals(Object)metodę, to zastąpienie jest wywoływane.

.

public static bool operator ==(Foo objA, Foo objB) {
    return Object.Equals(objA, objB);
}

0

odpowiadanie bardziej na przesłanianie operatora, jak porównać z null, który przekierowuje tutaj jako duplikat.

W przypadkach, w których robi się to w celu obsługi obiektów wartości, uważam, że nowa notacja jest przydatna i lubię upewnić się, że jest tylko jedno miejsce, w którym dokonuje się porównania. Wykorzystanie również Object.Equals (A, B) upraszcza sprawdzanie wartości null.

Spowoduje to przeciążenie ==,! =, Equals i GetHashCode

    public static bool operator !=(ValueObject self, ValueObject other) => !Equals(self, other);
    public static bool operator ==(ValueObject self, ValueObject other) => Equals(self, other);
    public override bool Equals(object other) => Equals(other as ValueObject );
    public bool Equals(ValueObject other) {
        return !(other is null) && 
               // Value comparisons
               _value == other._value;
    }
    public override int GetHashCode() => _value.GetHashCode();

W przypadku bardziej skomplikowanych obiektów dodaj dodatkowe porównania w Equals i bogatszym GetHashCode.


0

Aby uzyskać nowoczesną i skondensowaną składnię:

public static bool operator ==(Foo x, Foo y)
{
    return x is null ? y is null : x.Equals(y);
}

public static bool operator !=(Foo x, Foo y)
{
    return x is null ? !(y is null) : !x.Equals(y);
}

-3

Częstym błędem w przeciążeń operatora == jest użycie (a == b), (a ==null)lub (b == null)w celu sprawdzenia równości odniesienia. Zamiast tego powoduje wywołanie przeciążonego operatora ==, powodując błąd infinite loop. Użyj ReferenceEqualslub rzuć typ na Object, aby uniknąć pętli.

Sprawdź to

// If both are null, or both are same instance, return true.
if (System.Object.ReferenceEquals(a, b))// using ReferenceEquals
{
    return true;
}

// If one is null, but not both, return false.
if (((object)a == null) || ((object)b == null))// using casting the type to Object
{
    return false;
}

odniesienia Wytyczne dotyczące przeciążania równa się () i operator ==


1
Istnieje już wiele odpowiedzi zawierających wszystkie te informacje. Nie potrzebujemy 7. kopii tej samej odpowiedzi.
Servy

-5

Możesz spróbować użyć właściwości obiektu i przechwycić wynikowy wyjątek NullReferenceException. Jeśli próbowana właściwość jest dziedziczona lub zastępowana przez Object, to działa to dla każdej klasy.

public static bool operator ==(Foo foo1, Foo foo2)
{
    //  check if the left parameter is null
    bool LeftNull = false;
    try { Type temp = a_left.GetType(); }
    catch { LeftNull = true; }

    //  check if the right parameter is null
    bool RightNull = false;
    try { Type temp = a_right.GetType(); }
    catch { RightNull = true; }

    //  null checking results
    if (LeftNull && RightNull) return true;
    else if (LeftNull || RightNull) return false;
    else return foo1.field1 == foo2.field2;
}

Jeśli masz wiele obiektów zerowych, obsługa wyjątków może być dużym narzutem.
Kasprzol,

2
Haha, zgadzam się, że to nie jest najlepsza metoda. Po opublikowaniu tej metody natychmiast poprawiłem mój bieżący projekt, aby zamiast tego używał ReferenceEquals. Jednak pomimo tego, że jest nieoptymalny, działa, a zatem jest prawidłową odpowiedzią na to pytanie.
The Digital Gabeg,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.