实现 Equals 方法

有关实现相等运算符 (==) 的信息,请参见 Equals 和相等运算符 (==) 的实现准则

  • 重写 GetHashCode 方法,使类型可以在哈希表中正确地工作。

  • 不要在 Equals 方法实现中引发异常。 相反,对于空参数返回 false

  • 遵循在 Object.Equals 方法上定义的协定,如下所示:

    • x.Equals(x) 返回 true

    • x.Equals(y) 与 y.Equals(x) 返回相同的值。

    • 当且仅当 x.Equals(z) 返回 true 时,(x.Equals(y) && y.Equals(z)) 返回 true

    • 只要不修改 x 和 y 所引用的对象,x.Equals(y) 的连续调用就返回相同的值。

    • x.Equals(null) 返回 false。

  • 对于某些类型的对象,让 Equals 测试值相等而不是引用相等会更好。 如果两个对象有相同的值,这些 Equals 实现返回 true,即使它们不是相同的实例。 构成对象值的定义由类型的实施者决定,但一般是对象的实例变量中存储的部分或全部数据。 例如,字符串的值基于字符串的字符;对于字符串的任何两个按相同的顺序包含完全相同的字符的实例,String 类的 Equals 方法返回 true

  • 当基类的 Equals 方法提供值相等性时,派生类中的 Equals 重写应调用继承的 Equals 实现。

  • 如果用支持运算符重载的语言进行编程,并选择重载指定类型的相等运算符 (==),则该类型应重写 Equals 方法。 Equals 方法的这些实现应与相等运算符返回相同的结果。 遵循这些准则有助于确保使用 Equals 的类库代码(如 ArrayListHashtable)的工作方式与应用程序代码使用相等运算符的方式一致。

  • 如果实现值类型,应考虑重写 Equals 方法,以获得优于 ValueType 的默认 Equals 方法实现的性能。 如果重写 Equals 并且语言支持运算符重载,应重载值类型的相等运算符。

  • 如果实现引用类型,并且类型看起来像一个基类(如 Point、String、BigNumber 等),应考虑重写引用类型的 Equals 方法。 对于大多数引用类型而言,即使重写 Equals,也不应重载相等运算符。 但是,如果实现的引用类型将具有值语义(如复数类型),应重写相等运算符。

  • 如果在给定类型上实现 IComparable 接口,应重写该类型的 Equals

示例

下面的代码示例演示如何实现、重写调用及重载 Equals 方法。

实现 Equals 方法

下面的代码示例包含两个对 Equals 方法的默认实现的调用。

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
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 方法

下面的代码示例展示一个 Point 类,该类重写 Equals 方法以提供值相等性;还展示了一个从 Point 派生的 Point3D 类。 由于 Point 类对 Equals 的重写是继承链中引入值相等性的第一个重写方法,因此不调用基类的 Equals 方法(它从 Object 继承并检查引用相等性)。 但是,由于 Point 以提供值相等性的方式实现 Equals,因此 Point3D.Equals 调用 Point.Equals。

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

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) 
   {
      // Check for null values and compare run-time types.
      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;
   }
}
}
using namespace System;

namespace Examples { namespace DesignGuidelines { namespace EqualsImplementation
{
    ref class Point : Object
    {
    protected:
        int x, y;

    public:
        Point(int xValue, int yValue)
        {
            x = xValue;
            y = yValue;
        }

        virtual bool Equals(Object^ obj) override
        {
            // Check for null values and compare run-time types.
            if (obj == nullptr || GetType() != obj->GetType())
            {
                return false;
            }

            Point^ p = (Point^)obj;

            return (x == p->x) && (y == p->y);
       }

       virtual int GetHashCode() override
       {
           return x ^ y;
       }
    };

    ref class Point3D : Point
    {
    private:
        int z;

    public:
        Point3D(int xValue, int yValue, int zValue) : Point(xValue, yValue)
        {
            z = zValue;
        }

        virtual bool Equals(Object^ obj) override
        {
            return Point::Equals(obj) && z == ((Point3D^)obj)->z;
        }

        virtual int GetHashCode() override
        {
            return Point::GetHashCode() ^ z;
        }
    };
}}}

Point.Equals 方法检查 obj 参数是否非空以及它是否与此对象引用了相同类型的实例。 如果任何一个检查失败,该方法都返回 falseEquals 方法使用 GetType 方法确定两个对象的运行时类型是否相同。 注意,这里不使用 typeof(在 Visual Basic 中为 TypeOf),因为它返回静态类型。 如果该方法改为使用 obj is Point 形式的检查,则当 obj 是从 Point 派生的类的实例时,检查结果将返回 true,即使 obj 和当前实例不属于相同的运行时类型亦如此。 验证了两个对象的类型相同后,该方法将 obj 强制转换为类型 Point,并返回两个对象实例变量的比较结果。

在 Point3D.Equals 中,在完成任何其他操作之前调用继承的 Equals 方法。 继承的 Equals 方法执行下列检查:obj 是否不为 null,obj 是否是与此对象相同的类的实例,以及继承的实例变量是否匹配。 仅当继承的 Equals 返回 true 时,方法才比较派生类中引入的实例变量。 具体说来,除非 obj 已被确定属于 Point3D 类型或者是从 Point3D 派生的类,否则不执行到 Point3D 的强制转换。

使用 Equals 方法比较实例变量

在前面的示例中,相等运算符 (==) 用于比较各实例变量。 在某些情况下,在 Equals 实现中用 Equals 方法比较实例变量是适当的,如下面的代码示例所示。

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)
      ' Use Equals to compare instance variables.
      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 
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;
      // Use Equals to compare instance variables.
      return a.Equals(r.a) && b.Equals(r.b);
   }
   public override int GetHashCode() 
   {
      return a.GetHashCode() ^ b.GetHashCode();
   }
}

重写相等运算符 (==) 和 Equals 方法

有些编程语言(如 C#)支持运算符重载。 当类型重载相等运算符 (==) 时,它还应该重写 Equals 方法以提供相同的功能。 一般是通过根据重载的相等运算符 (==) 编写 Equals 方法来达到这一目的,如下面的代码示例所示。

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# struct(一种值类型),我们知道没有类从 Complex 派生。 因此,Equals 方法不需要比较每个对象的 GetType 结果。 相反,它使用 is 运算符检查 obj 参数的类型。

部分版权所有 2005 Microsoft Corporation。 保留所有权利。

部分版权所有 Addison-Wesley Corporation。 保留所有权利。

设计指引的详细信息,请参阅"框架设计准则: 公约、 成语和可重复使用的模式。网络图书馆"书 Krzysztof Cwalina 和布拉德 · 艾布拉姆斯,2005年艾迪生 - 韦斯利,发表。

请参见

其他资源

类库开发的设计准则