我在Essential C#3.0和.NET 3.5书中读到:
GetHashCode()的返回应该是特定对象的生命周期 常量(相同的值),即使对象的数据发生变化。在很多 在这种情况下,你应该缓存方法返回以强制执行此操作。
这是一个有效的指南吗?
我在.NET中尝试了几种内置类型,但它们的行为并不像这样。
答案 0 :(得分:119)
答案 1 :(得分:89)
答案主要是,这是一个有效的指导方针,但可能不是一个有效的规则。它也没有说明整个故事。
对于可变类型,您不能将哈希代码基于可变数据,因为两个相等的对象必须返回相同的哈希代码,并且哈希代码必须在对象的生命周期内有效。如果哈希代码发生更改,您最终会得到一个在哈希集合中丢失的对象,因为它不再存在于正确的哈希框中。
例如,对象A返回1的散列。因此,它进入散列表的bin 1。然后你改变对象A使得它返回2的散列。当散列表寻找它时,它在bin 2中查找并且找不到它 - 该对象在bin 1中是孤立的。这就是散列码必须的原因。不要在对象的生命周期中更改 ,只是编写GetHashCode实现的一个原因是对接中的痛苦。
<强>更新强>
Eric Lippert has posted a blog提供有关GetHashCode
的出色信息。
其他更新
我上面做了几处修改:
指南只是一个指南,而不是规则。实际上,当事物期望对象遵循指南时,GetHashCode
只需遵循这些准则,例如当它存储在哈希表中时。如果您从未打算在哈希表中使用您的对象(或任何依赖于GetHashCode
规则的其他内容),那么您的实现不需要遵循这些准则。
当您看到“对于对象的生命周期”时,您应该阅读“对象需要与哈希表协作的时间”或类似内容。像大多数事情一样,GetHashCode
就是知道何时违反规则。
答案 2 :(得分:9)
来自MSDN
如果两个对象相等,那么 每个对象的GetHashCode方法 必须返回相同的值。然而, 如果两个对象不比较 等于,GetHashCode方法 两个对象不必返回 不同的价值观
对象的GetHashCode方法 必须始终返回相同的哈希值 代码只要没有 修改对象状态 确定的返回值 对象的等于方法。请注意这一点 仅适用于当前执行 一个应用程序,那个 如果可以返回不同的哈希码 应用程序再次运行。
为获得最佳性能,请使用哈希 函数必须生成随机 所有输入的分配。
这意味着如果对象的值发生变化,则哈希码应该更改。例如,将“Name”属性设置为“Tom”的“Person”类应该有一个哈希码,如果将名称更改为“Jerry”,则应该使用不同的代码。否则,汤姆==杰里,这可能不是你想要的。
修改:
同样来自MSDN:
覆盖GetHashCode的派生类还必须重写Equals以保证两个被认为相等的对象具有相同的哈希码;否则,Hashtable类型可能无法正常工作。
密钥对象必须是不可变的,只要它们在Hashtable中用作密钥即可。
我读这个的方式是,可变对象 应该返回不同的哈希码,因为它们的值会发生变化,除非它们被设计用于哈希表。
在System.Drawing.Point的示例中,对象是可变的,当X或Y值更改时, 返回不同的哈希码。这将使它成为在哈希表中原样使用的不良候选者。
答案 3 :(得分:9)
我认为有关GetHashcode的文档有点令人困惑。
一方面,MSDN声明对象的哈希码永远不会改变,并且是常量 另一方面,MSDN还声明GetHashcode的返回值对于2个对象应该相等,如果这2个对象被认为是相等的。
哈希函数必须具有以下属性:
- 如果两个对象比较相等,则为每个对象提供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)
查看Eric Lippert的Guidelines and rules for GetHashCode