拼吾爱程序人生

首页 » .Net编程 » Visual Studio.NET » 深入解析.NET的相等操作符
cobra - 2008-6-4 13:07:00
文/Jonathan Allen  译/曹云飞  出处/InfoQ

重写相等操作符是非常容易出错的。不仅因为相等操作符有许多内涵,而且目前有很多指导文档有瑕疵,甚至在MSDN网站上有些指导文档也有瑕疵。我们将分别对支持相等操作的引用类型和值类型给出系统的分析,来澄清事实。

为了清晰起见,这里将类称作引用类型而结构称作值类型。

通常在结构中操作符重载比在类中有意义,所以我们先来展示在结构中的情况。类和结构的主要区别是,类需要检查空值,而在结构中你需要意识到可能存在的类型装箱。这一点将在后面说明。

类签名

结构的签名是直接了当的。你仅仅需要用System.IEquatable接口来标识该结构。请注意这个接口没有非泛型的版本,泛型的版本的角色由基础类Object承担。
C#


Code



类的签名本质上和结构签名是一样的。类的继承会破坏相等性,这会造成问题。如果a是一个基类而b是一个重写了Equals方法的子类,那么 a.Equals(b)会返回与b.Equals(a)不同的返回值。后面我们通过封闭(sealing)的Equals方法来解决这个问题。

Code


成员变量和属性

任何用于相等性比较的成员变量必须是不可变的。通常,这意味着类中所有的属性是只读的或者类有一个类似于数据库主键的唯一标识符。

在使用任何依赖哈希的东西的时候这条规则都是至关重要的。这样的例子包括Hashtable、Dictionary、HashSet和 KeyedCollection。这些类都使用哈希码作查找和存储。如果对象的哈希码变化了,它会被放在错误的槽中而且集合不能再正确的工作。最常见的故障是不能找到以前放在集合中的对象。

为了确保成员变量是不可变的,它们被标记为只读的。由于成员变量可以在构造器中设置,所以改变成员变量有点象写错名字。但是一旦初始化完成了,没有方法可以直接改变成员变量的值。
C#

Code


VB

Code


由于类版本的代码与上面的代码几乎是相同的,且在VB中是完全相同的,所以这里不给出类版本代码。

类型安全的相等方法

我们实现的第一个方法是类型安全的相等方法,在IEquatable接口中使用。

Code


对于类,需要额外检查空值。按照惯例,所有非空值被认为与空值不相等。

你会注意到我们没有使用地道的C#代码来检查空值。这是由于C#和VB处理相等性的方式有一处不同。

Visual Basic在处理引用相等和值相等上有明确的区别。前者使用Is擦作符,后者使用=操作符。

C#缺乏这种区别,对两者都使用==操作符。由于我们会重写==操作符,所以不想使用 ==,不得不转而使用一个后门。这个后门是Object.ReferenceEquals方法。

由于类总是与自己相等,所以在进行潜在的更昂贵的相等性检查之前,我们首先作这个检查。在下面代码中我们比较了私有成员变量,也可以使用属性来作比较。

Code


 您可能对 [Visual Studio.NET] 的这些文章也感兴趣:

Expression Studio 2.0 中文版发布了
利用运行时代码生成实现Duck typing技术
Dime Casts .NET - 学习.NET开发新技术的视频播客
Composite Application Library介绍
.NET Framework全部源代码下载
MindScape发布域建模和ORM工具LightSpeed 2.0
BizTalk Services进行了更新
.NET框架图解之一:Net基础,编译与执行引擎(上)
.NET框架图解之一:Net基础,编译与执行引擎(下)
PowerShell Management Library for Hyper-V登上CodePlex
新的FxCop规则偕同Pipeline Builder力助System.AddIn框架
.NET框架图解之二:System
cobra - 2008-6-4 13:13:00
哈希码

下一步是产生哈希码。最简单的方法是将所有用于相等性比较的成员变量的哈希码作异或运算。

Code


如果你确实决定要从头写自己的哈希码,你必须确保对于一套给定的值,你总是能返回相同的哈希码。换言之,如果a等于b,那么它们的哈希码也相等。

哈希码不必是唯一的,不同的值可以有相同的哈希码。但是它们应该有一个良好的分布。对于每一个哈希码都返回42在技术上是合法的,但是任何使用该算法的应用在性能上都会很糟糕。

哈希码应该以非常快的速度计算出来。由于计算哈希值可能成为瓶颈,所以宁可选用一个快速的有合理良好分布的哈希码算法,而不选择一个慢的,复杂的有着完美的均匀分布的算法。

相等(对象)

重写基类的Equals方法是一个基础工作,该方法被Object.Equals(Object, Object)函数和其他方法调用。

你应该注意到由于类型转换做了两次,所以可能存在一点性能问题:一次是看它是否有效,第二次是真正的执行它。不幸的是,在结构中是无法避免这样作的。

Code


对于类可以只用一次类型转换。在处理步骤中,我们可以早点检测空值然后跳过对Equals(PointClass)方法的调用。C#必须用ReferenceEquals函数来检查空值。

为了防止子类破坏相等性,我们封闭(Seal)了方法。

Code


操作符重载所有的难题都被攻克了,我们现在可以进行操作符的重写了。这里和调用类型安全的Equlas方法一样简单。

Code


对于类,需要检查空值。幸运的是,Object.Equals(object, object)为你处理了这种情况。然后调用已经被重写的Object.Equals(Object)方法。

Code


性能

你会注意到每个调用链都有点长,尤其是不相等操作符。如果需要考虑性能问题,你可以分别在每个方法中实现比较逻辑来提高速度。这样很容易出错而且使得维护工作比较辣手,所以仅仅当你使用性能检测工具证明了必须这样作之后,才应该这样作。
cobra - 2008-6-4 13:18:00
测试

本文使用了下面的测试。它使用了列表的形式使得你可以方便的将它们翻译到你最喜欢的单元测试框架中。因为相等性很容易被破坏,所以这是单元测试的首要的测试目标。

请注意测试不是全面的。你应该测试左右值部分相等的情况,例如PointStruct(1, 2) and PointStruct(1, 5)。


 附件: 您所在的用户组无法下载或查看附件


 附件: 您所在的用户组无法下载或查看附件


 附件: 您所在的用户组无法下载或查看附件


 附件: 您所在的用户组无法下载或查看附件
1
查看完整版本: 深入解析.NET的相等操作符
Modify by pin5i DZNT_ExpandPackage 2.1.3295 2007-2009 pin5i.com
 Total Unique Visitors: