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());
}
}
何必再纠结另一个答案?
如果列表中有多个具有相同哈希码的项目,accepted answer 可能会给出危险的不准确结果。例如考虑这些输入:
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 都是相同的。为什么?
如果你将一个数字与它本身进行异或,你基本上将它取消,即
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 都会自行抵消。