覆盖GetHashCode()

在本文中 ,Jon Skeet提到他通常使用这种算法来覆盖GetHashCode()

public override int GetHashCode() { unchecked // Overflow is fine, just wrap { int hash = 17; // Suitable nullity checks etc, of course :) hash = hash * 23 + Id.GetHashCode(); return hash; } } 

现在,我已经尝试过使用它了,但是Resharper告诉我,方法GetHashCode()应该只使用只读字段进行散列(尽管它编译得很好)。 什么是一个好的做法,因为现在我不能真正让我的字段是只读的?

我尝试通过Resharper生成这个方法,这是结果。

 public override int GetHashCode() { return base.GetHashCode(); } 

这没什么贡献,说实话……

如果您的所有字段都是可变的并且您必须实现GetHashCode方法,那么我担心这是您需要的实现。

 public override int GetHashCode() { return 1; } 

是的,这是低效的,但这至少是正确的。

问题是Dictionary和HashSet集合正在使用GetHashCode将每个项目放在存储桶中。 如果基于某些可变字段计算哈希码,并且在将对象放入HashSet或Dictionary后实际更改了字段,则无法再从HashSet或Dictionary中找到该对象。

请注意,对于返回相同HashCode 1的所有对象,这基本上意味着所有对象都放在HashSet或Dictionary中的同一个桶中。 因此,HashSet或Dictionary中始终只有一个存储桶。 在尝试查找对象时,它将对唯一存储桶中的每个对象执行相等性检查。 这就像在链表中进行搜索一样。

有人可能会争辩说,如果我们可以确保在将对象添加到HashCode或Dictionary集合后永远不会更改字段,那么基于可变字段实现哈希码就可以了。 我个人认为这很容易出错。 两年后接管你的代码的人可能不会意识到这一点并且意外地破坏了代码。

请注意,您的GetHashCode必须与您的Equals方法齐头并进。 如果你可以使用引用相等(当你的类中没有两个不同的实例可以相等时),那么你可以安全地使用inheritance自Object的Equals和GetHashCode。 这比仅仅从GetHashCode return 1要好得多。

我个人倾向于在没有不可变字段的类中为GetHashCode()每个实现返回不同的数值。 这意味着如果我有一个包含不同实现类型的字典,则有可能将不同类型的不同实例放在不同的存储区中。

例如

 public class A { // TODO Equals override public override int GetHashCode() { return 21313; } } public class B { // TODO Equals override public override int GetHashCode() { return 35507; } } 

然后,如果我有一个Dictionary包含AB和其他类型的实例,那么查找的性能将比GetHashCode所有实现返回相同的数值更好。

还应该注意的是,我使用素数来获得更好的分布。

根据评论我在这里提供了一个LINQPad示例,它演示了为不同类型使用return 1和为每种类型返回不同值之间的性能差异。