TL; DR
public static int GetSequenceHashCode<T>(this IList<T> sequence)
{
const int seed = 487;
const int modifier = 31;
unchecked
{
return sequence.Aggregate(seed, (current, item) =>
(current*modifier) + item.GetHashCode());
}
}
Зачем беспокоиться о другом ответе?
Принятый ответ может дать опасно неточные результаты, если в списке несколько элементов с одинаковым хеш-кодом. Например, рассмотрим эти входные данные:
var a = new []{ "foo" };
var b = new []{ "foo", "bar" };
var c = new []{ "foo", "bar", "spam" };
var d = new []{ "seenoevil", "hearnoevil", "speaknoevil" };
Все они дают разные результаты, предполагая, что все они являются уникальными коллекциями. Большой! Теперь давайте попробуем с дубликатом:
var e = new []{ "foo", "bar", "spam" };
GetSequenceHashCode
должен давать одинаковый результат для c
и e
- и это так. Все идет нормально. Теперь давайте попробуем с элементами вне последовательности:
var f = new []{ "spam", "bar", "foo" };
Э-э-э ... GetSequenceHashCode
означает, что f
равно как c
, так и e
, что не равно. Почему это происходит? Сначала разбейте его на фактические значения хеш-кода, используя в качестве примера c
:
int hashC = "foo".GetHashCode() ^
"bar".GetHashCode() ^
"spam".GetHashCode();
Поскольку точные числа здесь не очень важны, и для большей наглядности давайте представим, что хэш-коды трех строк - foo=8
, bar=16
и spam=32
. Итак:
int hashC = 8 ^ 16 ^ 32;
или разбить его на двоичное представление:
8 ^ 16 ^ 32 == 56;
// 8 = 00001000
// ^
// 16 = 00010000
// ^
// 32 = 00100000
// =
// 56 00111000
Теперь вы должны увидеть, почему порядок реализации элементов в списке игнорируется этой реализацией, т.е. 8^16^32 = 16^8^32 = 32^16^8
и т. Д.
Во-вторых, есть проблема с дубликатами. Даже если вы предполагаете, что иметь одинаковое содержимое в другой последовательности - это нормально (я бы не поощрял такой подход), я не думаю, что кто-то будет утверждать, что приведенное ниже поведение желательно. Давайте попробуем варианты с дубликатами в каждом списке.
var a = new []{ "foo", "bar", "spam" };
var b = new []{ "foo", "bar", "spam", "foo" };
var c = new []{ "foo", "bar", "spam", "foo", "foo" };
var d = new []{ "foo", "bar", "spam", "foo", "foo", "spam", "foo", "spam", "foo" };
Хотя a
и b
генерируют различные хэши последовательностей, GetSequenceHashCode
предполагает, что a
, c
и d
одинаковы. Зачем?
Если вы XOR номер с самим собой, вы по существу отменяете его, т. Е.
8 ^ 8 == 0;
// 8 = 00001000
// ^
// 8 = 00001000
// =
// 0 = 00000000
XOR с тем же номером снова дает вам исходный результат, то есть
8 ^ 8 ^ 8 == 8;
// 8 = 00001000
// ^
// 8 = 00001000
// ^
// 8 = 00001000
// =
// 8 = 00001000
Итак, если мы посмотрим на a
и c
снова, подставив упрощенные хеш-коды:
var a = new []{ 8, 16, 32 };
var c = new []{ 8, 16, 32, 8, 8 };
хеш-коды рассчитываются как:
int hashA = 8 ^ 16 ^ 32; // = 56
int hashC = 8 ^ 16 ^ 32 ^ 8 ^ 8; // = 56
// ↑ ↑
// these two cancel each other out
и аналогично с d
, где каждая пара foo
и spam
обнуляется.