Jaki jest najlepszy algorytm do nadpisywania kodu GetHashCode?

In. NET, the GetHashCode metoda jest używana w wielu miejscach w bibliotekach bazowych. NET. Jego prawidłowe wdrożenie jest szczególnie ważne, aby szybko znaleźć przedmioty w kolekcji lub przy ustalaniu równości.

Czy istnieje standardowy algorytm lub najlepsza praktyka, jak zaimplementować GetHashCode dla moich niestandardowych klas, aby nie pogarszać wydajności?

Author: poke, 2008-11-04

21 answers

Zazwyczaj wybieram coś takiego jak implementacja podana w Fabulous Josha Blocha efektywna Java . Jest szybki i tworzy całkiem dobry hash, który prawdopodobnie nie spowoduje kolizji. Wybierz dwie różne liczby pierwsze, np.]}

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

Jak wspomniano w komentarzach, może się okazać, że lepiej wybrać dużą liczbę pierwszą, aby ją pomnożyć. Widocznie 486187739 jest dobry... i chociaż większość przykładów, które widziałem z małymi liczbami, zwykle używa liczb pierwszych, są na najmniej podobne algorytmy, w których często stosuje się liczby niepierwsze. W przykładzie nie do końcaFNV później, na przykład, użyłem liczb, które najwyraźniej działają dobrze-ale wartość początkowa nie jest liczbą pierwszą. (Stała mnożenia jest pierwsza. Nie wiem, jakie to ważne.)

Jest to lepsze niż powszechna praktyka XORing hashcodes z dwóch głównych powodów. Załóżmy, że mamy typ Z dwoma polami int:

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

Przy okazji, im wcześniej algorytm jest obecnie używany przez kompilator C# dla typów anonimowych.

Ta strona daje sporo opcji. Myślę, że w większości przypadków powyższe jest "wystarczająco dobre" i jest niesamowicie łatwe do zapamiętania i poprawienia. Alternatywa FNV jest podobnie prosta, ale używa różnych stałych i XOR zamiast ADD jako operacji łączącej. Wygląda coś jak poniższy kod, ale normalny algorytm FNV działa na poszczególnych bajtach, więc to wymaga modyfikacji, aby wykonać jedną iterację na bajt, zamiast na 32-bitową wartość skrótu. FNV jest również przeznaczony do zmiennych długości danych, podczas gdy sposób, w jaki go używamy, jest zawsze dla tej samej liczby wartości pól. Komentarze na temat tej odpowiedzi sugerują, że kod tutaj nie działa tak dobrze (w testowanym przypadku przykładowym) jak podejście dodawania powyżej.

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

Zauważ, że należy pamiętać o tym, że najlepiej zapobiegać wrażliwości na równość (a więc hashcode-sensitive) stan od zmiany po dodaniu go do kolekcji, która zależy od kodu hashowego.

Wg dokumentacji :

Możesz zastąpić GetHashCode dla niezmiennych typów referencyjnych. Ogólnie rzecz biorąc, dla zmiennych typów odniesienia, powinieneś nadpisać GetHashCode tylko wtedy, gdy:

  • możesz obliczyć hash kodu z pól, które nie są mutowalne; lub
  • możesz upewnić się, że kod skrótu zmiennego obiektu nie zmieni się podczas obiekt jest zawarty w kolekcji, która opiera się na jego kodzie skrótu.
 1658
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-01-22 18:57:40

ValueTuple-Update for C # 7

Jak wspomina @cactuaroid w komentarzach, można użyć krotki wartości. Zapisuje to kilka naciśnięć klawiszy i co ważniejsze wykonuje wyłącznie na stosie (bez śmieci):

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

(Uwaga: oryginalna technika wykorzystująca typy anonimowe wydaje się tworzyć obiekt na stercie, tj. śmieci, ponieważ typy anonimowe są implementowane jako klasy, chociaż może to być zoptymalizowane przez kompilator. Ciekawe byłoby porównanie tych opcji, ale krotka opcja powinna być lepsza.)

Typ Anonimowy (Oryginalna Odpowiedź)

Microsoft już zapewnia dobry generator generycznych HashCode: po prostu skopiuj wartości właściwości / pól do anonimowego typu i zaszyfruj je:

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

To zadziała dla dowolnej liczby właściwości. Nie używa boksu. Po prostu wykorzystuje algorytm już zaimplementowany w frameworku dla typów anonimowych.

 466
Author: Rick Love,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-07-15 15:19:35

Oto mój hashcode helper.
Jego zaletą jest to, że używa argumentów typu generycznego i dlatego nie spowoduje boksu:

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

Posiada również metodę rozszerzenia zapewniającą płynny interfejs, więc możesz go używać w następujący sposób:

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

Lub Tak:

public override int GetHashCode()
{
    return 0.CombineHashCode(Manufacturer)
        .CombineHashCode(PartN)
        .CombineHashCode(Quantity);
}
 106
Author: nightcoder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-25 15:29:38

Mam klasę Hashującą w Bibliotece Helpera, której używam do tego celu.

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

Wtedy po prostu możesz użyć go jako:

public override int GetHashCode()
{
    return Hashing.RSHash(_field1, _field2, _field3);
}
Nie oceniałem jego działania, więc wszelkie opinie są mile widziane.
 63
Author: Wahid Shalaly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-22 12:00:19

Oto moja klasa pomocnicza wykorzystująca implementację Jona Skeeta .

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

Użycie:

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

Jeśli chcesz uniknąć pisania metody rozszerzenia dla System.Int32:

public readonly 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;
}

Nadal unika przydzielania sterty i jest używany dokładnie w ten sam sposób:

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);
}
W związku z tym, że nie jest to możliwe, nie jest to możliwe, ponieważ nie jest to możliwe, ponieważ nie jest to możliwe.
 59
Author: Şafak Gür,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-02-14 23:43:03

. Net Standard 2.1 i wyżej

Jeśli używasz. NET Standard 2.1 lub nowszego, możesz użyć systemu .HashCode struct. Istnieją dwie metody jej używania:

HashCode.Połącz

Metoda Combine może być użyta do utworzenia kodu hashowego, składającego się z maksymalnie ośmiu obiektów.

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

HashCode.Dodaj

Metoda Add pomaga radzić sobie ze zbiorami:

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 Made Easy]}

Możesz przeczytać cały wpis na blogu ' GetHashCode Made Easy ' więcej szczegółów i komentarzy.

Przykład Użycia

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

Implementacja

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

Co sprawia, że dobry algorytm?

Wydajność

Algorytm, który oblicza kod hash, musi być szybki. Prosty algorytm zwykle będzie szybszy. Taki, który nie przydziela dodatkowej pamięci, zmniejszy również potrzebę zbierania śmieci, co z kolei poprawi wydajność.

Deterministyczny

Algorytm haszujący musi być deterministyczne {[12] } tzn. biorąc pod uwagę to samo wejście, musi zawsze produkować to samo wyjście.

Zmniejsz Kolizje

Algorytm, który oblicza kod hash, musi zachować kolizje hash do minimum. Kolizja skrótu to sytuacja, która ma miejsce, gdy dwa wywołania GetHashCode na dwóch różnych obiektach wytwarzają identyczne kody skrótu. Zauważ, że kolizje są dozwolone (niektórzy mają błędne przekonania, że nie są), ale powinny być trzymane w minimum.

Dobra funkcja skrótu powinna mapować oczekiwane dane wejściowe tak równomiernie, jak to możliwe w zakresie wyjściowym. Powinien mieć jednolitość.

Prevent ' s DoS

W. Net Core za każdym razem, gdy ponownie uruchomisz aplikację, otrzymasz różne kody skrótu. Jest to funkcja zabezpieczająca przed atakami typu Denial of Service (DoS). Dla.NET Framework należy włączyć tę funkcję, dodając następującą aplikację.plik konfiguracyjny:

<?xml version ="1.0"?>  
<configuration>  
   <runtime>  
      <UseRandomizedStringHashAlgorithm enabled="1" />  
   </runtime>  
</configuration>

Z powodu tej funkcji, kody hash nigdy nie powinny być używane poza domeną aplikacji, w której zostały utworzone, nigdy nie powinny być używane jako kluczowe pola w kolekcji i nigdy nie powinny być utrzymywane.

Więcej o tym tutaj .

Kryptograficznie Bezpieczne?

Algorytm nie musi być kryptograficzną funkcją skrótu. Oznacza to, że nie musi spełniać następujących warunków:

  • nie jest możliwe wygenerowanie wiadomości, która daje dany hash Wartość
  • Nie można znaleźć dwóch różnych wiadomości o tej samej wartości skrótu.]}
  • mała zmiana w wiadomości powinna zmienić wartość skrótu tak szeroko, że nowa wartość skrótu wydaje się nieskorelowana ze starą wartością skrótu (efekt avalanche).
 53
Author: Muhammad Rehan Saeed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-07-10 16:17:52

W większości przypadków, gdy Equals () porównuje wiele pól, tak naprawdę nie ma znaczenia, czy Twoje gethash() hashuje na jednym polu, czy na wielu. Musisz tylko upewnić się, że obliczanie hasha jest naprawdę tanie (bez alokacji, proszę) i szybkie (bez ciężkich obliczeń i na pewno bez połączeń z bazami danych) i zapewnia dobrą dystrybucję.

Podnoszenie ciężarów powinno być częścią metody Equals (); hash powinien być bardzo tanią operacją, aby umożliwić wywołanie Equals () jako kilka przedmiotów, jak to możliwe.

I ostatnia wskazówka: nie polegaj na tym, że GetHashCode() jest stabilne w wielu aplikacjach . Wiele typów. Net nie gwarantuje, że ich kody skrótu pozostaną takie same po ponownym uruchomieniu, więc powinieneś używać tylko wartości GetHashCode () dla struktur danych w pamięci.

 30
Author: Bert Huijben,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-23 11:55:44

Do niedawna moja odpowiedź byłaby bardzo zbliżona do Jona Skeeta. Jednak niedawno rozpocząłem projekt, który wykorzystywał moc dwóch tabel hash, czyli tabel hash, gdzie Rozmiar wewnętrznej tabeli wynosi 8, 16, 32 itd. Istnieje dobry powód, aby faworyzować rozmiary liczb pierwszych, ale są też pewne zalety mocy dwóch rozmiarów.

I to było do bani. Tak więc po odrobinie eksperymentów i badań zacząłem ponownie hashować moje hasze z po:
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);
  }
}

I wtedy mój stół haszujący power of two nie był już do bani.

To mnie jednak zaniepokoiło, ponieważ powyższe nie powinno działać. A dokładniej, nie powinno działać, chyba że oryginał GetHashCode() był ubogi w bardzo szczególny sposób.

Ponowne zmieszanie hashcode nie może poprawić Wielkiego hashcode, ponieważ jedynym możliwym efektem jest wprowadzenie kilku kolizji.

Ponowne mieszanie kodu hashowego nie może poprawić strasznego kodu hashowego, ponieważ tylko możliwy efekt to zmiana np. dużej liczby kolizji na wartość 53 na dużą liczbę wartości 18,3487,291.

Ponowne zmieszanie kodu skrótu może tylko poprawić kod skrótu, który przynajmniej dość dobrze uniknął kolizji bezwzględnych w całym jego zakresie (232 Możliwe wartości), ale źle w unikaniu kolizji, gdy modulo ' D w dół do rzeczywistego wykorzystania w tabeli hash. O ile prostsze modulo tabeli mocy dwóch sprawiło, że było to bardziej widoczne, miało to również negatywny wpływ na bardziej powszechne tabele liczb pierwszych, które po prostu nie były tak oczywiste(dodatkowa praca w rehashingu przeważyłaby nad korzyścią, ale korzyść nadal by istniała).

Edit: używałem również adresowania otwartego, co również zwiększyłoby wrażliwość na kolizje, być może bardziej niż fakt, że była to siła dwóch.

I dobrze, to było niepokojące, jak bardzo string.GetHashCode() implementacje w . NET (lub studium tutaj ) można poprawić w ten sposób (w kolejności testy działają około 20-30 razy szybciej z powodu mniejszej liczby kolizji) i bardziej niepokojące, jak bardzo można poprawić własne kody hashowe(znacznie więcej).

Wszystkie implementacje GetHashCode (), które zakodowałem w przeszłości, i rzeczywiście używane jako podstawa odpowiedzi na tej stronie, były znacznie gorsze niż ja przez {38]}. Przez większość czasu było to "wystarczająco dobre" dla większości zastosowań, ale chciałem czegoś lepszego.

Więc odłożyłem ten projekt na bok (i tak był to pet project) I zacząłem zastanawiać się, jak szybko stworzyć dobry, dobrze rozproszony kod hashowy w. NET.

W końcu zdecydowałem się na przeniesienie SpookyHash do .NET. rzeczywiście powyższy kod jest szybką wersją używania SpookyHash do produkcji 32-bitowego wyjścia z 32-bitowego wejścia.

SpookyHash nie jest miłym, szybkim do zapamiętania kawałkiem kodu. Mój port jest jeszcze mniejszy, ponieważ ręcznie włożyłem go dużo, aby uzyskać lepszą prędkość*. Ale po to jest ponowne użycie kodu.

Potem kładę to projekt na jedną stronę, ponieważ tak jak oryginalny projekt wytworzył pytanie, jak wyprodukować lepszy kod hashowy, tak ten projekt wytworzył pytanie, jak wyprodukować lepszą memcpy. NET.

Potem wróciłem i wytworzyłem wiele przeciążeń, aby łatwo wprowadzić prawie wszystkie natywne typy (z wyjątkiem decimal†) do kodu hashowego.

Jest szybki, Za co Bob Jenkins zasługuje na większość uznania, ponieważ jego oryginalny kod, z którego przeportowałem, jest wciąż szybszy, szczególnie na maszynach 64-bitowych, dla których algorytm jest zoptymalizowany.

Pełny kod można zobaczyć na https://bitbucket.org/JonHanna/spookilysharp/src ale weź pod uwagę, że powyższy kod jest jego uproszczoną wersją.

Jednak, ponieważ jest już napisany, można z niego łatwiej korzystać:

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

Pobiera również wartości seed, więc jeśli chcesz poradzić sobie z niezaufanymi danymi wejściowymi i chcesz chronić się przed atakami Hash DoS, możesz ustawić seed na podstawie czasu pracy lub podobne, i sprawiają, że wyniki są nieprzewidywalne przez atakujących:

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

*dużym zaskoczeniem w tym jest to, że hand-inlining metoda rotacji, która zwróciła (x << n) | (x >> -n) ulepszone rzeczy. Byłbym pewien, że jitter by to dla mnie wpisał, ale profilowanie pokazało inaczej.

decimal nie jest natywny z perspektywy. NET, choć jest z C#. Problem w tym, że jego własna GetHashCode() traktuje precyzję jako znaczącą, podczas gdy jego własna Equals() nie. Oba są ważnymi wyborami, ale nie zmieszane w ten sposób. Implementując własną wersję, musisz wybrać jedną lub drugą, ale nie wiem, którą byś chciał.

‡dla porównania. SpookyHash na 64 bitach jest znacznie szybszy niż string.GetHashCode() na 32 bitach, co jest nieco szybsze niż string.GetHashCode() na 64 bitach, co jest znacznie szybsze niż SpookyHash na 32 bitach, choć nadal wystarczająco szybkie, aby być rozsądnym wyborem.

 25
Author: Jon Hanna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-04 21:11:03

Od https://github.com/dotnet/coreclr/pull/14863 , istnieje nowy sposób generowania kodów hash, który jest super prosty! Po prostu napisz

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

Wygeneruje to wysokiej jakości Kod hashowy bez konieczności martwienia się o szczegóły implementacji.

 15
Author: James Ko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-23 15:06:05

To jest dobre:

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

A oto jak go używać:

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);
    }
}
 13
Author: Magnus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-02 14:20:42

Oto kolejna płynna implementacja algorytmu zamieszczonego powyżej przez Jona Skeeta , ale który nie zawiera żadnych alokacji ani operacji bokserskich:

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

Użycie:

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

Kompilator zapewni, że HashValue nie zostanie wywołana z klasą ze względu na ograniczenie typu ogólnego. Nie ma jednak wsparcia dla kompilatora HashObject, ponieważ dodanie argumentu ogólnego dodaje również operację bokserską.

 9
Author: Scott Wegner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:37

Oto moje uproszczone podejście. Używam do tego klasycznego wzoru budowniczego. Jest to typesafe (bez boksu/unboxingu), a także compatbile z. NET 2.0 (bez metod rozszerzeń itp.).

Używa się go tak:

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

A oto Klasa budowniczego acutal:

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
Author: bitbonk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-15 06:18:32

Jeśli mamy nie więcej niż 8 właściwości (miejmy nadzieję), oto inna alternatywa.

ValueTuple jest strukturą i wydaje się mieć solidną implementację GetHashCode.

Oznacza to, że możemy po prostu zrobić to:

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

Przyjrzyjmy się obecnej implementacji. NET Core dla ValueTuple'S GetHashCode.

To jest z 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);
    }

A to jest z 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;
        }
    }

W Języku Angielskim:

  • Left rotate (circular shift) h1 o 5 pozycji.
  • Dodaj wynik i h1 razem.
  • XOR wynik z h2.
  • zacznij od wykonania powyższej operacji na {static random seed, h1 }.
  • dla każdego kolejnego elementu wykonaj operację na poprzednim wyniku i następnym elemencie(np. h2).

Byłoby miło dowiedzieć się więcej o właściwościach tego algorytmu skrótu ROL-5.

Niestety, odroczenie do {[3] } dla naszych własnych GetHashCode może nie być tak szybkie, jak byśmy chcieli i spodziewaj się. ten komentarz w powiązanej dyskusji pokazuje, że bezpośrednie wywołanie HashHelpers.Combine jest bardziej wydajne. Z drugiej strony, ta jest wewnętrzna, więc musielibyśmy skopiować kod, poświęcając wiele z tego, co tutaj zyskaliśmy. Ponadto, będziemy odpowiedzialni za pamiętanie o pierwszym Combine z przypadkowym nasieniem. Nie wiem, jakie będą konsekwencje, jeśli pominiemy ten krok.

 6
Author: Timo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-15 12:00:46

ReSharper użytkownicy mogą generować GetHashCode, Equals i inne z 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;
    }
}
 5
Author: Charles Burns,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-05 14:19:15

Większość mojej pracy jest wykonywana z łącznością z bazą danych, co oznacza, że wszystkie moje klasy mają unikalny identyfikator z bazy danych. Zawsze używam ID z bazy danych, aby wygenerować hashcode.

// Unique ID from database
private int _id;

...    
{
  return _id.GetHashCode();
}
 3
Author: Mark G,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-05 05:03:24

Bardzo podobne do rozwiązania nightcodera, ale łatwiej jest podnieść liczby pierwsze, jeśli chcesz.

PS: to jeden z tych przypadków, kiedy rzygasz trochę do ust, wiedząc, że można to zrefakturować w jedną metodę z 9 domyślnymi, ale będzie wolniej, więc po prostu zamknij oczy i spróbuj o tym zapomnieć.

/// <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;
        }
    }
}
 3
Author: Dbl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-21 17:49:34

Microsoft prowadzi do kilku sposobów haszowania...

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

Domyślam się, że dla wielu dużych int możesz użyć tego:

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;

I to samo dla multi-type: all converted first to int using GetHashCode() następnie wartości int będą xor ' ed i wynikiem będzie twój hash.

Dla tych, którzy używają hash jako ID (mam na myśli unikalną wartość), hash jest naturalnie ograniczony do liczby cyfr, myślę, że było to 5 bajtów dla algorytmu haszującego, przynajmniej MD5.

Możesz zmienić wiele wartości na wartość haszowana i niektóre z nich są takie same, więc nie używaj jej jako identyfikatora. (może kiedyś użyję twojego komponentu)

 2
Author: deadManN,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-06 11:48:24

Napotkałem problem z pływakami i dziesiętnikami używając implementacji wybranej jako odpowiedź powyżej.

Ten test nie powiedzie się (pływa; hash jest taki sam, mimo że zamieniłem 2 wartości na ujemne):

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

Ale ten test przechodzi (z 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));

Zmieniłem swoją implementację, aby nie używać GetHashCode dla prymitywnych typów i wydaje się działać lepiej

    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;
                }
        }
    }
 1
Author: HokieMike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-28 16:44:25

Jest to statyczna Klasa pomocnicza, która implementuje implementację Josha Blocha; i zapewnia wyraźne przeciążenia, aby "zapobiec" boksowi, a także zaimplementować hash specjalnie dla długich prymitywów.

Możesz przekazać porównanie łańcuchów, które pasuje do Twojej implementacji equals.

Ponieważ wyjście Hash jest zawsze int, możesz po prostu łączyć połączenia Hash.

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;
        }
    }
}
 1
Author: Steven Coco,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-05-09 00:16:59

In case you want to polyfill HashCode from netstandard2.1

public static class HashCode
{
    public static int Combine(params object[] instances)
    {
        int hash = 17;

        foreach (var i in instances)
        {
            hash = unchecked((hash * 31) + (i?.GetHashCode() ?? 0));
        }

        return hash;
    }
}

Uwaga: jeśli jest używana z struct, przydziela pamięć ze względu na boks

 0
Author: Ivan Sanz-Carasa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-04-20 04:54:54

Może próbować przyjąć podejście z bibliotek C++ Boost. Coś takiego:

class HashUtil
{
  public static int HashCombine(int seed, int other)
  {
    unchecked
    {
      return other + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    }
  }
}

A następnie:

class MyClass
{
  private string _field1;
  private int _field2;
  private AnotherClass _field3;
  private YetAnotherClass _field4;

  public override int GetHashCode()
  {
    int result = HashUtil.HashCombine(_field1.GetHashCode(), _field2);
    result = HashUtil.HashCombine(result, _field3.GetHashCode());
    return HashUtil.HashCombine(result, _field4.GetHashCode());
  }
}
 0
Author: ivan.ukr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-25 19:40:41