鉴于以下课程
public class Foo { public int FooId { get; set; } public string FooName { get; set; } public override bool Equals(object obj) { Foo fooItem = obj as Foo; if (fooItem == null) { return false; } return fooItem.FooId == this.FooId; } public override int GetHashCode() { // Which is preferred? return base.GetHashCode(); //return this.FooId.GetHashCode(); } }
我已经覆盖了该Equals
方法,因为它Foo
代表了Foo
s表的一行.哪个是覆盖的首选方法GetHashCode
?
覆盖为什么重要GetHashCode
?
是的,重要的是,您的项目将用作字典中的键,或者HashSet
等等 - 因为这是用于(在没有自定义的情况下IEqualityComparer
)将项目分组到存储桶中.如果两个项的哈希码不匹配,它们可能永远不会被认为是相等的(Equals
将永远不会被调用).
该GetHashCode()
方法应反映Equals
逻辑; 规则是:
如果两个东西相等(Equals(...) == true
)那么它们必须返回相同的值GetHashCode()
如果GetHashCode()
是相等的,它是不必要对他们是相同的; 这是一次碰撞,Equals
将被调用以查看它是否是真正的平等.
在这种情况下,看起来" return FooId;
"是一个合适的GetHashCode()
实现.如果您正在测试多个属性,通常使用下面的代码组合它们,以减少对角线冲突(即,new Foo(3,5)
具有不同的哈希码new Foo(5,3)
):
unchecked // only needed if you're compiling with arithmetic checks enabled { // (the default compiler behaviour is *disabled*, so most folks won't need this) int hash = 13; hash = (hash * 7) + field1.GetHashCode(); hash = (hash * 7) + field2.GetHashCode(); ... return hash; }
哦 - 为了方便起见,你也可以考虑提供==
和!=
操作员覆盖Equals
和GetHashCode
.
当你弄错了会发生什么事的证明就在这里.
实际上很难GetHashCode()
正确实现,因为除了Marc已经提到的规则之外,哈希代码在对象的生命周期内不应该改变.因此,用于计算哈希码的字段必须是不可变的.
当我使用NHibernate时,我终于找到了解决这个问题的方法.我的方法是从对象的ID计算哈希码.只能通过构造函数设置ID,因此如果要更改ID,这是非常不可能的,您必须创建一个具有新ID的新对象,因此需要新的哈希代码.这种方法最适用于GUID,因为您可以提供随机生成ID的无参数构造函数.
通过重写Equals,您基本上声明自己是更了解如何比较给定类型的两个实例的人,因此您很可能是提供最佳哈希码的最佳候选者.
这是ReSharper如何为您编写GetHashCode()函数的示例:
public override int GetHashCode() { unchecked { var result = 0; result = (result * 397) ^ m_someVar1; result = (result * 397) ^ m_someVar2; result = (result * 397) ^ m_someVar3; result = (result * 397) ^ m_someVar4; return result; } }
正如您所看到的,它只是尝试根据类中的所有字段猜测一个好的哈希代码,但由于您知道对象的域或值范围,您仍然可以提供更好的哈希代码.
请不要忘记null
在覆盖时检查obj参数Equals()
.并且还比较了类型.
public override bool Equals(object obj) { Foo fooItem = obj as Foo; if (fooItem == null) { return false; } return fooItem.FooId == this.FooId; }
原因是:Equals
必须在比较时返回false null
.另请参见http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx
怎么样:
public override int GetHashCode() { return string.Format("{0}_{1}_{2}", prop1, prop2, prop3).GetHashCode(); }
假设性能不是问题:)
我们有两个问题需要解决.
GetHashCode()
如果可以更改对象中的任何字段,则无法提供合理的信息.通常,对象永远不会用在依赖于的集合中 GetHashCode()
.因此,实施的成本GetHashCode()
通常是不值得的,或者是不可能的.
如果有人将您的对象放入一个调用的集合中
GetHashCode()
并且您已经覆盖Equals()
而没有
GetHashCode()
以正确的方式表现,那么该人可能需要花费数天来追踪问题.
所以默认我这样做.
public class Foo { public int FooId { get; set; } public string FooName { get; set; } public override bool Equals(object obj) { Foo fooItem = obj as Foo; if (fooItem == null) { return false; } return fooItem.FooId == this.FooId; } public override int GetHashCode() { // Some comment to explain if there is a real problem with providing GetHashCode() // or if I just don't see a need for it for the given class throw new Exception("Sorry I don't know what GetHashCode should do for this class"); } }
这是因为框架要求两个相同的对象必须具有相同的哈希码.如果重写equals方法对两个对象进行特殊比较,并且方法认为两个对象相同,则两个对象的哈希码也必须相同.(字典和Hashtables依赖于这个原则).
只是添加以上答案:
如果不重写等于,则默认行为是比较对象的引用.这同样适用于hashcode - 默认的implmentation通常基于引用的内存地址.因为你确实覆盖了Equals,所以它意味着正确的行为是比较你在Equals而不是引用上实现的任何东西,所以你应该对hashcode做同样的事情.
您的类的客户端将期望哈希码具有与equals方法类似的逻辑,例如使用IEqualityComparer的linq方法首先比较哈希码,并且只有当它们相等时它们才会比较可能更昂贵的Equals()方法要运行,如果我们没有实现hashcode,那么equ对象可能会有不同的hashcode(因为它们有不同的内存地址),并且会被错误地判断为不相等(Equals()甚至不会命中).
此外,除了您在字典中使用它时可能无法找到对象的问题(因为它是由一个哈希码插入的,当您查找它时,默认的哈希码可能会有所不同,而且Equals()甚至不会被调用,就像Marc Gravell在他的回答中解释的那样,你也引入了违反字典或hashset概念的行为,它不应该允许相同的键 - 你已经声明当你覆盖Equals时这些对象基本相同所以你不要不希望它们都是数据结构上的不同键,它们假设有一个唯一键.但是因为它们有不同的哈希码,所以"相同"键将作为不同的键插入.
散列代码用于基于散列的集合,如Dictionary,Hashtable,HashSet等.此代码的目的是通过将特定对象放入特定组(存储桶)来非常快速地对其进行预排序.当您需要从哈希集合中检索此对象时,这种预排序有助于找到此对象,因为代码必须仅在一个存储桶中而不是在其包含的所有对象中搜索您的对象.哈希码的更好分布(更好的唯一性)更快的检索.在每个对象具有唯一哈希码的理想情况下,找到它是O(1)操作.在大多数情况下,它接近O(1).
它不一定重要;它取决于集合的大小和性能要求,以及是否在可能不知道性能要求的库中使用您的类。我经常知道我的集合大小不是很大,并且我的时间比通过创建完美的哈希码获得几微秒的性能更有价值。因此(为了摆脱编译器的烦人警告),我只需使用:
public override int GetHashCode() { return base.GetHashCode(); }
(当然,我也可以使用#pragma来关闭警告,但我更喜欢这种方式。)
当然,当您确实需要表现时,这里其他人提到的所有问题都适用。最重要的是 -否则从哈希集或字典中检索项目时,您会得到错误的结果:哈希码不得随对象的生存时间而变化(更准确地说,在需要哈希码的时间段内,例如字典中的键):例如,以下错误是由于Value是公共的,因此可以在实例的生存期内在类的外部进行更改,因此您不得将其用作哈希代码的基础:
class A { public int Value; public override int GetHashCode() { return Value.GetHashCode(); //WRONG! Value is not constant during the instance's life time } }
另一方面,如果无法更改Value,则可以使用:
class A { public readonly int Value; public override int GetHashCode() { return Value.GetHashCode(); //OK Value is read-only and can't be changed during the instance's life time } }