C#中的GetHashCode指南

时间:2009-01-20 18:22:17

标签: c# .net hashcode

我在Essential C#3.0和.NET 3.5书中读到:

  

GetHashCode()的返回应该是特定对象的生命周期   常量(相同的值),即使对象的数据发生变化。在很多   在这种情况下,你应该缓存方法返回以强制执行此操作。

这是一个有效的指南吗?

我在.NET中尝试了几种内置类型,但它们的行为并不像这样。

9 个答案:

答案 0 :(得分:119)

答案 1 :(得分:89)

答案主要是,这是一个有效的指导方针,但可能不是一个有效的规则。它也没有说明整个故事。

对于可变类型,您不能将哈希代码基于可变数据,因为两个相等的对象必须返回相同的哈希代码,并且哈希代码必须在对象的生命周期内有效。如果哈希代码发生更改,您最终会得到一个在哈希集合中丢失的对象,因为它不再存在于正确的哈希框中。

例如,对象A返回1的散列。因此,它进入散列表的bin 1。然后你改变对象A使得它返回2的散列。当散列表寻找它时,它在bin 2中查找并且找不到它 - 该对象在bin 1中是孤立的。这就是散列码必须的原因。不要在对象的生命周期中更改 ,只是编写GetHashCode实现的一个原因是对接中的痛苦。

<强>更新
Eric Lippert has posted a blog提供有关GetHashCode的出色信息。

其他更新
我上面做了几处修改:

  1. 我对指南和规则作了区分。
  2. 我在“对象的生命周期”中表示。
  3. 指南只是一个指南,而不是规则。实际上,当事物期望对象遵循指南时,GetHashCode只需遵循这些准则,例如当它存储在哈希表中时。如果您从未打算在哈希表中使用您的对象(或任何依赖于GetHashCode规则的其他内容),那么您的实现不需要遵循这些准则。

    当您看到“对于对象的生命周期”时,您应该阅读“对象需要与哈希表协作的时间”或类似内容。像大多数事情一样,GetHashCode就是知道何时违反规则。

答案 2 :(得分:9)

来自MSDN

  

如果两个对象相等,那么   每个对象的GetHashCode方法   必须返回相同的值。然而,   如果两个对象不比较   等于,GetHashCode方法   两个对象不必返回   不同的价值观

     

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

     

为获得最佳性能,请使用哈希   函数必须生成随机   所有输入的分配。

这意味着如果对象的值发生变化,则哈希码应该更改。例如,将“Name”属性设置为“Tom”的“Person”类应该有一个哈希码,如果将名称更改为“Jerry”,则应该使用不同的代码。否则,汤姆==杰里,这可能不是你想要的。


修改

同样来自MSDN:

  

覆盖GetHashCode的派生类还必须重写Equals以保证两个被认为相等的对象具有相同的哈希码;否则,Hashtable类型可能无法正常工作。

来自MSDN's hashtable entry

  

密钥对象必须是不可变的,只要它们在Hashtable中用作密钥即可。

我读这个的方式是,可变对象 应该返回不同的哈希码,因为它们的值会发生变化,除非它们被设计用于哈希表。

在System.Drawing.Point的示例中,对象是可变的,当X或Y值更改时, 返回不同的哈希码。这将使它成为在哈希表中原样使用的不良候选者。

答案 3 :(得分:9)

我认为有关GetHashcode的文档有点令人困惑。

一方面,MSDN声明对象的哈希码永远不会改变,并且是常量 另一方面,MSDN还声明GetHashcode的返回值对于2个对象应该相等,如果这2个对象被认为是相等的。

MSDN:

  

哈希函数必须具有以下属性:

     
      
  • 如果两个对象比较相等,则为每个对象提供GetHashCode方法   必须返回相同的值。然而,   如果两个对象不比较   等于,GetHashCode方法   两个对象不必返回   不同的价值观。
  •   
  • 对象的GetHashCode方法必须始终返回   只要没有相同的哈希码   修改对象状态   确定的返回值   对象的等于方法。请注意这一点   仅适用于当前执行   一个应用程序,那个   如果可以返回不同的哈希码   应用程序再次运行。
  •   
  • 为获得最佳性能,哈希函数必须生成随机数   所有投入的分配。
  •   

然后,这意味着所有对象都应该是不可变的,或者GetHashcode方法应该基于对象的不可变属性。 假设你有这个类(天真的实现):

public class SomeThing
{
      public string Name {get; set;}

      public override GetHashCode()
      {
          return Name.GetHashcode();
      }

      public override Equals(object other)
      {
           SomeThing = other as Something;
           if( other == null ) return false;
           return this.Name == other.Name;
      }
}

此实现已违反MSDN中可以找到的规则。 假设你有这个类的2个实例; instance1的Name属性设置为'Pol',instance2的Name属性设置为'Piet'。 两个实例都返回不同的哈希码,它们也不相等。 现在,假设我将instance2的名称更改为'Pol',然后,根据我的Equals方法,两个实例应该相等,并且根据MSDN的其中一个规则,它们应该返回相同的哈希码。
但是,这不能做,因为instance2的哈希码会改变,MSDN声明不允许这样做。

然后,如果您有一个实体,您可以实现哈希码,以便它使用该实体的“主要标识符”,这可能是理想的代理键或不可变属性。 如果您有一个值对象,则可以实现Hashcode,以便它使用该值对象的“属性”。这些属性构成了值对象的“定义”。这当然是价值对象的本质;你不是对它的身份感兴趣,而是对它的价值感兴趣 因此,值对象应该是不可变的。 (就像它们在.NET框架中一样,字符串,日期等......都是不可变对象)。

记住另一件事:
在'session'期间(​​我真的不知道应该如何调用它)应该'GetHashCode'返回一个常量值。 假设您打开应用程序,从DB(实体)中加载对象的实例,并获取其哈希码。它会返回一定数量。 关闭应用程序,然后加载相同的实体。是否要求此次哈希码具有与第一次加载实体时相同的值? 恕我直言,不是。

答案 4 :(得分:8)

这是一个很好的建议。以下是Brian Pepin就此事所说的话:

  

这让我绊倒了   一次:确保始终使用GetHashCode   返回相同的值   实例的生命周期。记住这一点   哈希码用于识别   大多数哈希表中的“桶”   实现。如果是对象的话   “桶”更改,哈希表可能不会   能够找到你的对象。这些可以   是非常难找到的错误,所以得到它   这是第一次。

答案 5 :(得分:5)

不直接回答您的问题,但是 - 如果您使用Resharper,请不要忘记它具有为您生成合理的GetHashCode实现(以及Equals方法)的功能。您当然可以指定在计算哈希码时将考虑该类的哪些成员。

答案 6 :(得分:5)

查看Marc Brooks撰写的这篇博文:

VTOs, RTOs and GetHashCode() -- oh, my!

然后查看后续帖子(不能链接,因为我是新的,但在initlal文章中有一个链接),它进一步讨论并涵盖了初始实现中的一些小缺点。

这是我需要知道的关于创建GetHashCode()实现的所有内容,他甚至提供了他的方法以及其他一些实用程序的简短下载。

答案 7 :(得分:4)

哈希码永远不会改变,但了解Hashcode的来源也很重要。

如果您的对象使用值语义,即对象的标识由其值定义(如String,Color,所有结构)。如果对象的标识独立于其所有值,则Hashcode由其值的子集标识。例如,您的StackOverflow条目存储在某个数据库中。如果您更改了您的姓名或电子邮件,您的客户条目将保持不变,尽管某些值已更改(最终您通常会被一些长客户ID识别)。

简而言之:

值类型语义 - Hashcode由值定义 引用类型语义 - Hashcode由某个id

定义

我建议你阅读埃里克埃文斯的领域驱动设计,他进入实体与价值类型(这或多或少是我上面尝试过的),如果这仍然没有意义。

答案 8 :(得分:3)