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


Angesichts der folgenden 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;

        return fooItem.FooId == this.FooId;
    }

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

        return base.GetHashCode();

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

Ich habe das übergangen Equals Methode weil Foo repräsentieren eine Zeile für die Foos Tisch. Welches ist die bevorzugte Methode zum Überschreiben der GetHashCode?

Warum ist es wichtig zu überschreiben? GetHashCode?


1164
2017-12-16 13:41


Ursprung


Antworten:


Ja, es ist wichtig, dass Ihr Artikel als Schlüssel in einem Wörterbuch verwendet wird, oder HashSet<T>, usw. - da dies verwendet wird (in Ermangelung einer Gewohnheit IEqualityComparer<T>) um Gegenstände in Eimer zu gruppieren. Wenn der Hash-Code für zwei Elemente nicht übereinstimmt, können sie dies tun noch nie als gleich angesehen werden (Equals wird nie aufgerufen werden).

Das GetHashCode() Methode sollte das widerspiegeln Equals Logik; Die Regeln sind:

  • wenn zwei Dinge gleich sind (Equals(...) == true) dann werden sie Muss gib den gleichen Wert für zurück GetHashCode()
  • wenn die GetHashCode() ist gleich, ist es nicht notwendig für sie gleich zu sein; das ist eine Kollision, und Equals wird gerufen, um zu sehen, ob es eine echte Gleichheit ist oder nicht.

In diesem Fall sieht es so aus "return FooId;"ist ein geeignetes GetHashCode() Implementierung. Wenn Sie mehrere Eigenschaften testen, ist es üblich, sie unter Verwendung von Code wie unten zu kombinieren, um diagonale Kollisionen zu reduzieren (d. H., Dass new Foo(3,5) hat einen anderen Hash-Code zu new Foo(5,3)):

int hash = 13;
hash = (hash * 7) + field1.GetHashCode();
hash = (hash * 7) + field2.GetHashCode();
...
return hash;

Oh - aus Gründen der Bequemlichkeit, könnten Sie auch in Betracht ziehen == und != Operatoren beim Überschreiben Equals und GetHashCode.


Eine Demonstration dessen, was passiert, wenn du das falsch machst, ist Hier.


1097
2017-12-16 13:47



Es ist wirklich sehr schwer zu implementieren GetHashCode() richtig, denn neben den bereits erwähnten Regeln sollte sich der Hash-Code während der Lebensdauer eines Objekts nicht ändern. Daher müssen die Felder, die zur Berechnung des Hash-Codes verwendet werden, unveränderlich sein.

Ich habe schließlich eine Lösung für dieses Problem gefunden, als ich mit NHibernate arbeitete. 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 Hash-Code hat. Dieser Ansatz funktioniert am besten mit GUIDs, da Sie einen parameterlosen Konstruktor bereitstellen können, der nach dem Zufallsprinzip eine ID generiert.


114
2017-12-21 12:39



Indem Sie Equals überschreiben, geben Sie im Grunde an, dass Sie derjenige sind, der es besser versteht, zwei Instanzen eines bestimmten Typs zu vergleichen, so dass Sie wahrscheinlich der beste Kandidat für die Bereitstellung des besten Hash-Codes sind.

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 können, versucht es nur, einen guten Hash-Code auf Basis aller Felder in der Klasse zu erraten, aber da Sie die Domain oder Wertebereiche Ihres Objekts kennen, könnten Sie immer noch einen besseren liefern.


41
2017-12-16 13:48



Bitte vergiss nicht, den obj-Parameter zu überprüfen null beim Überschreiben Equals(). Vergleichen Sie auch den Typ.

public override bool Equals(object obj)
{
    if (obj == null || GetType() != obj.GetType())
        return false;

    Foo fooItem = obj as Foo;

    return fooItem.FooId == this.FooId;
}

Der Grund dafür ist: Equals muss im Vergleich zu falsch zurückgeben null. Siehe auch http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx


32
2017-11-17 07:46



Wie wäre es mit:

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

Angenommen, Leistung ist kein Problem :)


23
2017-11-25 00:48



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


9
2017-12-16 13:48



Nur um obige Antworten hinzuzufügen:

Wenn Sie Equals nicht überschreiben, lautet das Standardverhalten, dass Referenzen der Objekte verglichen werden. Das Gleiche gilt für Hashcode - die Standardimplementation basiert normalerweise auf einer Speicheradresse der Referenz. Da Sie Equals überschreiben, bedeutet dies, dass das korrekte Verhalten darin besteht, zu vergleichen, was auch immer Sie an Equals und nicht an den Referenzen implementiert haben. Daher sollten Sie das Gleiche für den Hashcode tun.

Clients Ihrer Klasse erwarten, dass der Hashcode eine ähnliche Logik wie die equals-Methode hat, z. B. linq-Methoden, die einen IEqualityComparer verwenden, zuerst die Hashcodes vergleichen und nur dann, wenn sie gleich sind, die Equals () -Methode, die teurer sein kann um zu laufen, wenn wir keinen Hashcode implementiert haben, wird das gleiche Objekt wahrscheinlich unterschiedliche Hashcodes haben (weil sie unterschiedliche Speicheradressen haben) und fälschlicherweise als nicht gleich bestimmt werden (Equals () wird nicht einmal getroffen).

Abgesehen von dem Problem, dass Sie Ihr Objekt möglicherweise nicht finden können, wenn Sie es in einem Wörterbuch verwenden (weil es durch einen 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, führen Sie auch eine Verletzung des Wörterbuchs oder des Hashset-Konzepts ein, die keine identischen Schlüssel erlauben sollte - Sie haben bereits deklariert, dass diese Objekte im Wesentlichen identisch sind, wenn Sie Equals überschreiben, sodass Sie nicht beide als unterschiedliche Schlüssel in einer Datenstruktur haben wollen, die einen eindeutigen Schlüssel haben sollen. Da sie jedoch einen anderen Hashcode haben, wird der "gleiche" Schlüssel als ein anderer eingefügt.


8
2017-11-12 13:48