Как лучше всего реализовать Equals для пользовательских типов? - PullRequest
34 голосов
/ 20 февраля 2009

Скажем, для класса Point2, и следующие равные:

public override bool Equals ( object obj )

public bool Equals ( Point2 obj )

Это тот, который показан в действующем C # 3:

public override bool Equals ( object obj )
{
    // STEP 1: Check for null
    if ( obj == null )
    {
        return false;
    }

    // STEP 3: equivalent data types
    if ( this.GetType ( ) != obj.GetType ( ) )
    {
        return false;
    }
    return Equals ( ( Point2 ) obj );
}

public bool Equals ( Point2 obj )
{
    // STEP 1: Check for null if nullable (e.g., a reference type)
    if ( obj == null )
    {
        return false;
    }
    // STEP 2: Check for ReferenceEquals if this is a reference type
    if ( ReferenceEquals ( this, obj ) )
    {
        return true;
    }
    // STEP 4: Possibly check for equivalent hash codes
    if ( this.GetHashCode ( ) != obj.GetHashCode ( ) )
    {
        return false;
    }
    // STEP 5: Check base.Equals if base overrides Equals()
    System.Diagnostics.Debug.Assert (
        base.GetType ( ) != typeof ( object ) );

    if ( !base.Equals ( obj ) )
    {
        return false;
    }

    // STEP 6: Compare identifying fields for equality.
    return ( ( this.X.Equals ( obj.X ) ) && ( this.Y.Equals ( obj.Y ) ) );
}

Ответы [ 10 ]

34 голосов
/ 20 февраля 2009

В том, который принимает obj, если тип obj - Point2, вызовите специфический для типа Equals. Внутри конкретного типа Equals убедитесь, что все члены имеют одинаковое значение.

public override bool Equals ( object obj )
{
   return Equals(obj as Point2);
}

public bool Equals ( Point2 obj )
{
   return obj != null && obj.X == this.X && obj.Y == this.Y ... 
   // Or whatever you think qualifies as the objects being equal.
}

Вы, вероятно, должны также переопределить GetHashCode, чтобы убедиться, что "равные" объекты имеют одинаковый хэш-код.

25 голосов
/ 20 февраля 2009

Существует также целый набор рекомендаций для MSDN . Вы должны прочитать их хорошо, это сложно и важно.

Несколько пунктов, которые я нашел наиболее полезными:

  • Типы значений не имеют идентификаторов, поэтому в struct Point вы обычно делаете сравнение элементов по элементам.

  • Типы ссылок обычно имеют идентичность, и поэтому тест Equals обычно останавливается на ReferenceEquals (по умолчанию, нет необходимости переопределять). Но есть исключения, такие как string и your class Point2, где объект не имеет полезной идентичности, а затем вы переопределяете члены Equality, чтобы обеспечить свою семантику. В этой ситуации следуйте инструкциям, чтобы сначала пройти через нулевые и другие случаи.

  • И есть веские причины для синхронизации GethashCode() и operator==.

9 голосов
/ 26 сентября 2014

Техника, которую я использовал, которая работала для меня, заключается в следующем. Обратите внимание, я сравниваю только на основе одного свойства (Id), а не двух значений. Отрегулируйте при необходимости

using System;
namespace MyNameSpace
{
    public class DomainEntity
    {
        public virtual int Id { get; set; }

        public override bool Equals(object other)
        {
            return Equals(other as DomainEntity);
        }

        public virtual bool Equals(DomainEntity other)
        {
            if (other == null) { return false; }
            if (object.ReferenceEquals(this, other)) { return true; }
            return this.Id == other.Id;
        }

        public override int GetHashCode()
        {
            return this.Id;
        }

        public static bool operator ==(DomainEntity item1, DomainEntity item2)
        {
            if (object.ReferenceEquals(item1, item2)) { return true; }
            if ((object)item1 == null || (object)item2 == null) { return false; }
            return item1.Id == item2.Id;
        }

        public static bool operator !=(DomainEntity item1, DomainEntity item2)
        {
            return !(item1 == item2);
        }
    }
}
2 голосов
/ 20 февраля 2009
  • Определите, что означает идентификатор. Если ссылочная идентификация будет работать, унаследованные равные по умолчанию будут работать.
  • Если тип значения (и, следовательно, значение идентичности) вам необходимо определить.
  • Если тип класса, но имеет семантику значений, определите.

Скорее всего, вы хотите и переопределить Equals (объект) и определить Equals (MyType), потому что последний избегает бокса. И переопределить оператор равенства.

В Руководстве по .NET Framework (2-е изд.) Больше информации.

0 голосов
/ 16 марта 2019

Использование C # 7 и шаблона is type varname (https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/is#type), обеспечивает чистую Equals(object), которая имеет дело с проверкой нуля и типа, используя любой из следующих подходов:

// using Equals(point)
public override bool Equals(object obj) =>
    (obj is Point other) && this.Equals(other);

// using the == operator
public override bool Equals(object obj) =>
    (obj is Point other) && this == other;

Очевидно, вам необходимо реализовать хотя бы также одно из следующих действий:

public bool Equals(Point2 other);
public static bool operator == (Point2 lhs, Point2 rhs);
0 голосов
/ 18 декабря 2018

Простой и лучший способ переопределить Equals выглядит следующим образом:

public class Person
    {
        public int Age { get; set; }
        public string Name { get; set; }

        public override bool Equals(object other)
        {
            Person otherItem = other as Person;

            if (otherItem == null)
                return false;

            return Age == otherItem.Age && Name == otherItem.Name;
        }
        public override int GetHashCode()
        {
            int hash = 13;
            hash = (hash * 7) + Age.GetHashCode();
            hash = (hash * 7) + Name.GetHashCode();
            return hash;
        }
    }

Переопределите метод GetHashCode, чтобы тип работал правильно в хеш-таблице.

0 голосов
/ 12 мая 2016

Существует также плагин Fody Equals.Fody , который автоматически генерирует Equals () и GetHashCode ()

0 голосов
/ 21 ноября 2015

Небольшие варианты форм, уже опубликованные несколькими другими ...

using System;
...
public override bool Equals ( object obj ) {
   return Equals(obj as SomeClass);
}

public bool Equals ( SomeClass someInstance ) {
    return Object.ReferenceEquals( this, someInstance ) 
        || ( !Object.ReferenceEquals( someInstance, null ) 
            && this.Value == someInstance.Value );
}

public static bool operator ==( SomeClass lhs, SomeClass rhs ) {
    if( Object.ReferenceEquals( lhs, null ) ) {
        return Object.ReferenceEquals( rhs, null );
    }
    return lhs.Equals( rhs );
    //OR
    return Object.ReferenceEquals( lhs, rhs )
            || ( !Object.ReferenceEquals( lhs, null ) 
                && !Object.ReferenceEquals( rhs, null )
                && lhs.Value == rhs.Value );
}

public static bool operator !=( SomeClass lhs, SomeClass rhs ) {
    return !( lhs == rhs );
    // OR
    return ( Object.ReferenceEquals( lhs, null ) || !lhs.Equals( rhs ) )
            && !Object.ReferenceEquals( lhs, rhs );
}

Попытка найти способ реализации оператора == с использованием Equals, чтобы избежать дублирования логики сравнения значений ... без каких-либо избыточных тестов (вызовы ReferenceEquals с теми же параметрами) или ненужных тестов (это не может быть нулевым в метод instance.Equals) и без каких-либо явных условий («ifs»). Больше тизер ума, чем что-либо полезное.

Самое близкое, что я могу придумать, это то, но оно кажется как будто это возможно без дополнительного метода:)

public bool Equals ( SomeClass someInstance ) {
    return Object.ReferenceEquals( this, someInstance ) 
        || (!Object.ReferenceEquals( someInstance, null ) && EqualsNonNullInstance( someInstance );
}

public static bool operator ==( SomeClass lhs, SomeClass rhs ) {
    return Object.ReferenceEquals( lhs, rhs ) 
    || ( !Object.ReferenceEquals( lhs, null ) && !Object.ReferenceEquals( rhs, null ) && lhs.EqualsNonNullInstance( rhs ) );
}

//super fragile method which returns logical non-sense
protected virtual bool EqualsNonNullInstance ( SomeClass someInstance ) {
    //In practice this would be a more complex method...
    return this.Value == someInstance.Value;
}

Вспоминая, насколько все это утомительно и подвержено ошибкам (я почти уверен, что в приведенном выше коде есть ошибка ... которая все еще отстой, потому что кто хочет создать подкласс типа просто , чтобы немного проверить на равенство проще?), в будущем, я думаю, я просто создам некоторые статические методы, которые обрабатывают все проверки на ноль и принимают делегат или требуют и интерфейс для выполнения сравнения значений (единственная часть, которая действительно изменяет тип на тип).

Было бы замечательно, если бы мы могли просто добавить атрибуты к полям / свойствам / методам, которые необходимо сравнить, и позволить компилятору / среде выполнения обрабатывать всю скуку.

Также убедитесь, что значения GetHashCode () равны для любых случаев, когда .Equals (объект) возвращает true или может произойти сумасшедшее дерьмо.

0 голосов
/ 20 февраля 2009
public override bool Equals ( object obj )
{
   // struct
   return obj  is Point2 && Equals (  ( Point2 ) value );
   // class
   //return Equals ( obj as Point2 );
}

public bool Equals ( Point2 obj )
0 голосов
/ 20 февраля 2009

Ложь Даниэля Л сказала,

public override bool Equals(object obj) {
    Point2 point = obj as Point2; // Point2? if Point2 is a struct
    return point != null && this.Equals(point);
}

public bool Equals(Point2 point) {
    ...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...