Wie überprüfe ich eine '==' Operatorüberladung ohne unendliche Rekursion auf Nullen?

113

Das Folgende führt zu einer unendlichen Rekursion der Überladungsmethode des Operators ==

    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);
    }

Wie überprüfe ich auf Nullen?

Andrew Jones
quelle

Antworten:

138

Verwendung 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);
}
Abe Heidebrecht
quelle
Diese Lösung funktioniert nicht fürAssert.IsFalse(foo2 == foo1);
FIL
Und was heißt foo1.Equals(foo2), wenn ich zum Beispiel foo1 == foo2nur will , wenn foo1.x == foo2.x && foo1.y == foo2.y? Ignoriert diese Antwort nicht den Fall wo foo1 != nullaber foo2 == null?
Daniel
Hinweis: Dieselbe Lösung mit einfacherer Syntax:if (foo1 is null) return foo2 is null;
Rem
20

In der Überladungsmethode auf Objekt umwandeln:

public static bool operator ==(Foo foo1, Foo foo2) {
    if ((object) foo1 == null) return (object) foo2 == null;
    return foo1.Equals(foo2);
}
Andrew Jones
quelle
1
Genau. Beides (object)foo1 == nulloder foo1 == (object)nullgeht zur eingebauten Überlastung ==(object, object)und nicht zur benutzerdefinierten Überlastung ==(Foo, Foo). Es ist wie eine Überlastungsauflösung bei Methoden.
Jeppe Stig Nielsen
2
Für zukünftige Besucher - die akzeptierte Antwort ist eine Funktion, die das == von Objekt ausführt. Dies ist im Grunde die gleiche wie die akzeptierte Antwort, mit einem Nachteil: Es braucht eine Besetzung. Die Antwort ist somit überlegen.
Mafii
1
@ Mafii Die Besetzung ist eine reine Kompilierungsoperation. Da der Compiler weiß, dass die Umwandlung nicht fehlschlagen kann, muss er zur Laufzeit nichts überprüfen. Die Unterschiede zwischen den Methoden sind völlig ästhetisch.
Servy
8

Verwenden Sie ReferenceEquals. Aus den MSDN-Foren :

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;
}
Jon Adams
quelle
4

Versuchen Object.ReferenceEquals(foo1, null)

Auf jeden Fall würde ich nicht empfehlen, den ==Bediener zu überlasten . Es sollte zum Vergleichen von Referenzen und Equalsfür "semantische" Vergleiche verwendet werden.

Santiago Palladino
quelle
4

Wenn ich overrided haben bool Equals(object obj)und ich den Betreiber wollen ==und Foo.Equals(object obj)die gleiche Antwort zurück, ich in der Regel die Umsetzung !=Operator wie folgt aus :

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);
}

Der Bediener ==ruft dann nach Durchführung aller Nullprüfungen für mich auf, foo1.Equals(foo2)dass ich überschrieben habe, um die eigentliche Prüfung durchzuführen, wenn beide gleich sind.

Hallgrim
quelle
Das fühlt sich sehr angemessen an; Wenn man sich die Implementierung von Object.Equals(Object, Object)Seite an Seite mit ansieht Object.ReferenceEquals(Object, Object), ist es ziemlich klar, dass Object.Equals(Object, Object)alles so funktioniert, wie es in den anderen Antworten sofort vorgeschlagen wird. Warum nicht benutzen?
Der
@tne Weil es keinen Sinn macht, den ==Operator zu überladen , wenn Sie nur das Standardverhalten wünschen. Sie sollten nur dann überladen, wenn Sie eine benutzerdefinierte Vergleichslogik implementieren müssen, dh etwas mehr als eine Referenzgleichheitsprüfung.
Dan Bechard
@ Dan Ich bin zuversichtlich, dass Sie meine Bemerkung falsch verstanden haben; In einem Kontext, in dem bereits festgestellt wurde, dass eine Überladung ==wünschenswert ist (die Frage impliziert dies), unterstütze ich diese Antwort einfach, indem ich vorschlage, Object.Equals(Object, Object)dass andere Tricks wie die Verwendung ReferenceEqualsoder explizite Besetzung unnötig sind (also "warum nicht verwenden?", "es"). Sein Equals(Object, Object)). Auch wenn Ihr Standpunkt nicht in Beziehung steht, ist er auch richtig, und ich würde noch weiter gehen: Nur Überladung ==für Objekte, die wir als "Wertobjekte" klassifizieren können.
25.
@tne Der Hauptunterschied besteht darin, dass Object.Equals(Object, Object)wiederum Object.Equals (Object) aufgerufen wird, eine virtuelle Methode, die Foo wahrscheinlich überschreibt. Die Tatsache, dass Sie einen virtuellen Aufruf in Ihre Gleichheitsprüfung aufgenommen haben, kann sich auf die Fähigkeit des Compilers auswirken, diese Aufrufe zu optimieren (z. B. inline). Dies ist für die meisten Zwecke wahrscheinlich vernachlässigbar, aber in bestimmten Fällen können geringe Kosten in einem Gleichheitsoperator große Kosten für Schleifen oder sortierte Datenstrukturen bedeuten.
Dan Bechard
@tne Weitere Informationen zu den Feinheiten der Optimierung virtueller Methodenaufrufe finden Sie unter stackoverflow.com/questions/530799/… .
Dan Bechard
3

Wenn Sie C # 7 oder höher verwenden, können Sie den Null-Konstanten-Mustervergleich verwenden:

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

Dies gibt Ihnen etwas saubereren Code als das aufrufende Objekt.ReferenceEquals (foo1, null)

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

nullIn diesem Fall gibt es tatsächlich eine einfachere Möglichkeit, dies zu überprüfen :

if (foo is null)

Das ist es!

Diese Funktion wurde in C # 7 eingeführt

Reto Messerli
quelle
@ Eliasar zum Glück Fragen haben kein Ablaufdatum :)
Grooveplex
1

Mein Ansatz ist zu tun

(object)item == null

Ich verlasse mich auf objectden eigenen Gleichstellungsoperator, der nichts falsch machen kann. Oder eine benutzerdefinierte Erweiterungsmethode (und eine Überladung):

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;
}

oder um mehr Fälle zu behandeln, kann sein:

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

Die Einschränkung verhindert IsNullWerttypen. Jetzt ist es so süß wie anrufen

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

Das heißt, ich habe einen konsistenten / nicht fehleranfälligen Stil, um durchgehend nach Nullen zu suchen. Ich habe auch festgestellt, dass (object)item == nulles sehr, sehr, etwas schneller ist alsObject.ReferenceEquals(item, null) , aber nur, wenn es darauf ankommt (ich arbeite gerade an etwas, bei dem ich alles mikrooptimieren muss!).

Eine vollständige Anleitung zum Implementieren von Gleichstellungsprüfungen finden Sie unter "Best Practice" für den Vergleich von zwei Instanzen eines Referenztyps.

nawfal
quelle
Nitpick: Leser sollten ihre Abhängigkeiten beobachten, bevor sie auf Funktionen wie Vergleichen zugreifen. DbNullIMO sind Fälle, in denen dies keine Probleme im Zusammenhang mit SRP verursachen würde, ziemlich selten. Nur auf den Code-Geruch hinzuweisen, könnte durchaus angebracht sein.
Der
0

Die statische Equals(Object, Object)Methode gibt an, ob zwei Objekte objAund objBgleich sind. Sie können damit auch Objekte testen, deren Wert nullfür Gleichheit steht. Es vergleicht objAund objBfür die Gleichheit wie folgt:

  • Es bestimmt, ob die beiden Objekte dieselbe Objektreferenz darstellen. Wenn dies der Fall ist, wird die Methode zurückgegeben true. Dieser Test entspricht dem Aufruf der ReferenceEqualsMethode. Wenn beides objAund objBsind null, gibt die Methode außerdem zurück true.
  • Es bestimmt, ob entweder objAoder objBist null. Wenn ja, kehrt es zurück false. Wenn die beiden Objekte nicht dieselbe Objektreferenz darstellen und dies auch nicht ist null, wird objA.Equals(objB)das Ergebnis aufgerufen und zurückgegeben. Dies bedeutet, dass diese Überschreibung aufgerufen wird , wenn objAdie Object.Equals(Object)Methode überschrieben wird.

.

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

Antworten Sie mehr auf den überschreibenden Operator, wie man ihn mit null vergleicht , der hier als Duplikat umleitet.

In den Fällen, in denen dies zur Unterstützung von Wertobjekten durchgeführt wird, finde ich die neue Notation praktisch und möchte sicherstellen, dass es nur einen Ort gibt, an dem der Vergleich durchgeführt wird. Durch die Nutzung von Object.Equals (A, B) werden auch die Nullprüfungen vereinfacht.

Dies wird == ,! =, Equals und GetHashCode überladen

    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();

Fügen Sie für kompliziertere Objekte zusätzliche Vergleiche in Equals und einen umfangreicheren GetHashCode hinzu.

CCondron
quelle
0

Für eine moderne und komprimierte Syntax:

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);
}
mr5
quelle
-3

Ein üblicher Fehler bei Überlastungen des Operators == zu verwenden ist (a == b), (a ==null)oder (b == null)zu Referenz Gleichheit zu überprüfen. Dies führt stattdessen zu einem Aufruf des überladenen Operators ==, wodurch ein infinite loop. Verwenden Sie ReferenceEqualsden Typ oder wandeln Sie ihn in Objekt um, um die Schleife zu vermeiden.

Schau dir das an

// 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;
}

Referenz Richtlinien zum Überladen von Equals () und Operator ==

Basheer AL-MOMANI
quelle
1
Mit all diesen Informationen gibt es bereits mehrere Antworten. Wir brauchen keine 7. Kopie derselben Antwort.
Servy
-5

Sie können versuchen, eine Objekteigenschaft zu verwenden und die resultierende NullReferenceException abzufangen. Wenn die Eigenschaft, die Sie versuchen, von Object geerbt oder überschrieben wird, funktioniert dies für jede Klasse.

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;
}
Der digitale Gabeg
quelle
Wenn Sie viele Nullobjekte haben, kann die Ausnahmebehandlung einen großen Aufwand bedeuten.
Kasprzol
2
Haha, ich stimme zu, dass dies nicht die beste Methode ist. Nach dem Posten dieser Methode habe ich mein aktuelles Projekt sofort überarbeitet, um stattdessen ReferenceEquals zu verwenden. Obwohl es nicht optimal ist, funktioniert es jedoch und ist somit eine gültige Antwort auf die Frage.
Der digitale Gabeg