Каков наилучший алгоритм для переопределенного System.Object.GetHashCode?

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

1229
задан bitbonk 04 нояб. '08 в 23:53
источник поделиться
18 ответов

Обычно я занимаюсь чем-то вроде реализации, представленной в Josh Bloch, потрясающей эффективной 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, например, я использовал числа, которые, по-видимому, хорошо работают, но начальное значение не является простым. (Константа умножения - это просто, хотя я не знаю, насколько это важно).

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

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

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

На этой странице представлено несколько вариантов. Я думаю, что в большинстве случаев это "достаточно хорошо", и это невероятно легко запомнить и получить право. Альтернатива 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;
    }
}

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

Согласно документации:

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

  • Вы можете вычислить хэш-код из полей, которые не изменяются; или
  • Вы можете убедиться, что хеш-код изменяемого объекта не изменяется, пока объект содержится в коллекции, которая полагается на свой хэш-код.
1370
ответ дан Jon Skeet 04 нояб. '08 в 23:56
источник поделиться

Анонимный тип

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

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

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

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

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

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

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

304
ответ дан Rick Love 08 янв. '11 в 0:38
источник поделиться

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

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);
}
94
ответ дан nightcoder 04 апр. '10 в 21:26
источник поделиться

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

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

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

57
ответ дан Wahid Shalaly 23 февр. '09 в 14:46
источник поделиться

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

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

Если вы хотите избежать написания метода расширения для System.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 a 'HashCode' instance.
    // And the result is implicitly converted to 'Int32'.
    return HashCode.Start
        .Hash(_field1)
        .Hash(_field2)     
        .Hash(_field3);
}

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

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

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

Изменить (май 2018 года):

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

49
ответ дан Şafak Gür 04 сент. '13 в 15:32
источник поделиться

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

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

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

26
ответ дан Bert Huijben 23 февр. '09 в 14:55
источник поделиться

До недавнего времени мой ответ был очень близок к Джону Скиту здесь. Тем не менее, я недавно начал проект, в котором использовались две хэш-таблицы с силовыми характеристиками, то есть хеш-таблицы, где размер внутренней таблицы составляет 8, 16, 32 и т.д. Там есть веская причина для предпочтения простых размеров -n но есть и некоторые преимущества для двух размеров.

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

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

И тогда моя силовая таблица с хэшем больше не сосала.

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

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

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

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

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

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

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

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

В итоге я решил портировать SpookyHash на.NET. Действительно, приведенный выше код представляет собой версию с быстрым путём использования 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) (x << n) | (x >> -n) улучшил ситуацию. Я был бы уверен, что джиттер вложил бы это для меня, но профилирование показало иначе.

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

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

19
ответ дан Jon Hanna 14 янв. '14 в 17:15
источник поделиться

Это хороший вариант:

/// <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);
    }
}
12
ответ дан Magnus 07 окт. '10 в 13:51
источник поделиться

Вот мой упрощенный подход. Для этого я использую шаблон классического построителя. Это типы (без бокса/распаковки), а также совместимые с .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);
        }
    }
}
8
ответ дан bitbonk 22 марта '11 в 15:15
источник поделиться

Вот еще одна текущая реализация алгоритма, опубликованного выше Jon Skeet, но который не включает в себя распределения или операции по боксу:

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, так как добавление общего аргумента также добавляет операцию бокса.

8
ответ дан Scott Wegner 21 янв. '14 в 2:41
источник поделиться

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

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

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

5
ответ дан James Ko 23 нояб. '17 в 18:06
источник поделиться

Пользователи ReSharper могут генерировать GetHashCode, Equals и другие с помощью ReSharper → Edit → Generate Code → Equality Members.

// ReSharper 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;
    }
}
4
ответ дан Charles Burns 01 сент. '16 в 22:19
источник поделиться

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

// Unique ID from database
private int _id;

...    
{
  return _id.GetHashCode();
}
3
ответ дан Mark G 05 нояб. '08 в 8:03
источник поделиться

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

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;
        }
    }
}
3
ответ дан Dbl 21 окт. '14 в 20:49
источник поделиться

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

Этот тест терпит неудачу (floats; 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;
                }
        }
    }
1
ответ дан HokieMike 28 сент. '14 в 19:44
источник поделиться

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

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

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

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

Давайте взглянем на текущую реализацию.NET Core для ValueTuple 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 на ValueTuple для нашего собственного GetHashCode может быть не такой быстрой, как хотелось бы и ожидать. Этот комментарий в соответствующем обсуждении показывает, что прямое обращение к HashHelpers.Combine более показательно. С другой стороны, этот является внутренним, поэтому нам нужно будет скопировать код, жертвуя большей частью того, что мы получили здесь. Кроме того, мы будем нести ответственность за то, что помним, чтобы сначала Combine со случайным семенем. Я не знаю, каковы последствия, если мы пропустим этот шаг.

1
ответ дан Timo 15 мая '18 в 15:00
источник поделиться

Вот класс, который я построил с помощью методов для использования внутри переопределений Equals (объект obj) и GetHashCode(). Я решил использовать generics и алгоритм хэширования, который должен быть способен охватить большинство объектов. Пожалуйста, дайте мне знать, если вы видите что-то здесь, что не работает для некоторых типов объектов, и у вас есть способ его улучшить.

public class Equality<T>
{
    public int GetHashCode(T classInstance)
    {
        List<FieldInfo> fields = GetFields();

        unchecked
        {
            int hash = 17;

            foreach (FieldInfo field in fields)
            {
                hash = hash * 397 + field.GetValue(classInstance).GetHashCode();
            }
            return hash;
        }
    }

    public bool Equals(T classInstance, object obj)
    {
        if (ReferenceEquals(null, obj))
        {
            return false;
        }
        if (ReferenceEquals(this, obj))
        {
            return true;
        }
        if (classInstance.GetType() != obj.GetType())
        {
            return false;
        }

        return Equals(classInstance, (T)obj);
    }

    private bool Equals(T classInstance, T otherInstance)
    {
        List<FieldInfo> fields = GetFields();

        foreach (var field in fields)
        {
            if (!field.GetValue(classInstance).Equals(field.GetValue(otherInstance)))
            {
                return false;
            }
        }

        return true;
    }

    private List<FieldInfo> GetFields()
    {
        Type myType = typeof(T);

        List<FieldInfo> fields = myType.GetTypeInfo().DeclaredFields.ToList();
        return fields;
    }
}

Вот как он используется в классе:

public override bool Equals(object obj)
    {
        return new Equality<ClassName>().Equals(this, obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return new Equality<ClassName>().GetHashCode(this);
        }
    }
0
ответ дан kb4000 14 окт. '17 в 9:09
источник поделиться

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

//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.

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

0
ответ дан deadManN 30 нояб. '12 в 22:35
источник поделиться

Другие вопросы по меткам