webentwicklung-frage-antwort-db.com.de

Was ist "Best Practice" für den Vergleich zweier Instanzen eines Referenztyps?

Ich habe dies kürzlich festgestellt, bis jetzt habe ich den Gleichheitsoperator ( == ) und/oder Equals -Methode überschrieben, um festzustellen, ob zwei Referenztypen tatsächlich dasselbe enthielten data (dh zwei verschiedene Instanzen, die gleich aussehen).

Ich benutze dies sogar noch mehr, seit ich mich mehr mit automatisierten Tests beschäftigt habe (Vergleich von Referenz-/erwarteten Daten mit den zurückgegebenen).

Beim Durchsuchen einiger Richtlinien für Kodierungsstandards in MSDN bin ich auf einen Artikel gestoßen , der davon abrät. Jetzt verstehe ich warum, dass der Artikel dies sagt (weil sie nicht gleich Instanz sind), aber er beantwortet die Frage nicht:

  1. Wie lassen sich zwei Referenztypen am besten vergleichen?
  2. Sollen wir IComparable implementieren? (Ich habe auch erwähnt, dass dies nur für Werttypen reserviert sein sollte).
  3. Gibt es eine Schnittstelle, von der ich nichts weiß?
  4. Sollen wir nur unsere eigenen rollen ?!

Vielen Dank ^ _ ^

Aktualisieren

Es sieht so aus, als hätte ich einige der Dokumentationen falsch gelesen (es war ein langer Tag) und überschreibe Gleich könnte der richtige Weg sein.

Wenn Sie Referenztypen implementieren, sollten Sie die Equals-Methode für einen Referenztyp überschreiben, wenn Ihr Typ einem Basistyp wie Point, String, BigNumber usw. ähnelt. Die meisten Referenztypen sollten den Operator equality nicht überladen, auch nicht, wenn sie Equals überschreiben. Wenn Sie jedoch einen Referenztyp implementieren, der eine Wertesemantik haben soll, z. B. einen komplexen Zahlentyp, sollten Sie den Gleichheitsoperator überschreiben.

44
Rob Cooper

Es sieht so aus, als würden Sie in C # codieren, das eine Methode namens Equals hat, die Ihre Klasse implementieren soll, wenn Sie zwei Objekte mit einer anderen Metrik vergleichen wollen als "sind diese beiden Zeiger (da Objekthandles eben Zeiger sind) die gleiche Speicheradresse? ".

Ich habe mir einen Beispielcode von here :

class TwoDPoint : System.Object
{
    public readonly int x, y;

    public TwoDPoint(int x, int y)  //constructor
    {
        this.x = x;
        this.y = y;
    }

    public override bool Equals(System.Object obj)
    {
        // If parameter is null return false.
        if (obj == null)
        {
            return false;
        }

        // If parameter cannot be cast to Point return false.
        TwoDPoint p = obj as TwoDPoint;
        if ((System.Object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public bool Equals(TwoDPoint p)
    {
        // If parameter is null return false:
        if ((object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public override int GetHashCode()
    {
        return x ^ y;
    }
}

Java hat sehr ähnliche Mechanismen. Die equals () -Methode ist Teil der Object -Klasse und wird von Ihrer Klasse überlastet, wenn Sie diese Art von Funktionalität wünschen.

Der Grund für das Überladen von '==' kann für Objekte eine schlechte Idee sein, da Sie normalerweise immer noch die Möglichkeit haben sollten, die Vergleiche "Sind diese Zeiger" zu verwenden. Diese werden normalerweise zum Beispiel für das Einfügen eines Elements in eine Liste verwendet, in der keine Duplikate zulässig sind. Einige dieser Framework-Elemente funktionieren möglicherweise nicht, wenn dieser Operator auf eine nicht standardmäßige Weise überladen wird.

21
Matt J

Gleichheit in .NET korrekt, effizient und ohne Code-Duplikation zu implementieren, ist schwierig. Insbesondere für Referenztypen mit Wertsemantik (dh nveränderliche Typen, die Gleichheit als Gleichheit behandeln ) sollten Sie die System.IEquatable<T> -Schnittstelle implementieren, und Sie sollten alle implementieren verschiedene Operationen (Equals, GetHashCode und ==, !=).

Als Beispiel hier eine Klasse, die Wertgleichheit implementiert:

class Point : IEquatable<Point> {
    public int X { get; }
    public int Y { get; }

    public Point(int x = 0, int y = 0) { X = x; Y = y; }

    public bool Equals(Point other) {
        if (other is null) return false;
        return X.Equals(other.X) && Y.Equals(other.Y);
    }

    public override bool Equals(object obj) => Equals(obj as Point);

    public static bool operator ==(Point lhs, Point rhs) => object.Equals(lhs, rhs);

    public static bool operator !=(Point lhs, Point rhs) => ! (lhs == rhs);

    public override int GetHashCode() => X.GetHashCode() ^ Y.GetHashCode();
}

Die einzigen beweglichen Teile im obigen Code sind die fett gedruckten Teile: die zweite Zeile in Equals(Point other) und die GetHashCode() -Methode. Der andere Code sollte unverändert bleiben.

Implementieren Sie für Referenzklassen, die keine unveränderlichen Werte darstellen, die Operatoren == und != nicht. Verwenden Sie stattdessen die Standardbedeutung zum Vergleichen der Objektidentität.

Der Code setzt absichtlich gerade Objekte eines abgeleiteten Klassentyps gleich. Dies ist häufig nicht wünschenswert, da die Gleichheit zwischen der Basisklasse und den abgeleiteten Klassen nicht genau definiert ist. Leider sind .NET und die Kodierungsrichtlinien hier nicht sehr klar. Der von Resharper erstellte Code, veröffentlicht in einer anderen Antwort , kann in solchen Fällen zu unerwünschtem Verhalten führen, da Equals(object x) und Equals(SecurableResourcePermission x) behandeln diesen Fall anders.

Um dieses Verhalten zu ändern, muss eine zusätzliche Typprüfung in die oben stehende stark typisierte Methode Equals eingefügt werden:

public bool Equals(Point other) {
    if (other is null) return false;
    if (other.GetType() != GetType()) return false;
    return X.Equals(other.X) && Y.Equals(other.Y);
}
26
Konrad Rudolph

Im Folgenden habe ich zusammengefasst, was Sie bei der Implementierung von IEquatable tun müssen, und die Begründung der verschiedenen MSDN-Dokumentationsseiten gegeben.


Zusammenfassung

  • Wenn das Testen auf Wertgleichheit gewünscht wird (z. B. bei der Verwendung von Objekten in Auflistungen), sollten Sie die IEquatable-Schnittstelle implementieren, Object.Equals und GetHashCode für Ihre Klasse überschreiben.
  • Wenn Sie die Referenz auf Gleichheit testen möchten, sollten Sie Operator ==, Operator! = Und Object.ReferenceEquals verwenden.
  • Sie sollten Operator == und Operator! = Nur für ValueTypes und unveränderliche Referenztypen überschreiben.

Rechtfertigung

IEquatable

Die System.IEquatable-Schnittstelle wird verwendet, um zwei Instanzen eines Objekts auf Gleichheit zu vergleichen. Die Objekte werden anhand der in der Klasse implementierten Logik verglichen. Der Vergleich führt zu einem booleschen Wert, der angibt, ob die Objekte unterschiedlich sind. Dies steht im Gegensatz zur System.IComparable-Schnittstelle, die eine Ganzzahl zurückgibt, die angibt, wie unterschiedlich die Objektwerte sind.

Die IEquatable-Schnittstelle deklariert zwei Methoden, die überschrieben werden müssen. Die Equals-Methode enthält die Implementierung, um den tatsächlichen Vergleich durchzuführen und true zurückzugeben, wenn die Objektwerte gleich sind, oder false, wenn sie nicht gleich sind. Die GetHashCode-Methode sollte einen eindeutigen Hashwert zurückgeben, mit dem identische Objekte eindeutig identifiziert werden können, die unterschiedliche Werte enthalten. Der verwendete Hash-Algorithmus ist implementierungsspezifisch. 

IEquatable.Equals-Methode

  • Sie sollten IEquatable für Ihre Objekte implementieren, um die Möglichkeit zu berücksichtigen, dass sie in einem Array oder einer generischen Sammlung gespeichert werden.
  • Wenn Sie IEquatable implementieren, sollten Sie auch die Basisklassenimplementierungen von Object.Equals (Object) und GetHashCode überschreiben, damit ihr Verhalten mit dem der IEquatable.Equals-Methode übereinstimmt

Richtlinien zum Überschreiben von Equals () und Operator == (C # -Programmierhandbuch)

  • x.Equals (x) gibt true zurück.
  • x.Equals (y) liefert den gleichen Wert wie y.Equals (x)
  • wenn (x.Equals (y) && y.Equals (z)) true zurückgibt, gibt x.Equals (z) true zurück.
  • Aufeinander folgende Aufrufe von x. Equals (y) gibt denselben Wert zurück, solange die von x und y referenzierten Objekte nicht geändert werden.
  • x. Equals (null) gibt "false" zurück (nur für nicht nullfähige Werttypen. Weitere Informationen finden Sie unter Nullable Types (C # -Programmierhandbuch) .) 
  • Die neue Implementierung von Equals sollte keine Ausnahmen auslösen.
  • Es wird empfohlen, dass jede Klasse, die Equals überschreibt, auch Object.GetHashCode überschreibt.
  • Es wird empfohlen, dass zusätzlich zu Equals (object) jede Klasse Equals (type) für ihren eigenen Typ implementiert, um die Leistung zu verbessern.

Standardmäßig prüft der Operator == auf Referenzgleichheit, indem er bestimmt, ob zwei Referenzen dasselbe Objekt angeben. Daher müssen Referenztypen den Operator == nicht implementieren, um diese Funktionalität zu erhalten. Wenn ein Typ unveränderlich ist, dh die Daten, die in der Instanz enthalten sind, nicht geändert werden können, kann das Überladen des Operators == zum Vergleichen von Wertgleichheit anstelle von Referenzgleichheit hilfreich sein, da sie als unveränderliche Objekte als lang betrachtet werden können da sie den gleichen Wert haben. Es ist keine gute Idee, den Operator == in nicht unveränderlichen Typen zu überschreiben.

  • Überladener Operator == Implementierungen sollten keine Ausnahmen auslösen.
  • Jeder Typ, der Operator überlastet ==, sollte auch Operator! = Überladen.

== Operator (C # -Referenz)

  • Bei vordefinierten Werttypen gibt der Gleichheitsoperator (==) true zurück, wenn die Werte seiner Operanden gleich sind, andernfalls false.
  • Bei anderen Referenztypen als String gibt == true zurück, wenn seine beiden Operanden auf dasselbe Objekt verweisen. 
  • Für den Zeichenfolgentyp vergleicht == die Werte der Zeichenfolgen.
  • Stellen Sie beim Testen auf Null mit == Vergleichen innerhalb Ihres Operators == Überschreibungen sicher, dass Sie den Basisobjektklassenoperator verwenden. Andernfalls tritt eine unendliche Rekursion auf, die zu einem Stackoverflow führt.

Object.Equals-Methode (Object)

Wenn Ihre Programmiersprache das Überladen von Operatoren unterstützt und Sie den Gleichheitsoperator für einen bestimmten Typ überladen, muss dieser Typ die Equals-Methode überschreiben. Solche Implementierungen der Equals-Methode müssen dieselben Ergebnisse wie der Gleichheitsoperator zurückgeben

Die folgenden Richtlinien gelten für die Implementierung eines value type :

  • Erwägen Sie das Überschreiben von Equals, um die Leistung gegenüber der Standardimplementierung von Equals on ValueType zu steigern.
  • Wenn Sie Equals überschreiben und die Sprache das Überladen von Operatoren unterstützt, müssen Sie den Gleichheitsoperator für Ihren Werttyp überladen.

Die folgenden Richtlinien gelten für die Implementierung eines Referenztyps :

  • Erwägen Sie das Überschreiben von Equals für einen Referenztyp, wenn die Semantik des Typs auf der Tatsache basiert, dass der Typ einen oder mehrere Werte repräsentiert.
  • Die meisten Referenztypen dürfen den Gleichheitsoperator nicht überladen, auch wenn sie Equals überschreiben. Wenn Sie jedoch einen Referenztyp implementieren, der eine Wertesemantik aufweisen soll, z. B. einen komplexen Zahlentyp, müssen Sie den Gleichheitsoperator überschreiben.

Zusätzliche Gotchas

16
Zach Burlingame

In diesem Artikel wird lediglich empfohlen, den Gleichheitsoperator (für Referenztypen) nicht zu überschreiben, nicht aber Equals zu überschreiben. Sie sollten Equals innerhalb Ihres Objekts (Referenz oder Wert) überschreiben, wenn Gleichheitsprüfungen mehr als Referenzprüfungen bedeuten. Wenn Sie eine Schnittstelle wünschen, können Sie auch IEquatable (von generischen Sammlungen verwendet) implementieren. Wenn Sie IEquatable implementieren, sollten Sie jedoch auch equals überschreiben, da im Abschnitt "IEquatable" Folgendes angegeben ist:

Wenn Sie IEquatable <T> implementieren, sollten Sie auch die Basisklassenimplementierungen von Object.Equals (Object) und GetHashCode überschreiben, sodass ihr Verhalten mit dem der IEquatable <T> .Equals-Methode übereinstimmt. Wenn Sie Object.Equals (Object) überschreiben, wird Ihre überschriebene Implementierung auch in Aufrufen der statischen Equals-Methode (System.Object, System.Object) für Ihre Klasse aufgerufen. Dadurch wird sichergestellt, dass alle Aufrufe der Equals-Methode konsistente Ergebnisse liefern. 

Ob Sie Equals und/oder den Gleichheitsoperator implementieren sollten:

Von Implementierung der Equals-Methode

Die meisten Referenztypen sollten den Gleichheitsoperator nicht überladen, auch wenn sie Equals überschreiben.

Von Richtlinien für die Implementierung von Equals und des Gleichheitsoperators (==)

Überschreiben Sie die Equals-Methode, wenn Sie den Gleichheitsoperator (==) implementieren, und veranlassen Sie, dass sie dasselbe tun.

Dies bedeutet nur, dass Sie Equals überschreiben müssen, wenn Sie den Gleichheitsoperator implementieren. nicht sagen Sie, dass Sie den Gleichheitsoperator überschreiben müssen, wenn Sie Equals überschreiben.

3
bdukes

Für komplexe Objekte, die spezifische Vergleiche liefern, ist die Implementierung von IComparable und das Definieren des Vergleichs in den Compare-Methoden eine gute Implementierung.

Beispielsweise haben wir "Fahrzeug" -Objekte, bei denen der einzige Unterschied die Registrierungsnummer sein kann, und wir verwenden diese zum Vergleichen, um sicherzustellen, dass der erwartete Wert, der beim Testen zurückgegeben wird, der ist, den wir wollen.

2
Paul Shannon

Ich neige dazu, das zu verwenden, was Resharper automatisch macht. Dies wurde beispielsweise für einen meiner Referenztypen automatisch erstellt:

public override bool Equals(object obj)
{
    if (ReferenceEquals(null, obj)) return false;
    if (ReferenceEquals(this, obj)) return true;
    return obj.GetType() == typeof(SecurableResourcePermission) && Equals((SecurableResourcePermission)obj);
}

public bool Equals(SecurableResourcePermission obj)
{
    if (ReferenceEquals(null, obj)) return false;
    if (ReferenceEquals(this, obj)) return true;
    return obj.ResourceUid == ResourceUid && Equals(obj.ActionCode, ActionCode) && Equals(obj.AllowDeny, AllowDeny);
}

public override int GetHashCode()
{
    unchecked
    {
        int result = (int)ResourceUid;
        result = (result * 397) ^ (ActionCode != null ? ActionCode.GetHashCode() : 0);
        result = (result * 397) ^ AllowDeny.GetHashCode();
        return result;
    }
}

Wenn Sie == überschreiben möchten und trotzdem Überprüfungen durchführen möchten, können Sie Object.ReferenceEquals weiterhin verwenden.

1
mattlant

Es scheint, als habe Microsoft ihre Stimmung geändert, oder zumindest gibt es widersprüchliche Informationen darüber, dass der Gleichheitsoperator nicht überladen wird. Entsprechend diesem Microsoft-Artikel Titel Gewusst wie: Definieren der Wertgleichheit für einen Typ:

"Die Operatoren == und! = Können mit Klassen verwendet werden, auch wenn die Klasse sie nicht überlastet. Standardmäßig wird jedoch eine Referenz auf Gleichheit überprüft. Wenn Sie in einer Klasse die Equals-Methode überladen, sollten Sie die überladen == und! = Operatoren, aber es ist nicht erforderlich. "

Laut Eric Lippert in seiner Antwort zu einer Frage, die ich nach Minimalcode für Gleichheit in C # - gefragt habe, sagt er:

"Die Gefahr, auf die Sie hier stoßen, ist, dass Sie einen ==-Operator für Sie definieren, der standardmäßig auf Gleichheit verweist. Sie könnten leicht in einer Situation enden, in der eine überladene Equals-Methode Gleichheit wertet und == auf Gleichheit und dann Sie verwenden versehentlich eine Referenzgleichheit für nicht referenzgleiche Dinge, die wertgleich sind. Dies ist eine fehleranfällige Vorgehensweise, die bei der Überprüfung des menschlichen Codes nur schwer zu erkennen ist.

Vor ein paar Jahren habe ich an einem statischen Analysealgorithmus gearbeitet, um diese Situation statistisch zu ermitteln, und wir haben in allen untersuchten Codebasen eine Fehlerrate von etwa zwei Instanzen pro Million Codezeilen festgestellt. Bei der Betrachtung von Codebases, bei denen Equals irgendwo außer Kraft gesetzt wurde, war die Fehlerrate offensichtlich erheblich höher!

Berücksichtigen Sie außerdem die Kosten gegenüber den Risiken. Wenn Sie bereits IComparable-Implementierungen haben, sind das Schreiben aller Operatoren triviale Einzeiler, die keine Fehler enthalten und niemals geändert werden. Es ist der billigste Code, den Sie jemals schreiben werden. Wenn ich die Wahl habe zwischen den festen Kosten für das Schreiben und Testen eines Dutzend winziger Methoden und den unbegrenzten Kosten für das Finden und Beheben eines schwer zu erkennenden Fehlers, bei dem Referenzgleichheit anstelle von Wertgleichheit verwendet wird, weiß ich, welche ich wählen würde. "

.NET Framework verwendet niemals == oder! = Für jeden Typ, den Sie schreiben. Die Gefahr besteht jedoch darin, was passieren würde, wenn jemand anderes dies tut. Wenn die Klasse für eine dritte Partei ist, würde ich immer die Operatoren == und! = Angeben. Wenn die Klasse nur für die interne Verwendung durch die Gruppe vorgesehen ist, würde ich wahrscheinlich noch die Operatoren == und! = Implementieren.

Ich würde die Operatoren <, <=,> und> = nur implementieren, wenn IComparable implementiert wurde. IComparable sollte nur implementiert werden, wenn der Typ die Sortierung unterstützen muss, z. B. beim Sortieren oder in einem geordneten generischen Container wie SortedSet.

Wenn die Gruppe oder das Unternehmen eine Richtlinie eingeführt hätte, um die Operatoren == und! = Nicht zu implementieren, dann würde ich natürlich dieser Richtlinie folgen. Wenn eine solche Richtlinie vorhanden ist, ist es ratsam, sie mit einem Q/A-Codeanalyse-Tool durchzusetzen, das jedes Vorkommen der Operatoren == und! = Kennzeichnet, wenn sie mit einem Referenztyp verwendet wird.

1
Bob Bryan

Es ist bemerkenswert, wie schwer es ist, richtig zu werden ...

Die Empfehlung von Microsoft, Gleiche zu haben und in diesem Fall unterschiedliche Dinge zu tun, ist für mich nicht sinnvoll. Irgendwann erwartet jemand (zu Recht), dass Equals und == dasselbe Ergebnis erzielen, und der Code wird bombardieren.

Ich habe nach einer Lösung gesucht, die:

  • erzeugen das gleiche Ergebnis, egal ob Equals oder == in allen Fällen verwendet wird
  • in allen Fällen vollständig polymorph sein (Aufruf der abgeleiteten Gleichheit durch Basisreferenzen)

Das verwende ich:

  class MyClass : IEquatable<MyClass> {
    public int X { get; }
    public int Y { get; }
    public MyClass(int X, int Y) { this.X = X; this.Y = Y; }

    public override bool Equals(object obj) => obj is MyClass o && X == o.X && Y == o.Y;
    public bool Equals(MyClass o) => object.Equals(this, o);
    public static bool operator ==(MyClass o1, MyClass o2) => object.Equals(o1, o2);
    public static bool operator !=(MyClass o1, MyClass o2) => !object.Equals(o1, o2);

    public override int GetHashCode() => HashCode.Combine(X, Y);
  }

Hier endet alles in Equals(object), das immer polymorph ist, so dass beide Ziele erreicht werden.

So ableiten:

  class MyDerived : MyClass, IEquatable<MyDerived> {
    public int Z { get; }
    public int K { get; }
    public MyDerived(int X, int Y, int Z, int K) : base(X, Y) { this.Z = Z; this.K = K; }

    public override bool Equals(object obj) => obj is MyDerived o && base.Equals(obj) && Z == o.Z && K == o.K;
    public bool Equals(MyDerived o) => object.Equals(this, o);
    public static bool operator ==(MyDerived o1, MyDerived o2) => object.Equals(o1, o2);
    public static bool operator !=(MyDerived o1, MyDerived o2) => !object.Equals(o1, o2);

    public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), Z, K);
  }

Das ist im Grunde dasselbe, bis auf ein Gotcha - wenn Equals(object)base.Equals aufrufen möchte, seien Sie vorsichtig mit base.Equals(object) und nicht base.Equals(MyClass) (was zu einer endlosen Rekursion führt).

Ein Nachteil hierbei ist, dass Equals(MyClass) in dieser Implementierung einige Boxen durchführt, jedoch ist das Boxen/Unboxing stark optimiert, und dies ist für mich durchaus wert, die oben genannten Ziele zu erreichen.

demo: https://dotnetfiddle.net/lrMWV8

(Beachten Sie, dass dies für C #> 7.0 gilt)
(basierend auf der Antwort von Konrad)

0
kofifus

Ich glaube, etwas so Einfaches wie das Überprüfen der Gleichheit von Objekten ist mit dem Design von .NET etwas schwierig.

Für Struct

1) Implementiere IEquatable<T>. Es verbessert die Leistung spürbar.

2) Da Sie jetzt Ihr eigenes Equals haben, überschreiben Sie GetHashCode, und um mit verschiedenen Gleichheitsprüfungen übereinzustimmen, überschreiben Sie object.Equals ebenfalls.

3) Überladen der Operatoren == und != muss nicht religiös erfolgen, da der Compiler warnt, wenn Sie eine Struktur versehentlich mit einer anderen mit == oder != gleichsetzen, aber es ist gut dies zu tun, um mit den Equals Methoden übereinzustimmen.

public struct Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        throw new NotImplementedException("Your equality check here...");
    }

    public override bool Equals(object obj)
    {
        if (obj == null || !(obj is Entity))
            return false;

        return Equals((Entity)obj);
    }

    public static bool operator ==(Entity e1, Entity e2)
    {
        return e1.Equals(e2);
    }

    public static bool operator !=(Entity e1, Entity e2)
    {
        return !(e1 == e2);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}

Für Klasse

Von MS:

Die meisten Referenztypen sollten den Gleichheitsoperator nicht überladen, auch wenn sie Equals überschreiben.

Für mich fühlt sich == wie Wertgleichheit an, eher wie ein syntaktischer Zucker für die Equals Methode. Das Schreiben von a == b ist viel intuitiver als das Schreiben von a.Equals(b). In seltenen Fällen müssen wir die Referenzgleichheit überprüfen. In abstrakten Ebenen, die sich mit logischen Darstellungen physischer Objekte befassen, ist dies nicht etwas, was wir überprüfen müssten. Ich denke, eine andere Semantik für == und Equals kann tatsächlich verwirrend sein. Ich glaube, es hätte an erster Stelle == für Wertgleichheit und Equals für Referenz (oder einen besseren Namen wie IsSameAs) Gleichheit sein sollen. Ich würde die MS-Richtlinie hier gerne nicht ernst nehmen, nicht nur, weil es für mich nicht selbstverständlich ist, sondern auch, weil das Überladen von == keinen größeren Schaden anrichtet. Das ist anders, als wenn nicht generische Equals oder GetHashCode nicht überschrieben werden, da das Framework == nirgendwo verwendet, sondern nur, wenn wir es selbst verwenden. Der einzige wirkliche Vorteil, den ich durch das Nichtüberladen von == und != erhalte, ist die Konsistenz mit dem Design des gesamten Frameworks, über das Ich habe keine Kontrolle über. Und das ist in der Tat eine große Sache, so traurig, dass ich mich daran halten werde.

Mit Referenzsemantik (veränderbare Objekte)

1) Überschreiben Sie Equals und GetHashCode.

2) Die Implementierung von IEquatable<T> ist kein Muss, wird aber nett sein, wenn Sie eines haben.

public class Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        if (ReferenceEquals(this, other))
            return true;

        if (ReferenceEquals(null, other))
            return false;

        //if your below implementation will involve objects of derived classes, then do a 
        //GetType == other.GetType comparison
        throw new NotImplementedException("Your equality check here...");
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as Entity);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}

Mit Wertsemantik (unveränderliche Objekte)

Dies ist der schwierige Teil. Kann leicht durcheinander gebracht werden, wenn nicht darauf geachtet wird.

1) Überschreiben Sie Equals und GetHashCode.

2) Überladen Sie == und !=, um Equals abzugleichen. Stellen Sie sicher, dass es für Nullen funktioniert .

2) Die Implementierung von IEquatable<T> ist kein Muss, wird aber nett sein, wenn Sie eines haben.

public class Entity : IEquatable<Entity>
{
    public bool Equals(Entity other)
    {
        if (ReferenceEquals(this, other))
            return true;

        if (ReferenceEquals(null, other))
            return false;

        //if your below implementation will involve objects of derived classes, then do a 
        //GetType == other.GetType comparison
        throw new NotImplementedException("Your equality check here...");
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as Entity);
    }

    public static bool operator ==(Entity e1, Entity e2)
    {
        if (ReferenceEquals(e1, null))
            return ReferenceEquals(e2, null);

        return e1.Equals(e2);
    }

    public static bool operator !=(Entity e1, Entity e2)
    {
        return !(e1 == e2);
    }

    public override int GetHashCode()
    {
        throw new NotImplementedException("Your lightweight hashing algorithm, consistent with Equals method, here...");
    }
}

Achten Sie besonders darauf, wie es sich verhalten soll, wenn Ihre Klasse geerbt werden kann. In solchen Fällen müssen Sie feststellen, ob ein Basisklassenobjekt einem abgeleiteten Klassenobjekt entsprechen kann. Idealerweise, wenn keine Objekte einer abgeleiteten Klasse vorhanden sind Wird für die Gleichheitsprüfung verwendet, kann eine Basisklasseninstanz einer abgeleiteten Klasseninstanz entsprechen, und in solchen Fällen ist es nicht erforderlich, die Type Gleichheit in generischem Equals der Basisklasse zu prüfen.

Achten Sie im Allgemeinen darauf, keinen Code zu duplizieren. Ich hätte eine generische abstrakte Basisklasse (IEqualizable<T> oder so) als Vorlage erstellen können, um die Wiederverwendung zu vereinfachen, aber leider in C #, was mich davon abhält, von zusätzlichen Klassen abzuleiten.

0
nawfal