Warum ist es wichtig, GetHashCode zu überschreiben, wenn die Equals-Methode überschrieben wird?

1444

Gegeben die folgende Klasse

public class Foo
{
    public int FooId { get; set; }
    public string FooName { get; set; }

    public override bool Equals(object obj)
    {
        Foo fooItem = obj as Foo;

        if (fooItem == null) 
        {
           return false;
        }

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Which is preferred?

        return base.GetHashCode();

        //return this.FooId.GetHashCode();
    }
}

Ich habe die EqualsMethode überschrieben , weil Foosie eine Zeile für die FooTabelle s darstellt. Welches ist die bevorzugte Methode zum Überschreiben der GetHashCode?

Warum ist es wichtig zu überschreiben GetHashCode?

David Basarab
quelle
36
Aufgrund von Kollisionen ist es wichtig, sowohl Gleichheit als auch Gethashcode zu implementieren, insbesondere bei der Verwendung von Wörterbüchern. Wenn zwei Objekte denselben Hashcode zurückgeben, werden sie verkettet in das Wörterbuch eingefügt. Beim Zugriff auf das Element wird die Methode equals verwendet.
DarthVader

Antworten:

1319

Ja, es ist wichtig, ob Ihr Artikel als Schlüssel in einem Wörterbuch oder HashSet<T>usw. verwendet wird, da dies (ohne benutzerdefinierte Funktion IEqualityComparer<T>) zum Gruppieren von Artikeln in Buckets verwendet wird. Wenn der Hash-Code für zwei Elemente nicht übereinstimmt, kann sie nie als gleich betrachtet werden ( Equals wird einfach nie genannt werden).

Die GetHashCode () -Methode sollte die EqualsLogik widerspiegeln . Die Regeln sind:

  • Wenn zwei Dinge gleich sind ( Equals(...) == true), müssen sie den gleichen Wert für zurückgebenGetHashCode()
  • wenn das GetHashCode()gleich ist, ist es nicht notwendig, dass sie gleich sind; Dies ist eine Kollision und Equalswird aufgerufen, um zu sehen, ob es sich um eine echte Gleichheit handelt oder nicht.

In diesem Fall scheint " return FooId;" eine geeignete GetHashCode()Implementierung zu sein. Wenn Sie mehrere Eigenschaften testen, ist es üblich, diese mit dem folgenden Code zu kombinieren, um diagonale Kollisionen zu reduzieren (dh new Foo(3,5)einen anderen Hash-Code als new Foo(5,3)):

unchecked // only needed if you're compiling with arithmetic checks enabled
{ // (the default compiler behaviour is *disabled*, so most folks won't need this)
    int hash = 13;
    hash = (hash * 7) + field1.GetHashCode();
    hash = (hash * 7) + field2.GetHashCode();
    ...
    return hash;
}

Oh - der Einfachheit halber können Sie auch prüfen , Bereitstellung ==und !=Betreiber beim Überschreiben Equalsund GetHashCode.


Eine Demonstration dessen, was passiert, wenn Sie dies falsch verstehen, finden Sie hier .

Marc Gravell
quelle
49
Kann ich fragen, warum Sie mit solchen Faktoren multiplizieren?
Leandro López
22
Eigentlich könnte ich wahrscheinlich einen von ihnen verlieren; Der Punkt ist, zu versuchen, die Anzahl der Kollisionen zu minimieren - so dass ein Objekt {1,0,0} einen anderen Hash hat als {0,1,0} und {0,0,1} (wenn Sie sehen, was ich meine ),
Marc Gravell
13
Ich habe die Zahlen angepasst, um es klarer zu machen (und einen Samen hinzugefügt). Einige Codes verwenden unterschiedliche Zahlen. Beispielsweise verwendet der C # -Compiler (für anonyme Typen) einen Startwert von 0x51ed270b und einen Faktor von -1521134295.
Marc Gravell
76
@Leandro López: Normalerweise werden die Faktoren als Primzahlen gewählt, da dadurch die Anzahl der Kollisionen verringert wird.
Andrei Rînea
29
"Oh - der Einfachheit halber können Sie beim Überschreiben von Equals und GethashCode auch Operatoren == und! = Bereitstellen.": Microsoft rät davon ab, operator == für Objekte zu implementieren, die nicht unveränderlich sind - msdn.microsoft.com/en-us/library/ ms173147.aspx - "Es ist keine gute Idee, operator == in nicht unveränderlichen Typen zu überschreiben."
Antiduh
137

Die GetHashCode()korrekte Implementierung ist tatsächlich sehr schwierig, da sich der Hash-Code zusätzlich zu den bereits erwähnten Regeln von Marc während der Lebensdauer eines Objekts nicht ändern sollte. Daher müssen die Felder, die zur Berechnung des Hash-Codes verwendet werden, unveränderlich sein.

Als ich mit NHibernate arbeitete, fand ich endlich eine Lösung für dieses Problem. Mein Ansatz besteht darin, den Hash-Code aus der ID des Objekts zu berechnen. Die ID kann nur über den Konstruktor festgelegt werden. Wenn Sie also die ID ändern möchten, was sehr unwahrscheinlich ist, müssen Sie ein neues Objekt erstellen, das eine neue ID und daher einen neuen Hashcode hat. Dieser Ansatz funktioniert am besten mit GUIDs, da Sie einen parameterlosen Konstruktor bereitstellen können, der zufällig eine ID generiert.

Albic
quelle
20
@vanja. Ich glaube, es hat damit zu tun: Wenn Sie das Objekt einem Wörterbuch hinzufügen und dann die ID des Objekts ändern, verwenden Sie beim späteren Abrufen einen anderen Hash, um es abzurufen, sodass Sie es nie aus dem Wörterbuch erhalten.
ANeves
74
In der Microsoft-Dokumentation der Funktion GetHashCode () wird weder angegeben noch impliziert, dass der Objekt-Hash während seiner gesamten Lebensdauer konsistent bleiben muss. Tatsächlich wird speziell ein zulässiger Fall erläutert, in dem dies möglicherweise nicht der Fall ist : "Die GetHashCode-Methode für ein Objekt muss konsistent denselben Hashcode zurückgeben, solange der Objektstatus, der den Rückgabewert der Equals-Methode des Objekts bestimmt, nicht geändert wird . "
PeterAllenWebb
37
"Der Hash-Code sollte sich während der Lebensdauer eines Objekts nicht ändern" - das ist nicht wahr.
Apokalypse
7
Eine bessere Möglichkeit zu sagen ist, dass sich "der Hash-Code (noch die Auswertung von Gleichen) während des Zeitraums ändern sollte, in dem das Objekt als Schlüssel für eine Sammlung verwendet wird". Wenn Sie das Objekt also als Schlüssel zu einem Wörterbuch hinzufügen, müssen Sie dies sicherstellen GetHashCode und Equals ändern ihre Ausgabe für eine bestimmte Eingabe erst, wenn Sie das Objekt aus dem Wörterbuch entfernen.
Scott Chamberlain
11
@ScottChamberlain Ich denke, Sie haben NICHT in Ihrem Kommentar vergessen, es sollte sein: "Der Hash-Code (noch die Auswertung von Gleichen) sollte sich NICHT während des Zeitraums ändern, in dem das Objekt als Schlüssel für eine Sammlung verwendet wird." Recht?
Stan Prokop
57

Wenn Sie Equals überschreiben, geben Sie im Grunde an, dass Sie derjenige sind, der besser weiß, wie zwei Instanzen eines bestimmten Typs zu vergleichen sind. Sie sind also wahrscheinlich der beste Kandidat, um den besten Hash-Code bereitzustellen.

Dies ist ein Beispiel dafür, wie ReSharper eine GetHashCode () - Funktion für Sie schreibt:

public override int GetHashCode()
{
    unchecked
    {
        var result = 0;
        result = (result * 397) ^ m_someVar1;
        result = (result * 397) ^ m_someVar2;
        result = (result * 397) ^ m_someVar3;
        result = (result * 397) ^ m_someVar4;
        return result;
    }
}

Wie Sie sehen, wird nur versucht, einen guten Hash-Code basierend auf allen Feldern in der Klasse zu erraten. Da Sie jedoch die Domäne oder die Wertebereiche Ihres Objekts kennen, können Sie immer noch einen besseren bereitstellen.

Falle
quelle
7
Wird dies nicht immer Null zurückgeben? Sollte wahrscheinlich das Ergebnis auf 1 initialisieren! Benötigt auch noch ein paar Semikolons.
Sam Mackrill
16
Sie wissen, was der XOR-Operator (^) tut?
Stephen Drew
1
Wie gesagt, dies ist, was R # für Sie schreibt (zumindest ist es das, was es 2008 getan hat), wenn Sie dazu aufgefordert werden. Offensichtlich soll dieses Snippet vom Programmierer auf irgendeine Weise optimiert werden. Die fehlenden Semikolons ... ja, es sieht so aus, als hätte ich sie weggelassen, als ich den Code aus einer Regionsauswahl in Visual Studio kopiert habe. Ich dachte auch, die Leute würden beides herausfinden.
Falle
3
@ SamMackrill Ich habe in den fehlenden Semikolons hinzugefügt.
Matthew Murdoch
5
@SamMackrill Nein, wird es nicht immer 0 zurück 0 ^ a = a, so 0 ^ m_someVar1 = m_someVar1. Er könnte genauso gut den Anfangswert von resultauf setzen m_someVar1.
Millie Smith
41

Bitte vergessen Sie nicht, den Parameter obj nullbeim Überschreiben zu überprüfen Equals(). Und vergleichen Sie auch den Typ.

public override bool Equals(object obj)
{
    Foo fooItem = obj as Foo;

    if (fooItem == null)
    {
       return false;
    }

    return fooItem.FooId == this.FooId;
}

Der Grund dafür ist: Equalsmuss beim Vergleich mit false zurückgeben null. Siehe auch http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx

huha
quelle
6
Diese Prüfung auf Typ schlägt fehl, wenn eine Unterklasse im Rahmen ihres eigenen Vergleichs auf die Methode der Oberklasse Equals verweist (dh base.Equals (obj)) - sollte stattdessen verwendet werden
sweetfa
@sweetfa: Es hängt davon ab, wie die Equals-Methode der Unterklasse implementiert ist. Es könnte auch base.Equals ((BaseType) obj)) aufrufen, was gut funktionieren würde.
Huha
2
Nein, wird es nicht: msdn.microsoft.com/en-us/library/system.object.gettype.aspx . Außerdem sollte die Implementierung einer Methode je nach Aufruf nicht fehlschlagen oder erfolgreich sein. Wenn der Laufzeittyp eines Objekts eine Unterklasse einer Basisklasse ist, sollte Equals () der Basisklasse true zurückgeben, wenn es objtatsächlich gleich ist, thisunabhängig davon, wie Equals () der Basisklasse aufgerufen wurde.
Jupiter
2
Wenn Sie das fooItemnach oben verschieben und dann auf Null prüfen, ist die Leistung bei Null oder einem falschen Typ besser.
IllidanS4 will Monica zurück
1
@ 40Alpha Na ja, dann obj as Foowäre das ungültig.
IllidanS4 will Monica
35

Wie wäre es mit:

public override int GetHashCode()
{
    return string.Format("{0}_{1}_{2}", prop1, prop2, prop3).GetHashCode();
}

Vorausgesetzt, Leistung ist kein Problem :)

Ludmil Tinkov
quelle
1
ähm - aber Sie geben einen String für eine int-basierte Methode zurück; _0
jim tollan
32
Nein, er ruft GetHashCode () vom String-Objekt auf, das ein int zurückgibt.
Richard Clayton
3
Ich erwarte nicht, dass dies so schnell ist, wie ich es gerne hätte, nicht nur für das Boxen für Werttypen, sondern auch für die Leistung von string.Format. Ein anderer geeky, den ich gesehen habe, ist new { prop1, prop2, prop3 }.GetHashCode(). Ich kann jedoch nicht sagen, welches zwischen diesen beiden langsamer wäre. Missbrauche keine Werkzeuge.
Nawfal
16
Dies wird für { prop1="_X", prop2="Y", prop3="Z" }und true zurückgeben { prop1="", prop2="X_Y", prop3="Z_" }. Das willst du wahrscheinlich nicht.
Voetsjoeba
2
Ja, Sie können das Unterstrichsymbol jederzeit durch etwas ersetzen, das nicht so häufig vorkommt (z. B. •, ▲, ►, ◄, ☺, ☻), und hoffen, dass Ihre Benutzer diese Symbole nicht verwenden ... :)
Ludmil Tinkov
13

Wir haben zwei Probleme zu bewältigen.

  1. Sie können keinen Sinn angeben, GetHashCode()wenn ein Feld im Objekt geändert werden kann. Außerdem wird ein Objekt NIEMALS in einer Sammlung verwendet, die davon abhängt GetHashCode(). Die Implementierungskosten GetHashCode()lohnen sich also oft nicht oder sind nicht möglich.

  2. Wenn jemand Ihr Objekt in eine Sammlung legt, die aufruft, GetHashCode()und Sie es überschrieben haben, Equals()ohne sich auch GetHashCode()korrekt zu verhalten, kann diese Person Tage damit verbringen, das Problem aufzuspüren.

Deshalb mache ich das standardmäßig.

public class Foo
{
    public int FooId { get; set; }
    public string FooName { get; set; }

    public override bool Equals(object obj)
    {
        Foo fooItem = obj as Foo;

        if (fooItem == null)
        {
           return false;
        }

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Some comment to explain if there is a real problem with providing GetHashCode() 
        // or if I just don't see a need for it for the given class
        throw new Exception("Sorry I don't know what GetHashCode should do for this class");
    }
}
Ian Ringrose
quelle
5
Das Auslösen einer Ausnahme von GetHashCode stellt eine Verletzung des Objektvertrags dar. Es ist nicht schwierig, eine GetHashCodeFunktion so zu definieren, dass zwei gleich große Objekte denselben Hash-Code zurückgeben. return 24601;und return 8675309;wären beide gültige Implementierungen von GetHashCode. Die Leistung von Dictionaryist nur dann anständig, wenn die Anzahl der Elemente gering ist, und wird sehr schlecht, wenn die Anzahl der Elemente groß wird, aber es wird auf jeden Fall korrekt funktionieren.
Supercat
2
@supercat, Es ist nicht möglich, GetHashCode sinnvoll zu implementieren, wenn sich die Identifizierungsfelder im Objekt ändern können, da sich der Hash-Code niemals ändern darf. Wenn Sie das tun, was Sie sagen, muss jemand viele Tage damit verbringen, das Leistungsproblem aufzuspüren, und dann viele Wochen mit der Neugestaltung eines großen Systems, um die Verwendung der Wörterbücher zu entfernen.
Ian Ringrose
2
Ich habe so etwas für alle Klassen gemacht, die ich definiert habe und die Equals () benötigen, und bei denen ich mir völlig sicher war, dass ich dieses Objekt niemals als Schlüssel in einer Sammlung verwenden würde. Dann stürzte eines Tages ein Programm ab, in dem ich ein solches Objekt als Eingabe für ein DevExpress XtraGrid-Steuerelement verwendet hatte. Es stellte sich heraus, dass XtraGrid hinter meinem Rücken eine HashTable oder etwas basierend auf meinen Objekten erstellt hat. Ich bin mit den DevExpress-Support-Leuten in einen kleinen Streit darüber geraten. Ich sagte, es sei nicht klug, dass sie die Funktionalität und Zuverlässigkeit ihrer Komponente auf einer unbekannten Kundenimplementierung einer obskuren Methode basierten.
RenniePet
Die DevExpress-Leute waren ziemlich scharfsinnig und sagten im Grunde, ich müsse ein Idiot sein, um eine Ausnahme in einer GetHashCode () -Methode auszulösen. Ich denke immer noch, dass sie eine alternative Methode finden sollten, um das zu tun, was sie tun - ich erinnere mich an Marc Gravell in einem anderen Thread, der beschreibt, wie er ein Wörterbuch beliebiger Objekte erstellt, ohne von GetHashCode () abhängig zu sein - ich kann mich nicht erinnern, wie er es getan hat obwohl.
RenniePet
4
@RenniePet, muss besser verknallt sein, weil eine Ausnahme ausgelöst wird, als einen sehr schwer zu findenden Fehler aufgrund einer ungültigen Implementierung.
Ian Ringrose
12

Dies liegt daran, dass das Framework erfordert, dass zwei Objekte, die identisch sind, denselben Hashcode haben müssen. Wenn Sie die Methode equals überschreiben, um einen speziellen Vergleich zweier Objekte durchzuführen, und die beiden Objekte von der Methode als gleich angesehen werden, muss auch der Hash-Code der beiden Objekte identisch sein. (Wörterbücher und Hashtabellen basieren auf diesem Prinzip).

kemiller2002
quelle
11

Nur um die obigen Antworten hinzuzufügen:

Wenn Sie Equals nicht überschreiben, werden standardmäßig Referenzen der Objekte verglichen. Gleiches gilt für Hashcode - die Standardimplementierung basiert normalerweise auf einer Speicheradresse der Referenz. Da Sie Equals überschrieben haben, bedeutet dies, dass das richtige Verhalten darin besteht, alles zu vergleichen, was Sie auf Equals implementiert haben, und nicht die Referenzen. Daher sollten Sie dasselbe für den Hashcode tun.

Clients Ihrer Klasse erwarten, dass der Hashcode eine ähnliche Logik wie die equals-Methode aufweist. Beispielsweise vergleichen linq-Methoden, die einen IEqualityComparer verwenden, zuerst die Hashcodes und nur dann, wenn sie gleich sind, vergleichen sie die Equals () -Methode, die möglicherweise teurer ist Wenn wir keinen Hashcode implementiert haben, hat das gleiche Objekt wahrscheinlich unterschiedliche Hashcodes (weil sie unterschiedliche Speicheradressen haben) und wird fälschlicherweise als ungleich bestimmt (Equals () trifft nicht einmal).

Abgesehen von dem Problem, dass Sie Ihr Objekt möglicherweise nicht finden können, wenn Sie es in einem Wörterbuch verwendet haben (weil es von einem Hashcode eingefügt wurde und wenn Sie danach suchen, wird der Standard-Hashcode wahrscheinlich anders sein und wieder Equals () wird nicht einmal aufgerufen, wie Marc Gravell in seiner Antwort erklärt, Sie führen auch eine Verletzung des Wörterbuch- oder Hashset-Konzepts ein, die keine identischen Schlüssel zulassen sollte - Sie haben bereits erklärt, dass diese Objekte im Wesentlichen gleich sind, wenn Sie Equals überschreiben, also ziehen Sie an Sie möchten nicht, dass beide Schlüssel in einer Datenstruktur unterschiedliche Schlüssel haben, die einen eindeutigen Schlüssel haben. Da sie jedoch einen anderen Hashcode haben, wird der "gleiche" Schlüssel als unterschiedlicher Schlüssel eingefügt.

BornToCode
quelle
8

Hash-Code wird für Hash-basierte Sammlungen wie Dictionary, Hashtable, HashSet usw. verwendet. Der Zweck dieses Codes besteht darin, ein bestimmtes Objekt sehr schnell vorab zu sortieren, indem es in eine bestimmte Gruppe (Bucket) eingeordnet wird. Diese Vorsortierung hilft enorm beim Auffinden dieses Objekts, wenn Sie es aus der Hash-Sammlung zurückholen müssen, da der Code Ihr Objekt in nur einem Bucket anstatt in allen darin enthaltenen Objekten suchen muss. Je besser die Verteilung der Hash-Codes (bessere Eindeutigkeit) ist, desto schneller wird sie abgerufen. In einer idealen Situation, in der jedes Objekt einen eindeutigen Hashcode hat, handelt es sich um eine O (1) -Operation. In den meisten Fällen nähert es sich O (1).

Maciej
quelle
7

Es ist nicht unbedingt wichtig; Dies hängt von der Größe Ihrer Sammlungen und Ihren Leistungsanforderungen ab und davon, ob Ihre Klasse in einer Bibliothek verwendet wird, in der Sie die Leistungsanforderungen möglicherweise nicht kennen. Ich weiß häufig, dass meine Sammlungsgrößen nicht sehr groß sind und meine Zeit wertvoller ist als ein paar Mikrosekunden Leistung, die durch das Erstellen eines perfekten Hash-Codes erzielt werden. Also (um die nervige Warnung des Compilers loszuwerden) benutze ich einfach:

   public override int GetHashCode()
   {
      return base.GetHashCode();
   }

(Natürlich könnte ich auch ein #pragma verwenden, um die Warnung auszuschalten, aber ich bevorzuge diesen Weg.)

Wenn Sie in der Lage sind , dass Sie tun , die Leistung als alle Probleme von anderen erwähnt müssen hier gelten selbstverständlich. Am wichtigsten - andernfalls erhalten Sie beim Abrufen von Elementen aus einem Hash-Set oder Wörterbuch falsche Ergebnisse: Der Hash-Code darf nicht mit der Lebensdauer eines Objekts variieren (genauer gesagt während der Zeit, in der der Hash-Code benötigt wird, z. B. während des Aufenthalts ein Schlüssel in einem Wörterbuch): Beispielsweise ist Folgendes falsch, da Value öffentlich ist und daher während der Lebensdauer der Instanz extern für die Klasse geändert werden kann. Sie dürfen ihn daher nicht als Grundlage für den Hash-Code verwenden:


   class A
   {
      public int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //WRONG! Value is not constant during the instance's life time
      }
   }    

Wenn der Wert jedoch nicht geändert werden kann, kann Folgendes verwendet werden:


   class A
   {
      public readonly int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //OK  Value is read-only and can't be changed during the instance's life time
      }
   }
ILoveFortran
quelle
3
Abgestimmt. Das ist einfach falsch. Sogar Microsoft gibt in MSDN ( msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx ) an, dass sich der Wert von GetHashCode ändern muss, wenn sich der Objektstatus in einer Weise ändert, die sich auf den Rückgabewert eines Aufrufs auswirken kann to Equals () und zeigt sogar in seinen Beispielen GetHashCode-Implementierungen, die vollständig von öffentlich veränderbaren Werten abhängen.
Sebastian PR Gingter
Sebastian, ich bin anderer Meinung: Wenn Sie einer Sammlung, die Hash-Codes verwendet, ein Objekt hinzufügen, wird es abhängig vom Hash-Code in einen Bin gelegt. Wenn Sie jetzt den Hash-Code ändern, finden Sie das Objekt nicht wieder in der Sammlung, da der falsche Bin durchsucht wird. Dies ist in der Tat etwas, was in unserem Code passiert ist, und deshalb fand ich es notwendig, darauf hinzuweisen.
ILoveFortran
2
Sebastian, Außerdem kann ich im Link ( msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx ) keine Anweisung sehen , die GetHashCode () ändern muss. Im Gegensatz dazu darf es sich NICHT ändern, solange Equals denselben Wert für dasselbe Argument zurückgibt: "Die GetHashCode-Methode für ein Objekt muss konsistent denselben Hashcode zurückgeben, solange der Objektstatus, der den Rückgabewert bestimmt, nicht geändert wird der Equals-Methode des Objekts. "Diese Anweisung impliziert nicht das Gegenteil, dass sie sich ändern muss, wenn sich der Rückgabewert für Equals ändert.
ILoveFortran
2
@Joao, Sie verwechseln die Client / Consumer-Seite des Vertrags mit dem Produzenten / Implementierer. Ich spreche von der Verantwortung des Implementierers, der GetHashCode () überschreibt. Sie sprechen von dem Verbraucher, der den Wert verwendet.
ILoveFortran
1
Vollständiges Missverständnis ... :) Die Wahrheit ist, dass sich der Hash-Code ändern muss, wenn sich der Status des Objekts ändert, es sei denn, der Status ist für die Identität des Objekts irrelevant. Außerdem sollten Sie niemals ein MUTABLE-Objekt als Schlüssel in Ihren Sammlungen verwenden. Verwenden Sie zu diesem Zweck schreibgeschützte Objekte. GetHashCode, Equals ... und einige andere Methoden, an deren Namen ich mich noch nicht erinnere, sollten NIEMALS geworfen werden.
Liebling
0

Sie sollten immer sicherstellen, dass zwei Objekte, die gemäß Equals () gleich sind, denselben Hash-Code zurückgeben, wenn sie gleich sind. Wie in einigen anderen Kommentaren angegeben, ist dies theoretisch nicht obligatorisch, wenn das Objekt niemals in einem Hash-basierten Container wie HashSet oder Dictionary verwendet wird. Ich würde Ihnen jedoch raten, diese Regel immer zu befolgen. Der Grund liegt einfach darin, dass es für jemanden viel zu einfach ist, eine Sammlung von einem Typ in einen anderen zu ändern, um die Leistung tatsächlich zu verbessern oder die Codesemantik besser zu vermitteln.

Angenommen, wir behalten einige Objekte in einer Liste. Einige Zeit später erkennt jemand tatsächlich, dass ein HashSet eine viel bessere Alternative ist, zum Beispiel aufgrund der besseren Sucheigenschaften. Dies ist der Zeitpunkt, an dem wir in Schwierigkeiten geraten können. List würde intern den Standardgleichheitsvergleich für den Typ verwenden, was in Ihrem Fall Gleich bedeutet, während HashSet GetHashCode () verwendet. Wenn sich die beiden unterschiedlich verhalten, wird dies auch Ihr Programm tun. Und denken Sie daran, dass solche Probleme nicht am einfachsten zu beheben sind.

Ich habe dieses Verhalten mit einigen anderen Fallstricken von GetHashCode () in a zusammengefasst Blog-Beitrag zusammengefasst, in dem Sie weitere Beispiele und Erklärungen finden.

Vasil Kosturski
quelle
0

Ab .NET 4.7dem bevorzugten Verfahren zur Überschreiben GetHashCode()wird unten gezeigt. Wenn Sie auf ältere .NET-Versionen abzielen, fügen Sie das System.ValueTuple-Nuget- Paket hinzu.

// C# 7.0+
public override int GetHashCode() => (FooId, FooName).GetHashCode();

In Bezug auf die Leistung übertrifft diese Methode die meisten Implementierungen von zusammengesetztem Hashcode. Das ValueTuple ist ein, structdamit es keinen Müll gibt, und der zugrunde liegende Algorithmus ist so schnell wie es nur geht.

l33t
quelle
-1

Nach meinem Verständnis gibt der ursprüngliche GetHashCode () die Speicheradresse des Objekts zurück. Daher ist es wichtig, diese zu überschreiben, wenn Sie zwei verschiedene Objekte vergleichen möchten.

BEARBEITET: Das war falsch, die ursprüngliche GetHashCode () -Methode kann die Gleichheit von 2 Werten nicht gewährleisten. Gleiche Objekte geben jedoch denselben Hashcode zurück.

user2855602
quelle
-6

Im Folgenden scheint mir die Verwendung von Reflektion eine bessere Option zu sein, wenn man öffentliche Eigenschaften berücksichtigt, da man sich dabei nicht um das Hinzufügen / Entfernen von Eigenschaften kümmern muss (obwohl dies kein so häufiges Szenario ist). Dies hat sich auch als besser erwiesen (Vergleich der Zeit mit der Stoppuhr Diagonistics).

    public int getHashCode()
    {
        PropertyInfo[] theProperties = this.GetType().GetProperties();
        int hash = 31;
        foreach (PropertyInfo info in theProperties)
        {
            if (info != null)
            {
                var value = info.GetValue(this,null);
                if(value != null)
                unchecked
                {
                    hash = 29 * hash ^ value.GetHashCode();
                }
            }
        }
        return hash;  
    }
Guanxi
quelle
12
Die Implementierung von GetHashCode () wird voraussichtlich sehr leicht sein. Ich bin mir nicht sicher, ob die Verwendung von Reflection bei StopWatch bei Tausenden von Anrufen spürbar ist, aber bei Millionen von Anrufen (denken Sie daran, ein Wörterbuch aus einer Liste auszufüllen).
Bohdan_Trotsenko