Effective C#原则10:明白GetHashCode()的缺陷

这是本书中唯一一个被一整个函数占用的原则,你应该避免写这样的函数。 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( );

时间: 2024-08-03 05:29:17

Effective C#原则10:明白GetHashCode()的缺陷的相关文章

Effective C#原则7: 选择恒定的原子值类型数据

恒定类型(immutable types)其实很简单,就是一但它们被创建,它们(的值) 就是固定的.如果你验证一些准备用于创建一个对象的参数,你知道它在验证状 态从前面的观点上看.你不能修改一个对象的内部状态使之成为无效的.在一个 对象被创建后,你必须自己小心翼翼的保护对象,否则你不得不做错误验证来禁 止改变任何状态.恒定类型天生就具有线程完全性的特点:多访问者可同时访问 相同的内容.如果内部状态不能修改,那么就不能给不同的线程提供查看不一致 的数据视图的机会.恒定类型可以从你的类上安全的暴露出

Effective C#原则9:明白几个相等运算之间的关系

明白ReferenceEquals(), static Equals(), instance Equals(), 和运算行 符==之间的关系. 当你创建你自己的类型时(不管是类还是结构),你要 定义类型在什么情况下是相等的.C#提供了4个不同的方法来断定两个对象是否 是相等的: public static bool ReferenceEquals ( object left, object right ); public static bool Equals ( object left, obj

Effective C#原则47:选择安全的代码

.Net运行时已经设计好了,一些怀有恶意的代码不能渗透到远程计算机上并 执行.目前一些分部式系统依懒于从远程机器上下载和执行代码.如果你可以通 过Internet或者以太网来发布你的软件,或者直接从web上运行,但你须要明白 CRL在你的程序集中的一些限制.如果CLR不是完全相信一个程序集,它会限制一 些的行为.这些调用代码要有访问安全认证(CAS).从另一方面来说,CLR强制要 求基于角色的安全认证,这样这些代码才能或者不能在基于一个特殊的角色帐号 下运行. 安全违例是运行时条件,编译器不能强

Effective C#原则1:尽可能的使用属性(property),而不是数据成员(field)

我们的目标:尽可能编写出运行效率更高,更健壮,更容易维护的C#代码. 原则一:尽可能的使用属性(property),而不是数据成员(field). Always use properties instead of accessible data members. 出于以下几点原因,请在设计类时,尽可能的使用属性,而不 是成员. 1..Net对属性的支持远远大于对成员的支持,你可以对属性进 行数据绑定,设计时说明等很多数据成员不被支持的内容.看看.net里的属性面 板,你会明白的. 2.数据安全性

Effective C#原则50:了解ECMA标准

ECMA标准是C#语言所有功能的官方说明.ECMA-334定义了C#语言1.0的标准, 你可以从The C# Programming Language这本书上学习C#2.0的计划(译注:现在 已经不是计划了),这本书的作者是Anders Hejlsberg, Scott Wiltamuth, 和 Peter Golde (Addison-Wesley, 2003).这本书是一个语言手册,而不是指南. 它详细说明了这门语言书面定义的每一个功能.每一种语言都只一种标记,可以 让你更加明白每一种语言的

Effective C#原则41:选择DataSet而不是自定义的数据结构

因为两个原则,把DataSet的名声搞的不好.首先就是使用XML序列化的 DataSet与其它的非.Net代码进行交互时不方便.如果在Web服务的API中使用 DataSet时,在与其它没有使用.Net框架的系统进行交互时会相当困难.其次, 它是一个很一般的容器.你可以通过欺骗.Net框架里的一些安全类型来错误 DataSet.但在现代软件系统中,DataSet还可以解决很多常规的问题.如果你明 白它的优势,避免它的缺点,你就可以扩展这个类型了. DataSet类设计 出来是为了离线使用一些存储

Effective C#原则40:根据需求选择集合

"哪种集合是最好的?"答案是:"视情况而定." 不同的集合有不同的性能,而且在不同的行为上有不同的优化..Net框架支持很 多类似的集合:链表,数组,队列,栈,以及其它的一些集合.C#支持多维的数 组,它的性能与一维的数组和锯齿数组都有所不同..Net框架同样包含了很多特 殊的集合,在你创建你自己的集合类之前,请仔细参阅这些集合.你可以发现很 多集合很快,因为所有的集合都实现了ICollection接口.在说明文档中列出了 所有实现了ICollection接口的集合

Effective C#原则37:使用标准的配置机制

我们要寻求一种避免直接写代码的应用程序配置和信息设置方法,我们已经 创建了多种不同的策略来存储配置信息.而我们是要寻求一种正确的方法,我们 要不断提高和改我们的想法,关于哪里是放置这些信息的好地方.INI文件?这 是Windows3.1做的事,配置信息的结构是受限制的,而且在文件名上可能还会与 其它程序程序相冲突.注册表?是的,是这个正确的想法,但它也有它的限制. 乱七八糟的程序可能会通过在注册表里写一些错误信息来严重破坏计算机.正因 为写注册表存在危险,一个应用程序必须有管理员权限来写注册表的

Effective C#原则27:避免使用ICloneable

ICloneable看上去是个不错的主意:为一个类型实现ICloneable接口后就可 以支持拷贝了.如果你不想支持拷贝,就不要实现它. 但你的对象并不 是在一个"真空"的环境中运行,但考虑到对派生类的些影响,最好 还是对ICloneable支持.一但某个类型支持ICloneable, 那么所有的派生类都必 须保持一致,也就是所有的成员必须支持ICloneable接口或者提供一种机制支持 拷贝.最后,支持深拷贝的对象,在创建设计时如果包含有网络结构的对象,会 使拷贝很成问题.IClon