关于实现等式操作符(==)的相关信息,请参考:[Equals 与等式操作符(==)的实现指南]。
- 重载 GetHashCode 方法来允许类型在散列数据表中正确地运作。
- 不要在 Equals 方法的实现中抛出异常。而是为 null 参量而返回 false。
遵循 Object.Equals 方法中被定义的约定,如下所示:
- x.Equals(x) 返回 true。
- x.Equals(y) 与 y.Equals(x) 返回相同的值。
- (x.Equals(y) && y.Equals(z)) 返回 true,如果 x.Equals(y) 或者 x.Equals(z) 的其中一个返回了 true,就直接返回 true。
- 只要通过 x 与 y 被引用的对象没有被更改,对于 x.Equals(y) 的连续调用就会返回相同的值。
- x.Equals(null) 返回 false。
- 至于一些类别的对象,为值的相等而进行 Equals 测试来替代参考相等的做法是值得的。如果两个对象都有相同的值,那么就为 Equals 的实现而返回 true,即使它们不是相同的实例。对象值的组成定义等于类型的实现器,但是它通常是部分或者所有被存储在对象的实例变量中的数据。例如,一个基于字符串的值;String 类的 Equals 方法会为任何两个包含相同内容的字符串实例而返回 true 值。
- 在一个基类的 Equals 方法中提供了相等的值的时候,派生类中的 Equals 方法重载就应该调用被继承的 Equals 实现。
- 如果你使用了一种支持操作符重载的编程语言,并且你为一个被指定的类型而重载了等式操作符(==),那么这个类型就应该重载 Equals 方法。这种 Equals 方法的实现应该能够返回与等式操作符相同的结果。下列指南将有助于确保类库的代码中使用了 Equals(如 ArrayList 与 Hashtable)并且正确地运作在以等式操作符通过应用程序代码而被使用的一致风格中。
- 如果你实现了一个值类型,那么你应该考虑重载 Equals 方法来获取在 ValueType 的默认 Equals 方法实现之上的被增强的性能。如果你重载了 Equals 以及编程语言所支持的操作符重载,那么你应该为你的值类型而重载等式操作符。
- 如果你实现了引用类型,并且如果你的类型与基类型(如 Point、String、BigNumber,以及等等)一样,那么你应该考虑重载一个引用类型的 Equals 方法。大部分的引用类型都不应该重载等式操作符,即使它们已经重载了 Equals 方法。但是,如果你实现了一个被打算用来持有值语法的引用类型(如一个复合的数字类型),那么你就应该重载等式操作符。
- 如果你为一个给定的类型实现了 IComparable 接口,你应该为那个类型而重载 Equals。
范例
下列代码范例示范了 Equals 方法的实现、重载调用,以及重载。
实现 Equals 方法
下列代码范例包含了对于默认 Equals 方法实现的两个调用。
Visual Basic
Imports System Class SampleClass Public Shared Sub Main() Dim obj1 As New System.Object() Dim obj2 As New System.Object() Console.WriteLine(obj1.Equals(obj2)) obj1 = obj2 Console.WriteLine(obj1.Equals(obj2)) End Sub End Class
C#
using System; class SampleClass { public static void Main() { Object obj1 = new Object(); Object obj2 = new Object(); Console.WriteLine(obj1.Equals(obj2)); obj1 = obj2; Console.WriteLine(obj1.Equals(obj2)); } }
前述代码的输出如下:
False True
重载 Equals 方法
下列代码范例说明了重载 Equals 方法来提供值等式的一个 Point 类以及 Point 的派生类 Point3D。因为 Point 类的 Equals 重载是最先在继承链中被用来引入值等式的重载,所以基类(派生自 Object 并且检查参考等式)的 Equals 方法不会被调用。但是,Point3D.Equals 会调用 Point.Equals,因为 Point 是以提供值等式的风格来实现 Equals 的。
Visual Basic
Namespace Examples.DesignGuidelines.EqualsImplementation Public Class Point Protected x As Integer Protected y As Integer Public Sub New (xValue As Integer, yValue As Integer) Me.x = xValue Me.y = yValue End Sub Public Overrides Overloads Function Equals(obj As Object) As Boolean If obj Is Nothing OrElse Not Me.GetType() Is obj.GetType() Then Return False End If Dim p As Point = CType(obj, Point) Return Me.x = p.x And Me.y = p.y End Function Public Overrides Function GetHashCode() As Integer Return x Xor y End Function End Class Public Class Point3D Inherits Point Private z As Integer Public Sub New (xValue As Integer, yValue As Integer, zValue As Integer) MyBase.New(xValue, yValue) Me.z = zValue End Sub Public Overrides Overloads Function Equals(obj As Object) As Boolean Return MyBase.Equals(obj) And z = CType(obj, Point3D).z End Function Public Overrides Function GetHashCode() As Integer Return MyBase.GetHashCode() Xor z End Function End Class End Namespace
C#
using System; namespace Examples.DesignGuidelines.EqualsImplementation { class Point: object { protected int x, y; public Point(int xValue, int yValue) { x = xValue; y = yValue; } public override bool Equals(Object obj) { // 检查 null 值并且比较运行时类型。 if (obj == null || GetType() != obj.GetType()) return false; Point p = (Point)obj; return (x == p.x) && (y == p.y); } public override int GetHashCode() { return x ^ y; } } class Point3D: Point { int z; public Point3D(int xValue, int yValue, int zValue) : base(xValue, yValue) { z = zValue; } public override bool Equals(Object obj) { return base.Equals(obj) && z == ((Point3D)obj).z; } public override int GetHashCode() { return base.GetHashCode() ^ z; } } }
Point.Equals 方法检查到 obj 参量不是 null 值并且它还引用了与这个对象相同的一个类型实例。如果其中一种检查失败,方法返回 false 值。Equals 方法使用 GetType 方法来检查这两个对象的运行时类型是否完全相同。注意:typeof(在 Visual Basic 中是 TypeOf)在这里没有被使用,因为它返回静态的类型。如果方法已经被用来检查 obj 是否被替换成 Point,那么在 obj 是由 Point 所派生的一个实例的情况下,检查将会返回 true,尽管 obj 以及当前实例不是相同的运行时类型。已经被核实的那两个对象就是相同的类型,方法把 obj 转换成 Point 类型并且返回两个对象实例变量的对比结果。
在 Point3D.Equals 中,被继承的 Equals 方法在任何操作被完成之前被调用。被继承的 Equals 方法会核实那个 obj 不是 null、那个 obj 是与该对象一样的相同实例,并且被继承的实例变量相匹配。只在被继承的 Equals 返回 true 值的时候,方法才能够对比被引入到派生类中的实例变量。尤其是,对于 Point3D 的转换并没有被执行,除非已经检测到 obj 是 Point3D 类型或者是一个派生自 Point3D 的类。
使用 Equals 方法来对比实例变量
在前面的范例中,等式操作符(==)被用来对比单独的实例变量。在有些情况下,它适合于使用 Equals 方法在 Equals 的一个实现中对比实例变量,如下代码范例所示。
Visual Basic
Imports System Class Rectangle Private a, b As Point Public Overrides Overloads Function Equals(obj As [Object]) As Boolean If obj Is Nothing Or Not Me.GetType() Is obj.GetType() Then Return False End If Dim r As Rectangle = CType(obj, Rectangle) ' 使用 Equals 来对比实例变量。 Return Me.a.Equals(r.a) And Me.b.Equals(r.b) End Function Public Overrides Function GetHashCode() As Integer Return a.GetHashCode() ^ b.GetHashCode() End Function End Class
C#
using System; class Rectangle { Point a, b; public override bool Equals(Object obj) { if (obj == null || GetType() != obj.GetType()) return false; Rectangle r = (Rectangle)obj; // 使用 Equals 来对比实例变量。 return a.Equals(r.a) && b.Equals(r.b); } public override int GetHashCode() { return a.GetHashCode() ^ b.GetHashCode(); } }
重载等式操作符(==)与 Equals 方法
在有些编程语言中(如 C#),操作符重载是被支持的。在一个类型重载了等式操作符(==)的时候,它应该同样重载 Equals 方法来提供相同的功能。这典型地通过在被重载的等式操作符(==)术语中编写 Equals 方法而被完成,如下代码范例所示。
C#
public struct Complex { double re, im; public override bool Equals(Object obj) { return obj is Complex && this == (Complex)obj; } public override int GetHashCode() { return re.GetHashCode() ^ im.GetHashCode(); } public static bool operator ==(Complex x, Complex y) { return x.re == y.re && x.im == y.im; } public static bool operator !=(Complex x, Complex y) { return !(x == y); } }
因为 Complex 是一个 C# 结构(一个值类型),并且已知没有任何类将会从 Complex 那里被派生。因此,Equals 方法不需要对比每个对象的 GetType 结果。代替它的是使用 is 操作符来检查 obj 参数的类型。