Какая разница между IEquatable и просто переопределением Object.Equals()?

Я хочу, чтобы мой класс Food мог тестировать каждый раз, когда он равен другому экземпляру Food. Я позже использую его против списка, и я хочу использовать его метод List.Contains(). Должен ли я реализовать IEquatable<Food> или просто переопределить Object.Equals()? Из MSDN:

Этот метод определяет равенство используя сопоставитель равенства по умолчанию, как определено объектом реализации Метод IEquatable.Equals для T (тип значений в списке).

Итак, мой следующий вопрос: какие функции/классы .NET Framework используют Object.Equals()? Должен ли я использовать его в первую очередь?

Ответ 1

Основная причина - производительность. Когда в .NET 2.0 были введены обобщения, они смогли добавить кучу опрятных классов, таких как List<T>, Dictionary<K,V>, HashSet<T> и т.д. Эти структуры широко используют GetHashCode и Equals. Но для типов значений это требовало бокса. IEquatable<T> позволяет структуре реализовать строго типизированный метод Equals, поэтому не требуется бокс. Таким образом, гораздо более высокая производительность при использовании типов значений с общими коллекциями.

Типы ссылок не приносят столько же пользы, но реализация IEquatable<T> позволяет избегать приведения из System.Object, который может иметь значение, если он часто вызывает.

Как отмечено в блоге Джареда Парсона, вы все равно должны реализовать переопределения объектов.

Ответ 2

В соответствии с MSDN:

Если вы реализуете IEquatable (T), вы должен также переопределить базовый класс реализации Object.Equals(Object) и GetHashCode так что их поведение непротиворечиво с темой IEquatable (T).Equals метод. Если вы переопределите Object.Equals(Object), ваш переопределенный реализация также вызывается в вызовах к статическому Equals (System.Object, System.Object) в вашем классе. Это гарантирует, что все вызовы метод Equals возвращает последовательность Результаты.

Таким образом, кажется, что нет никакой реальной функциональной разницы между двумя, за исключением того, что можно было бы вызывать в зависимости от того, как используется класс. С точки зрения производительности, лучше использовать общую версию, потому что нет связанного с ней штрафа за бокс /unboxing.

С логической точки зрения также лучше реализовать интерфейс. Переопределение объекта не говорит никому, что ваш класс на самом деле равнозначен. Переопределение может быть просто неэффективным или неглубоким. Использование интерфейса явно говорит: "Эй, эта вещь справедлива для проверки равенства!" Это просто лучший дизайн.

Ответ 3

Расширение того, что сказал Джош с практическим примером. +1 Джошу - я собирался написать то же самое в своем ответе.

public abstract class EntityBase : IEquatable<EntityBase>
{
    public EntityBase() { }

    #region IEquatable<EntityBase> Members

    public bool Equals(EntityBase other)
    {
        //Generic implementation of equality using reflection on derived class instance.
        return true;
    }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as EntityBase);
    }

    #endregion
}

public class Author : EntityBase
{
    public Author() { }
}

public class Book : EntityBase
{
    public Book() { }
}

Таким образом, у меня есть повторно используемый метод Equals(), который работает из коробки для всех моих производных классов.