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 Equals
Methode überschrieben , weil Foo
sie eine Zeile für die Foo
Tabelle s darstellt. Welches ist die bevorzugte Methode zum Überschreiben der GetHashCode
?
Warum ist es wichtig zu überschreiben GetHashCode
?
c#
overriding
hashcode
David Basarab
quelle
quelle
Antworten:
Ja, es ist wichtig, ob Ihr Artikel als Schlüssel in einem Wörterbuch oder
HashSet<T>
usw. verwendet wird, da dies (ohne benutzerdefinierte FunktionIEqualityComparer<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
Equals
Logik widerspiegeln . Die Regeln sind:Equals(...) == true
), müssen sie den gleichen Wert für zurückgebenGetHashCode()
GetHashCode()
gleich ist, ist es nicht notwendig, dass sie gleich sind; Dies ist eine Kollision undEquals
wird aufgerufen, um zu sehen, ob es sich um eine echte Gleichheit handelt oder nicht.In diesem Fall scheint "
return FooId;
" eine geeigneteGetHashCode()
Implementierung zu sein. Wenn Sie mehrere Eigenschaften testen, ist es üblich, diese mit dem folgenden Code zu kombinieren, um diagonale Kollisionen zu reduzieren (dhnew Foo(3,5)
einen anderen Hash-Code alsnew Foo(5,3)
):Oh - der Einfachheit halber können Sie auch prüfen , Bereitstellung
==
und!=
Betreiber beim ÜberschreibenEquals
undGetHashCode
.Eine Demonstration dessen, was passiert, wenn Sie dies falsch verstehen, finden Sie hier .
quelle
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.
quelle
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:
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.
quelle
0 ^ a = a
, so0 ^ m_someVar1 = m_someVar1
. Er könnte genauso gut den Anfangswert vonresult
auf setzenm_someVar1
.Bitte vergessen Sie nicht, den Parameter obj
null
beim Überschreiben zu überprüfenEquals()
. Und vergleichen Sie auch den Typ.Der Grund dafür ist:
Equals
muss beim Vergleich mit false zurückgebennull
. Siehe auch http://msdn.microsoft.com/en-us/library/bsc2ak47.aspxquelle
obj
tatsächlich gleich ist,this
unabhängig davon, wie Equals () der Basisklasse aufgerufen wurde.fooItem
nach oben verschieben und dann auf Null prüfen, ist die Leistung bei Null oder einem falschen Typ besser.obj as Foo
wäre das ungültig.Wie wäre es mit:
quelle
string.Format
. Ein anderer geeky, den ich gesehen habe, istnew { prop1, prop2, prop3 }.GetHashCode()
. Ich kann jedoch nicht sagen, welches zwischen diesen beiden langsamer wäre. Missbrauche keine Werkzeuge.{ prop1="_X", prop2="Y", prop3="Z" }
und true zurückgeben{ prop1="", prop2="X_Y", prop3="Z_" }
. Das willst du wahrscheinlich nicht.Wir haben zwei Probleme zu bewältigen.
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ängtGetHashCode()
. Die ImplementierungskostenGetHashCode()
lohnen sich also oft nicht oder sind nicht möglich.Wenn jemand Ihr Objekt in eine Sammlung legt, die aufruft,
GetHashCode()
und Sie es überschrieben haben,Equals()
ohne sich auchGetHashCode()
korrekt zu verhalten, kann diese Person Tage damit verbringen, das Problem aufzuspüren.Deshalb mache ich das standardmäßig.
quelle
GetHashCode
Funktion so zu definieren, dass zwei gleich große Objekte denselben Hash-Code zurückgeben.return 24601;
undreturn 8675309;
wären beide gültige Implementierungen vonGetHashCode
. Die Leistung vonDictionary
ist 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.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).
quelle
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.
quelle
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).
quelle
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:
(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:
Wenn der Wert jedoch nicht geändert werden kann, kann Folgendes verwendet werden:
quelle
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.
quelle
Ab
.NET 4.7
dem bevorzugten Verfahren zur ÜberschreibenGetHashCode()
wird unten gezeigt. Wenn Sie auf ältere .NET-Versionen abzielen, fügen Sie das System.ValueTuple-Nuget- Paket hinzu.In Bezug auf die Leistung übertrifft diese Methode die meisten Implementierungen von zusammengesetztem Hashcode. Das ValueTuple ist ein,
struct
damit es keinen Müll gibt, und der zugrunde liegende Algorithmus ist so schnell wie es nur geht.quelle
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.
quelle
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).
quelle