Was muss in einer Struktur überschrieben werden, um sicherzustellen, dass die Gleichstellung ordnungsgemäß funktioniert?

Antworten:

90

Ein Beispiel aus msdn

public struct Complex 
{
   double re, im;
   public override bool Equals(Object obj) 
   {
        return obj is Complex c && this == c;
   }
   public override int GetHashCode() 
   {
      return re.GetHashCode() ^ im.GetHashCode();
   }
   public static bool operator ==(Complex x, Complex y) 
   {
      return x.re == y.re && x.im == y.im;
   }
   public static bool operator !=(Complex x, Complex y) 
   {
      return !(x == y);
   }
}
UpTheCreek
quelle
Ich frage mich, ob es für die Leistung nicht besser wäre, sie zu verwenden, Complex other = obj as Complexund dann zu prüfen, ob other == nullanstelle einer Verwendung isund dann eine Besetzung ...
Clément
5
@Clement: Das können Sie für eine Struktur nicht tun. Das Ergebnis kann nicht null sein. Sie würden einen Kompilierungsfehler erhalten.
Matthew Watson
@MatthewWatson: Ich denke, man könnte verwenden Complex? other = obj as Complex?, aber nullbare Typen sind oft nicht effizient.
Supercat
1
@HaraldCoppoolse - Werttypen sind natürlich versiegelt, sodass es nicht möglich ist, einen abzuleiten, MyComplexwie Sie vorschlagen.
M.Babcock
1
Warum nicht obj ist SaveOptions op && this == op; ?
Billy Jake O'Connor
45

Sie sollten auch IEquatable <T> implementieren. Hier ist ein Auszug aus den Framework Design Guidelines:

Implementieren Sie IEquatable für Werttypen. Die Object.Equals-Methode für Werttypen verursacht Boxing, und ihre Standardimplementierung ist nicht sehr effizient, da sie die Refektion verwendet. IEquatable.Equals bieten eine viel bessere Leistung und können so implementiert werden, dass kein Boxen verursacht wird.

public struct Int32 : IEquatable<Int32> {
    public bool Equals(Int32 other){ ... }
}

Befolgen Sie bei der Implementierung von IEquatable.Equals die gleichen Richtlinien wie beim Überschreiben von Object.Equals. In Abschnitt 8.7.1 finden Sie detaillierte Richtlinien zum Überschreiben von Object.Equals

Dzmitry Huba
quelle
Dies wird also nur für Werttypen verwendet? (keine Referenz?)
UpTheCreek
1
Da Referenztypen bei der Übergabe als Objekt nicht eingerahmt werden müssen, würde IEquatable <T> keinen Vorteil bieten. Werttypen werden normalerweise vollständig auf den Stapel (oder in das Layout der äußeren Typen) kopiert. Um also einen Objektverweis darauf zu erhalten und die Lebensdauer des Objekts korrekt zu handhaben, muss es verpackt (mit einem speziellen Typ umwickelt) und kopiert werden zum Haufen; Nur dann kann der Verweis auf das Heap-Objekt an eine Funktion wie Object.Equals übergeben werden.
Gimpf
15

Leider habe ich nicht genug Ruf, um andere Einträge zu kommentieren. Daher veröffentliche ich hier eine mögliche Verbesserung der Top-Lösung.

Korrigieren Sie mich, wenn ich falsch liege, aber die oben erwähnte Implementierung

public struct Complex 
{
   double re, im;
   public override bool Equals(Object obj) 
   {
      return obj is Complex && this == (Complex)obj;
   }
   public override int GetHashCode() 
   {
      return re.GetHashCode() ^ im.GetHashCode();
   }
   public static bool operator ==(Complex x, Complex y) 
   {
      return x.re == y.re && x.im == y.im;
   }
   public static bool operator !=(Complex x, Complex y) 
   {
      return !(x == y);
   }
}

Hat einen großen Fehler. Ich beziehe mich auf

  public override int GetHashCode() 
   {
      return re.GetHashCode() ^ im.GetHashCode();
   }

XORing ist symmetrisch, daher würden Complex (2,1) und Complex (1,2) denselben Hashcode ergeben.

Wir sollten wahrscheinlich mehr machen wie:

  public override int GetHashCode() 
   {
      return re.GetHashCode() * 17 ^ im.GetHashCode();
   }
Ajk
quelle
7
Hashcode-Kollisionen sind nicht unbedingt ein Problem. Tatsächlich haben Sie immer die Möglichkeit einer Kollision (Informationen zu Pigionlöchern / Geburtstagsparadoxon). In Ihrem Fall kollidieren Komplex (1,4) und Komplex (4,1) (zugegebenermaßen gab es weniger Kollisionen). Dies hängt von Ihren Daten ab . Der Hashcode wird verwendet, um 99,999% der unerwünschten Objekte (z. B. in einem Wörterbuch) schnell auszusortieren. Die Gleichheitsoperatoren haben das letzte Wort.
DarcyThomas
Das heißt, je mehr Eigenschaften Sie auf der Struktur haben, desto größer ist die Wahrscheinlichkeit einer Kollision. Dies kann ein besserer Hash-Algorithmus sein: stackoverflow.com/a/263416/309634
DarcyThomas
9

Meistens können Sie die Implementierung von Equals und GetHashcode in Strukturen vermeiden, da der Compiler eine automatische Implementierung für Werttypen mit bitweisem Inhalt + Reflexion für Referenzelemente durchführt.

Schauen Sie sich diesen Beitrag an: Welches ist am besten für Datenspeicher Struct / Classes geeignet?

Zur Vereinfachung der Verwendung können Sie also weiterhin == und! = Implementieren.

Meistens können Sie jedoch die Implementierung von Equals und GetHashcode vermeiden.
Ein Fall, in dem Sie Equals und GetHashCode implementieren müssten, betrifft ein Feld, das Sie nicht berücksichtigen möchten.
Zum Beispiel ein Feld, das sich im Laufe der Zeit ändert, wie das Alter einer Person oder die InstantSpeed ​​eines Autos (die Identität des Objekts sollte sich nicht ändern, wenn Sie es wieder im Wörterbuch an derselben Stelle finden möchten).

Grüße, bester Code

Emmanuel DURIN
quelle
Die Reflexion ist im Vergleich zu einer manuellen Implementierung viel langsamer. Wenn Sie Wert auf Leistung legen, schreiben Sie diese manuell.
Károly Ozsvárt
3

Der grundlegende Unterschied zwischen den beiden besteht darin, dass der ==Operator statisch ist, dh die geeignete aufzurufende Methode wird zur Kompilierungszeit bestimmt, während die EqualsMethode auf einer Instanz dinamisch aufgerufen wird.
Beides zu definieren ist wahrscheinlich das Beste, auch wenn dies bei Strukturen weniger wichtig ist, da Strukturen nicht erweitert werden können (eine Struktur kann nicht von einer anderen erben).

Paolo Tedesco
quelle
1

Nur zur Vervollständigung würde ich auch raten, EqualsMethode zu überladen :

public bool Equals(Complex other) 
{
   return other.re == re && other.im == im;
}

Dies ist eine echte Verbesserung, da das Eingabeargument der Equals(Object obj)Methode nicht in einem Boxing auftritt

Einige bewährte Methoden für die Verwendung von Werttypen:

  • mach sie unveränderlich
  • Override Equals (derjenige, der ein Objekt als Argument verwendet);
  • overload Equals, um eine andere Instanz desselben Werttyps zu verwenden (z. B. * Equals (Complex other));
  • Überlastoperatoren == und! =;
  • GetHashCode überschreiben

Dies kommt aus diesem Beitrag: http://theburningmonk.com/2015/07/beware-of-implicit-boxing-of-value-types/

Tomasz Jaskuλa
quelle