CSharp - 为什么当重写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;

 return fooItem.FooId == this.FooId;
 }

 public override int GetHashCode()
 {
//Which is preferred?

 return base.GetHashCode();

//return this.FooId.GetHashCode();
 }
}

我重写了Equals方法,因为Foo代表了Foos表的一行。 这是首选的重写GetHashCode的方法?

为什么重写GetHashCode很重要?

时间:

是的,这一点很重要,如果你的项目将被用作字典中的键,或者 HashSet<T> ( 在没有自定义 IEqualityComparer<T>的情况下) 等等- - 因为这是使用矩形将报表项组合到存储桶中。 如果两个项不匹配,他们可能的hash-code 从不与上述密封并不相同( Equals 将永远不会被调用) 。

GetHashCode() 方法应该反映 Equals 逻辑;规则是:

  • 如果两者相等于相关( Equals(...) == true ) 则它们必须返回相同的值
  • 如果 GetHashCode()的大小等于,它是不必要内容即可是相同的,这里是发生碰撞,并 Equals 将被调用以查看它是否是一个真正的相等与否。

在这种情况下," return FooId;"似乎是一个合适的GetHashCode() 实现。 如果你正在测试多个属性,通常使用如下代码组合它们,以减少对角线冲突( 例如 。 这样 new Foo(3,5) 就有了一个不同的hash-code:


int hash = 13;
hash = (hash * 7) + field1.GetHashCode();
hash = (hash * 7) + field2.GetHashCode();
...
return hash;

哦- 还为方便起见,你可能会考虑提供 ==!= 运算符重写 EqualsGetHashCode 时。


当你得到错误的时候,一个演示的结果是

实际上很难正确实现 GetHashCode(),因为除了Marc已经提到的规则,哈希代码在对象的生命周期中不应该改变。 因此,用于计算哈希代码的字段必须是不可变的。

我在使用 NHibernate 时终于找到了解决这个问题的解决方案。 我的方法是从对象的ID计算哈希代码。 标识只能设置为构造函数,所以如果你想更改标识,那么你必须创建一个新的对象,它有一个新的标识,因此一个新的哈希代码。 这种方法最适合于 guid,因为你可以提供一个无参数构造函数,它随机生成一个标识。

通过重写 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;
 }
 }

就像你所看到的,它只是试图基于类中的所有字段猜测一个好的哈希代码,但是由于你知道你的对象或者值范围的域,你仍然可以提供一个更好的散列。

怎么办?


 public override int GetHashCode()
 {
 return string.Format("{0}_{1}_{2}", prop1, prop2, prop3).GetHashCode();
 }

假设性能不是问题:)

并比较该类型。


public override bool Equals(object obj)
{
 if (obj == null || GetType()!= obj.GetType())
 return false;

 Foo fooItem = obj as Foo;

 return fooItem.FooId == this.FooId;
}

原因如下: 比较,以上 Equals 必须 另请参阅 http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx

这并不重要;它取决于你的集合大小和性能需求,以及你的类是否在你可能不知道性能需求的库中使用。 我经常知道我的收集大小不是很大,而且我的时间比通过创建完美的哈希代码获得的性能更高一些;所以( 消除编译器发出的烦人警告) 只使用:


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

( 当然,我也可以使用 #pragma 来关闭警告,但我更喜欢这样。)

当你在你的位置为止 需要这里的性能优于其他所提到所有已经解决的问题应用所做的,当然。 在一个散列集合或者 dictionary, 最重要- - 否则你将得到错误的结果检索items:时 一个对象的生存时间 的哈希代码一定不能 vary: 例如下面的值是错误的,因为值是公共的,所以可以在实例的生命周期内将它的更改为类,因此你不能将它的用作哈希代码的基础:



 class A
 {
 public int Value;

 public override int GetHashCode()
 {
 return Value.GetHashCode();//WRONG! Value is not constant during the instance's life time
 }
 } 

另一方面,如果不能更改值,可以使用:



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


...