Frage Was ist der beste Algorithmus für ein überschriebenes System.Object.GetHashCode?


In .NET System.Object.GetHashCode Methode wird an vielen Stellen in den .NET-Basisklassenbibliotheken verwendet. Vor allem, wenn Sie Gegenstände in einer Sammlung schnell finden oder Gleichheit feststellen wollen. Gibt es einen Standardalgorithmus / Best Practice zur Implementierung des GetHashCode Override für meine benutzerdefinierten Klassen, damit ich die Leistung nicht verschlechtern?


1216
2017-11-04 20:53


Ursprung


Antworten:


Ich gehe normalerweise mit etwas wie der Implementierung in Josh Bloch's fabelhaft  Wirksames Java. Es ist schnell und erzeugt einen ziemlich guten Hash, der wahrscheinlich keine Kollisionen verursacht. Wählen Sie zwei verschiedene Primzahlen, z.B. 17 und 23, und tun:

public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = 17;
        // Suitable nullity checks etc, of course :)
        hash = hash * 23 + field1.GetHashCode();
        hash = hash * 23 + field2.GetHashCode();
        hash = hash * 23 + field3.GetHashCode();
        return hash;
    }
}

Wie in den Kommentaren erwähnt, ist es möglicherweise besser, eine große Primzahl auszuwählen, um stattdessen zu multiplizieren. Anscheinend ist 486187739 gut ... und obwohl die meisten Beispiele, die ich mit kleinen Zahlen gesehen habe, dazu tendieren, Primzahlen zu verwenden, gibt es zumindest ähnliche Algorithmen, bei denen oft Nicht-Primzahlen verwendet werden. In der nicht ganzFNV Beispiel: Ich habe zum Beispiel Zahlen benutzt, die scheinbar gut funktionieren - aber der Anfangswert ist kein Primzahlwert. (Die Multiplikationskonstante ist aber Prime. Ich weiß nicht, wie wichtig das ist.)

Das ist besser als die übliche Praxis von XORHash-Codes aus zwei Hauptgründen. Angenommen, wir haben einen Typ mit zwei int Felder:

XorHash(x, x) == XorHash(y, y) == 0 for all x, y
XorHash(x, y) == XorHash(y, x) for all x, y

Übrigens ist der frühere Algorithmus der Algorithmus, der derzeit vom C # -Compiler für anonyme Typen verwendet wird.

Diese Seite gibt einige Optionen. Ich denke in den meisten Fällen ist das oben genannte "gut genug" und es ist unglaublich einfach sich daran zu erinnern und richtig zu kommen. Das FNV Alternative ist ähnlich einfach, verwendet aber andere Konstanten und XOR Anstatt von ADD als eine Kombinationsoperation. Es sieht aus etwas wie der unten stehende Code, aber der normale FNV-Algorithmus arbeitet mit einzelnen Bytes, so dass dies eine Änderung erfordern würde, um eine Iteration pro Byte anstelle eines 32-Bit-Hash-Wertes durchzuführen. FNV ist auch für variable Datenlängen ausgelegt, während wir es hier immer für die gleiche Anzahl von Feldwerten verwenden. Kommentare zu dieser Antwort deuten darauf hin, dass der Code hier nicht so gut funktioniert (im getesteten Beispielfall) wie der obige Additionsansatz.

// Note: Not quite FNV!
public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = (int) 2166136261;
        // Suitable nullity checks etc, of course :)
        hash = (hash * 16777619) ^ field1.GetHashCode();
        hash = (hash * 16777619) ^ field2.GetHashCode();
        hash = (hash * 16777619) ^ field3.GetHashCode();
        return hash;
    }
}

Beachten Sie, dass Sie im Idealfall verhindern sollten, dass sich Ihr gleichheitsrelevanter (und damit hascode-sensitiver) Zustand ändert, nachdem Sie ihn einer Sammlung hinzugefügt haben, die vom Hash-Code abhängt.

Gemäß der Dokumentation:

Sie können GetHashCode für unveränderbare Referenztypen überschreiben. Im Allgemeinen sollten Sie für änderbare Referenztypen GetHashCode nur überschreiben, wenn

  • Sie können den Hash-Code aus Feldern berechnen, die nicht änderbar sind. oder
  • Sie können sicherstellen, dass sich der Hash-Code eines veränderbaren Objekts nicht ändert, während das Objekt in einer Sammlung enthalten ist, die auf seinem Hash-Code beruht.

1357
2017-11-04 20:56



Microsoft bietet bereits einen guten generischen HashCode-Generator: Kopieren Sie einfach Ihre Eigenschaften / Feldwerte in einen anonymen Typ und hashen Sie es:

new { PropA, PropB, PropC, PropD }.GetHashCode();

Dies funktioniert für eine beliebige Anzahl von Eigenschaften. Es verwendet keine Boxen oder zusätzliche Ressourcen. Es verwendet nur den Algorithmus, der bereits im Framework für anonyme Typen implementiert wurde.


302
2018-01-07 21:38



Hier ist mein Hashcode-Helfer.
Es hat den Vorteil, dass es generische Argumente verwendet und daher nicht zum Boxen führt:

public static class HashHelper
{
    public static int GetHashCode<T1, T2>(T1 arg1, T2 arg2)
    {
         unchecked
         {
             return 31 * arg1.GetHashCode() + arg2.GetHashCode();
         }
    }

    public static int GetHashCode<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3)
    {
        unchecked
        {
            int hash = arg1.GetHashCode();
            hash = 31 * hash + arg2.GetHashCode();
            return 31 * hash + arg3.GetHashCode();
        }
    }

    public static int GetHashCode<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, 
        T4 arg4)
    {
        unchecked
        {
            int hash = arg1.GetHashCode();
            hash = 31 * hash + arg2.GetHashCode();
            hash = 31 * hash + arg3.GetHashCode();
            return 31 * hash + arg4.GetHashCode();
        }
    }

    public static int GetHashCode<T>(T[] list)
    {
        unchecked
        {
            int hash = 0;
            foreach (var item in list)
            {
                hash = 31 * hash + item.GetHashCode();
            }
            return hash;
        }
    }

    public static int GetHashCode<T>(IEnumerable<T> list)
    {
        unchecked
        {
            int hash = 0;
            foreach (var item in list)
            {
                hash = 31 * hash + item.GetHashCode();
            }
            return hash;
        }
    }

    /// <summary>
    /// Gets a hashcode for a collection for that the order of items 
    /// does not matter.
    /// So {1, 2, 3} and {3, 2, 1} will get same hash code.
    /// </summary>
    public static int GetHashCodeForOrderNoMatterCollection<T>(
        IEnumerable<T> list)
    {
        unchecked
        {
            int hash = 0;
            int count = 0;
            foreach (var item in list)
            {
                hash += item.GetHashCode();
                count++;
            }
            return 31 * hash + count.GetHashCode();
        }
    }

    /// <summary>
    /// Alternative way to get a hashcode is to use a fluent 
    /// interface like this:<br />
    /// return 0.CombineHashCode(field1).CombineHashCode(field2).
    ///     CombineHashCode(field3);
    /// </summary>
    public static int CombineHashCode<T>(this int hashCode, T arg)
    {
        unchecked
        {
            return 31 * hashCode + arg.GetHashCode();   
        }
    }

Es hat auch eine Erweiterungsmethode, um eine flüssige Oberfläche zu bieten, so dass Sie es wie folgt verwenden können:

public override int GetHashCode()
{
    return HashHelper.GetHashCode(Manufacturer, PartN, Quantity);
}

oder so:

public override int GetHashCode()
{
    return 0.CombineHashCode(Manufacturer)
        .CombineHashCode(PartN)
        .CombineHashCode(Quantity);
}

94
2018-04-04 18:26



Ich habe eine Hashing-Klasse in der Helper-Bibliothek, die ich für diesen Zweck verwende.

/// <summary> 
/// This is a simple hashing function from Robert Sedgwicks Hashing in C book.
/// Also, some simple optimizations to the algorithm in order to speed up
/// its hashing process have been added. from: www.partow.net
/// </summary>
/// <param name="input">array of objects, parameters combination that you need
/// to get a unique hash code for them</param>
/// <returns>Hash code</returns>
public static int RSHash(params object[] input)
{
    const int b = 378551;
    int a = 63689;
    int hash = 0;

    // If it overflows then just wrap around
    unchecked
    {
        for (int i = 0; i < input.Length; i++)
        {
            if (input[i] != null)
            {
                hash = hash * a + input[i].GetHashCode();
                a = a * b;
            }
        }
    }

    return hash;
}

Dann können Sie es einfach als:

public override int GetHashCode()
{
    return Hashing.RSHash(_field1, _field2, _field3);
}

Ich habe seine Leistung nicht bewertet, daher ist jede Rückmeldung willkommen.


57
2018-02-23 11:46



Hier ist meine Hilfsklasse mit Jon Skeets Implementierung.

public static class HashCode
{
    public const int Start = 17;

    public static int Hash<T>(this int hash, T obj)
    {
        var h = EqualityComparer<T>.Default.GetHashCode(obj);
        return unchecked((hash * 31) + h);
    }
}

Verwendung:

public override int GetHashCode()
{
    return HashCode.Start
        .Hash(_field1)
        .Hash(_field2)
        .Hash(_field3);
}

Wenn Sie vermeiden möchten, eine Erweiterungsmethode für System.Int32 zu schreiben:

public struct HashCode
{
    private readonly int _value;

    public HashCode(int value) => _value = value;

    public static HashCode Start { get; } = new HashCode(17);

    public static implicit operator int(HashCode hash) => hash._value;

    public HashCode Hash<T>(T obj)
    {
        var h = EqualityComparer<T>.Default.GetHashCode(obj);
        return unchecked(new HashCode((_value * 31) + h));
    }

    public override int GetHashCode() => _value;
}

Es ist immer noch generisch, es vermeidet immer noch die Heap-Zuweisung und es wird genau so verwendet:

public override int GetHashCode()
{
    // This time `HashCode.Start` is not an `Int32`, it's a `HashCode` instance.
    // And the result is implicitly converted to `Int32`.
    return HashCode.Start
        .Hash(_field1)
        .Hash(_field2)     
        .Hash(_field3);
}

Update nach Martins Kommentar:

obj != null verursacht Boxen, also wechselte ich zum Standard-Vergleich.

  • Sehen diese Antwort in Bezug auf die Leistung des Standardvergleichs.
  • Sehen diese Frage für eine Diskussion über die Hash-Codes von Nullwerten.

Bearbeiten (Mai 2018):

EqualityComparer<T>.Default Getter ist jetzt ein JIT intrinsisch - der Pull-Anfrage wird von Stephen Toub in erwähnt dieser Blogbeitrag.


49
2017-09-04 12:32



In den meisten Fällen, in denen Equals () mehrere Felder vergleicht, spielt es keine Rolle, ob GetHash () Hashes für ein Feld oder für viele verwendet. Sie müssen nur sicherstellen, dass die Hash-Berechnung wirklich billig ist (Keine Zuordnungen, bitte) und schnell (Keine schweren Berechnungen und sicherlich keine Datenbankverbindungen) und bietet eine gute Verteilung.

Das schwere Heben sollte Teil der Equals () Methode sein; Der Hash sollte eine sehr billige Operation sein, um es zu ermöglichen, Equals () auf so wenig Gegenstände wie möglich aufzurufen.

Und noch ein letzter Tipp: Verlassen Sie sich nicht darauf, dass GetHashCode () über mehrere Anwendungsläufe hinweg stabil ist. Viele .Net-Typen garantieren nicht, dass ihre Hash-Codes nach einem Neustart gleich bleiben. Daher sollten Sie nur den Wert von GetHashCode () für Datenstrukturen im Speicher verwenden.


26
2018-02-23 11:55



Bis vor kurzem wäre meine Antwort Jon Skeet sehr nahe gekommen. Ich habe jedoch kürzlich ein Projekt gestartet, das Zwei-Potenz-Hashtabellen verwendet, also Hashtabellen, bei denen die Größe der internen Tabelle 8, 16, 32 usw. beträgt. Es gibt einen guten Grund, die Primzahlgrößen zu bevorzugen sind auch Vorteile gegenüber Zweier-Größen.

Und es hat ziemlich viel gelutscht. Nach ein bisschen Experimentieren und Nachforschen begann ich meine Hashes mit den folgenden Hashwerten zu wiederholen:

public static int ReHash(int source)
{
  unchecked
  {
    ulong c = 0xDEADBEEFDEADBEEF + (ulong)source;
    ulong d = 0xE2ADBEEFDEADBEEF ^ c;
    ulong a = d += c = c << 15 | c >> -15;
    ulong b = a += d = d << 52 | d >> -52;
    c ^= b += a = a << 26 | a >> -26;
    d ^= c += b = b << 51 | b >> -51;
    a ^= d += c = c << 28 | c >> -28;
    b ^= a += d = d << 9 | d >> -9;
    c ^= b += a = a << 47 | a >> -47;
    d ^= c += b << 54 | b >> -54;
    a ^= d += c << 32 | c >> 32;
    a += d << 25 | d >> -25;
    return (int)(a >> 1);
  }
}

Und dann hat meine Zweier-Potenz-Hash-Tabelle nicht mehr gelutscht.

Das hat mich aber gestört, weil das obige nicht funktionieren sollte. Genauer gesagt, es sollte nicht funktionieren, außer das Original GetHashCode() war auf eine ganz besondere Art und Weise arm.

Das erneute Mischen eines Hashcodes kann einen großen Hashcode nicht verbessern, da der einzige mögliche Effekt darin besteht, dass wir einige weitere Kollisionen einführen.

Das erneute Mischen eines Hash-Codes kann einen schrecklichen Hash-Code nicht verbessern, da der einzig mögliche Effekt darin besteht, z.B. eine große Anzahl von Kollisionen auf Wert 53 zu einer großen Anzahl von Wert 18.3487.291.

Das erneute Mischen eines Hash-Codes kann nur einen Hash-Code verbessern, der zumindest ziemlich gut dazu beigetragen hat, absolute Kollisionen über seinen gesamten Bereich hinweg zu vermeiden (232 mögliche Werte), aber die Vermeidung von Kollisionen bei der eigentlichen Verwendung in einer Hash-Tabelle. Während der einfachere Modulo einer Zweierpotenz-Tabelle dies deutlicher machte, hatte es auch negative Auswirkungen auf die gebräuchlicheren Primzahltabellen, die einfach nicht so offensichtlich waren (die zusätzliche Arbeit in der Umwertung würde den Nutzen überwiegen , aber der Vorteil wäre immer noch da).

Edit: Ich habe auch Open-Adressing verwendet, was auch die Kollisionsempfindlichkeit erhöht hätte, vielleicht sogar mehr als die Tatsache, dass es sich um eine Zweierpotenz handelte.

Und gut, es war beunruhigend wie sehr die string.GetHashCode() Implementierungen in .NETZ (oder studieren Hier) könnte auf diese Weise verbessert werden (in der Größenordnung von Tests, die wegen weniger Kollisionen etwa 20 bis 30 mal schneller laufen) und mehr störend, wie sehr meine eigenen Hash-Codes verbessert werden könnten (viel mehr als das).

Alle GetHashCode () - Implementierungen, die ich in der Vergangenheit programmiert hatte und in der Tat als Grundlage für Antworten auf dieser Site verwendet wurde, waren viel schlimmer als ich es mir vorgestellt hatte. Meistens war es für viele Anwendungen "gut genug", aber ich wollte etwas Besseres.

Also stellte ich dieses Projekt auf die Seite (es war sowieso ein Haustier-Projekt) und begann, mir zu überlegen, wie man einen guten, gut verteilten Hash-Code in .NET schnell erzeugen kann.

Am Ende habe ich mich auf Portierung festgelegt Spooky Hash sich vernetzten. Tatsächlich ist der obige Code eine Fast-Path-Version von SpookyHash, um eine 32-Bit-Ausgabe von einer 32-Bit-Eingabe zu erzeugen.

Jetzt ist SpookyHash kein nettes, schnell zu merkendes Stück Code. Mein Port davon ist noch weniger, weil ich viel davon für eine bessere Geschwindigkeit handinline *. Aber dafür ist Code-Wiederverwendung gedacht.

Dann lege ich Das Projekt auf die eine Seite, denn genau wie das ursprüngliche Projekt die Frage hervorgebracht hatte, wie man einen besseren Hash-Code erzeugen kann, stellte das Projekt die Frage, wie man ein besseres .NET memcpy erzeugen kann.

Dann kam ich zurück und produzierte viele Überladungen, um einfach alle nativen Typen zu füttern (außer decimal†) in einen Hash-Code.

Es ist schnell, wofür Bob Jenkins den meisten Dank verdient, weil sein ursprünglicher Code, den ich portiert habe, noch schneller ist, besonders auf 64-Bit-Rechnern, für die der Algorithmus optimiert ist.

Der vollständige Code kann unter gesehen werden https://bitbucket.org/JonHanna/spookilysharp/src aber bedenken Sie, dass der obige Code eine vereinfachte Version davon ist.

Da es jetzt aber schon geschrieben ist, kann man es leichter nutzen:

public override int GetHashCode()
{
  var hash = new SpookyHash();
  hash.Update(field1);
  hash.Update(field2);
  hash.Update(field3);
  return hash.Final().GetHashCode();
}

Wenn Sie mit nicht vertrauenswürdigen Eingaben umgehen müssen und Schutz vor Hash-DoS-Angriffen haben möchten, können Sie einen Startwert basierend auf der Betriebszeit oder ähnlichem festlegen und die Ergebnisse für Angreifer unvorhersehbar machen:

private static long hashSeed0 = Environment.TickCount;
private static long hashSeed1 = DateTime.Now.Ticks;
public override int GetHashCode()
{
  //produce different hashes ever time this application is restarted
  //but remain consistent in each run, so attackers have a harder time
  //DoSing the hash tables.
  var hash = new SpookyHash(hashSeed0, hashSeed1);
  hash.Update(field1);
  hash.Update(field2);
  hash.Update(field3);
  return hash.Final().GetHashCode();
}

* Eine große Überraschung darin ist, dass eine Rotationsmethode, die zurückgegeben wurde, in die Hand genommen wird (x << n) | (x >> -n) verbesserte Dinge. Ich wäre mir sicher gewesen, dass der Jitter das für mich inline hätte, aber das Profiling zeigte etwas anderes.

decimal ist nicht nativ aus der .NET-Perspektive, obwohl es aus dem C # stammt. Das Problem damit ist das eigene GetHashCode() behandelt Präzision als wichtig, während es seine eigene ist Equals() nicht. Beide sind gültige Entscheidungen, aber nicht so gemischt. Bei der Implementierung Ihrer eigenen Version müssen Sie sich entscheiden, ob Sie die eine oder die andere Version verwenden möchten, aber ich kann nicht wissen, welche Sie möchten.

‡ Zum Vergleich. Wenn es auf einer Zeichenkette verwendet wird, ist der SpookyHash auf 64 Bits beträchtlich schneller als string.GetHashCode() auf 32 Bits, die etwas schneller ist als string.GetHashCode() auf 64 Bit, was wesentlich schneller ist als SpookyHash auf 32 Bit, aber immer noch schnell genug, um eine vernünftige Wahl zu sein.


18
2018-01-14 14:15



Das ist ein guter:

/// <summary>
/// Helper class for generating hash codes suitable 
/// for use in hashing algorithms and data structures like a hash table. 
/// </summary>
public static class HashCodeHelper
{
    private static int GetHashCodeInternal(int key1, int key2)
    {
        unchecked
        {
           var num = 0x7e53a269;
           num = (-1521134295 * num) + key1;
           num += (num << 10);
           num ^= (num >> 6);

           num = ((-1521134295 * num) + key2);
           num += (num << 10);
           num ^= (num >> 6);

           return num;
        }
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="arr">An array of objects used for generating the 
    /// hash code.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// </returns>
    public static int GetHashCode(params object[] arr)
    {
        int hash = 0;
        foreach (var item in arr)
            hash = GetHashCodeInternal(hash, item.GetHashCode());
        return hash;
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="obj1">The first object.</param>
    /// <param name="obj2">The second object.</param>
    /// <param name="obj3">The third object.</param>
    /// <param name="obj4">The fourth object.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and
    /// data structures like a hash table.
    /// </returns>
    public static int GetHashCode<T1, T2, T3, T4>(T1 obj1, T2 obj2, T3 obj3,
        T4 obj4)
    {
        return GetHashCode(obj1, GetHashCode(obj2, obj3, obj4));
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="obj1">The first object.</param>
    /// <param name="obj2">The second object.</param>
    /// <param name="obj3">The third object.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// </returns>
    public static int GetHashCode<T1, T2, T3>(T1 obj1, T2 obj2, T3 obj3)
    {
        return GetHashCode(obj1, GetHashCode(obj2, obj3));
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="obj1">The first object.</param>
    /// <param name="obj2">The second object.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// </returns>
    public static int GetHashCode<T1, T2>(T1 obj1, T2 obj2)
    {
        return GetHashCodeInternal(obj1.GetHashCode(), obj2.GetHashCode());
    }
}

Und hier ist, wie man es benutzt:

private struct Key
{
    private Type _type;
    private string _field;

    public Type Type { get { return _type; } }
    public string Field { get { return _field; } }

    public Key(Type type, string field)
    {
        _type = type;
        _field = field;
    }

    public override int GetHashCode()
    {
        return HashCodeHelper.GetHashCode(_field, _type);
    }

    public override bool Equals(object obj)
    {
        if (!(obj is Key))
            return false;
        var tf = (Key)obj;
        return tf._field.Equals(_field) && tf._type.Equals(_type);
    }
}

12
2017-10-07 10:51



Hier ist mein vereinfachender Ansatz. Ich verwende hierfür das klassische Builder-Muster. Es ist typsicher (kein Boxen / Unboxing) und auch kompatibel mit .NET 2.0 (keine Erweiterungsmethoden etc.).

Es wird so benutzt:

public override int GetHashCode()
{
    HashBuilder b = new HashBuilder();
    b.AddItems(this.member1, this.member2, this.member3);
    return b.Result;
} 

Und hier ist die Actual Builder Klasse:

internal class HashBuilder
{
    private const int Prime1 = 17;
    private const int Prime2 = 23;
    private int result = Prime1;

    public HashBuilder()
    {
    }

    public HashBuilder(int startHash)
    {
        this.result = startHash;
    }

    public int Result
    {
        get
        {
            return this.result;
        }
    }

    public void AddItem<T>(T item)
    {
        unchecked
        {
            this.result = this.result * Prime2 + item.GetHashCode();
        }
    }

    public void AddItems<T1, T2>(T1 item1, T2 item2)
    {
        this.AddItem(item1);
        this.AddItem(item2);
    }

    public void AddItems<T1, T2, T3>(T1 item1, T2 item2, T3 item3)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
    }

    public void AddItems<T1, T2, T3, T4>(T1 item1, T2 item2, T3 item3, 
        T4 item4)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
        this.AddItem(item4);
    }

    public void AddItems<T1, T2, T3, T4, T5>(T1 item1, T2 item2, T3 item3, 
        T4 item4, T5 item5)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
        this.AddItem(item4);
        this.AddItem(item5);
    }        

    public void AddItems<T>(params T[] items)
    {
        foreach (T item in items)
        {
            this.AddItem(item);
        }
    }
}

8
2018-03-22 12:15



Hier ist eine weitere fließende Umsetzung von der oben von Jon Skeet veröffentlichte Algorithmus, aber die keine Zuweisungen oder Box-Operationen enthält:

public static class Hash
{
    public const int Base = 17;

    public static int HashObject(this int hash, object obj)
    {
        unchecked { return hash * 23 + (obj == null ? 0 : obj.GetHashCode()); }
    }

    public static int HashValue<T>(this int hash, T value)
        where T : struct
    {
        unchecked { return hash * 23 + value.GetHashCode(); }
    }
}

Verwendung:

public class MyType<T>
{
    public string Name { get; set; }

    public string Description { get; set; }

    public int Value { get; set; }

    public IEnumerable<T> Children { get; set; }

    public override int GetHashCode()
    {
        return Hash.Base
            .HashObject(this.Name)
            .HashObject(this.Description)
            .HashValue(this.Value)
            .HashObject(this.Children);
    }
}

Der Compiler wird dafür sorgen HashValue Wird aufgrund der Einschränkung des generischen Typs nicht mit einer Klasse aufgerufen. Aber es gibt keine Compiler-Unterstützung für HashObject Da ein generisches Argument hinzugefügt wird, wird auch eine Boxoperation hinzugefügt.


8
2018-01-20 23:41