Каков наилучший алгоритм для переопределенной системы.Объект.Метод GetHashCode?

в .NET System.Object.GetHashCode метод используется во многих местах, во всех библиотеках базовых классов .NET. Особенно при быстром поиске элементов в коллекции или определении равенства. Есть ли стандартный алгоритм / лучшая практика о том, как реализовать GetHashCode переопределить для моих пользовательских классов, чтобы не снизить производительность?

17 ответов


Я обычно иду с чем-то вроде реализации, приведенной в Джоша Блоха потрясающе Эффективная Java. Это быстро и создает довольно хороший хэш, который вряд ли вызовет столкновения. Выберите два разных простых числа, например 17 и 23, и сделайте:

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

как отмечено в комментариях, вы можете найти, что лучше выбрать Большое Простое Число для умножения. Видимо 486187739 хорошо... и хотя большинство примеров, которые я видел с небольшими числами, имеют тенденцию используйте простые числа, есть, по крайней мере, аналогичные алгоритмы, где часто используются не простые числа. В не-совсем -FNV пример позже, например, я использовал числа, которые, по-видимому, работают хорошо , но начальное значение не простое. (Константа умножения и prime хотя. Не знаю, насколько это важно.)

это лучше, чем обычная практика XORing хэш-коды по двум основным причинам. Предположим у нас есть тип с двумя int поля:

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

кстати, более ранний алгоритм в настоящее время используется компилятором C# для анонимных типов.

на этой странице дает довольно много вариантов. Я думаю, что для большинства случаев вышеизложенное "достаточно хорошо", и это невероятно легко запомнить и получить право. The FNV альтернатива аналогично проста, но использует разные константы и XOR вместо ADD как совмещать работу. Выглядит что-то как код ниже, но обычный алгоритм FNV работает на отдельных байтах, поэтому для этого потребуется изменить одну итерацию на байт, а не на 32-битное хэш-значение. FNV также предназначен для переменных длин данных, тогда как способ его использования здесь всегда для одного и того же количества значений полей. Комментарии к этому ответу предполагают, что код здесь на самом деле не работает (в проверенном примере), как подход добавления выше.

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

обратите внимание, что одна вещь следует помнить, что в идеале вы должны предотвратить изменение вашего чувствительного к равенству (и, следовательно, чувствительного к хэш-коду) состояния после добавления его в коллекцию, которая зависит от хэш-кода.

на документация:

вы можете переопределить GetHashCode для неизменяемых ссылочных типов. В общем случае для изменяемых ссылочных типов вы должны переопределить GetHashCode, только если:

  • вы можете вычислить хэш-код из поля они не изменчивы; или
  • вы можете гарантировать, что хэш-код изменяемого объекта не изменяется, пока объект содержится в коллекции, которая полагается на его хэш-код.

Анонимного Типа

Microsoft уже предоставляет хороший генератор общего хэш-кода: просто скопируйте значения свойств / полей в анонимный тип и хэшируйте его:

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

это будет работать для любого количества свойств. Он не использует бокс. Он просто использует алгоритм уже реализован в рамках для анонимных типов.

ValueTuple-обновление для C# 7

как упоминает @cactuaroid в комментариях, можно использовать кортеж значений. Это сохраняет несколько нажатий клавиш и, что более важно, выполняется чисто на стеке (без мусора):

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

(Примечание: исходный метод с использованием анонимных типов, похоже, создает объект в куче, т. е. мусор, поскольку анонимные типы реализуются как классы, хотя это может быть оптимизировано компилятором. Было бы интересно сравнить эти варианты, но вариант кортежа должен быть выше.)


вот мой помощник хэш-код.
Его преимущество заключается в том, что он использует аргументы универсального типа и поэтому не будет вызывать бокс:

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

также он имеет метод расширения для обеспечения плавного интерфейса, поэтому вы можете использовать его следующим образом:

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

или такой:

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

у меня есть класс хэширования в вспомогательной библиотеке, который я использую для этой цели.

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

тогда, просто вы можете использовать его как:

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

Я не оценил его производительность, поэтому любая обратная связь приветствуется.


вот мой вспомогательный класс с помощью реализация Джона Скита.

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

использование:

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

если вы хотите избежать написания метода расширения для системы.Int32:

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

он по-прежнему общий, он по-прежнему избегает выделения кучи и используется точно так же:

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

обновление после комментария Мартина:

obj != null вызвал бокс, поэтому я переключился на значение по умолчанию компаратор.

  • посмотреть ответ относительно производительности компаратора по умолчанию.
  • посмотреть этот вопрос для обсуждения хэш-кодов значений null.

Edit (Май 2018):

EqualityComparer<T>.Default геттер теперь JIT внутренний -pull-запрос упоминается Стивеном Тубом в этот блог.


в большинстве случаев, когда Equals() сравнивает несколько полей, на самом деле не имеет значения, хэши GetHash () на одном поле или на многих. Вы просто должны убедиться, что вычисление хэша действительно дешево (нет выделений, пожалуйста) и быстро (нет тяжелых вычислений и, конечно же, никаких подключений к базе данных) и обеспечивает хорошее распространение.

тяжелый подъем должен быть частью метода Equals (); хэш должен быть очень дешевой операцией для включения вызов Equals() как можно меньше элементов.

и последний совет:Не полагайтесь на GetHashCode () будучи стабильным в течение нескольких запусков приложений. Многие типы .Net не гарантируют, что их хэш-коды останутся неизменными после перезагрузки, поэтому вы должны использовать только значение GetHashCode() для структур данных в памяти.


и это в значительной степени отстой. Поэтому после нескольких экспериментов и исследований я начал повторно хэшировать свои хэши с помощью следующий:

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

и тогда мой хэш-стол power-of-two больше не сосать.

это меня беспокоило, потому что вышеизложенное не должно работать. Или, точнее, он не должен работать, если оригинал GetHashCode() был беден в очень специфическим способом.

повторное смешивание хэш-кода не может улучшить отличный хэш-код, потому что единственный возможный эффект заключается в том, что мы вводим еще несколько столкновений.

повторное смешивание хэш-кода не может улучшить ужасный хэш код, потому что единственный возможный эффект-это изменение, например, большого количества коллизий по значению 53 на большое количество значений 18,3487,291.

повторное смешивание хэш-кода может улучшить только хэш-код, который сделал, по крайней мере, довольно хорошо, избегая абсолютных столкновений во всем своем диапазоне (232 возможные значения), но плохо избегает столкновений, когда по модулю вниз для фактического использования в хэш-таблице. В то время как более простое по модулю таблицы степени двух делало это более очевидным, это было кроме того, отрицательный эффект с более распространенными таблицами простых чисел, что было не так очевидно (дополнительная работа по перефразированию перевешивала бы выгоду, но выгода все равно была бы там).

Edit: я также использовал открытую адресацию, которая также увеличила бы чувствительность к столкновению, возможно, больше, чем тот факт, что это была сила двух.

и хорошо, это было тревожно, насколько string.GetHashCode() реализации в .NET (или исследование здесь) можно было бы улучшить таким образом (по порядку тестов, работающих примерно в 20-30 раз быстрее из-за меньшего количества столкновений) и более тревожно, насколько мои собственные хэш-коды могут быть улучшены (гораздо больше).

все реализации GetHashCode (), которые я кодировал в прошлом и действительно использовал в качестве основы ответов на этом сайте, были намного хуже, чем я думал. Большую часть времени это было "достаточно хорошо" для большинства применений, но я чего-то хотел лучше.

поэтому я отложил этот проект в сторону (в любом случае это был любимый проект) и начал смотреть, как быстро создать хороший, хорошо распределенный хэш-код в .NET.

В конце концов я остановился на портирование SpookyHash to .Сеть. Действительно, приведенный выше код является быстрой версией использования SpookyHash для получения 32-битного вывода из 32-битного ввода.

теперь, SpookyHash не приятно быстро запомнить кусок кода. Мой порт этого еще меньше потому что я вложил много его для лучшей скорости*. Но для этого и нужно повторное использование кода.

тогда я ставлю это проект в одну сторону, потому что так же, как исходный проект создал вопрос о том, как создать лучший хэш-код, так и этот проект создал вопрос о том, как создать лучший .NET memcpy.

затем я вернулся и произвел много перегрузок, чтобы легко кормить почти все родные типы (кроме decimal†) в хэш код.

это быстро, за что Боб Дженкинс заслуживает большей части кредита, потому что его исходный код, из которого я портировал, еще быстрее, особенно на 64-битных машинах, для которых алгоритм оптимизирован для‡.

полный код можно увидеть на https://bitbucket.org/JonHanna/spookilysharp/src но учтите, что приведенный выше код является его упрощенной версией.

однако, поскольку он уже написан, можно использовать его больше легко:

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

он также принимает значения семян, поэтому, если вам нужно иметь дело с ненадежным вводом и хотите защитить от атак Hash DoS, вы можете установить семя на основе времени безотказной работы или аналогичного и сделать результаты непредсказуемыми злоумышленниками:

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

* большим сюрпризом в этом является то, что ручная вставка метода вращения, который вернул (x << n) | (x >> -n) улучшенные вещи. Я был бы уверен, что дрожание было бы для меня, но профилирование показало иначе.

decimal не является родным с точки зрения .NET, хотя он из C#. Проблема с ним в том, что его собственный GetHashCode() рассматривает точность как значимую, а свою собственную Equals() нет. И то и другое-правильный выбор, но не такой смешанный. При реализации вашей собственной версии вам нужно выбрать одну или другую, но я не могу знать, какую вы хотите.

‡для сравнения. Если используется в строке, SpookyHash на 64 битах значительно быстрее, чем string.GetHashCode() на 32 битах, что немного быстрее, чем string.GetHashCode() на 64 битах, что значительно быстрее, чем SpookyHash на 32 битах, хотя все еще достаточно быстро, чтобы быть разумным выбором.


Это:

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

а вот как его использовать:

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

вот мой упрощенный подход. Для этого я использую классический шаблон builder. Это typesafe (без бокса / распаковки), а также compatbile с .NET 2.0 (без методов расширения и т. д.).

Он используется следующим образом:

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

и вот класс acutal builder:

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

вот еще одна беглая реализация алгоритм, размещенный выше, Джон Скит, но не содержит каких выделений или бокса операций:

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

использование:

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

компилятор обеспечит HashValue не вызывается с классом из-за ограничение универсального типа. Но нет поддержки компилятора для HashObject поскольку добавление общего аргумента также добавляет операцию бокса.


по состоянию на https://github.com/dotnet/coreclr/pull/14863, есть новый способ генерации хэш-кодов, который очень прост! Просто напишите

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

Это позволит создать качественный хэш-код без необходимости беспокоиться о деталях реализации.


ReSharper пользователи могут генерировать GetHashCode, Equals и другие с 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;
    }
}

большая часть моей работы выполняется с подключением к базе данных, что означает, что все мои классы имеют уникальный идентификатор из базы данных. Я всегда использую ID из базы данных для генерации хэш-кода.

// Unique ID from database
private int _id;

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

очень похоже на решение nightcoder, за исключением того, что проще поднять простые числа, если вы хотите.

PS: Это один из тех случаев, когда вас немного рвет в рот, зная, что это может быть преобразовано в один метод с 9 по умолчанию, но это будет медленнее, поэтому вы просто закрываете глаза и пытаетесь забыть об этом.

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

я столкнулся с проблемой с поплавками и десятичных дробей с помощью реализации выбран в качестве ответа.

этот тест терпит неудачу (плавает; хэш одинаковый, хотя я переключил 2 значения на отрицательные):

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

но этот тест проходит (с 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));

Я изменил свою реализацию, чтобы не использовать GetHashCode для примитивных типов, и, похоже, работает лучше

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

если у нас есть не более 8 свойств (надеюсь), вот еще одна альтернатива.

ValueTuple является структурой и, похоже, имеет solid GetHashCode реализация.

это означает, что мы могли бы просто сделать это:

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

давайте посмотрим на текущую реализацию .NET Core для ValueTuple ' s GetHashCode.

это 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);
    }

и это от 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;
        }
    }

На Английском Языке:

  • левый поворот (круговой сдвиг) h1 на 5 позиций.
  • добавьте результат и h1 вместе.
  • XOR результат с h2.
  • начните с выполнения вышеуказанной операции над { static random seed, h1 }.
  • для каждого последующего элемента выполните операцию над предыдущим результатом и следующим элементом (например, h2).

было бы неплохо знать подробнее о свойствах этого алгоритма хэш-кода ROL-5.

к сожалению, отсрочка до ValueTuple для нашего собственного GetHashCode может быть не так быстро, как мы хотели бы и ожидали. комментарий в соответствующем обсуждении иллюстрирует, что прямой вызов HashHelpers.Combine более производительным. С другой стороны, это внутреннее, так что нам придется скопировать код, пожертвовав большей частью того, что мы получили здесь. Также, мы должны помнить, в первую Combine со случайным семя. Я не знаю, каковы будут последствия, если мы пропустим этот шаг.


Microsoft lead для нескольких способов хэширования...

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

Я могу догадаться, что для нескольких больших int вы можете использовать это:

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;

и то же самое для multi-type: все преобразованы сначала в int используя GetHashCode() тогда значения int будут xor'ED и результатом будет ваш хэш.

для тех, кто использует хэш в качестве идентификатора (я имею в виду уникальное значение), хэш естественно ограничен количеством цифр, я думаю, что это было 5 байт для алгоритма хэширования, по крайней мере Алгоритма MD5.

вы можете превратить несколько значений в хэшированное значение, и некоторые из них будут одинаковыми, поэтому не используйте его в качестве идентификатора. (возможно, когда-нибудь я буду использовать ваш компонент)