Ich habe im Essential C # 3.0- und .NET 3.5-Buch Folgendes gelesen:
Die Rückgabe von GetHashCode () über die Lebensdauer eines bestimmten Objekts sollte konstant sein (der gleiche Wert), auch wenn sich die Daten des Objekts ändern. In vielen Fällen sollten Sie die Methodenrückgabe zwischenspeichern, um dies zu erzwingen.
Ist das eine gültige Richtlinie?
Ich habe einige in .NET integrierte Typen ausprobiert, die sich nicht so verhalten haben.
Antworten:
Die Antwort ist meistens, es ist eine gültige Richtlinie, aber vielleicht keine gültige Regel. Es erzählt auch nicht die ganze Geschichte.
Der Punkt ist, dass Sie für veränderbare Typen den Hash-Code nicht auf den veränderlichen Daten basieren können, da zwei gleiche Objekte denselben Hash-Code zurückgeben müssen und der Hash-Code für die Lebensdauer des Objekts gültig sein muss. Wenn sich der Hash-Code ändert, erhalten Sie ein Objekt, das in einer Hash-Sammlung verloren geht, weil es nicht mehr im richtigen Hash-Bin gespeichert ist.
Zum Beispiel gibt Objekt A einen Hash von 1 zurück. Es geht also in Bin 1 der Hash-Tabelle. Dann ändern Sie Objekt A so, dass es einen Hash von 2 zurückgibt. Wenn eine Hash-Tabelle danach sucht, sucht sie in Bin 2 und kann es nicht finden - das Objekt ist in Bin 1 verwaist. Deshalb muss der Hash-Code nicht
für die Lebensdauer des Objektsändern , und nur ein Grund, warum das Schreiben von GetHashCode-Implementierungen ein Problem ist.Update
Eric Lippert hat einen Blog gepostet , der hervorragende Informationen zu bietet
GetHashCode
.Zusätzliches Update
Ich habe oben einige Änderungen vorgenommen:
Eine Richtlinie ist nur eine Richtlinie, keine Regel. In der Realität müssen
GetHashCode
diese Richtlinien nur befolgt werden, wenn erwartet wird, dass das Objekt den Richtlinien entspricht, z. B. wenn es in einer Hash-Tabelle gespeichert wird. Wenn Sie niemals beabsichtigen, Ihre Objekte in Hash-Tabellen zu verwenden (oder etwas anderes, das auf den Regeln von basiertGetHashCode
), muss Ihre Implementierung nicht den Richtlinien folgen.Wenn Sie "für die Lebensdauer des Objekts" sehen, sollten Sie "für die Zeit, die das Objekt benötigt, um mit Hash-Tabellen zusammenzuarbeiten" oder ähnliches lesen. Wie bei den meisten Dingen geht
GetHashCode
es darum zu wissen, wann die Regeln zu brechen sind.quelle
Es ist lange her, aber ich denke trotzdem, dass es immer noch notwendig ist, eine korrekte Antwort auf diese Frage zu geben, einschließlich Erklärungen über das Warum und Wie. Die bisher beste Antwort ist die, die den MSDN ausführlich zitiert - versuchen Sie nicht, Ihre eigenen Regeln zu erstellen, die MS-Leute wussten, was sie taten.
Aber das Wichtigste zuerst: Die in der Frage zitierte Richtlinie ist falsch.
Nun das Warum - es gibt zwei von ihnen
Erster Grund : Wenn der Hashcode so berechnet wird, dass er sich während der Lebensdauer eines Objekts nicht ändert, selbst wenn sich das Objekt selbst ändert, würde dies den Gleichstellungsvertrag brechen.
Denken Sie daran: "Wenn zwei Objekte als gleich verglichen werden, muss die GetHashCode-Methode für jedes Objekt denselben Wert zurückgeben. Wenn jedoch zwei Objekte nicht als gleich verglichen werden, müssen die GetHashCode-Methoden für die beiden Objekte keine unterschiedlichen Werte zurückgeben."
Der zweite Satz wird oft falsch interpretiert als "Die einzige Regel ist, dass zum Zeitpunkt der Objekterstellung der Hashcode gleicher Objekte gleich sein muss". Ich weiß nicht genau warum, aber das ist auch hier die Essenz der meisten Antworten.
Stellen Sie sich zwei Objekte vor, die einen Namen enthalten, wobei der Name in der Methode equals verwendet wird: Gleicher Name -> Gleiches. Instanz A erstellen: Name = Joe Instanz B erstellen: Name = Peter
Hashcode A und Hashcode B werden höchstwahrscheinlich nicht identisch sein. Was würde jetzt passieren, wenn der Name von Instanz B in Joe geändert wird?
Gemäß der Richtlinie aus der Frage würde sich der Hashcode von B nicht ändern. Das Ergebnis wäre: A.Equals (B) ==> true, aber gleichzeitig: A.GetHashCode () == B.GetHashCode () ==> false.
Aber genau dieses Verhalten ist im Equals & Hashcode-Vertrag ausdrücklich verboten.
Zweitens warum : Während es natürlich wahr ist, dass Änderungen im Hashcode Hash-Listen und andere Objekte unter Verwendung des Hashcodes beschädigen können, ist das Gegenteil auch der Fall. Wenn Sie den Hashcode nicht ändern, erhalten Sie im schlimmsten Fall Hash-Listen, in denen viele verschiedene Objekte denselben Hashcode haben und sich daher im selben Hash-Bin befinden. Dies geschieht beispielsweise, wenn Objekte mit einem Standardwert initialisiert werden.
Nun zum Hows Nun, auf den ersten Blick scheint es einen Widerspruch zu geben - so oder so wird Code brechen. Kein Problem kommt jedoch von geändertem oder unverändertem Hashcode.
Die Ursache der Probleme ist im MSDN gut beschrieben:
Aus dem Hashtabelleneintrag von MSDN:
Dies bedeutet:
Jedes Objekt, das einen Hashwert erstellt, sollte den Hashwert ändern, wenn sich das Objekt ändert, aber es darf keine Änderungen an sich selbst zulassen, wenn es in einer Hashtabelle verwendet wird (oder natürlich in einem anderen Hash-verwendenden Objekt). .
Erstens, wie einfach es natürlich wäre, unveränderliche Objekte nur für die Verwendung in Hashtabellen zu entwerfen, die bei Bedarf als Kopien der normalen, veränderlichen Objekte erstellt werden. Innerhalb der unveränderlichen Objekte ist es offensichtlich in Ordnung, den Hashcode zwischenzuspeichern, da er unveränderlich ist.
Zweitens: Oder geben Sie dem Objekt ein "Sie sind jetzt gehasht" -Flag, stellen Sie sicher, dass alle Objektdaten privat sind, überprüfen Sie das Flag in allen Funktionen, die Objektdaten ändern können, und lösen Sie Ausnahmedaten aus, wenn keine Änderung zulässig ist (dh das Flag ist gesetzt ). Wenn Sie das Objekt jetzt in einem Hash-Bereich platzieren, stellen Sie sicher, dass Sie das Flag setzen und das Flag deaktivieren, wenn es nicht mehr benötigt wird. Aus Gründen der Benutzerfreundlichkeit würde ich empfehlen, das Flag innerhalb der "GetHashCode" -Methode automatisch zu setzen - auf diese Weise kann es nicht vergessen werden. Und der explizite Aufruf einer "ResetHashFlag" -Methode stellt sicher, dass der Programmierer überlegen muss, ob er die Objektdaten jetzt ändern darf oder nicht.
Ok, was auch gesagt werden sollte: Es gibt Fälle, in denen es möglich ist, Objekte mit veränderlichen Daten zu haben, in denen der Hashcode dennoch unverändert bleibt, wenn die Objektdaten geändert werden, ohne den Gleichheits- und Hashcode-Vertrag zu verletzen.
Dies setzt jedoch voraus, dass die Equals-Methode nicht auch auf den veränderlichen Daten basiert. Wenn ich also ein Objekt schreibe und eine GetHashCode-Methode erstelle, die einen Wert nur einmal berechnet und im Objekt speichert, um ihn bei späteren Aufrufen zurückzugeben, muss ich erneut eine Equals-Methode erstellen, die verwendet wird gespeicherte Werte für den Vergleich, so dass A.Equals (B) niemals auch von false zu true wechselt. Andernfalls würde der Vertrag gebrochen. Das Ergebnis davon ist normalerweise, dass die Equals-Methode keinen Sinn ergibt - es ist nicht die ursprüngliche Referenz gleich, aber es ist auch kein Wert gleich. Manchmal kann dies beabsichtigtes Verhalten sein (dh Kundendatensätze), normalerweise jedoch nicht.
Nehmen Sie also einfach das Ergebnis von GetHashCode vor, wenn sich die Objektdaten ändern und wenn die Verwendung des Objekts innerhalb von Hash mithilfe von Listen oder Objekten beabsichtigt (oder nur möglich) ist, machen Sie das Objekt entweder unveränderlich oder erstellen Sie ein schreibgeschütztes Flag, das für das verwendet werden soll Lebensdauer einer Hash-Liste, die das Objekt enthält.
(Übrigens: All dies ist nicht C # oder .NET-spezifisch - es liegt in der Natur aller Hashtable-Implementierungen oder allgemeiner einer indizierten Liste, dass sich die Identifizierungsdaten von Objekten niemals ändern sollten, solange sich das Objekt in der Liste befindet Wenn diese Regel verletzt wird, tritt unerwartetes und unvorhersehbares Verhalten auf. Irgendwo kann es Listenimplementierungen geben, die alle Elemente in der Liste überwachen und die Liste automatisch neu indizieren - aber die Leistung dieser Elemente wird sicherlich bestenfalls grausam sein.)
quelle
Von MSDN
Dies bedeutet, dass sich der Hash-Code ändern sollte, wenn sich die Werte des Objekts ändern. Beispielsweise sollte eine "Person" -Klasse mit der Eigenschaft "Name" auf "Tom" einen Hashcode und einen anderen Code haben, wenn Sie den Namen in "Jerry" ändern. Ansonsten Tom == Jerry, was wahrscheinlich nicht das ist, was Sie beabsichtigt hätten.
Bearbeiten :
Auch von MSDN:
Aus dem Hashtabelleneintrag von MSDN :
Ich habe dies so gelesen, dass veränderbare Objekte unterschiedliche Hashcodes zurückgeben sollten , wenn sich ihre Werte ändern, es sei denn, sie sind für die Verwendung in einer Hashtabelle vorgesehen.
Im Beispiel von System.Drawing.Point ist das Objekt veränderbar und gibt einen anderen Hashcode zurück, wenn sich der X- oder Y-Wert ändert. Dies würde es zu einem schlechten Kandidaten machen, so wie es ist in einer Hashtabelle verwendet zu werden.
quelle
Ich denke, dass die Dokumentation zu GetHashcode etwas verwirrend ist.
Einerseits gibt MSDN an, dass sich der Hashcode eines Objekts niemals ändern und konstant sein sollte. Andererseits gibt MSDN auch an, dass der Rückgabewert von GetHashcode für 2 Objekte gleich sein sollte, wenn diese 2 Objekte als gleich angesehen werden.
MSDN:
Dies bedeutet dann, dass alle Ihre Objekte unveränderlich sein sollten oder dass die GetHashcode-Methode auf unveränderlichen Eigenschaften Ihres Objekts basieren sollte. Angenommen, Sie haben diese Klasse (naive Implementierung):
Diese Implementierung verstößt bereits gegen die Regeln, die in MSDN zu finden sind. Angenommen, Sie haben zwei Instanzen dieser Klasse. Die Name-Eigenschaft von Instanz1 wird auf 'Pol' und die Name-Eigenschaft von Instanz2 auf 'Piet' gesetzt. Beide Instanzen geben einen anderen Hashcode zurück und sind auch nicht gleich. Angenommen, ich ändere den Namen von Instanz2 in 'Pol', dann sollten gemäß meiner Equals-Methode beide Instanzen gleich sein und gemäß einer der Regeln von MSDN sollten sie denselben Hashcode zurückgeben.
Dies ist jedoch nicht möglich, da sich der Hashcode von Instanz2 ändert und MSDN angibt, dass dies nicht zulässig ist.
Wenn Sie dann eine Entität haben, können Sie den Hashcode möglicherweise so implementieren, dass er den 'primären Bezeichner' dieser Entität verwendet, der im Idealfall ein Ersatzschlüssel oder eine unveränderliche Eigenschaft ist. Wenn Sie ein Wertobjekt haben, können Sie den Hashcode so implementieren, dass er die 'Eigenschaften' dieses Wertobjekts verwendet. Diese Eigenschaften bilden die 'Definition' des Wertobjekts. Dies ist natürlich die Natur eines Wertobjekts; Sie interessieren sich nicht für die Identität, sondern für den Wert.
Und deshalb sollten Wertobjekte unveränderlich sein. (Genau wie im .NET Framework sind Zeichenfolge, Datum usw. unveränderliche Objekte.)
Eine andere Sache, die mir in den Sinn kommt:
Während welcher 'Sitzung' (ich weiß nicht wirklich, wie ich das nennen soll) sollte 'GetHashCode' einen konstanten Wert zurückgeben. Angenommen, Sie öffnen Ihre Anwendung, laden eine Instanz eines Objekts aus der Datenbank (einer Entität) und erhalten den Hashcode. Es wird eine bestimmte Nummer zurückgegeben. Schließen Sie die Anwendung und laden Sie dieselbe Entität. Ist es erforderlich, dass der Hashcode dieses Mal denselben Wert hat wie beim ersten Laden der Entität? IMHO nicht.
quelle
Das ist ein guter Rat. Hier ist, was Brian Pepin zu diesem Thema zu sagen hat:
quelle
X
undY
einmalX.Equals(Y)
oderY.Equals(X)
aufgerufen alle zukünftigen Aufrufe das gleiche Ergebnis liefern sollten. Wenn Sie eine andere Definition von Gleichheit verwenden möchten, verwenden Sie eineEqualityComparer<T>
.Nicht direkt auf Ihre Frage antworten, aber - wenn Sie Resharper verwenden, vergessen Sie nicht, dass es eine Funktion hat, die eine vernünftige GetHashCode-Implementierung (sowie die Equals-Methode) für Sie generiert. Sie können natürlich angeben, welche Mitglieder der Klasse bei der Berechnung des Hashcodes berücksichtigt werden.
quelle
Schauen Sie sich diesen Blog-Beitrag von Marc Brooks an:
VTOs, RTOs und GetHashCode () - oh mein Gott!
Schauen Sie sich dann den Follow-up-Beitrag an (kann nicht verlinkt werden, da ich neu bin, aber es gibt einen Link im ersten Artikel), der weiter diskutiert und einige kleinere Schwachstellen in der anfänglichen Implementierung abdeckt.
Dies war alles, was ich über das Erstellen einer GetHashCode () - Implementierung wissen musste. Er bietet sogar einen Download seiner Methode zusammen mit einigen anderen Dienstprogrammen an, kurz Gold.
quelle
Der Hashcode ändert sich nie, aber es ist auch wichtig zu verstehen, woher der Hashcode kommt.
Wenn Ihr Objekt eine Wertesemantik verwendet, dh die Identität des Objekts wird durch seine Werte definiert (wie String, Farbe, alle Strukturen). Wenn die Identität Ihres Objekts von allen Werten unabhängig ist, wird der Hashcode durch eine Teilmenge seiner Werte identifiziert. Beispielsweise wird Ihr StackOverflow-Eintrag irgendwo in einer Datenbank gespeichert. Wenn Sie Ihren Namen oder Ihre E-Mail-Adresse ändern, bleibt Ihr Kundeneintrag unverändert, obwohl sich einige Werte geändert haben (letztendlich werden Sie normalerweise durch eine lange Kunden-ID identifiziert).
Also kurz gesagt:
Werttypsemantik - Hashcode wird durch Werte definiert Referenztypsemantik - Hashcode wird durch eine ID definiert
Ich schlage vor, Sie lesen Domain Driven Design von Eric Evans, in dem er sich mit Entitäten und Werttypen befasst (was mehr oder weniger das ist, was ich oben versucht habe), wenn dies immer noch keinen Sinn ergibt.
quelle
Lesen Sie die Richtlinien und Regeln für GetHashCode von Eric Lippert
quelle