Frage

Ich kam vor kurzem in diesem, bisher habe ich den Gleichheitsoperator glücklich worden überschrieben ( == ) und / oder Equals Methode, um zu sehen, ob zwei Referenzen Typen enthielten tatsächlich die gleichen Daten (dh zwei verschiedene Instanzen, die gleich aussehen).

Ich habe mit diesem noch mehr, da ich mehr in den automatisierten Tests wurde immer (Vergleichsreferenz / erwartete Daten vor, dass zurückgegeben).

Während ein Teil des Codierungsstandards Richtlinien in MSDN ich stieß auf einen Artikel dass rät dagegen. Jetzt verstehe ich, Warum der Artikel dieses sagen (weil sie nicht gleich sind Instanz ), aber es ist nicht die Frage beantworten:

  1. Was ist der beste Weg, um zwei Referenztypen vergleichen?
  2. Sollten wir implementieren IComparable ? (Ich habe auch erwähnt gesehen, dass dies nur für Werttypen reserviert werden).
  3. Gibt es eine Schnittstelle, weiß ich nicht über?
  4. Sollten wir nur unsere eigene Rolle?

Vielen Dank ^ _ ^

Update

Sieht aus wie ich hatte einige der Dokumentation falsch lesen (es war ein langer Tag) und überschreibt Equals kann der Weg zu gehen ..

  

Wenn Sie Referenz implementieren   Typen, die Sie berücksichtigen sollten zwingende   Entspricht das Verfahren auf einem Referenz-Typ   wenn Ihr Typ sieht aus wie ein Basistyp   wie zum Beispiel ein Punkt, Schnur, bignumber,   und so weiter. Die meisten Referenztypen sollten   nicht die Überlastung Gleichheit Operator,   auch , wenn sie außer Kraft setzen Equals . Jedoch,   wenn Sie die Implementierung einer Referenz   Typ, der dazu bestimmt ist Wert haben   Semantik, wie zum Beispiel einer komplexen Zahl   Typ, sollten Sie die Gleichheit außer Kraft setzen   Operator.

War es hilfreich?

Lösung

Es sieht aus wie Sie in C # sind Codierung, die so genannte Methode hat Equals, dass Ihre Klasse implementieren sollte, sollten Sie zwei Objekte unter Verwendung eines anderen Metrik vergleichen wollen als „sind diese beiden Zeiger (da Objekt-Handles, genau das ist, Zeiger) auf die gleiche Speicheradresse?“.

Ich packte einige Beispiel-Code von hier :

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 der Klasse Überlastungen es, wenn Sie diese Art von Funktionalität wollen.

Der Grund Überlastung ‚==‘ kann eine schlechte Idee für Objekte sein, dass, in der Regel, Sie immer noch in der Lage sein wollen, die „sind diese die gleichen Zeiger“ Vergleiche zu tun. Diese werden in die Regel verläßt auf für, zum Beispiel, ein Element in eine Liste eingefügt, wo keine Duplikate erlaubt sind, und einige Ihrer Rahmen Sachen können nicht funktionieren, wenn dieser Operator in eine Nicht-Standard-Weise überlastet ist.

Andere Tipps

Die Umsetzung der Gleichstellung in .NET korrekt, effizient und ohne Code-Duplizierung ist hart. Insbesondere für Referenztypen mit Wertesemantik (dh unveränderliche Typen, die equvialence als Gleichheit ) zu behandeln, sollten Sie implementieren die System.IEquatable<T> Schnittstelle , und Sie sollten alle die verschiedenen Operationen (Equals, GetHashCode und ==, !=) implementieren.

Als Beispiel ist hier eine Klasse Umsetzung Wertgleichheit:

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 in dem obigen Code sind die fett gedruckten Teile: die zweite Linie in Equals(Point other) und die GetHashCode() Methode. Der andere Code sollte unverändert bleiben.

Für Referenzklassen, die keine unveränderliche Werte darstellen, nicht implementiert Betreiber == und !=. Verwenden Sie stattdessen die Standard Bedeutung, die Objektidentität zu vergleichen ist.

Der Code absichtlich entspricht auch Objekte einer abgeleiteten Klasse-Typ. Oft könnte dies nicht wünschenswert, weil die Gleichheit zwischen der Basisklasse und abgeleitete Klassen ist nicht gut definiert. Leider sind .NET und die Code-Richtlinien nicht ganz klar hier. Der Code, dass ReSharper erstellt, veröffentlicht in einer anderen Antwort , ist anfällig für unerwünschtes Verhalten in solchen Fällen, da Equals(object x) und Equals(SecurableResourcePermission x) < em> wird behandeln diesen Fall anders.

Um dieses Verhalten zu ändern, wird eine zusätzliche Typprüfung oben in der stark typisierte Equals Methode 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);
}

Unten habe ich zusammengefasst, was Sie tun müssen, wenn IEquatable Umsetzung und lieferte die Begründung der verschiedenen MSDN-Dokumentation Seiten.


Zusammenfassung

  • Wenn für Wertgleichheit Prüfung gewünscht wird (wie zum Beispiel, wenn Objekte in einer Sammlung verwenden) sollten Sie den IEquatable-Schnittstelle implementieren, außer Kraft setzen Object.Equals und GetHashCode für Ihre Klasse.
  • Wenn Referenz Gleichheit Prüfung gewünscht wird Sie Operator sollte == Operator! = Und Object.ReferenceEquals .
  • Sie sollten nur außer Kraft setzen Operator == und operator! = Für ValueTypes und unveränderliche Referenztypen.

Begründung

IEquatable

  

Die System.IEquatable Schnittstelle dient zur Gleichheit zwei Instanzen eines Objekts zu vergleichen. Die Objekte werden im Vergleich auf der Grundlage der in der Klasse implementiert Logik. Die Vergleichsergebnisse in einem Booleschen Wert, der angibt, ob die Objekte unterschiedlich sind. Dies steht im Gegensatz zu der System.IComparable-Schnittstelle, die eine ganze Zahl zurückgeben angibt, wie die Objektwerte unterschiedlich sind.

     

Die IEquatable Schnittstelle deklariert zwei Methoden, die überschrieben werden müssen. Das Gleichheits Verfahren enthält die Implementierung des Ist-Vergleich durchzuführen und wahr zurück, wenn die Objektwerte gleich sind, oder falsch ist, wenn sie es nicht sind. Die GetHashCode Verfahren sollte einen eindeutigen Hash-Wert zurückzugeben, die verwendet werden können, eindeutig identische Objekte zu identifizieren, die unterschiedliche Werte enthalten. Die Art des Hashing-Algorithmus verwendet wird, ist implementierungsspezifisch.

IEquatable.Equals Methode

  
      
  • Sie sollten IEquatable für Ihre Objekte implementieren, um die Möglichkeit zu handhaben, dass sie in einem Array oder generische Auflistung gespeichert werden.
  •   
  • Wenn Sie IEquatable implementieren sollten Sie auch die Basisklasse Implementierungen von Object.Equals außer Kraft setzen (Object) und GetHashCode so dass ihr Verhalten mit dem der IEquatable.Equals Methode
  • im Einklang   

Richtlinien für Übergeordnete Equals () und Operator == (C # Programming Guide)

  
      
  • x.Equals (x) gibt true zurück.
  •   
  • x.Equals (y) gibt den gleichen Wert wie y.Equals (x)
  •   
  • if (x.Equals (y) && y.Equals (z)) true zurückliefert, dann x.Equals (z) liefert true zurück.
  •   
  • Durch wiederholtes Aufrufen von x. Equals (y) gibt den gleichen Wert, solange die Objekte, die von x und y referenziert werden nicht geändert.
  •   
  • x. Equals (null) gibt false zurück (für Nicht-Nullable-Wertetypen nur. Weitere Informationen finden Sie unter == Operator (C # -Referenz)

      
        
    • Bei vordefinierten Wertetypen, der Gleichheitsoperator (==) gibt true zurück, wenn die Werte der Operanden gleich sind, andernfalls false.
    •   
    • Für Referenz andere Typen als String, gibt == true, wenn seine zwei Operanden auf das gleiche Objekt beziehen.
    •   
    • Für den String-Typen, == die Werte des Strings vergleicht.
    •   
    • Wenn für null mit == Vergleiche in Ihrem Operator == Überschreibungen zu testen, stellen Sie sicher, dass Sie die Basisobjektklasse Operator verwenden. Wenn Sie dies nicht tun, unendliche Rekursion in einem Stackoverflow resultierende auftreten.
    •   

    Object.Equals-Methode (Object)

      

    Wenn Ihre Programmiersprache unterstützt Betreiber Überlastung und wenn Sie den Gleichheitsoperator für einen bestimmten Typ wählen zu überlasten, dass Typ muss die Equals-Methode überschreiben. Solche Implementierungen der Equals-Methode müssen die gleichen Ergebnisse wie der Gleichheitsoperator zurückkehren

         

    Die folgenden Richtlinien sind für einen Wertart Umsetzung :

         
        
    • Betrachten Sie überwiegendes Equals wesentlich mehr Leistung als durch die Standardimplementierung von Equals auf Valuetype.
    • vorausgesetzt, dass zu gewinnen   
    • Wenn Sie Equals überschreiben und die Sprache unterstützt Betreiber Überlastung, müssen Sie den Gleichheitsoperator für Ihre Werttyp überlasten.
    •   
         

    Die folgenden Richtlinien sind für eine Referenzart Umsetzung :

         
        
    • Betrachten Sie überwiegendes Equals auf einem Referenz-Typ, wenn die Semantik des Typs auf der Tatsache beruht, dass der Typ einen Wert darstellt (s).
    •   
    • müssen die meisten Referenztypen nicht den Gleichheitsoperator überlasten, auch wenn sie Equals überschreiben. Wenn Sie jedoch einen Referenztyp implementieren, die Wert Semantik haben, wie eine komplexe Zahl Typ bestimmt ist, müssen Sie den Gleichheitsoperator außer Kraft setzen.
    •   

    Zusatz Gotchas

Dieser Artikel empfiehlt nur gegen den Gleichheitsoperator überschrieben (für Referenztypen), nicht gegen zwingende Equals. Sie sollten Equals in Ihrem Objekt (Referenz oder Wert) außer Kraft setzen, wenn Gleichheit prüft etwas mehr als Referenzprüfungen bedeuten. Wenn Sie eine Schnittstelle möchten, können Sie auch implementieren IEquatable (verwendet von generische Sammlungen). Wenn Sie IEquatable implementieren, sollten Sie jedoch auch equals überschreiben, da der IEquatable Abschnitt Zustände bemerkt:

  

Wenn Sie IEquatable implementieren , sollten Sie auch die Basisklasse Implementierungen von Object.Equals (Object) und GetHashCode überschreiben, damit ihr Verhalten mit dem der IEquatable .Equals Methode konsistent ist. Wenn Sie außer Kraft tun Object.Equals (Objekt), Ihre überschriebene Implementierung wird auch in Anrufen zu dem statischen Equals (System.Object, System.Object) -Methode auf Ihrer Klasse. Dadurch wird sichergestellt, dass alle Anrufungen der Equals-Methode konsistente Ergebnisse zurück.

In Bezug auf, ob Sie Equals und / oder der Gleichheitsoperator implementieren sollten:

Implementierung der Methode Equals

  

Die meisten Referenztypen sollten nicht den Gleichheitsoperator überlasten, auch wenn sie Equals außer Kraft setzen.

Leitfaden zur Durchführung von Equals und der Gleichheitsoperator ( ==)

  

Überschreiben Sie die Equals-Methode, wenn Sie den Gleichheitsoperator (==) implementieren, und machen sie das gleiche tun.

Das sagt nur, dass Sie außer Kraft setzen müssen Equals, wenn Sie den Gleichheitsoperator implementieren. Es tut nicht sagen, dass Sie den Gleichheitsoperator außer Kraft setzen müssen, wenn Sie Equals überschreiben.

Für komplexe Objekte, die bestimmte Vergleiche dann IComparable Implementierung und Definition des Vergleichs in dem Vergleich Verfahren ergeben wird, ist eine gute Umsetzung.

Zum Beispiel haben wir „Fahrzeug“ Objekte, bei denen der einzige Unterschied die Registrierungsnummer sein kann und wir nutzen diese, um sicherzustellen, zu vergleichen, dass der Erwartungswert bei der Prüfung zurückgegeben wird, ist das, was wir wollen.

Ich neige dazu zu verwenden, was ReSharper automatisch macht. beispielsweise automatisch erstellt es dies für einen meiner Referenztypen:

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 == außer Kraft setzen wollen und tun noch ref Kontrollen, können Sie immer noch Object.ReferenceEquals verwenden.

Microsoft scheint ihre Melodie geändert oder zumindest gibt es widersprüchlichen Informationen über nicht den Gleichheitsoperator zu überlasten. Gemäß dieser Microsoft Artikel dem Titel Gewusst wie: für eine Wertgleichheit definieren Typ:

"Die == und! = Können die Betreiber mit Klassen verwendet werden, selbst wenn die Klasse sie nicht überlastet werden. Allerdings ist das Standardverhalten eine Referenzgleichheitsprüfung durchzuführen. In einer Klasse, wenn Sie die Equals-Methode überlasten, Sie Überlastung sollte == und! = Betreiber, aber es ist nicht erforderlich. "

Laut Eric Lippert in seinem auf eine Frage beantworten ich minimal-Code für die Gleichstellung in C # - er sagt:

"Die Gefahr Sie in hier laufen ist, dass Sie einen Operator == für Sie definiert erhalten, die Referenz Gleichheit tut standardmäßig aktiviert. Sie leicht in eine Situation kommen könnte, wo eine überladene Methode Equals tut Wert der Gleichheit und der == tut Referenz Gleichheit und dann Verweisgleichheit auf nicht referenz gleich Dinge, die Sie versehentlich wert gleich verwenden, sind. Dies ist eine fehleranfällige Praxis, die durch menschlichen Code-Review schwer zu erkennen ist.

Vor ein paar Jahren arbeitete ich auf einer statischen Analyse-Algorithmus, um statistisch diese Situation zu erkennen, und wir fanden wir untersuchten eine Fehlerrate von etwa zwei Instanzen pro Million Codezeilen in allen Codebases. Wenn nur Codebases man bedenkt, die Equals irgendwo außer Kraft gesetzt hatte, war offensichtlich die Fehlerquote deutlich höher!

Darüber hinaus berücksichtigen die Kosten vs Risiken. Wenn Sie bereits Implementierungen von IComparable haben dann die Betreiber alle schreiben, ist trivial Einzeiler, die keine Fehler haben wird und wird nie geändert werden. Es ist die billigste Code, den Sie jemals schreiben würde. Wenn angesichts die Wahl zwischen den fest Kosten für das Schreiben und Testen einem Dutzend winzige Methoden gegen die unbeschränkt Kosten des Findens und Festsetzung ein schwer sehen Fehler, Referenz Gleichheit verwendet wird anstelle von Wertgleichheit, ich weiß, welche ich würde holen.“

Das .NET Framework jemals nicht verwenden == oder! = Mit jeder Art, die Sie schreiben. Aber die Gefahr, was passieren würde, wenn jemand anderes tut. Also, wenn die Klasse für eine dritte Partei ist, dann würde ich immer bietet die == und! = Betreiber. Wenn die Klasse nur verwendet von der Gruppe intern werden soll, würde ich wahrscheinlich immer noch implementieren, um die == und! = Betreiber.

Ich würde nur die <, <=,> und> implementieren = Operatoren wenn IComparable implementiert wurde. IComparable sollte nur dann umgesetzt werden, wenn der Typ Bestell unterstützen muss -. Wie beim Sortieren oder in einem geordneten generischen Container wie SortedSet verwendet werden

Wenn die Gruppe oder Unternehmen eine Politik im Ort mußten nicht immer die == zu implementieren und = Operatoren - dann würde ich natürlich, dass die Politik folgen. Wenn eine solche Politik an Ort und Stelle waren, dann wäre es klug, es zu erzwingen mit einem Q / A-Code-Analyse-Tool, das Flags jedes Vorkommen des == und! = Betreiber, wenn sie mit einem Referenztyp verwendet wird.

Ich glaube immer etwas so einfache wie Objekte auf Gleichheit richtig ist ein bisschen schwierig mit .NET-Design zu überprüfen.

Für Struct

1) Implementieren IEquatable<T>. Es verbessert die Leistung deutlich.

2) Da Sie Ihren eigenen Equals jetzt haben sollte, außer Kraft setzt GetHashCode und konsistent zu sein mit verschiedener Gleichheit sowie Überschreibung object.Equals zu überprüfen.

3) Überlastung == und != Betreiber müssen nicht religiös, da die Compiler warnen werden geschehen, wenn Sie versehentlich eine Struktur mit einem anderen mit einem == oder != gleichsetzen, aber es ist gut, dies zu tun mit Equals Methoden konsistent sein.

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 die Klasse

Von MS:

  

Die meisten Referenztypen sollten nicht den Gleichheitsoperator überlasten, auch wenn sie Equals außer Kraft setzen.

Für mich == fühlt sich an wie Wertgleichheit, eher wie ein syntaktischer Zucker für Equals Verfahren. a == b zu schreiben ist viel intuitiver als a.Equals(b) zu schreiben. In seltenen Fällen müssen wir Bezug Gleichheit überprüfen. In abstrakten Ebenen mit logischen Darstellungen von physischen Objekten zu tun dies ist nicht etwas, das wir überprüfen müssen würden. Ich denke, die unterschiedliche Semantik für == und Equals wirklich verwirrend sein kann. Ich glaube, es für Wertgleichheit und == Referenz (oder einen besseren Namen wie Equals) Gleichheit in erster Linie IsSameAs werden soll. Ich würde gerne Leitlinie nicht MS hier ernst nehmen, nicht nur, weil es mir nicht natürlich ist, sondern auch, weil == Überlastung keinen größeren Schaden nicht tun. Das ist im Gegensatz zu nicht überwiegendem nicht-generic Equals oder GetHashCode, die zurück beißen können, weil Rahmen überall verwendet nicht == aber nur, wenn wir uns selbst verwenden. Der einzige wirkliche Vorteil, den ich aus gewinnen nicht == und != Überlastung wird die Konsistenz mit Gestaltung des gesamten Rahmens sein, über die ich keine Kontrolle habe. Und das ist in der Tat eine große Sache, so traurig werde ich dabei bleiben .

Mit Bezug Semantik (veränderbare Objekte)

1) außer Kraft setzt Equals und GetHashCode.

2) Implementierung IEquatable<T> ist kein Muss, wird aber schön, wenn Sie eine 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 Wert Semantik (unveränderliche Objekte)

Das ist der schwierige Teil. Kann bis leicht verwirrt erhalten, wenn nicht darauf geachtet ..

1) außer Kraft setzt Equals und GetHashCode.

2) Überlast == und != passen Equals. Stellen Sie sicher, es für nulls funktioniert .

2) Implementierung IEquatable<T> ist kein Muss, wird aber schön, wenn Sie eine 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...");
    }
}

Besondere Vorsicht zu sehen, wie es soll, wenn Ihre Klasse ergehen, in solchen Fällen vererbt werden können müssen Sie bestimmen, ob ein Basisklassenobjekt gleich ein abgeleiteten Klassenobjekt sein kann. Idealerweise wird verwendet, wenn keine Objekte der abgeleiteten Klasse auf Gleichheit überprüft, dann eine Basisklasse Instanz gleich einer abgeleiteten Klasse Instanz sein kann, und in solchen Fällen ist es nicht notwendig Type Gleichheit in generic Equals der Basisklasse zu überprüfen.

In der Regel darauf achten, nicht den Code zu duplizieren. Ich könnte eine generische abstrakte Basisklasse gemacht habe (IEqualizable<T> oder so) als Vorlage wiederverwendet leichter zu ermöglichen, aber leider in C #, das mich hält von von zusätzlichen Klassen abgeleitet werden.

Alle Antworten oben sind nicht der Polymorphismus, oft wollen Sie abgeleitete Referenzen die abgeleitete verwenden Equals, selbst wenn über eine Basis Referenz verglichen. Bitte beachten Sie die Frage / Diskussion / Antworten hier - Gleichheit und Polymorphismus

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top