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

В .NET GetHashCode метод используется в много мест в библиотеках базовых классов .NET. Его правильная реализация особенно важна для быстрого поиска элементов в коллекции или при определении равенства.

Есть ли стандартный алгоритм или передовой опыт реализации GetHashCode для моих пользовательских классов, чтобы я не снижал производительность?


person bitbonk    schedule 04.11.2008    source источник
comment
Прочитав этот вопрос и статью ниже, я смог реализовать переопределение GetHashCode. Я надеюсь, что это будет полезно для других. Рекомендации и правила для GetHashCode, написанный Эриком Липпертом   -  person rene    schedule 23.03.2012
comment
или определить равенство: нет! Два объекта с одинаковым хэш-кодом не обязательно равны.   -  person Thomas Levesque    schedule 03.09.2015
comment
@ThomasLevesque Вы правы, два объекта с одинаковым хеш-кодом не обязательно равны. Но все же GetHashCode() используется во многих реализациях Equals(). Вот что я имел в виду в этом заявлении. GetHashCode() внутри Equals() часто используется как ярлык для определения неравенства, потому что, если два объекта имеют разный хэш-код, они должны быть объектами, которые не равны, а остальная часть проверка на равенство не требуется.   -  person bitbonk    schedule 03.09.2015
comment
@bitbonk Обычно и GetHashCode(), и Equals() должны просматривать все поля обоих объектов (Equals должен это сделать, если хэш-коды равны или не проверены). Из-за этого вызов GetHashCode() внутри Equals() часто является избыточным и может снизить производительность. Equals() также может иметь возможность короткого замыкания, что делает его намного быстрее - однако в некоторых случаях хэш-коды могут быть кэшированы, что делает GetHashCode() проверку более быстрой и полезной. Дополнительную информацию см. В этом вопросе.   -  person NotEnoughData    schedule 02.04.2017
comment
ОБНОВЛЕНИЕ ЯНВАРЯ 2020 ГОДА: блог Эрика Липперта, расположенный по адресу: docs.microsoft.com/en-us/archive/blogs/ericlippert/   -  person Rick Davin    schedule 15.01.2020
comment
ОБНОВЛЕНИЕ МАРТ 2020 ГОДА: ссылка с @RickDavin верна, но статья на docs.microsoft.com имеет неправильное форматирование. Вот такая же статья в блоге Эрика. ericlippert.com/2011/02/28/guidelines-and -rules-for-gethashcode   -  person zumalifeguard    schedule 19.03.2020
comment
Теперь вы можете просто использовать HashCode.Combine (field1, field2, ...)   -  person Ε Г И І И О    schedule 23.04.2020


Ответы (22)


Обычно я использую что-то вроде реализации, приведенной в книге Джоша Блоха fabulous Эффективная 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 позже Например, я использовал числа, которые, по-видимому, работают хорошо, но начальное значение не является простым. (Хотя константа умножения простая. Я не знаю, насколько это важно.)

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

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

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

На этой странице есть несколько вариантов. Я думаю, что для большинства случаев вышеперечисленное достаточно хорошо, и его невероятно легко запомнить и исправить. Альтернатива FNV также проста, но использует другие константы и XOR вместо ADD as комбинированная операция. Он выглядит чем-то как приведенный ниже код, но обычный алгоритм 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 только в том случае, если:

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

Ссылка на статью FNV не работает, но вот копия в Интернет-архиве: Eternal Confuzzled - Искусство хеширования

person Jon Skeet    schedule 04.11.2008
comment
Алгоритм, описанный в упомянутой вами книге, на самом деле немного более подробен, он, в частности, описывает, что делать с различными типами данных в полях. Например: для полей типа long используйте (int) (поле ^ f ››› 32) вместо простого вызова GetHashcode. Реализован ли таким образом long.GetHashCodes? - person bitbonk; 05.11.2008
comment
Ага, Int64.GetHashCode делает именно это. В Java, конечно, потребуется бокс. Это напоминает мне - пора добавить ссылку на книгу ... - person Jon Skeet; 05.11.2008
comment
23 не является хорошим выбором, поскольку (начиная с .net 3.5 SP1) Dictionary<TKey,TValue> предполагает хорошее распределение по модулю определенных простых чисел. И 23 - один из них. Итак, если у вас есть словарь с емкостью 23, только последний вклад в GetHashCode влияет на составной хэш-код. Так что я бы предпочел использовать 29 вместо 23. - person CodesInChaos; 22.11.2010
comment
@Ani: ваша реализация разместила в куче несколько новых объектов, поэтому производительность может быть ниже, чем при ручной реализации. Приемлемо ли это, зависит от вашего типа и использования. Проверьте некоторые другие ответы для помощников, использующих обобщения, которые позволяют избежать этой проблемы. - person CodesInChaos; 22.11.2010
comment
@CodeInChaos: только последний вклад влияет на ведро, поэтому в худшем случае ему придется просмотреть все 23 записи в словаре. Он по-прежнему будет проверять фактический хэш-код каждой записи, что будет дешево. Если у вас есть такой маленький словарь, вряд ли это будет иметь большое значение. - person Jon Skeet; 22.11.2010
comment
@Jon: Я должен спросить, несмотря на то, что уже открыл свой вопрос по этой теме, но какая хорошая версия VB это потому, что в VB отсутствуют ключевые слова checked и unchecked? Я попытался сделать tmpHash Int64 и выполнить операцию AND для младших 8 бит (согласно принял ответ на мой вопрос), но при достаточно большом наборе полей это каким-то образом привело к тому, что вычисление обернулось до 0 для оставшейся части цикла. - person Kumba; 18.01.2011
comment
@Kumba: Боюсь, я не знаю, как бы это сделать в VB. Проверяется ли арифметика всегда в VB? Могли бы вы иметь отдельную библиотеку классов, которой вы могли бы делегировать арифметику, написанную на C # или с отключенной проверенной арифметикой для всего проекта? - person Jon Skeet; 18.01.2011
comment
@Jon: VB явно проверяет много вещей. У него есть фетиш требовать, чтобы числа без знака преобразовывались в числа со знаком, прежде чем вы сможете их сдвинуть влево или вправо. Что заставляет меня взбираться по стене и по потолку. Я пытаюсь реализовать хеш Jenkins, чтобы обойти отсутствие отмеченных / непроверенных (вращающийся хеш также помогает, но меня беспокоят конфликты хешей с вводом). Я бы хотел избежать использования отдельной библиотеки C #, потому что она, по сути, допускает поражение. Если я дойду до этого, мне нужно будет просто переписать весь проект на C #. - person Kumba; 18.01.2011
comment
Разве «непроверенный» ненужный b / c CLR по умолчанию будет счастливо переполняться? - person pomeroy; 18.01.2011
comment
@pomeroy: Это зависит от настроек проекта. По сути, вы даете сборке контекст по умолчанию, отмеченный или не отмеченный. - person Jon Skeet; 18.01.2011
comment
@pomeroy: VB не такой детализированный, как C #. Поскольку в нем отсутствуют два вышеупомянутых ключевых слова, ваш единственный вариант - удалить целое число переполнений для всего проекта или нет. Я предполагаю, что если ваш проект завершен и в целом хорошо протестирован, удаление проверок переполнения является безопасным делом. Однако при его создании и отладке эти проверки хороши, потому что они выделяют ошибки, которые нужно исправить. Я открыл Connect Ticket # 636564 с Microsoft, чтобы порекомендовать включить поддержку ключевых слов checked / unchecked в следующий выпуск .NET. Однако не уверен, поступят ли они так. - person Kumba; 18.01.2011
comment
Я добавлю, что мне придется использовать алгоритм ротации хешей, связанный с ответом Джона выше. Он не переполняется, даже в Int32, не (пока) не переносится в 0 на большом количестве полей в вычислении, и выполняется просто и довольно быстро. Хеш Jenkins не сработал ... Даже это переполняется случайным образом, в зависимости от ввода. Кроме того, принудительный сдвиг битов в знаковой математике мешает многим вещам. Я мог бы открыть еще одну ошибку, если это не предполагалось каким-то образом. - person Kumba; 18.01.2011
comment
Разве вам не нужен override в объявлении вашего метода? Также было бы хорошо поставить нулевые проверки, поскольку это такой хорошо используемый пример. - person Rory; 05.02.2011
comment
@Rory: Я добавил переопределение, спасибо - я не собираюсь вводить нулевые проверки, так как я чувствую, что это заслонит важные моменты. ИМО комментария хватает. - person Jon Skeet; 05.02.2011
comment
Зачем начинать с простого, а не с нуля? есть ли у int hash = 17; какие-либо теоретически подтвержденные преимущества? - person fredoverflow; 06.02.2011
comment
@FredOverflow: я не знаю точных деталей всех причин, стоящих за этим, но начало с 0 означало бы, что хеш останется равным нулю, если отдельные хэши полей будут равны нулю ... и это, вероятно, не редкость (например, целое число нулевого значения, вероятно, будет хеширован до нуля). Просто предположение, но я подозреваю, что наличие константы, которая распространяется с каждым полем, полезно. На самом деле это просто скопировано из Effective Java :) - person Jon Skeet; 06.02.2011
comment
@JonSkeet Насколько безопасным будет этот алгоритм для сложного графа объектов, состоящего, скажем, из 500 объектов, каждый из которых имеет 10 свойств. Связанный вопрос: http://stackoverflow.com/questions/5308057/generating-an-safe-hashcode-for-an-objectgraph/5308237 - person bitbonk; 15.03.2011
comment
@bitbonk: Вероятность столкновения при любом отдельном изменении будет довольно низкой ... но в вопросе, о котором вы говорите, я бы, вероятно, использовал вместо этого криптографический хеш. - person Jon Skeet; 15.03.2011
comment
Тогда возникает вопрос: как мне создать криптографический хеш для объектной модели? - person bitbonk; 15.03.2011
comment
@bitbonk: Я бы настоятельно рекомендовал использовать обычный криптографический хеш для результата двоичной сериализации формы. - person Jon Skeet; 15.03.2011
comment
Этот алгоритм в основном представляет собой алгоритм хеширования строк DJB2, для которого рекомендуются константы 5381 и 33 (cse.yorku.ca/~oz/hash.html). Честно говоря, я не уверен, что константа имеет большое значение, но множитель важен. - person yoyo; 16.12.2011
comment
@JonSkeet Я понимаю, что воскрешаю здесь мертвых, но реализация хэшей для меня в новинку. Какие поля я включаю в хеш в вашей реализации? Только неизменяемые или какие-то поля хороши? - person KChaloux; 26.10.2012
comment
@KChaloux: Это полностью зависит от того, что вы хотите, чтобы равенство значило. Однако обычно включать изменяемые данные - плохая идея. - person Jon Skeet; 26.10.2012
comment
Как бы вы справились с недействительностью? Если просто игнорировать это поле, то для A = null, B = ss и для A = ss, B = null у нас будут коллизии. Не лучше ли умножать каждое поле на разные простые числа? - person Vajda; 22.01.2013
comment
@Vajda: я обычно использую 0 как эффективный хэш-код для null - это не то же самое, что игнорирование поля. - person Jon Skeet; 22.01.2013
comment
@ jnm2: Честно говоря, я не понимаю твоих аргументов. В частности, я только что попытался эффективно использовать хеширование 10 полей - и изменение значения только первого поля все равно изменило хэш, что противоречит вашему утверждению о том, что каждый бит первых хеш-кодов будет потерян. - person Jon Skeet; 20.11.2013
comment
Вы можете довольно просто продемонстрировать, что это дает плохое распределение. Возьмите этот вариант FNV и примените его к строкам (используйте небезопасные манипуляции с указателями, чтобы получать целые числа за раз, чтобы дать ему шанс). Используйте его для добавления строк в хеш-таблицу на основе степени двойки. С тем, над которым я сейчас работаю, если я сгенерирую 1, 2, ... 999999 и добавлю их, это займет около 34 секунд. Теперь возьмем тот же метод хеширования и повторно хэшируем результат с хорошо распределенным хешем. С хорошим хешем это может только усугубить ситуацию (тратится больше времени, и мы можем вводить новые коллизии, но никогда их не удалять). С ... - person Jon Hanna; 14.01.2014
comment
... та же хеш-таблица, над которой я работаю, тот же код для генерации 1 ... 999999 и их добавления занимает 1 секунду. Эффект менее выражен с хешами на основе простых чисел, поэтому в этом случае дополнительное время, потраченное на повторное хеширование (и, возможно, уменьшение возможных результатов, хотя это маловероятно), ничего не дает, но низкая производительность при использовании мощности. -два таблицы демонстрируют плохое распределение в целом. - person Jon Hanna; 14.01.2014
comment
@JonHanna: Спасибо за это. Не уверен, что вы имеете в виду, чтобы получить целые числа за раз, но я постараюсь взглянуть поближе. Мне все еще нравится это в первом приближении для хеша, но если у вас есть другой хеш, который так же просто запомнить и исправить, но с лучшим распределением, я был бы очень рад изменить свою практику :) - person Jon Skeet; 14.01.2014
comment
Я имел в виду, что использовал fixed(char* ptr = str){int* iPtr = (int*)ptr;..., но я также пробовал просто делать foreach(char c in str) и преобразовывать каждый char в int, и то же самое применимо. Относительная слабость стала очевидной для меня, когда у меня была причина использовать таблицы степени двух и я получал плохие результаты (я сам использовал почти то же, что и выше). Решение, которое я наконец нашел, - это забыть о том, что его легко запомнить, и один раз создать метод, который трудно запомнить, а затем упростить его использование и поместить его код в nuget.org/packages/SpookilySharp Я добавлю здесь полный ответ в обеденное время. - person Jon Hanna; 14.01.2014
comment
@JonSkeet и теперь ответил. - person Jon Hanna; 14.01.2014
comment
@JonHanna: Спасибо за это. Придется посмотреть поподробнее, когда будет куча времени :) - person Jon Skeet; 14.01.2014
comment
Я думаю, важно отметить, что мы должны быть осторожны с изменением хеш-кода во время выполнения. У нас была ошибка в моем проекте, потому что предыдущий разработчик реализовал алгоритм GetHashCode, основанный на этом ответе. Но в его реализации у него был список объектов, он использовал хэш каждого элемента в коллекции для генерации хеш-кода объекта. Поэтому при изменении коллекции изменился и хэш-код. Это вызывало проблемы с привязкой в ​​WPF. И если бы у вас был объект, например, в словаре, вы бы тоже получили ошибки. - person Dzyann; 14.02.2014
comment
@Dzyann: Да, изменять ключ таким образом, чтобы это влияло на равенство - и, следовательно, на хэш-код - это всегда плохая идея. Добавлю примечание. - person Jon Skeet; 14.02.2014
comment
@JonSkeet, вы правы, и это может привести к очень сложному отслеживанию ошибок. Как в этом случае с привязками WPF. Потребовались годы, прежде чем один из моих коллег нашел причину и решил ее. Поскольку это был не наш код, это было очень сложно. - person Dzyann; 14.02.2014
comment
Я предлагаю вам заменить 17 и 23 константами здесь. (Спасибо за ссылку.) Он дал простой поиск по словарю намного более производительный, в моем случае примерно на 60% лучше. - person jnm2; 23.04.2014
comment
@ jnm2: Это не тот алгоритм для начала - он использует XOR, а не ADD. Я буду придерживаться этих констант для этого ответа, но, может быть, вам стоит добавить свой собственный ответ? - person Jon Skeet; 23.04.2014
comment
На самом деле, я собирался предположить, что xoring вместо добавления не уменьшит простоту хеш-алгоритма перехода. Что вы думаете? - person jnm2; 23.04.2014
comment
В моем случае XOR ускоряет GetHashCode () на 12%. - person jnm2; 23.04.2014
comment
@ jnm2: Ну, это не уменьшило бы эту простоту - но это не то, чем я занимался последние несколько лет. Я добавлю FNV в качестве альтернативы. - person Jon Skeet; 23.04.2014
comment
int hash = 2166136261; Не хватает ли гипса? Компилятор говорит, что 2166136261 - это _3 _... Я изменил его на int hash = (int)2166136261; - person Roman Ganz; 24.04.2014
comment
Я попытался реализовать этот подход для ValueUtils, но в моем тестировании этот вариант FNV вызвал значительные конфликты (24 %) в некоторых симметричных наборах данных. И, возможно, это потому, что это НЕ хеш FNV? Традиционные хэши FNV на октет (байт), а не на 32-битное слово. Это дает этому варианту меньше возможностей смешивать эти биты ... - person Eamon Nerbonne; 01.06.2014
comment
@EamonNerbonne: Что вы имеете в виду под этим подходом? Теперь ответ содержит две разные версии ... - person Jon Skeet; 01.06.2014
comment
Я имею в виду этот вариант FNV - это не совсем FNV, и я почти уверен, что это только усугубляет ситуацию. Я, кстати, тоже пробовал h=prime; repeat h=h*prime + ? рецепт; это, кажется, меняется; он вполне подходит для больших простых чисел, особенно если ваш промежуточный имеет ширину 64 бита. - person Eamon Nerbonne; 01.06.2014
comment
@Eamon: Боюсь, я недостаточно знаю теорию, чтобы комментировать дальше :( - person Jon Skeet; 01.06.2014
comment
Да, теория, лежащая в основе этого, для меня совсем не очевидна. Однако этот ответ предполагает, что эта реализация является FNV, хорошо известным хорошим хешем. Но это не совсем так, поскольку это не FNV. Кроме того, FNV - это алгоритм хеширования строк, который должен удовлетворять гораздо более сложным требованиям, поскольку он должен работать с потенциально длинными строками переменной длины. Но опять же, алгоритм, представленный в настоящее время в ответе, не является FNV - он гораздо хуже смешивает биты. - person Eamon Nerbonne; 01.06.2014
comment
@EamonNerbonne: Хорошо. Я отредактирую, чтобы указать, что это модификация и что она не работает, по крайней мере, в некоторых случаях. - person Jon Skeet; 01.06.2014
comment
@EamonNerbonne: Какие лучшие коэффициенты вам известны? - person jnm2; 03.06.2014
comment
@ jnm2 В моих экспериментах смещение мало что значит, и тенденция такова, что большие простые числа работают лучше, с оговоркой, что все это сложно проверить, потому что это медленно (очень медленно), чтобы быть тщательным, и это зависит от того, как ваш набор данных испорчен. Если ваши поля имеют совершенно случайно распределенные хэш-коды - все это не имеет значения, но, конечно, в реальном мире эти хэш-коды не случайны, и поля коррелированы. Есть довольно веская причина, по которой большие простые числа тоже будут лучше - они лучше смешивают биты, особенно если ваши данные в основном состоят из небольших чисел. - person Eamon Nerbonne; 03.06.2014
comment
@ jnm2, поэтому я бы выбрал большое число (скажем, порядка 2 ^ 16) и настроился на реализацию словаря .NET, который НЕ используется Dictionary ‹,›: referenceource.microsoft.com/#mscorlib/system/collections/ - person Eamon Nerbonne; 03.06.2014
comment
@ jnm2 Я столкнулся с этими двумя вопросами, продолжая изучать эту проблему: stackoverflow.com/questions/1835976/ и stackoverflow.com/questions/1145217/, и оба приходят к выводу: используйте любое старое большое простое число. В принятом ответе на первый вопрос упоминаются два, выбранных принципиальным образом, но вряд ли этот принцип действительно относится к реальному миру, поэтому он все же рекомендует основную идею: выберите большое простое число, а НЕ 23 или 31. - person Eamon Nerbonne; 04.06.2014
comment
Кстати: обратите внимание, что смещение (насколько я могу судить) совершенно бессмысленно. Распределительные законы также действуют по модулю, а это означает, что это просто идентичное смещение, которое будут разделять все объекты, - это, безусловно, не влияет на какую-либо хеш-таблицу, которую я знаю. - person Eamon Nerbonne; 04.06.2014
comment
@EamonNerbonne: Думаю, это правда, если все объекты одного типа. Если у вас есть словарь, в котором некоторые ключи являются подклассами других ключей, это может иметь значение ... хотя в любом случае только тогда, когда значения дополнительных полей равны 0. Опять же, для меня это в основном привычка :( - person Jon Skeet; 04.06.2014
comment
@JonSkeet Да, если у вас есть объекты разного типа и вы используете разные смещения, у вас будет некоторое преимущество. Хотя, думаю, нет причин быть первоклассным ... В любом случае, дополнение настолько дешево, что нет особых причин избегать его. - person Eamon Nerbonne; 04.06.2014
comment
Я использовал этот алгоритм для псевдослучайного генератора, и он ведет себя немного странно: stackoverflow.com/questions/26847262/ - person Max Yankov; 10.11.2014
comment
Если вы получили номер 486187739 от stackoverflow.com/a/2816747/21499 - я действительно намеревался рекомендовать 92821. - person Hans-Peter Störr; 01.04.2015
comment
Поскольку каждый экземпляр класса object имеет уникальный хэш-код, мне пришла в голову идея, что было бы хорошо, если бы мы использовали base.GetHashCode () в качестве начального числа или чего-то еще для создания нашего хэш-кода для объекта. - person Ahmad Siavosh; 05.08.2015
comment
@AhmadSiavosh: Нет, это плохая идея, потому что вы хотите, чтобы разные, но равные объекты имели один и тот же хеш-код. (Я также не думаю, что object.GetHashCode гарантированно будет уникальным. Вполне возможно, что столкновение с ним будет очень маловероятным, но это не одно и то же.) - person Jon Skeet; 05.08.2015
comment
Если fieldL это List<obj>, он будет работать, просто выполнив hash = ... ^ fieldL.GetHashCode(), или мне придется пройти через такие пункты, как _4 _ ??? - person Jaider; 12.02.2016
comment
@Jaider: Это тоже не годится. List<T> не отменяет Equals или _3 _. # - person Jon Skeet; 12.02.2016
comment
Я пробовал этот код для 3 дублей и получил огромное количество коллизий. Мне нужно получить хэш-коды для 4194304 кортежей. Есть ли способ лучше? Использование некоторых более крупных простых чисел немного помогло, но я все еще получаю коллизии. - person dmarra; 16.02.2016
comment
@ user984444: Что ж, вы должны ожидать довольно много столкновений с таким количеством записей. Сколько вы получаете? - person Jon Skeet; 16.02.2016
comment
@JonSkeet Трудно сказать. Я использую это для кеширования вывода некоторого шума Перлина, а индикатор столкновения - это какой-то интересный вывод в моем изображении; Он выглядит как ... когда вы выигрываете пасьянс. Это смягчается (и шаблон меняется) с большими простыми числами. Я знаю, это бесполезно. Я изменил свою структуру (кортеж двойников в качестве ключа) на класс, чтобы сеть заботилась о хэш-коде за меня и больше не имела коллизий. - person dmarra; 16.02.2016
comment
@ user984444: Гм, тогда равные ключи не будут равны, если только вы не переопределите GetHashCode в своем классе, и в этом случае у вас такая же проблема. Может, стоит задать новый вопрос со всеми подробностями ... - person Jon Skeet; 16.02.2016
comment
@JonSkeet: Неправда; реализация GetHashCode по умолчанию работает отлично (в противном случае это было бы невероятно очевидно в моем конечном результате). Он также работает для структуры, но работает СОВЕРШЕННО МЕДЛЕННО. Я хотел использовать структуры, но использование класса, похоже, отлично подходит для моего варианта использования. - person dmarra; 16.02.2016
comment
@ user984444: Если вы сами не переопределите GetHashCode и Equals или не унаследуете от другого класса, который это делает, вы получите ссылочное равенство. Это не то, что вам даст структура. Похоже, нам нужен новый пост с подробностями. - person Jon Skeet; 16.02.2016
comment
@JonSkeet: Я считаю, что моя конкретная проблема решена, потому что я получаю желаемый результат, но если у меня будет возможность, я опубликую вопрос с подробностями, чтобы вы могли видеть, что происходит. - person dmarra; 17.02.2016
comment
будучи очень разборчивым, настройки StyleCop по умолчанию генерируют предупреждение для этого кода (SA1407), поскольку вы не использовали круглые скобки для определения приоритета арифметических операторов, даже если он понятен любому разработчику, читающему код, и компилятору, как мы все знаем Правило БОДМЫ. - person MikeW; 30.03.2016
comment
@MikeW: Я не думаю, что BODMAS включает XOR :) Я думаю, что заключительный фрагмент кода будет более понятным с круглыми скобками - добавлю их сейчас. Я согласен, что для версии с умножением и сложением они не нужны. - person Jon Skeet; 30.03.2016
comment
Для будущих читателей: рассмотрите возможность использования HashCode.Combine() - person RobIII; 23.11.2017
comment
@JonSkeet есть идеи, как это сделать в t-sql? Мне нужен хеш С # серии guid для соответствия хешу t-sql серии uniqueidentifier. но afaik в t-sql невозможно обернуть результаты целочисленной арифметики. - person BaltoStar; 02.03.2018
comment
@BaltoStar: я ничего не знаю о хешировании в T-SQL. Если он уже обеспечивает четко определенное хеширование для значений GUID, я бы, вероятно, попытался имитировать это на C #, а не наоборот. - person Jon Skeet; 02.03.2018
comment
@JonSkeet в C #, почему бы просто не хешировать MD5 для упорядоченной конкатенации идентификаторов GUID? - person BaltoStar; 02.03.2018
comment
@JamesKo: Я добавлю ссылку на HashCode.Combine, когда .NET Core 2.1 будет выпущен, и я могу ссылаться на документы. Не думаю, что до того времени многим он будет полезен. - person Jon Skeet; 16.03.2018
comment
@JonSkeet Конечно. - person James Ko; 17.03.2018
comment
Я не уверен, как здесь обрабатывать нули. Кажется, что ни один из ответов не затрагивает эту тему, если предположить, что все мы эксперты в этой теме. @JonSkeet В этих комментариях упоминается, что я обычно использую 0 как эффективный хэш-код для null - это не то же самое, что игнорирование поля. Однако как это на самом деле реализовано, у меня есть вопросы. Похоже, вы говорите, что свойство null должно обнулить текущее значение хеш-функции, но это кажется странным поведением. Некоторым может быть очевидно, что делать, но я был бы признателен за пример, показывающий, как обрабатывать нули, или лучшее объяснение. - person crush; 07.05.2018
comment
Прочитав несколько других вопросов и ответов по этой теме, я понял, что не очень хорошо понимаю, о чем говорит @JonSkeet. Я неправильно понял, что он говорит, что я должен заменить 0 как константу хеширования, когда свойство имеет значение null. Увидев пример здесь, я понял, что он просто заявил, что я должен заменить 0 в качестве хэш-кода свойства, что кажется таким теперь очевидно ... учитывая, что он сказал именно это. - person crush; 07.05.2018
comment
Действительно ли нужно использовать простые числа вроде 17 или 23, если хэш моего объекта зависит только от одного свойства int32? Могу я просто вернуть MyProperty.GetHashCode()? - person stt106; 14.05.2018
comment
@ stt106: Для одного свойства я бы просто вернул хэш-код этого свойства, да. - person Jon Skeet; 14.05.2018
comment
К вашему сведению, Visual Studio 2017 может генерировать GetHashCode() без ReSharper. docs.microsoft. ru / en-us / visualstudio / ide / reference / - person cactuaroid; 27.10.2018
comment
Зачем умножать хеш на каждой строке? Почему: int hash = 17; hash = hash * 23 + ...? Почему бы просто не использовать продукт явно, как, например, hash = 391 + field1.GetHashCode();? Поскольку порядок операций в любом случае будет сначала выполнять умножение? - person emery.noel; 08.10.2019
comment
@ emery.noel: Это не будет иметь никакого значения после первой строки (вам все равно нужно умножить, чтобы включить предыдущий хеш), и IMO имеет большое преимущество в том, чтобы сделать каждую строку согласованной. - person Jon Skeet; 08.10.2019
comment
Важному моменту уделялось не так много внимания. Важно, чтобы возвращаемый хэш-код НЕ МЕНЯЛСЯ, если объект является изменяемым и объект изменяется. Это связано с тем, что хэш-код используется (например) для размещения объектов в словарях. Если изменяемый объект изменяется после вставки в словарь, то объект не найден, когда вы идете искать его. Приведенное выше должно кэшировать хеш при первом вычислении и всегда возвращать исходное значение. Иначе будут странные баги. - person Tb.; 08.04.2020
comment
@Tb .: Или вы документируете это в соответствии с документами: если вы решите переопределить GetHashCode () для изменяемого ссылочного типа, ваша документация должна четко указывать, что пользователи вашего типа не должны изменять значения объекта, пока объект хранится в хеш-таблице. Часто это бывает полезно, поскольку вы можете создать объект, но не изменять его впоследствии. Он не такой уж чистый, но вполне практичный. - person Jon Skeet; 08.04.2020
comment
Ссылка на статью FNV не работает, но я нашел ее в архиве: archive.vn/KJeJy - person Jalal; 19.02.2021

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

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

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

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

Анонимный тип (оригинальный ответ)

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

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

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

person Rick Love    schedule 07.01.2011
comment
Да, анонимная реализация GetHashCode очень эффективна (кстати, она такая же, как в ответе Джона Скита), но единственная проблема с этим решением заключается в том, что вы генерируете новый экземпляр при любом GetHashCode вызове. Это может быть немного накладным, особенно в случае интенсивного доступа к большим хешированным коллекциям ... - person digEmAll; 08.01.2011
comment
Это работает в VB с .NET 4.0, но, просматривая IL, он использует box вызовы, поскольку тип использует обобщенные типы. Распаковки нет, но из того, что я здесь читал, простое присутствие бокса предполагает, что это может быть немного неэффективно. Кажется, это единственный выбор для VB, поскольку нет эквивалента checked / `unchecked '. - person Kumba; 11.01.2011
comment
@digEmAll Хороший момент, я не подумал о накладных расходах на создание нового объекта. Ответ Джона Скита наиболее эффективен и не использует бокс. (@Kumba Чтобы решить непроверенный в VB, просто используйте Int64 (длинный) и усеките его после вычислений.) - person Rick Love; 02.04.2011
comment
В VB.Net: New With {PropA, PropB, PropC, PropD}.GetHashCode() - person mwolfe02; 16.04.2013
comment
VB.NET должен использовать Key при создании анонимного типа: New With {Key PropA}.GetHashCode() В противном случае GetHashCode не будет возвращать один и тот же хэш-код для разных объектов с одинаковыми «идентифицирующими» свойствами. - person David Osborne; 20.08.2014
comment
Не забудьте перечислить свои IEnumerables, иначе случится что-то плохое. new { PropA, PropB, C = PropC.ToList() }.GetHashCode() - person Keith; 19.10.2015
comment
@Keith в этом случае я бы подумал о том, чтобы сохранить IEnumerable как значение списка где-нибудь, вместо того, чтобы перечислять его каждый раз, когда вычисляется хэш-код. Caclating ToList каждый раз внутри GetHashCode может снизить производительность во многих ситуациях. - person Rick Love; 20.10.2015
comment
И не забывайте, что приватное свойство / поля в этом случае не нужны;). - person shA.t; 29.08.2017
comment
@Keith: на хэш-код не обязательно влиять все свойства объекта. Хэш-код просто должен давать достаточно хорошее распределение ваших объектов. И должен быть быстрым для вычислений. Оставьте перечислимое. И если у вас есть список, не включайте весь список. Используйте Count и, возможно, первый элемент (используйте ноль, если элементов нет). если у вашего класса нет других вариантов, кроме списка; в этом случае, как предлагает Рик, лучше всего кэшировать хэш списка. Напомним, что по определению хэш объекта всегда должен быть одинаковым. Если коллекция изменяется, НЕ включайте ее в hash calc. - person ToolmakerSteve; 01.03.2018
comment
Для тех, кому это нравится, (PropA, PropB, PropC, PropD).GetHashCode() теперь доступен на C # 7 без проблем с давлением сборщика мусора @digEmAll. Быстрые и простые комбинации хеш-кода - person cactuaroid; 16.08.2018
comment
@cactuaroid Отлично! Итак, используя кортеж (который является структурой) вместо анонимного типа (класса). Использует ли он тот же расчет за кулисами для Tuple GetHashcode ()? - person Rick Love; 16.08.2018
comment
@RickLove Я не уверен в математике. Tuple.GetHashCode () и ValueTuple.GetHashCode () выглядят одинаково. ValueTuple.GetHashCode () вызывает HashHelper. Tuple.GetHashCode () вызывает Tuple.CombineHashCodes. Для анонимного типа Как Equals и GetHashCode реализованы для анонимных типов? - person cactuaroid; 16.08.2018
comment
@cactuaroid: действительно, это отличное решение! - person digEmAll; 16.08.2018
comment
Прошу прощения, что @Timo уже писал о ValueTuple.GetHashCode () ниже. - person cactuaroid; 17.08.2018
comment
Если допускает значение NULL, нужно ли нам проверять PropA, B и т. Д. На ноль, и если ноль передается в 0? - person ScubaSteve; 28.06.2021

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

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);
}
person nightcoder    schedule 04.04.2010
comment
Нет необходимости в T[] отдельно, так как это уже IEnumerable<T> - person nawfal; 14.04.2013
comment
Вы можете провести рефакторинг этих методов и ограничить основную логику одной функцией. - person nawfal; 14.04.2013
comment
Между прочим, 31 - это сдвиг и вычитание на ЦП, что очень быстро. - person Chui Tey; 23.08.2013
comment
Метод расширения в int - это неприятное загрязнение пространства имен - ответ ниже от @ safak-gur позволяет решить эту проблему. - person Eamon Nerbonne; 01.06.2014
comment
@nightcoder, вы можете использовать params. - person ANeves thinks SE is evil; 09.02.2015
comment
@ChuiTey Это то, что объединяет все простые числа Мерсенна. - person Pharap; 12.06.2015
comment
Разве переменная hash не должна начинаться с нуля? stackoverflow.com/a/113600/9638388 - person geekley; 20.07.2018
comment
Просто потому, что это круто, вы также можете сделать это с помощью однострочника: source?.Aggregate(0, (current, item) => unchecked(current * 31 + (item?.GetHashCode() ?? 0))) ?? 0; - person KnorxThieus; 09.03.2019
comment
@ANeves Я предлагаю вам не использовать params, если он предназначен для более широкого использования (например, публичная библиотека). params включает выделение массива (плюс затраты O (n) на заполнение массива), что плохо для ситуаций, требующих высокой производительности. params object[] вдвойне плохо теперь, когда вы вводите стоимость упаковки также для типов значений. - person nawfal; 06.05.2020

.NET Standard 2.1 и выше

Если вы используете .NET Standard 2.1 или выше, вы можете использовать System.HashCode структура. Есть два способа его использования:

HashCode.Combine

Метод Combine может использоваться для создания хэш-кода, содержащего до восьми объектов.

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

HashCode.Add

Метод Add помогает вам работать с коллекциями:

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 - это просто

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

Пример использования

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

Реализация

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

Что делает алгоритм хорошим?

Представление

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

Детерминированный

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

Уменьшить коллизии

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

Хорошая хеш-функция должна отображать ожидаемые входные данные как можно более равномерно по выходному диапазону. Он должен иметь единообразие.

Предотвратить DoS

В .NET Core каждый раз, когда вы перезапускаете приложение, вы будете получать разные хэш-коды. Это функция безопасности для предотвращения атак типа «отказ в обслуживании» (DoS). Для .NET Framework вам следует включить эту функцию, добавив следующий файл App.config:

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

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

Подробнее об этом здесь.

Криптографически безопасный?

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

  • Невозможно сгенерировать сообщение, которое дает заданное значение хеш-функции.
  • Невозможно найти два разных сообщения с одинаковым значением хеш-функции.
  • Небольшое изменение в сообщении должно настолько сильно изменить хеш-значение, что новое хеш-значение будет казаться некоррелированным со старым хеш-значением (эффект лавины).
person Muhammad Rehan Saeed    schedule 11.06.2019
comment
Это очень хороший ответ. В качестве дополнения вы можете рассмотреть возможность изменения скорости на производительность и добавления свойства отсутствия выделения памяти. Встроенный тип HashCode удовлетворяет и этому. - person Timo; 10.07.2020
comment
Как это соотносится с ValueTuple.GetHashCode() ответом, недавно обновленным @ricklove выше? - person Thiago Silva; 18.02.2021
comment
HashCode.Combine - это статический метод, который ничего не выделяет, а ValueTuple начинает с выделения в стеке. - person Muhammad Rehan Saeed; 18.02.2021
comment
HashCode.Of(this.Name).And(this.Age).AndEach(this.Powers) - красивый синтаксис :) - person Amos Egel; 09.03.2021

У меня есть класс хеширования в библиотеке 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);
}

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

person Wahid Shalaly    schedule 23.02.2009
comment
Что ж, это вызовет бокс, если поля являются типами значений. - person nightcoder; 04.04.2010
comment
может быть улучшено позже путем перехвата OverflowException Вся суть unchecked в том, чтобы избежать исключений при переполнении, которое желательно на GetHashCode. Так что это не неправильно, если значение выходит за пределы int, и это совсем не повредит. - person Tim Schmelter; 24.02.2014
comment
Одна из проблем этого алгоритма заключается в том, что любой массив, заполненный нулями, всегда будет возвращать 0, независимо от его длины. - person Nathan Adams; 17.04.2015
comment
Этот вспомогательный метод также выделяет новый объект [] - person James Newton-King; 20.07.2016
comment
Как упоминает @NathanAdams, тот факт, что null полностью пропущен, может дать вам неожиданные результаты. Вместо того, чтобы пропускать их, вы должны просто использовать какое-то постоянное значение вместо input[i].GetHashCode(), когда input[i] равно нулю. - person David Schwartz; 28.10.2016

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

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

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

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

Изменить (май 2018 г.): EqualityComparer<T>.Default getter теперь является встроенной функцией JIT - запрос на вытягивание является упомянутый Стивеном Тубом в это сообщение в блоге.

person Şafak Gür    schedule 04.09.2013
comment
Я бы изменил строку с третичным оператором на: var h = Equals(obj, default(T)) ? 0 : obj.GetHashCode(); - person Bill Barry; 05.09.2014
comment
Я считаю, что тернарный оператор с obj != null будет компилироваться в инструкцию box, которая будет выделять память, если T является типом значения. Вместо этого вы можете использовать obj.Equals(null), который будет компилироваться в виртуальный вызов метода Equals. - person Martin Liversage; 14.09.2014
comment
Потому что this.hashCode != h. Это не вернет то же значение. - person Şafak Gür; 15.06.2015
comment
Извините, мне удалось удалить мой комментарий вместо его редактирования. Более выгодно создать новую структуру, а затем изменить hashCode на non-readonly и сделать: unchecked {this.hashCode ^ = h * 397; } вернуть это; Например? - person Erik Karlsson; 15.06.2015
comment
Неизменяемость имеет свои преимущества (Почему изменчивые структуры - зло?). Что касается производительности, то, что я делаю, довольно дешево, поскольку оно не выделяет места в куче. - person Şafak Gür; 15.06.2015
comment
Не будет бокса, если вы назовете его как Hash (1), а не как Hash ‹MyInterface› (myStruct). stackoverflow.com/questions/8823239 - person user764754; 11.04.2016

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

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

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

person Bert Huijben    schedule 23.02.2009
comment
В большинстве случаев, когда Equals () сравнивает несколько полей, на самом деле не имеет значения, хеширует ваш GetHash () в одном поле или во многих. Это опасный совет, потому что для объектов, которые отличаются только нехешированными полями, вы получите коллизии хешей. Если это происходит часто, производительность коллекций на основе хешей (HashMap, HashSet и т. Д.) Будет снижаться (до O (n) в худшем случае). - person sleske; 15.04.2010
comment
На самом деле это произошло в Java: в ранних версиях JDK String.hashCode () рассматривал только начало строки; это привело к проблемам с производительностью, если вы использовали строки в качестве ключей в HashMaps, которые различались только в конце (что является обычным, например, для URL-адресов). Поэтому алгоритм был изменен (я полагаю, в JDK 1.2 или 1.3). - person sleske; 15.04.2010
comment
Если это одно поле «обеспечивает хорошее распределение» (последняя часть моего ответа), тогда одного поля достаточно. Если оно не обеспечивает хорошее распределение, тогда (и только тогда) вам нужно другой расчет. (Например, просто используйте другое поле, которое обеспечивает хорошее распределение, или используйте несколько полей) - person Bert Huijben; 16.04.2010
comment
Я не думаю, что есть проблема с GetHashCode распределением памяти, при условии, что это происходит только при первом использовании (с последующими вызовами, просто возвращающими кешированный результат). Важно не то, что нужно делать все возможное, чтобы избежать столкновений, а, скорее, нужно избегать системных столкновений. Если тип имеет два int поля, oldX и newX, которые часто отличаются на единицу, хеш-значение oldX^newX назначит 90% таких записей хеш-значений 1, 2, 4 или 8. Использование oldX+newX [непроверенная арифметика] может привести к большему количеству коллизий. ... - person supercat; 08.09.2013
comment
... чем более сложная функция, но набор из 1 000 000 вещей, которые имеют 500 000 различных значений хеш-функции, будет очень хорошо, если каждое значение хеш-функции имеет две связанные вещи, и очень плохо, если одно значение хеш-функции имеет 500 001 вещь, а другие - по одной. - person supercat; 08.09.2013

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

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

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

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

И что ж, меня беспокоит, насколько 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();
}

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

person Jon Hanna    schedule 14.01.2014
comment
При объединении нескольких хеш-значений в одно я обычно использую long значения для промежуточных результатов, а затем уменьшаю окончательный результат до int. Это кажется хорошей идеей? Меня беспокоит то, что один из них использует, например, hash = (hash * 31) + nextField, то пары совпадающих значений будут влиять только на верхние 27 бит хеша. Если позволить вычислению распространиться на long и обернуть в него материал, можно свести к минимуму эту опасность. - person supercat; 25.04.2014
comment
@supercat это зависит от того, как вы распределили последний раз. Библиотека SpookilySharp в идеале должна гарантировать хорошее распределение (потому что не требуется создание объекта) путем передачи указателя на непреобразуемый тип или передачи одного из перечисляемых элементов, которые он обрабатывает напрямую, но если у вас еще нет непреобразуемого объекта data или подходящее перечисление, а затем вызов .Update() с несколькими значениями в соответствии с ответом выше сделает трюк. - person Jon Hanna; 25.04.2014
comment
@JonHanna, не могли бы вы быть более точными в отношении проблемного поведения, с которым вы столкнулись? Я пытаюсь реализовать библиотеку, которая упрощает реализацию объектов значений (ValueUtils), и мне бы очень хотелось набор тестов, демонстрирующий плохую смешиваемость хешей в хэш-таблицах степени двойки. - person Eamon Nerbonne; 01.06.2014
comment
@EamonNerbonne У меня нет ничего более точного, чем то, что общее время было медленнее. Как я добавил при редактировании, тот факт, что я использовал открытую адресацию, мог быть более важным, чем фактор степени двойки. Я действительно планирую провести несколько тестовых примеров в конкретном проекте, где я буду сравнивать несколько разных подходов, поэтому у меня может быть лучший ответ для вас после этого, хотя это не является приоритетным (личный проект без насущной необходимости , так что я доберусь до него, когда доберусь до него ...) - person Jon Hanna; 02.06.2014
comment
@JonHanna: да, я знаю, как проходит личный график проекта - удачи! В любом случае, я вижу, что неправильно сформулировал этот последний комментарий: я хотел спросить о проблемных комментариях, а не обязательно о деталях возникших проблем. Я бы хотел использовать это в качестве тестового набора (или вдохновения для тестового набора). В любом случае - удачи в любимом проекте :-). - person Eamon Nerbonne; 02.06.2014
comment
Готов поспорить, что ваш ReHash - большой перебор. Я думаю, он работает хорошо, но может быть даже медленнее, чем криптографический хеш, который (вроде) доказал свою безупречную работу. Java также использует таблицы размера двойки, и раньше это было довольно сложное повторное хеширование. Он упростился, так как были введены узлы дерева для коллизий. - person maaartinus; 12.11.2017
comment
@maaartinus с точки зрения скорости и распространения хорошо продемонстрировал. Я сейчас придерживаюсь мнения, что для небольших ценностей это больше проблем, чем того стоит. Я бы по-прежнему использовал более полную реализацию SpookyHash, когда дело доходит до хеширования очень больших значений, таких как содержимое файла. - person Jon Hanna; 12.11.2017

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

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

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

person James Ko    schedule 23.11.2017
comment
Похоже на приятное дополнение ... есть ли способ узнать, какая версия .NET Core будет поставляться? - person Dan J; 14.12.2017
comment
@DanJ Какое счастливое совпадение, HashCode изменения для corefx были объединены всего за пару часов до вашего комментария :) Этот тип планируется выпустить в .NET Core 2.1. - person James Ko; 14.12.2017
comment
Это потрясающе - и довольно много времени на обработку. Проголосовали. :) - person Dan J; 14.12.2017
comment
@DanJ Еще лучшие новости - он должен быть доступен прямо сейчас в ночных сборках CoreFX, размещенных в ленте MyGet ядра dotnet. - person James Ko; 16.12.2017
comment
Милый - это не помогает мне в работе, поскольку мы не так на переднем крае, но хорошо об этом знать. Ваше здоровье! - person Dan J; 18.12.2017
comment
Вот вставляемый пакет полифилов, который можно использовать для всего. .NET 4.0+ (включая System.HashCode): nuget.org/packages/Gapotchenko.FX - person ogggre; 30.03.2019

Это хороший:

/// <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);
    }
}
person Magnus    schedule 07.10.2010
comment
Как определяются ключи? GetHashCode () не принимает никаких параметров, поэтому ему необходимо вызвать его с двумя ключами, которые нужно как-то определить. Извините, без дополнительных объяснений это только выглядит умно, но не так хорошо. - person Michael Stum; 07.10.2010
comment
А зачем вам общие перегрузки? Тип не важен (и не используется в вашем коде), поскольку все объекты имеют метод GetHashCode(), поэтому вы всегда можете использовать метод с параметром массива params. Или мне что-то здесь не хватает? - person gehho; 08.10.2010
comment
Речь идет о производительности, избегайте цикла для меньших ‹= 4 полей. Но я предполагаю, что дженерики можно пропустить и вместо этого просто использовать объект. - person Magnus; 08.10.2010
comment
Когда вы используете объект вместо дженериков, вы получаете боксы и выделения памяти, которые вам не нужны в GetHashCode. Так что дженерики - это то, что нужно. - person CodesInChaos; 22.11.2010
comment
Завершающие шаги shift / xor (h += (h << 10); h ^= (h >> 6); h += (h << 3); h ^= (h >> 11); h += (h << 15); имеют кодовый запах: они не зависят от каких-либо входных данных и кажутся мне ужасно избыточными. - person sehe; 22.04.2011
comment
@nawfal какие у вас соображения по скорости? - person Magnus; 24.12.2012
comment
@Magnus ничего особенного, кроме общего правила, что хеширование должно быть быстрым. Это не может быть так быстро, как мне бы хотелось. Но, как я уже сказал, это дает лучшее распределение значений, которое может быть подходящим для некоторых случаев. - person nawfal; 25.12.2012
comment
@nawfal Выполнение этого 100 миллионов раз занимает около 390 мс. Выполнение решения, предложенного Джоном Скитом, 100 миллионов раз занимает около 320 мс, так что это не большая разница. - person Magnus; 25.12.2012
comment
@Magnus да ладно, я удалю свой исходный комментарий. Небольшое замечание, что это может быть не так быстро, как некоторые другие решения здесь, но, как вы говорите, не должно иметь значения. Распределение отличное, лучше, чем у большинства решений здесь, так что +1 от меня! :) - person nawfal; 25.12.2012
comment
Как это соотносится по качеству (распределению) и производительности с простым использованием long промежуточного звена с умножением каждого ввода на большое простое число? Например. для двух значений, что-то вроде этого one liner: return ((long)v1 * 805306457 + (long)v2 * 189783887).GetHashCode(); [Простые числа выбираются, чтобы избежать числового переполнения long в проверяемой среде и иметь тенденцию устанавливать разные биты.] - person ToolmakerSteve; 01.03.2018

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

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

person Scott Wegner    schedule 20.01.2014

Вот мой упрощенный подход. Для этого я использую классический шаблон строителя. Он безопасен по типу (без упаковки / распаковки), а также совместим с .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);
        }
    }
}
person bitbonk    schedule 22.03.2011
comment
вы можете избежать создания объекта внутри функции gethashcode, как в ответе Мангуса. Просто вызовите чертовы статические хеш-функции (кого волнует стартовый хеш). Кроме того, вы можете чаще использовать AddItems<T>(params T[] items) метод во вспомогательном классе (чем каждый раз вызывать AddItem(T)). - person nawfal; 14.04.2013
comment
И какую пользу вы получаете от this.result * Prime2 * item.GetHashCode(), когда часто используется this.result * Prime2 + item.GetHashCode()? - person nawfal; 14.04.2013
comment
Я не могу использовать AddItems<T>(params T[] items) чаще, потому что typeof(T1) != typeof(T2) и т. Д. - person bitbonk; 15.04.2013

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

ValueTuple - это структура и, похоже, имеет надежную GetHashCode реализацию.

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

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

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

Это из _6 _:

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

И это из _8 _ :

    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 со случайным семенем. Я не знаю, каковы будут последствия, если мы пропустим этот шаг.

person Timo    schedule 15.05.2018
comment
Предполагая, что h1 >> 27 равно 0, чтобы игнорировать его, h1 << 5 равно h1 * 32, поэтому он такой же, как h1 * 33 ^ h2. Согласно эта страница, она называется Модифицированной страницей Бернштейна. - person cactuaroid; 17.08.2018

Пользователи 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;
    }
}
person Charles Burns    schedule 01.09.2016

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

// Unique ID from database
private int _id;

...    
{
  return _id.GetHashCode();
}
person Mark G    schedule 05.11.2008
comment
Это означает, что если у вас есть объекты Person и Account, и у них обоих есть ID = 1, они будут иметь одинаковый хэш-код. А это не нормально. - person pero; 22.03.2010
comment
На самом деле комментарий выше неверен. Всегда будет возможность коллизии хэш-кода (хеш-код определяет местонахождение только корзины, а не отдельного объекта). Таким образом, такая реализация - для хэш-кода, содержащего смешанные объекты - привела бы к множеству коллизий, что нежелательно, но было бы абсолютно нормально, если бы у вас когда-либо были объекты только одного типа в ваших хэш-таблицах. Кроме того, он не распределяется равномерно, однако и базовая реализация на system.object, поэтому я бы не стал слишком беспокоиться об этом ... - person piers7; 29.03.2010
comment
Хэш-код может быть просто идентификатором, поскольку идентификатор является целым числом. Нет необходимости вызывать GetHashCode для целого числа (это функция идентификации) - person Darrel Lee; 23.11.2012
comment
@DarrelLee, но его _id может быть гидом. Хорошая практика кодирования - делать _id.GetHashCode, поскольку цель ясна. - person nawfal; 14.04.2013
comment
@DarrelLee, это не лучший вариант, потому что последовательные идентификаторы из базы данных не обеспечивают хорошего распределения - person Trident D'Gao; 29.06.2013
comment
@ 1224, в зависимости от шаблонов использования, это может быть ужасно по той причине, которую вы указываете, но также может быть и великолепно; если у вас есть последовательность таких чисел без дырок, то у вас идеальный хеш, лучший, чем может произвести любой алгоритм. Если вы знаете, что это так, вы даже можете рассчитывать на это и пропустить проверку на равенство. - person Jon Hanna; 14.01.2014

Очень похоже на решение 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;
        }
    }
}
person Dbl    schedule 21.10.2014
comment
Не обрабатывает значения NULL. - person JJS; 27.12.2016

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;

То же самое и с несколькими типами: все сначала конвертируются в int с использованием GetHashCode(), затем значения int будут xor'ed, и результатом будет ваш хеш.

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

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

person deadManN    schedule 30.11.2012
comment
Иксоринг целых чисел для создания хэш-кода - хорошо известный антипаттерн, который имеет тенденцию приводить к особенно большому количеству конфликтов с реальными значениями. - person Jon Hanna; 14.01.2014
comment
Все здесь используют целые числа, и никогда не было никакой гарантии того, что хеш будет таким же, он просто попытался быть настолько разнообразным, насколько мало может произойти коллизий. - person deadManN; 16.09.2015
comment
Да, но ваши второй и пятый не пытаются избежать столкновений. - person Jon Hanna; 16.09.2015
comment
Не уверен, что это за поток ... но он сделал то же самое, msdn.microsoft.com/en-us/library/ - person deadManN; 19.09.2015
comment
Да, этот антипаттерн довольно распространен. - person Jon Hanna; 19.09.2015
comment
вот почему я полагаюсь на это, но спасибо за облегчение ... другое дело в том, что у другого шаблона меньше времени на расчет? вы знаете, вроде, collision vs calculation time материя тоже есть - person deadManN; 20.09.2015
comment
Необходимо достичь баланса. Используйте действительно хороший хэш-код, такой как Spookyhash, и вы получите намного, намного лучшее предотвращение столкновений, но у него будет гораздо больше времени на вычисление, чем у любого из них (но когда дело доходит до хеширования очень больших объемов данных, Spookyhash очень быстр). Простой сдвиг одного из значений перед xoring - это лишь незначительные дополнительные затраты для хорошего снижения коллизии. Умножение на простые числа снова увеличивает время и качество. Следовательно, вопрос о том, что лучше между shift или mult, остается спорным. Обычный xor, хотя очень часто имеет много конфликтов с реальными данными, и его лучше избегать - person Jon Hanna; 20.09.2015

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

Этот тест не проходит (плавает; хэш тот же, хотя я переключил 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));

Но этот тест проходит (с целыми числами):

        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;
                }
        }
    }
person HokieMike    schedule 28.09.2014
comment
Если вы задумали иначе, unchecked НЕ влияет на Convert.ToInt32: uint, long, float, double и decimal могут здесь переполниться. - person Mark Hurd; 30.09.2014

Это статический вспомогательный класс, реализующий реализацию Джоша Блоха; и предоставляет явные перегрузки для «предотвращения» упаковки, а также для реализации хеширования специально для длинных примитивов.

Вы можете передать сравнение строк, которое соответствует вашей реализации equals.

Поскольку вывод Hash всегда является int, вы можете просто связать вызовы 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;
        }
    }
}
person Steven Coco    schedule 28.04.2019
comment
Ура: Я нашел ошибку! Исправлен метод HashKeysAndValues: он вызывает HashKeyAndValue. - person Steven Coco; 09.05.2019

Если вы хотите выполнить полифил HashCode из 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;
    }
}

Примечание. При использовании с struct память будет выделяться из-за бокса.

person Ivan Sanz-Carasa    schedule 20.04.2020

Можно попробовать перенять подход из библиотек C ++ Boost. Что-то вроде этого:

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

а потом:

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());
  }
}
person ivan.ukr    schedule 25.01.2021

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

Моя текущая настройка визуальной студии / проекта обеспечивает функциональность для автоматического преобразования кортежей в структуры. Это сгенерирует такую ​​функцию GetHashCode:

        public override int GetHashCode()
        {
            int hashCode = -2088324004;
            hashCode = hashCode * -1521134295 + AuftragGesperrt.GetHashCode();
            hashCode = hashCode * -1521134295 + Auftrag_gesperrt_von.GetHashCode();
            hashCode = hashCode * -1521134295 + Auftrag_gesperrt_am.GetHashCode();
            return hashCode;
        }
person t0b4cc0    schedule 18.02.2021