Реализация метода Equals

Сведения, связанные с реализацией оператора равенства (==), см. в разделе Правила реализации метода Equals и оператора равенства (==).

  • Для правильной работы типа в таблице хеширования следует переопределить метод GetHashCode.

  • Не следует генерировать исключения в реализации метода Equals. Вместо этого в случае пустого аргумента возвращайте значение false.

  • Следуйте соглашениям, описанным для метода Object.Equals:

    • x.Equals(x) возвращает true.

    • x.Equals(y) возвращает то же значение, что и y.Equals(x).

    • (x.Equals(y) && y.Equals(z)) возвращает true в том и только в том случае, когда x.Equals(z) возвращает true.

    • Последовательные вызовы x.Equals(y) возвращают одно и то же значение до тех пор, пока не будут изменены объекты, на которые ссылаются x и y.

    • x.Equals(null) возвращает false.

  • Для некоторых видов объектов желательно иметь проверку Equals на равенство значений, а не на равенство ссылок. Такие реализации Equals возвращают true, если два объекта имеют одно и то же значение, даже если они не являются одним и тем же экземпляром. Определение того, что составляет значение объекта, остается на усмотрении лица, реализующего тип, но обычно значением объекта являются некоторые или все данные, хранящиеся в его переменных экземпляра. Например, значение строки основано на ее символах; метод Equals класса String возвращает true для любых двух экземпляров строки, содержащих совпадающую последовательность одних и те же символов.

  • Если метод Equals базового класса предоставляет проверку равенства, при переопределении Equals в производном классе должна вызываться наследуемая реализация Equals.

  • При программировании на языке, поддерживающем перегрузку операторов, и при перегрузке для конкретного типа оператора равенства (==) в указанном типе следует переопределить метод Equals. Такая реализация метода Equals должна возвращать тот же самый результат, что и оператор равенства. Следование этим рекомендациям позволит гарантировать, что код библиотеки классов, использующий Equals (например, ArrayList и Hashtable) функционирует так же, как и оператор равенства, используемый кодом приложения.

  • При реализации типа значения следует рассмотреть возможность переопределения метода Equals с целью улучшения производительности по сравнению с реализацией метода Equals по умолчанию в ValueType. Если выполняется переопределение Equals и язык поддерживает перегрузку операторов, следует перегрузить оператор равенства для данного типа значения.

  • При реализации ссылочных типов следует рассмотреть возможность перегрузки метода Equals для ссылочного типа, если данный тип подобен базовому, например, Point, String, BigNumber и т. д. Для большинства ссылочных типов не следует перегружать оператор равенства, даже если в них переопределяется метод 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 так, чтобы равенство определялось по значениям, и класс Point3D, производный от класса Point. Поскольку метод Equals, переопределенный в классе Point, является первым в цепочке наследования методом, который вводится для проверки равенства значений, метод Equals базового класса (наследуемый от Object и проверяющий ссылочное равенство), не вызывается. Однако Point3D.Equals вызывает Point.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 был отличен от null и чтобы он указывал на экземпляр того же типа, что и данный объект. Если какая-либо из этих проверок завершается неудачно, метод возвращает false. Метод Equals использует метод GetType для определения того, совпадают ли типы двух объектов во время исполнения. Обратите внимание что typeof (TypeOf в Visual Basic) не используется в данном случае, поскольку он возвращает статический тип. Если в данном методе вместо этого использовать проверку вида obj is Point , то она будет возвращать true в случае, если obj представляет собой экземпляр, производный от класса Point, даже если obj и текущий экземпляр не принадлежат к одному и тому же типу во время исполнения. Убедившись, что оба объекта относятся к одному и тому же типу, метод приводит obj к типу Point и возвращает результат сравнения экземпляров переменных двух объектов.

В Point3D.Equals наследуемый метод Equals вызывается перед выполнением каких-либо иных операций. В наследуемом методе Equals проверяется, что obj не null, что obj представляет собой экземпляр того же класса, что и сам объект, и что значения экземпляров наследуемых переменных совпадают. Только после возврата значения true наследуемым методом Equals данный метод выполняет сравнение значений экземпляров переменных, которые были добавлены в производном классе. В частности, приведение к Point3D выполняется только в том случае, если obj определен как тип 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 имеет тип struct языка C# (тип значения), ясно, что не будет производных классов от Complex. Таким образом, в методе Equals не требуется выполнять сравнение результатов GetType для каждого объекта. Вместо этого в нем используется оператор is для проверки типа obj.

Охраняется авторским правом Copyright 2005 Microsoft Corporation. Все права защищены.

Фрагменты — © Addison-Wesley Corporation. Все права защищены.

Для дополнительной информации о разработке руководящих принципов, смотрите "руководства по разработке рамок: Конвенций, идиомы и шаблоны для повторного использования.NET библиотек"книга, Кшиштоф Cwalina и Брэд Абрамс, опубликованных Addison-Wesley, 2005 года.

См. также

Другие ресурсы

Руководство по разработке библиотек классов