Frage

In .NET System.Object.GetHashCode Methode wird in vielen Orten, in den .NET-Basisklassenbibliotheken verwendet. Vor allem, wenn die Suche nach Elementen in einer Sammlung schnell oder Gleichheit zu bestimmen. Gibt es eine Standard-Algorithmus / bewährten Praktiken für die GetHashCode Überschreibung für meine benutzerdefinierten Klassen zu implementieren, so dass ich die Leistung nicht beeinträchtigen?

War es hilfreich?

Lösung

Normalerweise gehe ich mit so etwas wie die gegebenen Implementierung in Josh Blochs fabelhaften FNV Beispiel später zum Beispiel habe ich Zahlen verwendet, die anscheinend gut funktionieren - aber der Anfangswert ist keine Primzahl. (Die Multiplikationskonstante ist prime though. Ich weiß nicht genau, wie wichtig das ist.)

Das ist besser als die gängige Praxis des Hashcodes aus zwei Gründen XORing. Angenommen, wir einen Typ mit zwei int Felder haben:

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

Durch die Art und Weise, der frühere Algorithmus ist derjenige, die derzeit von den C # Compiler für anonyme Typen verwendet.

Diese Seite gibt durchaus ein paar Optionen. Ich denke, für die meisten Fälle der oben „gut genug“ ist, und es ist unglaublich leicht zu merken und richtig. Die FNV Alternative ist ähnlich einfach, aber nutzt verschiedene Konstanten und XOR statt ADD als eine Kombinationsoperation. Es sieht aus, etwas wie der Code unten, aber der normale FNV-Algorithmus arbeitet auf einzelne Bytes, so würde dies zu modifizieren eine Iteration pro Byte auszuführen, statt pro 32-Bit-Hash-Wert. FNV ist auch für variable Längen von Daten ausgelegt, während die Art, wie wir es hier verwenden immer für die gleiche Anzahl von Feldwerten ist. Kommentare zu dieser Antwort deuten darauf hin, dass der Code hier eigentlich nicht so gut funktionieren (im Beispielfall geprüft) als Zusatz Ansatz oben.

// 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 eine Sache bewusst zu sein, ist, dass im Idealfall sollen Sie Ihre Gleichheit empfindlich (und damit hashcode empfindlich) Zustand zu verhindern, ändern, nachdem es zu einer Sammlung hinzufügen, die auf dem Hash-Code abhängig ist.

Gemäß der Dokumentation :

  

Sie können GetHashCode überschreiben für unveränderliche Referenztypen. In der Regel für veränderbare Referenztypen, sollten Sie GetHashCode überschreiben nur, wenn:

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

Andere Tipps

Anonym Typ

Microsoft bietet bereits einen guten allgemeine HashCode Generator: Kopieren Sie einfach Ihre Immobilie / Feldwert an einen anonymen Typen und Hash es:

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

Dies wird für eine beliebige Anzahl von Eigenschaften arbeiten. Dabei spielt es keine Boxen verwenden. Es verwendet nur den Algorithmus bereits im Rahmen für anonyme Typen umgesetzt werden.

ValueTuple - Update für C # 7

Wie @cactuaroid in den Kommentaren erwähnt, kann ein Wert Tupel verwendet werden. Das spart ein paar Tastenanschläge und was noch wichtiger ausführt rein auf dem Stapel (kein Abfall):

(PropA, PropB, PropC, PropD).GetHashCode();

(Anmerkung: Die ursprüngliche Technik anonyme Typen scheint ein Objekt auf dem Heap zu erzeugen, dh Müll, da anonyme Typen wie Klassen implementiert werden, obwohl dies durch den Compiler aus optimiert werden könnte Es wäre zu Benchmark interessant sein, diesen Optionen. , aber die Tupel Option sollte überlegen sein.)

Hier ist mein hashcode Helfer.
Es ist Vorteil ist, dass es generische Typargumente verwendet und wird daher nicht Boxen verursachen:

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();   
        }
    }

Auch sie hat Erweiterungsmethode eine fließend-Schnittstelle zur Verfügung zu stellen, so können Sie es wie folgt verwendet werden:

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

oder wie folgt aus:

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

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

/// <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 einfach können Sie es als verwenden:

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

ich nicht beurteilen, seine Leistung, so dass jede Rückmeldung wird begrüßt.

Hier ist mein Helfer-Klasse Jon Skeet 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);
    }
}

Verbrauch:

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

Wenn Sie eine Erweiterungsmethode für System.Int32 vermeiden 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, vermeidet es noch jede Zuordnung Haufen und es wird benutzt, um genau die gleiche Art und Weise:

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 Martin Kommentar:

obj != null Boxen verursachten so dass ich auf den Standardvergleich geschaltet.

  • Siehe diese Antwort in Bezug auf die Leistung der Standardvergleich.
  • Siehe diese Frage für eine Diskussion über den Hash-Codes von Nullwerten.

Bearbeiten (Mai 2018):

EqualityComparer<T>.Default Getter ist nun ein JIT intrinsische - der -Pull-Request von Stephen Toub erwähnt wird in diesem Blog-Eintrag .

In den meisten Fällen, in denen Equals () vergleicht mehrere Felder, die es nicht wirklich wichtig, wenn Ihr GetHash () Hashes auf einem Feld oder auf vielen. Sie müssen nur darauf achten, dass die Hash-Berechnung wirklich billig ist ( Keine Zuweisungen , bitte) und schnell ( Keine schweren Berechnungen und schon gar keine Datenbankverbindungen) und bietet eine gute Verteilung .

Die schwere Hebe sollte Teil des equals () Methode sein; der Hash sollte eine sehr billige Betrieb sein Equals zu ermöglichen () aufrufen, auf möglichst wenige Elemente wie möglich.

Und ein letzter Tipp: Verlassen Sie sich nicht auf GetHashCode () stabil über mehrere aplication sein läuft . Viele .NET-Typen nicht garantieren, ihren Hash-Codes gleich nach einem Neustart zu bleiben, so dass Sie sollten nur den Wert von GetHashCode () für in Speicherdatenstrukturen verwenden.

Bis vor kurzem meiner Antwort wäre zu Jon Skeet war hier ganz in der Nähe. Allerdings habe ich ein Projekt vor kurzem damit begonnen, die verwendeten Power-of-two-Hash-Tabellen ist, dass Hash-Tabellen, in denen die Größe der internen Tabelle 8, 16, 32, usw. Es gibt einen guten Grund, Primzahl Größen für die Begünstigung, aber es einige Vorteile sind Power-of-zwei Nummern zu.

Und es ist ziemlich viel gesaugt. So nach einem wenig Experimentieren und Forschung begann ich wieder Hashing meiner Hashes mit dem folgenden:

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 meine Potenz von zwei Hash-Tabelle saugt nicht mehr.

Das störte mich aber, weil die oben nicht funktionieren sollte. Oder genauer gesagt, sollte es nicht funktionieren, wenn die ursprüngliche GetHashCode() in einer ganz besonderen Art und Weise schlecht war.

Re-Mischen eines Hash-Code kann nicht eine große hashcode verbessern, weil die einzig mögliche Effekt ist, dass wir ein paar weitere Kollisionen einzuführen.

Re-Mischen einen Hash-Code kann nicht einen schrecklichen Hash-Code verbessern, weil der einzig mögliche Effekt ist, dass wir ändern z.B. eine große Anzahl von Kollisionen auf Wert 53 zu einer großen Anzahl von Wert 18,3487,291.

Re-Mischen einen Hash-Code kann nur einen Hash-Code verbessern, die während seiner Strecke bei der Vermeidung von Kollisionen absolutem zumindest ziemlich gut taten (2 32 mögliche Werte), aber schlecht bei Kollisionen zu vermeiden, wenn modulo'd unten für die tatsächliche Verwendung in einer Hash-Tabelle. Während die einfachere Modulo eines Potenz von zwei Tisch dies deutlicher gemacht, wurde es auch einen negativen Effekt mit den häufigeren Primzahl Tabellen hat, das war einfach nicht so offensichtlich (die zusätzliche Arbeit in Wiederkäuen den Nutzen überwiegen würde , aber der Nutzen wäre immer noch da sein).

Edit:. Ich war auch mit Open-Adressierung, die auch die Empfindlichkeit gegenüber Kollision erhöht habe, vielleicht mehr als die Tatsache, es war Potenz von zwei

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

Alle GetHashCode () -Implementierungen ich in der Vergangenheit codiert hatte, und in der Tat als Grundlage der Antworten auf dieser Website verwendet, waren viel schlimmer als ich würde Throught . Ein großer Teil der Zeit, die „gut genug“ für einen Großteil der Anwendungen war, aber ich wollte etwas besser.

Also habe ich das Projekt zur Seite gelegt (es war ein Lieblingsprojekt sowieso) und begann suchen, wie ein guten, gut verteilte Hash-Code in .NET schnell zu produzieren.

Am Ende ließ ich mich auf die Portierung SpookyHash .NET. Tatsächlich oben der Code ist eine schnell Pfad Version von SpookyHash unter Verwendung eine 32-Bit-Ausgabe von einem 32-Bit-Eingang zu erzeugen.

Nun SpookyHash ist keine schöne schnelles Stück Code zu erinnern. Mein Hafen ist es noch weniger, weil ich eine Menge davon für eine bessere Geschwindigkeit * Hand inlined. Aber das ist, was die Wiederverwendung von Code ist für.

Dann habe ich , die Projekt zur Seite, denn wie das ursprüngliche Projekt die Frage, hergestellt hatte, wie eine bessere Hash-Code zu erzeugen, so dass Projekt die Frage, erzeugt, wie eine bessere produzieren .NET memcpy.

Dann kam ich zurück, und eine Menge von Überlastungen produziert leicht fast alle nativen Typen zu füttern (mit Ausnahme decimal †) in einen Hash-Code.

Es ist schnell, für den Bob Jenkins die meisten des Kredit verdient, weil sein ursprünglicher Code, den ich aus portierte noch schneller ist, vor allem auf 64-Bit-Maschinen, die der Algorithmus für ‡ optimiert ist.

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

Doch jetzt ist es da schon geschrieben, kann man davon Gebrauch macht leichter:

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

Es braucht auch Werte Samen, also, wenn Sie brauchen, um mit nicht vertrauenswürdigen Eingaben zu behandeln und gegen Hash DoS schützen wollen greift man einen Samen basierend auf Verfügbarkeit oder ähnlich eingestellt wird, und die Ergebnisse von Angreifern unberechenbar 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 dabei ist, dass hand inlining ein Rotationsverfahren, die (x << n) | (x >> -n) zurück Dinge verbessert. Ich hätte sicher gewesen, dass der Jitter, dass für mich inlined hätte, aber Profilierung anders gezeigt.

decimal nicht nativ von .NET Perspektive ist, obwohl es von der C # ist. Das Problem damit ist, dass seine eigene GetHashCode() behandelt Präzision als signifikant, während seine eigene Equals() nicht. Beide gelten Entscheidungen, aber nicht so gemischt. In Ihre eigene Version der Umsetzung, müssen Sie wählen eine oder das andere zu tun, aber ich kann nicht wissen, was man sich wünschen kann.

‡ Im Vergleich. Wenn an einer Schnur verwendet wird, ist der SpookyHash auf 64 Bits wesentlich schneller als string.GetHashCode() auf 32 Bits, die auf 64 Bits etwas schneller als string.GetHashCode() ist, die auf 32 Bits wesentlich schneller als SpookyHash ist, aber immer noch schnell genug, um eine vernünftige Wahl zu sein.

Dies 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 es zu benutzen:

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);
    }
}

Hier ist eine weitere fließend Implementierung von den Algorithmus oben geschrieben von Jon Skeet , die aber enthält keine Zuweisungen oder Boxen Operationen:

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(); }
    }
}

Verbrauch:

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 HashValue sicherzustellen, dass nicht mit einer Klasse aufgrund des generischen Art Zwang genannt. Aber es gibt keine Compiler-Unterstützung für HashObject da das Hinzufügen eines generisches Argument fügt auch einen Box-Betrieb.

Wie von https://github.com/dotnet/coreclr/pull/14863 gibt es eine neue Möglichkeit, Hash-Codes zu erzeugen, die super einfach ist! Schreiben Sie einfach

public override int GetHashCode()
    => HashCode.Combine(field1, field2, field3);

Dies wird einen Qualitäts Hash-Code generieren, ohne dass Sie über die Implementierungsdetails kümmern müssen.

Hier ist mein vereinfachenden Ansatz. Ich bin mit dem klassischen Muster Builder für diesen. Es ist typsicher (keine Boxen / Unboxing) und auch compatbile mit .NET 2.0 (keine Erweiterungsmethoden usw.).

Es wird wie folgt verwendet:

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

Und hier ist die aktuelle 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);
        }
    }
}

ReSharper Benutzer können GetHashCode erzeugen, Equals, und andere mit ReSharper -> Edit -> Generate Code -> Equality Members.

// ReSharper's GetHashCode looks like this
public override int GetHashCode() {
    unchecked {
        int hashCode = Id;
        hashCode = (hashCode * 397) ^ IntMember;
        hashCode = (hashCode * 397) ^ OtherIntMember;
        hashCode = (hashCode * 397) ^ (RefMember != null ? RefMember.GetHashCode() : 0);
        // ...
        return hashCode;
    }
}

Die meisten meiner Arbeit mit Datenbank-Konnektivität, das bedeutet, dass meine Klassen alle eine eindeutige ID aus der Datenbank haben. Ich benutze immer die ID aus der Datenbank, um den Hash-Code zu erzeugen.

// Unique ID from database
private int _id;

...    
{
  return _id.GetHashCode();
}

So ziemlich ähnlich wie nightcoder Lösung außer es einfacher ist, Primzahlen zu erhöhen, wenn Sie wollen.

PS: Dies ist eine jener Zeiten, in denen man ein wenig in den Mund kotzen, wohl wissend, dass dies mit 9 default ist in einem Verfahren Refactoring werden könnte, aber es wäre langsamer, so dass Sie nur die Augen schließen und versuchen, es zu vergessen .

/// <summary>
/// Try not to look at the source code. It works. Just rely on it.
/// </summary>
public static class HashHelper
{
    private const int PrimeOne = 17;
    private const int PrimeTwo = 23;

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();
            hash = hash * PrimeTwo + arg8.GetHashCode();
            hash = hash * PrimeTwo + arg9.GetHashCode();
            hash = hash * PrimeTwo + arg10.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8, T9>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();
            hash = hash * PrimeTwo + arg8.GetHashCode();
            hash = hash * PrimeTwo + arg9.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();
            hash = hash * PrimeTwo + arg8.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();

            return hash;
        }
    }

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

            return hash;
        }
    }

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

            return hash;
        }
    }

    public static int GetHashCode<T1, T2>(T1 arg1, T2 arg2)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();

            return hash;
        }
    }
}

Wenn wir nicht mehr als 8 Eigenschaften (hoffentlich), hier ist eine weitere Alternative.

ValueTuple ist eine Struktur und scheint eine solide GetHashCode Implementierung zu haben.

Das heißt, wir könnten einfach dies tun:

// Yay, no allocations and no custom implementations!
public override int GetHashCode() => (this.PropA, this.PropB).GetHashCode();

Lassen Sie uns für ValueTuple der GetHashCode aktuellen Implementierung einen Blick auf .NET Core nehmen.

Dies ist von ValueTuple :

    internal static int CombineHashCodes(int h1, int h2)
    {
        return HashHelpers.Combine(HashHelpers.Combine(HashHelpers.RandomSeed, h1), h2);
    }

    internal static int CombineHashCodes(int h1, int h2, int h3)
    {
        return HashHelpers.Combine(CombineHashCodes(h1, h2), h3);
    }

Und das ist von HashHelper :

    public static readonly int RandomSeed = Guid.NewGuid().GetHashCode();

    public static int Combine(int h1, int h2)
    {
        unchecked
        {
            // RyuJIT optimizes this to use the ROL instruction
            // Related GitHub pull request: dotnet/coreclr#1830
            uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27);
            return ((int)rol5 + h1) ^ h2;
        }
    }

In Englisch:

  • links drehen (circular shift) h1 um 5 Positionen.
  • Fügen Sie das Ergebnis und h1 zusammen.
  • XOR das Ergebnis mit h2.
  • Starten Sie durch die obige Operation an {static random seed, h1}.
  • Für jedes weitere Element, führen die Operation auf dem vorherigen Ergebnis und das nächste Element (zum Beispiel h2).

Es wäre schön, mehr über die Eigenschaften dieses ROL-5 Hash-Code-Algorithmus kennen.

Leider für unseren eigenen ValueTuple GetHashCode Aufschieben kann nicht so schnell, wie wir möchten und erwarten. Dieser Kommentar in einer verwandten Diskussion zeigt, dass direkt anrufen HashHelpers.Combine performanter ist . Auf der anderen Seite, ist, dass man intern, so würden wir den Code kopieren müssen, zu opfern viel von dem, was wir hier gewonnen hatten. Auch würden wir für die Erinnerung an ersten Combine mit dem zufälligen Samen verantwortlich. Ich weiß nicht, was die Konsequenzen sind, wenn wir diesen Schritt überspringen.

.NET-Core 2.1 und höher

Wenn Sie .NET-Core 2.1 oder höher verwenden, können Sie die System.HashCode struct. Es gibt zwei Methoden, es zu benutzen:

HashCode.Combine

Das Combine Verfahren verwendet werden kann, einen Hash-Code zu erstellen, zu acht Objekte aufgegeben.

public override int GetHashCode() => HashCode.Combine(this.object1, this.object2);

HashCode.Add

Die Add Methode hilft Ihnen mit Sammlungen zu behandeln:

public override int GetHashCode()
{
    var hashCode = new HashCode();
    hashCode.Add(this.object1);
    foreach (var item in this.collection)
    {
        hashCode.Add(item);
    }
    return hashCode.ToHashCode();
}

GetHashCode leicht gemacht

Sie können den vollständigen Blog-Eintrag ' GetHashCode Made Easy ' um weitere Informationen lesen und Kommentare.

Verwendung Beispiel

public class SuperHero
{
    public int Age { get; set; }
    public string Name { get; set; }
    public List<string> Powers { get; set; }

    public override int GetHashCode() =>
        HashCode.Of(this.name).And(this.age).AndEach(this.powers);
}

Implementierung

public struct HashCode : IEquatable<HashCode>
{
    private const int EmptyCollectionPrimeNumber = 19;
    private readonly int value;

    private HashCode(int value) => this.value = value;

    public static implicit operator int(HashCode hashCode) => hashCode.value;

    public static bool operator ==(HashCode left, HashCode right) => left.Equals(right);

    public static bool operator !=(HashCode left, HashCode right) => !(left == right);

    public static HashCode Of<T>(T item) => new HashCode(GetHashCode(item));

    public static HashCode OfEach<T>(IEnumerable<T> items) =>
        items == null ? new HashCode(0) : new HashCode(GetHashCode(items, 0));

    public HashCode And<T>(T item) => 
        new HashCode(CombineHashCodes(this.value, GetHashCode(item)));

    public HashCode AndEach<T>(IEnumerable<T> items)
    {
        if (items == null)
        {
            return new HashCode(this.value);
        }

        return new HashCode(GetHashCode(items, this.value));
    }

    public bool Equals(HashCode other) => this.value.Equals(other.value);

    public override bool Equals(object obj)
    {
        if (obj is HashCode)
        {
            return this.Equals((HashCode)obj);
        }

        return false;
    }

    public override int GetHashCode() => this.value.GetHashCode();

    private static int CombineHashCodes(int h1, int h2)
    {
        unchecked
        {
            // Code copied from System.Tuple a good way to combine hashes.
            return ((h1 << 5) + h1) ^ h2;
        }
    }

    private static int GetHashCode<T>(T item) => item?.GetHashCode() ?? 0;

    private static int GetHashCode<T>(IEnumerable<T> items, int startHashCode)
    {
        var temp = startHashCode;

        var enumerator = items.GetEnumerator();
        if (enumerator.MoveNext())
        {
            temp = CombineHashCodes(temp, GetHashCode(enumerator.Current));

            while (enumerator.MoveNext())
            {
                temp = CombineHashCodes(temp, GetHashCode(enumerator.Current));
            }
        }
        else
        {
            temp = CombineHashCodes(temp, EmptyCollectionPrimeNumber);
        }

        return temp;
    }
}

Ich lief in ein Problem mit Schwimmern und Dezimalzahlen, die Umsetzung als Antwort oben ausgewählt werden.

Dieser Test fehlschlägt (Schwimmer; Hash gleich ist, obwohl ich zwei Werten geschaltet negativ sein):

        var obj1 = new { A = 100m, B = 100m, C = 100m, D = 100m};
        var obj2 = new { A = 100m, B = 100m, C = -100m, D = -100m};
        var hash1 = ComputeHash(obj1.A, obj1.B, obj1.C, obj1.D);
        var hash2 = ComputeHash(obj2.A, obj2.B, obj2.C, obj2.D);
        Assert.IsFalse(hash1 == hash2, string.Format("Hashcode values should be different   hash1:{0}  hash2:{1}",hash1,hash2));

Aber dieser Test bestanden wird (mit ints):

        var obj1 = new { A = 100m, B = 100m, C = 100, D = 100};
        var obj2 = new { A = 100m, B = 100m, C = -100, D = -100};
        var hash1 = ComputeHash(obj1.A, obj1.B, obj1.C, obj1.D);
        var hash2 = ComputeHash(obj2.A, obj2.B, obj2.C, obj2.D);
        Assert.IsFalse(hash1 == hash2, string.Format("Hashcode values should be different   hash1:{0}  hash2:{1}",hash1,hash2));

änderte ich meine Umsetzung nicht GetHashCode für die primitiven Typen zu verwenden, und es scheint besser zu funktionieren

    private static int InternalComputeHash(params object[] obj)
    {
        unchecked
        {
            var result = (int)SEED_VALUE_PRIME;
            for (uint i = 0; i < obj.Length; i++)
            {
                var currval = result;
                var nextval = DetermineNextValue(obj[i]);
                result = (result * MULTIPLIER_VALUE_PRIME) + nextval;

            }
            return result;
        }
    }



    private static int DetermineNextValue(object value)
    {
        unchecked
        {

                int hashCode;
                if (value is short
                    || value is int
                    || value is byte
                    || value is sbyte
                    || value is uint
                    || value is ushort
                    || value is ulong
                    || value is long
                    || value is float
                    || value is double
                    || value is decimal)
                {
                    return Convert.ToInt32(value);
                }
                else
                {
                    return value != null ? value.GetHashCode() : 0;
                }
        }
    }

Dies ist eine statische Hilfsklasse, die Josh Bloch-Implementierung implementiert; und bietet explizite Überlastungen zu „verhindern“ Boxen und auch den Hash speziell für die langen Primitiven zu implementieren.

Sie können einen String-Vergleich übergeben, die Ihre equals Implementierung entspricht.

Da der Hash-Ausgang ist immer ein int, können Sie einfach Kette Hash Anrufe.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;


namespace Sc.Util.System
{
    /// <summary>
    /// Static methods that allow easy implementation of hashCode. Example usage:
    /// <code>
    /// public override int GetHashCode()
    ///     => HashCodeHelper.Seed
    ///         .Hash(primitiveField)
    ///         .Hsh(objectField)
    ///         .Hash(iEnumerableField);
    /// </code>
    /// </summary>
    public static class HashCodeHelper
    {
        /// <summary>
        /// An initial value for a hashCode, to which is added contributions from fields.
        /// Using a non-zero value decreases collisions of hashCode values.
        /// </summary>
        public const int Seed = 23;

        private const int oddPrimeNumber = 37;


        /// <summary>
        /// Rotates the seed against a prime number.
        /// </summary>
        /// <param name="aSeed">The hash's first term.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static int rotateFirstTerm(int aSeed)
        {
            unchecked {
                return HashCodeHelper.oddPrimeNumber * aSeed;
            }
        }


        /// <summary>
        /// Contributes a boolean to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aBoolean">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, bool aBoolean)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + (aBoolean
                                ? 1
                                : 0);
            }
        }

        /// <summary>
        /// Contributes a char to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aChar">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, char aChar)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + aChar;
            }
        }

        /// <summary>
        /// Contributes an int to the developing HashCode seed.
        /// Note that byte and short are handled by this method, through implicit conversion.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aInt">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, int aInt)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + aInt;
            }
        }

        /// <summary>
        /// Contributes a long to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aLong">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, long aLong)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + (int)(aLong ^ (aLong >> 32));
            }
        }

        /// <summary>
        /// Contributes a float to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aFloat">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, float aFloat)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + Convert.ToInt32(aFloat);
            }
        }

        /// <summary>
        /// Contributes a double to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aDouble">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, double aDouble)
            => aSeed.Hash(Convert.ToInt64(aDouble));

        /// <summary>
        /// Contributes a string to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aString">The value to contribute.</param>
        /// <param name="stringComparison">Optional comparison that creates the hash.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(
                this int aSeed,
                string aString,
                StringComparison stringComparison = StringComparison.Ordinal)
        {
            if (aString == null)
                return aSeed.Hash(0);
            switch (stringComparison) {
                case StringComparison.CurrentCulture :
                    return StringComparer.CurrentCulture.GetHashCode(aString);
                case StringComparison.CurrentCultureIgnoreCase :
                    return StringComparer.CurrentCultureIgnoreCase.GetHashCode(aString);
                case StringComparison.InvariantCulture :
                    return StringComparer.InvariantCulture.GetHashCode(aString);
                case StringComparison.InvariantCultureIgnoreCase :
                    return StringComparer.InvariantCultureIgnoreCase.GetHashCode(aString);
                case StringComparison.OrdinalIgnoreCase :
                    return StringComparer.OrdinalIgnoreCase.GetHashCode(aString);
                default :
                    return StringComparer.Ordinal.GetHashCode(aString);
            }
        }

        /// <summary>
        /// Contributes a possibly-null array to the developing HashCode seed.
        /// Each element may be a primitive, a reference, or a possibly-null array.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aArray">CAN be null.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, IEnumerable aArray)
        {
            if (aArray == null)
                return aSeed.Hash(0);
            int countPlusOne = 1; // So it differs from null
            foreach (object item in aArray) {
                ++countPlusOne;
                if (item is IEnumerable arrayItem) {
                    if (!object.ReferenceEquals(aArray, arrayItem))
                        aSeed = aSeed.Hash(arrayItem); // recursive call!
                } else
                    aSeed = aSeed.Hash(item);
            }
            return aSeed.Hash(countPlusOne);
        }

        /// <summary>
        /// Contributes a possibly-null array to the developing HashCode seed.
        /// You must provide the hash function for each element.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aArray">CAN be null.</param>
        /// <param name="hashElement">Required: yields the hash for each element
        /// in <paramref name="aArray"/>.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash<T>(this int aSeed, IEnumerable<T> aArray, Func<T, int> hashElement)
        {
            if (aArray == null)
                return aSeed.Hash(0);
            int countPlusOne = 1; // So it differs from null
            foreach (T item in aArray) {
                ++countPlusOne;
                aSeed = aSeed.Hash(hashElement(item));
            }
            return aSeed.Hash(countPlusOne);
        }

        /// <summary>
        /// Contributes a possibly-null object to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aObject">CAN be null.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, object aObject)
        {
            switch (aObject) {
                case null :
                    return aSeed.Hash(0);
                case bool b :
                    return aSeed.Hash(b);
                case char c :
                    return aSeed.Hash(c);
                case int i :
                    return aSeed.Hash(i);
                case long l :
                    return aSeed.Hash(l);
                case float f :
                    return aSeed.Hash(f);
                case double d :
                    return aSeed.Hash(d);
                case string s :
                    return aSeed.Hash(s);
                case IEnumerable iEnumerable :
                    return aSeed.Hash(iEnumerable);
            }
            return aSeed.Hash(aObject.GetHashCode());
        }


        /// <summary>
        /// This utility method uses reflection to iterate all specified properties that are readable
        /// on the given object, excluding any property names given in the params arguments, and
        /// generates a hashcode.
        /// </summary>
        /// <param name="aSeed">The developing hash code, or the seed: if you have no seed, use
        /// the <see cref="Seed"/>.</param>
        /// <param name="aObject">CAN be null.</param>
        /// <param name="propertySelector"><see cref="BindingFlags"/> to select the properties to hash.</param>
        /// <param name="ignorePropertyNames">Optional.</param>
        /// <returns>A hash from the properties contributed to <c>aSeed</c>.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int HashAllProperties(
                this int aSeed,
                object aObject,
                BindingFlags propertySelector
                        = BindingFlags.Instance
                        | BindingFlags.Public
                        | BindingFlags.GetProperty,
                params string[] ignorePropertyNames)
        {
            if (aObject == null)
                return aSeed.Hash(0);
            if ((ignorePropertyNames != null)
                    && (ignorePropertyNames.Length != 0)) {
                foreach (PropertyInfo propertyInfo in aObject.GetType()
                        .GetProperties(propertySelector)) {
                    if (!propertyInfo.CanRead
                            || (Array.IndexOf(ignorePropertyNames, propertyInfo.Name) >= 0))
                        continue;
                    aSeed = aSeed.Hash(propertyInfo.GetValue(aObject));
                }
            } else {
                foreach (PropertyInfo propertyInfo in aObject.GetType()
                        .GetProperties(propertySelector)) {
                    if (propertyInfo.CanRead)
                        aSeed = aSeed.Hash(propertyInfo.GetValue(aObject));
                }
            }
            return aSeed;
        }


        /// <summary>
        /// NOTICE: this method is provided to contribute a <see cref="KeyValuePair{TKey,TValue}"/> to
        /// the developing HashCode seed; by hashing the key and the value independently. HOWEVER,
        /// this method has a different name since it will not be automatically invoked by
        /// <see cref="Hash(int,object)"/>, <see cref="Hash(int,IEnumerable)"/>,
        /// or <see cref="HashAllProperties"/> --- you MUST NOT mix this method with those unless
        /// you are sure that no KeyValuePair instances will be passed to those methods; or otherwise
        /// the generated hash code will not be consistent. This method itself ALSO will not invoke
        /// this method on the Key or Value here if that itself is a KeyValuePair.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="keyValuePair">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int HashKeyAndValue<TKey, TValue>(this int aSeed, KeyValuePair<TKey, TValue> keyValuePair)
            => aSeed.Hash(keyValuePair.Key)
                    .Hash(keyValuePair.Value);

        /// <summary>
        /// NOTICE: this method is provided to contribute a collection of <see cref="KeyValuePair{TKey,TValue}"/>
        /// to the developing HashCode seed; by hashing the key and the value independently. HOWEVER,
        /// this method has a different name since it will not be automatically invoked by
        /// <see cref="Hash(int,object)"/>, <see cref="Hash(int,IEnumerable)"/>,
        /// or <see cref="HashAllProperties"/> --- you MUST NOT mix this method with those unless
        /// you are sure that no KeyValuePair instances will be passed to those methods; or otherwise
        /// the generated hash code will not be consistent. This method itself ALSO will not invoke
        /// this method on a Key or Value here if that itself is a KeyValuePair or an Enumerable of
        /// KeyValuePair.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="keyValuePairs">The values to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int HashKeysAndValues<TKey, TValue>(
                this int aSeed,
                IEnumerable<KeyValuePair<TKey, TValue>> keyValuePairs)
        {
            if (keyValuePairs == null)
                return aSeed.Hash(null);
            foreach (KeyValuePair<TKey, TValue> keyValuePair in keyValuePairs) {
                aSeed = aSeed.HashKeyAndValue(keyValuePair);
            }
            return aSeed;
        }
    }
}

Microsoft führen mehrere Wege Hashing ...

//for classes that contain a single int value
return this.value;

//for classes that contain multiple int value
return x ^ y;

//for classes that contain single number bigger than int    
return ((int)value ^ (int)(value >> 32)); 

//for classes that contain class instance fields which inherit from object
return obj1.GetHashCode();

//for classes that contain multiple class instance fields which inherit from object
return obj1.GetHashCode() ^ obj2.GetHashCode() ^ obj3.GetHashCode(); 

kann ich denke, dass für mehrere große int Sie diese verwenden können:

int a=((int)value1 ^ (int)(value1 >> 32));
int b=((int)value2 ^ (int)(value2 >> 32));
int c=((int)value3 ^ (int)(value3 >> 32));
return a ^ b ^ c;

Und gleich für Multi-Typ: alle umgewandelt ersten int mit bis GetHashCode() dann werden die int-Werte EXOR'ed und das Ergebnis ist Ihre Hash.

Für diejenigen, die Hash als ID verwenden (ich meine einen eindeutigen Wert), Hash natürlich auf eine Anzahl von Stellen beschränkt ist, halte ich es für 5 Byte war Algorithmus für Hashing, mindestens MD5.

Sie können mehrere Werte auf einen Hash-Wert drehen und einige von ihnen gleich, so kann es nicht als Bezeichner verwenden. (Vielleicht eines Tages ich Ihre Komponente verwenden werde)

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top