文/Jonathan Allen 译/曹云飞 出处/InfoQ
重写相等操作符是非常容易出错的。不仅因为相等操作符有许多内涵,而且目前有很多指导文档有瑕疵,甚至在MSDN网站上有些指导文档也有瑕疵。我们将分别对支持相等操作的引用类型和值类型给出系统的分析,来澄清事实。
为了清晰起见,这里将类称作引用类型而结构称作值类型。
通常在结构中操作符重载比在类中有意义,所以我们先来展示在结构中的情况。类和结构的主要区别是,类需要检查空值,而在结构中你需要意识到可能存在的类型装箱。这一点将在后面说明。
类签名结构的签名是直接了当的。你仅仅需要用System.IEquatable接口来标识该结构。请注意这个接口没有非泛型的版本,泛型的版本的角色由基础类Object承担。
C#

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

Code
C#
class PointClass : System.IEquatable
VB
Public Class PointClass Implements IEquatable(Of PointClass)
成员变量和属性任何用于相等性比较的成员变量必须是不可变的。通常,这意味着类中所有的属性是只读的或者类有一个类似于数据库主键的唯一标识符。
在使用任何依赖哈希的东西的时候这条规则都是至关重要的。这样的例子包括Hashtable、Dictionary、HashSet和 KeyedCollection。这些类都使用哈希码作查找和存储。如果对象的哈希码变化了,它会被放在错误的槽中而且集合不能再正确的工作。最常见的故障是不能找到以前放在集合中的对象。
为了确保成员变量是不可变的,它们被标记为只读的。由于成员变量可以在构造器中设置,所以改变成员变量有点象写错名字。但是一旦初始化完成了,没有方法可以直接改变成员变量的值。
C#

Code
readonly int _X;
readonly int _Y;
public PointStruct (int x, int y)
{
_X = x;
_Y = y;
}
int X
{
get { return _X; }
}
int Y
{
get { return _Y; }
}
VB

Code
Private ReadOnly m_X As Integer
Private ReadOnly m_Y As Integer
Public Sub New(ByVal x As Integer, ByVal y As Integer)
m_X = x
m_Y = y
End Sub
Public ReadOnly Property X() As Integer
Get
Return m_X
End Get
End Property
Public ReadOnly Property Y() As Integer
Get
Return m_Y
End Get
End Property
由于类版本的代码与上面的代码几乎是相同的,且在VB中是完全相同的,所以这里不给出类版本代码。
类型安全的相等方法我们实现的第一个方法是类型安全的相等方法,在IEquatable接口中使用。

Code
C#
public bool Equals(PointStruct other)
{
return (this._X == other._X) && (this._Y == other._Y);
}
VB Public Overloads Function Equals(ByVal other As PointStruct) As Boolean _
Implements System.IEquatable(Of PointStruct).Equals
Return m_X = other.m_X AndAlso m_Y = other.m_Y
End Function
对于类,需要额外检查空值。按照惯例,所有非空值被认为与空值不相等。
你会注意到我们没有使用地道的C#代码来检查空值。这是由于C#和VB处理相等性的方式有一处不同。
Visual Basic在处理引用相等和值相等上有明确的区别。前者使用Is擦作符,后者使用=操作符。
C#缺乏这种区别,对两者都使用==操作符。由于我们会重写==操作符,所以不想使用 ==,不得不转而使用一个后门。这个后门是Object.ReferenceEquals方法。
由于类总是与自己相等,所以在进行潜在的更昂贵的相等性检查之前,我们首先作这个检查。在下面代码中我们比较了私有成员变量,也可以使用属性来作比较。

Code
C#
public bool Equals(PointClass other)
{
if (Object.ReferenceEquals(other, null))
{
return false;
}
if (Object.ReferenceEquals(other, this))
{
return true;
}
return (this._X == other._X) && (this._Y == other._Y);
}
VB
Public Overloads Function Equals(ByVal other As PointClass) As Boolean
Implements System.IEquatable(Of PointClass).Equals
If other Is Nothing Then Return False
If other Is Me Then Return True
Return m_X = other.m_X AndAlso m_Y = other.m_Y
End Function
您可能对 [Visual Studio.NET] 的这些文章也感兴趣: