1670 Stimmen

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

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, weil Foo stellen eine Zeile für die Foo s Tisch. Welches ist die bevorzugte Methode zum Überschreiben der GetHashCode ?

Warum ist es wichtig, die GetHashCode ?

1493voto

Marc Gravell Punkte 970173

Ja, es ist wichtig, wenn Ihr Element als Schlüssel in einem Wörterbuch verwendet werden soll, oder HashSet<T> usw. - da dies (in Ermangelung eines benutzerdefinierten IEqualityComparer<T> ), um Elemente in Eimer zu gruppieren. Wenn der Hash-Code für zwei Elemente nicht übereinstimmt, können sie niemals als gleichwertig betrachtet werden ( Entspricht wird einfach nie aufgerufen).

El GetHashCode() Methode sollte die Equals Logik; die Regeln sind:

  • wenn zwei Dinge gleich sind ( Equals(...) == true ), dann werden sie muss den gleichen Wert zurück für GetHashCode()
  • wenn die GetHashCode() gleich ist, ist es pas notwendig, dass sie gleich sind; dies ist eine Kollision, und Equals wird aufgerufen, um festzustellen, ob es sich um eine echte Gleichheit handelt oder nicht.

In diesem Fall sieht es so aus: " return FooId; " ist eine geeignete GetHashCode() Umsetzung. Wenn Sie mehrere Eigenschaften testen, ist es üblich, sie mit Code wie unten zu kombinieren, um diagonale Kollisionen zu reduzieren (d.h. damit new Foo(3,5) hat einen anderen Hash-Code als new Foo(5,3) ):

In modernen Rahmenwerken ist die HashCode Typ verfügt über Methoden, die Ihnen helfen, einen Hashcode aus mehreren Werten zu erstellen; bei älteren Frameworks müssten Sie ohne auskommen, also etwas wie:

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önnten Sie auch Folgendes anbieten == y != Operatoren beim Überschreiben Equals y GetHashCode .


Eine Demonstration dessen, was passiert, wenn man das falsch macht, ist aquí .

156voto

Albic Punkte 3481

Es ist tatsächlich sehr schwer, das umzusetzen. GetHashCode() korrekt, denn zusätzlich zu den von Marc 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 gearbeitet habe. 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 wollen, was sehr unwahrscheinlich ist, müssen Sie ein neues Objekt erstellen, das eine neue ID und damit einen neuen Hash-Code hat. Dieser Ansatz funktioniert am besten mit GUIDs, weil man einen parameterlosen Konstruktor bereitstellen kann, der zufällig eine ID generiert.

73voto

Trap Punkte 11603

Indem Sie Equals überschreiben, geben Sie im Grunde an, dass Sie am besten wissen, wie man zwei Instanzen eines bestimmten Typs vergleicht, so dass Sie wahrscheinlich der beste Kandidat sind, um den besten Hash-Code zu liefern.

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 einfach, einen guten Hash-Code auf der Grundlage aller Felder in der Klasse zu erraten, aber da Sie die Domäne oder die Wertebereiche Ihres Objekts kennen, können Sie immer noch einen besseren Code angeben.

55voto

l33t Punkte 15267

Ab dem .NET 4.7 die bevorzugte Methode zur Aufhebung von GetHashCode() ist unten dargestellt. Wenn Sie auf ältere .NET-Versionen abzielen, fügen Sie die System.ValueTuple nuget Paket.

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

In Bezug auf die Leistung übertrifft diese Methode die meisten Verbundwerkstoff Hash-Code-Implementierungen. Die ValueTuple es un struct Es gibt also keinen Müll, und der zugrunde liegende Algorithmus ist so schnell wie nur möglich.

45voto

Ludmil Tinkov Punkte 507

どうでしょう。

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

Vorausgesetzt, die Leistung spielt keine Rolle :)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X