这是本书中唯一一个被一整个函数占用的原则,你应该避免写这样的函数。 GetHashCode()仅在一种情况下使用:那就是对象被用于基于散列的集合的关键 词,如经典的HashTable或者Dictionary容器。这很不错,由于在基类上实现的 GetHashCode()存在大量的问题。对于引用类型,它可以工作,但高效不高;对 于值类型,基类的实现经常出错。这更糟糕。你自己完全可以写一个即高效又正 确的GetHashCode()。没有那个单一的函数比GetHashCode()讨论的更多,且令人 困惑。往下看,为你解释困惑。
如果你定义了一个类型,而且你决不准 备把它用于某个容器的关键词,那就没什么事了。像窗体控件,网页控件,或者 数据库链接这样的类型是不怎像要做为某个任何的关键词的。在这些情况下,什 么都不用做了。所有的引用类型都会得到一个正确的散列值,即使这样效率很糟 糕。值类型应该是恒定的(参见原则7),这种情况下,默认的实现总是工作的, 尽管这样的效率也是很糟糕的。在大多数情况下,你最好完全避免在类型的实例 上使用GetHashCode()。
然而,在某天你创建了一个要做为HashTable的 关键词来使用的类型,那么你就须要重写你自己的GetHashCode()的实现了。继 续看,基于散列(算法)的集合用散列值来优化查找。每一个对象产生一个整型的 散列值,而该对象就存储在基于这个散列值的“桶”中。为了查找某 个对象,你通过它的散列值来找到这个(存储了实际对象的)“桶”。 在.Net里,每一对象都有一个散列值,它是由System.Object.GetHashCode()断 定的。任何对GetHashCode()的重写都必须遵守下面的三个规则:
1、如 果两个对象是相等的(由操作符==所定义),那么它们必须产生相同的散列值。否 则,无法通过散列值在容器中找到对象。
2、对于任意对象A, A.GetHashCode()必须是实例不变的。不管在A上调用了什么方法, A.GetHashCode()必须总是返回同样的散列值。这就保证在某个“桶 ”中的对象始终是在这个“桶”中。
3、对于任意的输 入,散列函数总是产生产生一个介于整型内的随机分布。这会让你在一个基于散 列的容器取得好的效率。
为一个类型写一个正确且高效的散列函数来满 足上面的三条,要对该类型有广泛的认识。System.Object和System.ValueType 的默认版本并不具备什么优势。这些版本必须为你的特殊类型提供默认的行为, 而同时它们对这些特殊的类型又并不了解。Object.GetHashCode()是使用 System.Object内在的成员来产生散列值。每个对象在产生时指定一个唯一的值 来做为对象关键词,(这个值)以整型来存储。这些关键词从1开始,在每次有任 何新的对象产生时逐渐增加。对象的ID字段在System.Object的构造函数进行设 置,并且今后再也不能修改。Object.GetHashCode()就是把这个值当成给定对象 的散列值来返回。
(译注:注意这里说的是默认的引用类型,其它情况就 不是这样的了。)
现在我们根据上面的三个原则来验证 Object.GetHashCode()。如果两个对象是相等的,Object.GetHashCode()返回同 样的散列值,除非你重写了操作符==。System.Object这个版本的==操作符是检 测对象的ID。GetHashCode()返回对象内部的ID字段,它是好的。然而,如果你 提供了自己的==版本,你就必须同时提供你自己版本的GetHashCode(),从而保 证遵守了前面说的第一条规则。相等的细节参见原则9。
第二条规则已经 遵守了:一个对象创建后,它的散列值是不能改变的。
第三条规则,对 所有的输入,在整型内进行随机分布,这并没有被支持。这个数字序列并不是整 型上的随机分布,除非你创建了大量的对象。Object.GetHashCode()所产生的散 列值主要集中在尽可能小的整型范围内。
这就是说这个 Object.GetHashCode()是正确的,但并不高效。如果你在你定义的引用类型上创 建一个散列表,这个默认从System.Object上继承的行为是工作的,但是比较慢 。当你创建准备用于散列关键词的引用类型时,你应该为你的特殊类型重写 GetHashCode(),从而提供更好的在整型范围上随机分布的散列值。
在讲 解如何重写你自己的GetHashCode()之前,这一节来验证 ValueType.GetHashCode()是否也遵守上面的三条规则。System.ValueType重写 了GetHashCode(),为所有的值类型提供默认的行为。这一版本从你所定义的类 型的第一个字段上返回散列。考虑这个例子:
public struct MyStruct
{
private string _msg;
private int _id;
private DateTime _epoch;
}
从MyStruct对 象上返回的散列值是从该对象的_msg成员上生成的。下面的代码片断总是返回 true:
MyStruct s = new MyStruct( );
return s.GetHashCode( ) == s._msg.GetHashCode( );