题
我在必要C#3.0。净3.5书:
GetHashCode()'s返回的生活的一个特定的对象应该是 恒定(同价值),即使对象的数据的变化。在许多 情况下,应该缓的方法返回强制执行这一点。
这是一个有效准则?
我已经尝试了几个内在的类型。净,他们不表现得像这样。
解决方案
答案是最主要的是,它是一个有效的准则,但或许不是一个有效的规则。它也不会告诉整个故事。
这一点正在作出是,对于可变的种类型,不能基散列码的可变数据,因为两个相等的对象必须返回同一散列码和散列码必须有效的使用寿命的物体。如果列码的变化,你结束了对象,会失去一散列的收集,因为它不再生活在正确的散列站。
例如,对象返回的散列1.因此,它会在站1的散列表。然后你改变对象的一样那返回哈希2。当哈希表去寻找它,它看起来站2和找不到它的目的是成为孤儿在bin1.这就是为什么列码必须不改变 为对象的生命周期, ,而只是其中一个原因为什么写GetHashCode的实现是一个痛苦的屁股。
更新
埃里克利珀特已经发布的一个博客的 这给了出色的信息 GetHashCode
.
另外更新
我已经做了几个变化上:
- 我做了一个之间的区别的准则和规则。
- 我击中通过"生命周期内的物体"。
一准则仅仅是一个指导,而不是一个规则。在现实中, GetHashCode
只有遵循这些准则当事期待的对象遵循的准则,例如当它被存储在一散列表。如果你从来没有打算使用对象中的散列表格(或其他任何依赖于规则的 GetHashCode
),您的实现并不需要遵循的准则。
当你看到"为对象的生命周期"的,你应该读"的对象时需要进行合作,散列表"或类似。最喜欢的东西, GetHashCode
是关于知道当打破规则。
其他提示
这是一个很长一段时间,但尽管如此,我认为仍然有必要给出正确回答这个问题,包括有关原因和怎么样了解释。最好的答案迄今已是一个理由是MSDN exhaustivly - 不要试图让自己的规则,在MS家伙知道他们在做什么。
但是,先: 在这个问题中引用的方针是错误的。
现在的个为什么 - 有两个人
<强>首先为什么强>: 如果哈希码以这样的方式来计算,其不对象的寿命期间改变,即使对象本身的变化,比它会打破等号合同。
记住: “如果两个对象的比较结果为相等,为每个对象GetHashCode方法必须返回相同的值。但是,如果两个对象不比较结果为相等,对于两个对象的GetHashCode方法不必返回不同的值。”
在第二句子经常被误解为“唯一的规则是,在该对象创建时间,等于对象的哈希码必须相等”。真的不知道为什么,但是这对大多数答案的精髓在这里。
认为包含名称的两个对象,其中名称是按照equals方法中使用的:同名称 - >同样的事情。 创建实例答:名称=乔 创建实例B:名称=彼得
Hashcode方法A和Hashcode方法乙将最有可能是不一样的。 现在会发生什么,当实例B的名称更改为乔?
据问题的准则,B的哈希码不会改变。这样做的结果将是: A.Equals(B)==>真 但在同一时间: A.GetHashCode()== B.GetHashCode()==>假。
但究竟这种行为是由equals&哈希码合同明确禁止。
<强>二为什么强>: 虽然这是 - 当然 - 真实的,那改变了哈希码可以打破使用的哈希码哈希表和其他对象,反过来也是如此。不改变的哈希码将在最坏的情况下获得哈希表,所有很多不同的对象将具有相同的哈希码,并为此在相同的散列斌 - 当对象与标准值进行初始化发生,例如:。 p>
现在来怎样完成 嗯,乍看之下,似乎有一个矛盾 - 无论哪种方式,代码将打破。 但既不问题确实来自改变或不变哈希码。
的问题的来源是公在MSDN描述:
从MSDN的散列表条目:
Key对象必须是不可变,只要 因为它们被用作密钥 哈希表。
这确实意味着:
这产生了散列值应该改变散列值中,当对象的变化,但它不能在任何对象 - 绝对不能 - 允许本身的任何变化,当它被一个Hashtable(或任何其它使用Hash-对象内部使用,当然)。
首先如何 当然,最简单的方法是只在哈希表的使用,这将作为正常的copys,需要时可变对象来创建设计不可变对象。 不可变对象中,这是obviusly确定缓存的哈希码,因为它是不可变的。
二如何 或者给该对象的“你现在散列” -flag,确保所有对象的数据是私有的,检查标志,可以更改对象数据,并抛出一个异常数据,如果不允许改变(即标志设置)的所有功能。 现在,当你把对象在任何散列区域,确保设置标志,以及 - 以及 - 取消设置标志,当不再需要它。 为了方便使用,我建议自动设置标志“的GetHashCode”方法里面 - 这样它不能被遗忘。和“ResetHashFlag”方法的显式调用将确保,程序员将不得不考虑,羯羊它还是不允许通过改变现在的对象数据。
好了,应该怎样以及说:在有些情况下,它是可能的具有可变的数据,其中,所述哈希码是不变仍然,当对象的数据被改变时,在不违反等号&哈希码合同的对象。
这确实需要然而,该等号方法不基于所述可变数据为好。 所以,如果我写的一个对象,并创建一个具有计算的值只有一次,并将其存储在对象内部返回它以后调用,那么我要再次强调一个GetHashCode的方法:绝对必须,创建一个equals方法,将使用存储的值用于比较,以便A.Equals(B)将不会改变由假变为真为好。否则,合同将被打破。这样做的结果通常是Equals方法没有任何意义 - 这不是原来的参考值等于,但是它既不是值等于为好。有时,这可预期的行为(即,客户记录),但通常是不
所以,只要使GetHashCode的结果变化,当对象数据的变化,并且如果使用列表或对象的散列的内部使用所述对象的预期(或只是可能的),则使对象不可变或创建只读标志用于包含对象的哈希表的寿命。
(顺便说一句:所有的这不是C#奥德.NET具体 - 它是在所有哈希表实现的性质,或更一般地任何索引列表,即识别对象的数据不应该改变,则在对象是。在列表中会出现意外的和不可预知的行为,如果这条规则被打破某处,有可能是列表实现,那会监控列表中的所有元素,并做自动重建索引列表中 - 但那些表现一定会在可怕的最好的。)
从 MSDN
如果两个对象的比较结果为相等,则 GetHashCode的方法为每个对象 必须返回相同的值。然而, 如果两个对象不作为比较 相等,则方法的GetHashCode为 两个Object不必返回 不同的值。
的对象GetHashCode方法 必须一致地返回相同的散列 代码只要没有 修改的对象状态 确定的返回值 对象的equals方法。请注意,这 是真的只为当前执行 一个应用程序的,而一个 不同的hash code的话可以退还 应用程序再次运行。
为了获得最佳性能,哈希 函数必须生成一个随机 分配所有输入。
这意味着,如果该对象的值改变(一个或多个),哈希码应该改变。例如,一个“人”类“名称”属性设置为“汤姆”应该有一个哈希代码,不同的代码,如果你将名称更改为“杰里”。否则,汤姆==杰里,这可能不是你本来打算。
修改强>:
另外,从MSDN:
重写GetHashCode还必须重写派生类Equals,以保证被视为相等的两个对象具有相同的散列码;否则,Hashtable的类型可能无法正常工作。
从 MSDN的散列表条目:
因为它们被用作在Hashtable键密钥对象必须只要是不可变的。
我看这是一个可变对象的应的返回不同的散列码作为他们的价值观发生变化,除非的他们是专为在哈希表中使用。
的方式在System.Drawing.Point的示例中,对象是可变的,并确实返回不同的散列码,当X或Y的值的变化。这将使差的候选被用作-是在哈希表中。
我认为,该文件关于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个此类的实例;该名称属的1设定为'Pol',名称的财产的instance2设定为'Piet'.这两种情况下返回一个不同哈希码,而且他们也不平等。现在,假定我的名称改instance2为'Pol',然后,根据我等方法,这两种情况下应该是平等的,并根据的规则之一MSDN,他们应该返回同一个哈希码.
然而,无法做到这一点,因为哈希码的instance2将会改变,MSDN国家,这是不允许的。
然后,如果你有一个实体,你能不能实现本哈希码所以,它采用的主要标识符'的实体,这也许是理想的一种替代的关键,或一成不变的财产。如果你有一个值的目的,可以实施哈希码,以便它使用的"属性"的,值的对象。这些属性的'定义'的价值的对象。这是当然的性质价值的对象;你们不是对它感兴趣的身份,而是在它的价值。
而且,因此,值的对象应该是一成不变的。(只是喜欢他们。Framework,string,日期,等等。都是不可改变的对象)。
另一件事涉及在记:
在其'届会'(我不知道是不是真的我应该怎么叫这个)应'GetHashCode'返回的一个恒定值。假设你打开你的应用程序,载对象的一个实例的数据库(实体),并得到它哈希码.它将回报一定数量。关闭应用程序,并载相同的实体。它是必需的,哈希码这次具有同样的价值作为当你装载的实体第一时间?恕我直言,没有。
这是很好的建议。下面是布赖恩·佩潘必须对此事说:
此已跳闸我多于 一次:务必确保的GetHashCode 返回跨越相同的值 一个实例的寿命。请记住, 散列码被用于识别 在大多数哈希表“桶” 实现。如果对象的 “斗”的转变,一个哈希表可能不 能够找到你的对象。这些都可以 很辛苦的错误被发现,所以把它 在第一时间。
不直接回答你的问题,但是 - 如果你使用ReSharper的,不要忘记它具有为您生成一个合理的GetHashCode的实现(以及Equals方法)的特征。可以当然指定的类的成员将计算的哈希码时加以考虑。
这是马克·布鲁克斯看看这个博客文章:
VTOS,RTOS和GetHashCode()方法 - - 哦,我的
然后检查了进一步讨论,并介绍了一些小的弱点,初步实现了跟进后(不能链接,因为我是新的,但还有的initlal文章中的链接)。
这是一切,我需要知道有关创建GetHashCode()方法的实现,他甚至提供了他的方法与一些其他实用程序一起下载,在做空黄金。
在哈希代码永远不会改变,但它也是重要的,了解其中的哈希码的来源。
如果您的对象是使用值语义,即该对象的身份是由它的值限定(如String,色彩,所有的结构)。如果你的对象的身份是独立于所有它的价值的,那么散列码是由它的值的子集标识。例如,你的StackOverflow条目存储在数据库中的某个地方。如果您更改姓名或电子邮件,您的客户准入保持不变,但也有一些改变过的(最终你通常是由一些长期客户编号标识)。
因此,在短期:
值类型语义 - Hashcode方法是通过值限定 引用类型的语义 - Hashcode方法是由一些ID定义
我建议你由Eric埃文斯,在那里他进入实体VS值类型(这是多了还是少了什么,我试图上面做的),如果这仍然是没有意义的阅读领域驱动设计。
查核指南和GetHashCode的规则通过埃里克利珀