关于如何正确覆盖object.GetHashCode()的一般建议和指南

时间:2009-09-04 11:28:21

标签: c# .net hashcode gethashcode

根据MSDN,哈希函数必须具有以下属性:

  
      
  1. 如果两个对象比较相等,则每个对象的GetHashCode方法必须返回相同的值。但是,如果两个对象的比较不相等,则两个对象的GetHashCode方法不必返回不同的值。

  2.   
  3. 对象的GetHashCode方法必须始终返回相同的哈希码,只要对对象状态没有修改即可确定对象的Equals方法的返回值。请注意,这仅适用于当前应用程序的执行,并且如果再次运行应用程序,则可以返回不同的哈希代码。

  4.   
  5. 为获得最佳性能,哈希函数必须为所有输入生成随机分布。

  6.   

我一直在以下场景中找到自己:我创建了一个类,实现了IEquatable<T>并覆盖了object.Equals(object)MSDN声明:

  

覆盖Equals的类型也必须覆盖GetHashCode;否则,Hashtable可能无法正常工作。

然后它通常会让我停下来。因为,您如何正确覆盖object.GetHashCode()?从来没有真正知道从哪里开始,这似乎是很多陷阱。

在StackOverflow中,有很多与GetHashCode重写有关的问题,但大多数问题似乎都是针对非常特殊的情况和具体问题。因此,我想在这里得到一个很好的汇编。概述与一般建议和指南。该怎么做,什么不该做,常见的陷阱,从哪里开始等等。

我希望它特别针对C#,但我认为它对其他.NET语言也有同样的作用(?)。


我想也许最好的方法是先为每个主题创建一个答案,先用快速简短的答案(如果可能的话尽量接近一行),然后再提供更多信息并以相关问题结束,讨论,博客文章等,如果有的话。然后,我可以创建一个帖子作为接受的答案(将其置于顶部),只需一个“目录”。尽量保持简洁明了。而且不要只链接到其他问题和博客文章。尝试采用它们的本质然后链接到源(特别是因为源可能会消失。另外,请尝试编辑和改进答案,而不是创建许多非常相似的答案。

我不是一个非常优秀的技术作家,但我至少会尝试格式化答案,使它们看起来相似,创建目录等。我也会尝试在这里搜索一些相关的问题在SO处回答其中的一部分,并且可能拉出我能管理的那些的本质。但由于我在这个主题上不是很稳定,所以我会尽量远离这个主题:p

11 个答案:

答案 0 :(得分:9)

目录


我希望涵盖的内容,但尚未完成:

  • 如何创建整数(如何将对象“转换”为int对我来说不是很明显)。
  • 基于哈希码的字段。
    • 如果它只应该在不可变字段上,那么如果只有可变字段呢?
  • 如何生成良好的随机分布。 (MSDN Property#3)
    • 部分到此,似乎选择了一个好的魔术素数(已经看过使用了17,23和397),但是你如何选择它,它究竟是什么呢?
  • 如何确保哈希码在整个对象生存期内保持不变。 (MSDN Property#2)
    • 特别是当相等基于可变字段时。 (MSDN Property#1)
  • 如何处理复杂类型的字段(不在built-in C# types中)。
    • 复杂对象和结构,数组,集合,列表,字典,泛型类型等。
    • 例如,即使列表或字典可能是只读的,但这并不意味着它的内容。
  • 如何处理继承的类。
    • 您是否应该以某种方式将base.GetHashCode()合并到您的哈希码中?
  • 你在技术上可能只是懒惰并返回0吗?将严重打破MSDN指南#3,但至少会确保#1和#2始终为真:P
  • 常见的陷阱和陷阱。

答案 1 :(得分:7)

在GetHashCode实现中常见的那些神奇数字是什么?

他们是素数。素数用于创建哈希码,因为素数最大化了哈希码空间的使用。

具体来说,从小素数3开始,只考虑结果的低阶nybbles

  • 3 * 1 = 3 = 3(mod 8)= 0011
  • 3 * 2 = 6 = 6(mod 8)= 1010
  • 3 * 3 = 9 = 1(mod 8)= 0001
  • 3 * 4 = 12 = 4(mod 8)= 1000
  • 3 * 5 = 15 = 7(mod 8)= 1111
  • 3 * 6 = 18 = 2(mod 8)= 0010
  • 3 * 7 = 21 = 5(mod 8)= 1001
  • 3 * 8 = 24 = 0(mod 8)= 0000
  • 3 * 9 = 27 = 3(mod 8)= 0011

我们重新开始。但是你会注意到,在开始重复之前,我们的素数的连续倍数在我们的nybble中生成了每个可能的位排列。我们可以使用任何素数和任意数量的位获得相同的效果,这使得素数最适合生成近似随机哈希码。我们通常在上面的例子中看到较大的素数而不是像3这样的小素数的原因是,对于哈希码中更大的比特数,使用小素数得到的结果甚至不是伪随机的 - 它们只是一个增加序列直到遇到溢出。为了获得最佳随机性,应使用导致相当小系数溢出的素数,除非您可以保证系数不会很小。

相关链接:

答案 2 :(得分:3)

答案 3 :(得分:2)

只要对该类型的对象有一个有意义的相等度量(即重写等于),就应该覆盖它。如果你知道对象不会因为任何原因而被删除,你可以离开它,但你不可能提前知道这一点。

哈希应该仅基于用于定义相等性的对象的属性,因为被认为相等的两个对象应该具有相同的哈希码。通常,您通常会执行以下操作:


public override int GetHashCode()
{
    int mc = //magic constant, usually some prime
    return mc * prop1.GetHashCode() * prop2.GetHashCode * ... * propN.GetHashCode();
}

我通常假设将值相乘将产生相当均匀的分布,假设每个属性的哈希码函数都是相同的,尽管这可能是错误的。使用此方法,如果对象的相等定义属性发生更改,则哈希代码也可能会更改,这在您的问题中定义#2时是可接受的。它还以统一的方式处理所有类型。

您可以为所有实例返回相同的值,但这会使任何使用散列的算法(例如dictionarys)非常慢 - 基本上所有实例都将被散列到同一个存储桶,然后查找将变为O(n)预期的O(1)。这当然否定了使用这种结构进行查找的任何好处。

答案 4 :(得分:2)

为什么我必须覆盖object.GetHashCode()

重写此方法很重要,因为以下属性必须始终为true:

  

如果两个对象比较相等,则每个对象的GetHashCode方法必须返回相同的值。

JaredParblog post中关于实施平等的原因是

  

许多类使用哈希代码对对象进行分类。特别是哈希表和字典倾向于根据哈希代码将对象放在存储桶中。当检查对象是否已经在哈希表中时,它将首先在桶中查找它。如果两个对象相等但具有不同的哈希码,则它们可能被放入不同的桶中,而字典将无法查找该对象。

相关链接:

答案 5 :(得分:2)

A)如果要使用值相等而不是默认引用相等,则必须覆盖Equals和GetHashCode。对于后者,如果它们都引用相同的对象实例,则两个对象引用相等。如果它们的值相同,即使它们引用不同的对象,它们与前者相比也是相等的。例如,您可能希望为Date,Money和Point对象使用值相等。

B)为了实现值相等,您必须重写Equals和GetHashCode。两者都应该取决于封装该值的对象的字段。例如,Date.Year,Date.Month和Date.Day;或Money.Currency和Money.Amount;或Point.X,Point.Y和Point.Z。您还应该考虑重写operator ==,operator!=,operator&lt;和operator&gt;。

C)哈希码不必在整个对象生存期内保持不变。但是,当它作为哈希中的键参与时,它必须保持不可变。从MSDN doco for Dictionary:“只要一个对象被用作Dictionary&lt;(Of&lt;(TKey,TValue&gt;)&gt;)中的一个键,它就不能以任何影响其哈希值的方式改变。”如果必须更改密钥的值,请从字典中删除条目,更改密钥值,然后替换该条目。

D)IMO,如果你的价值对象本身是不可变的,你将简化你的生活。

答案 6 :(得分:1)

答案 7 :(得分:0)

何时覆盖object.GetHashCode()

正如MSDN所述:

  

覆盖Equals的类型也必须覆盖GetHashCode;否则,Hashtable可能无法正常工作。

相关链接:

答案 8 :(得分:0)

基于哈希码的字段是什么?如果它只应该在不可变字段上,如果只有可变字段会怎么样?

它不需要仅基于不可变字段。我会把它建立在确定equals方法结果的字段上。

答案 9 :(得分:0)

如何确保哈希码在整个对象生存期内保持不变。 (MSDN属性#2)特别是当相等性基于可变字段时。 (MSDN Property#1)

你似乎误解了物业#2。在对象生存期内,哈希码不需要保持不变。只要确定equals方法结果的值不变,它就需要保持不变。因此,逻辑上,您只将哈希码基于这些值。那应该不会有问题。

答案 10 :(得分:-2)

public override int GetHashCode()
{
    return IntProp1 ^ IntProp2 ^ StrProp3.GetHashCode() ^ StrProp4.GetHashCode ^ CustomClassProp.GetHashCode;
}

在customClass的GetHasCode方法中执行相同操作。像魅力一样。