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

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

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

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


person devoured elysium    schedule 29.04.2010    source источник
comment
Здесь очень хорошее объяснение blogs.msdn.com/b/jaredpar/archive/2009/01/15/   -  person nawfal    schedule 14.04.2013
comment
возможный дубликат Общие сведения об IEquatable   -  person nawfal    schedule 08.10.2013


Ответы (4)


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

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

Как указано на блог Джареда Парсона, однако вы должны по-прежнему реализовать стандартные Object.Equals и Object.GetHashcode переопределения.

person Josh    schedule 29.04.2010
comment
Есть ли приведение между ссылочными типами? Я всегда думал, что приведение типов - это просто утверждения, которые вы делаете компилятору, когда назначаете неочевидные преобразования одного типа объекта другому. Это значит, что после компиляции код даже не узнает, что там было приведение. - person devoured elysium; 29.04.2010
comment
Это верно для C ++, но не для языков .NET, которые обеспечивают безопасность типов. Приведение выполняется во время выполнения, и если приведение не удается, выдается исключение. Таким образом, за кастинг взимается небольшой штраф за время выполнения. Компилятор может оптимизировать апкасты. Например, объект o = (объект) строка; Но понижение - строка s = (строка) o; - должно произойти во время выполнения. - person Josh; 29.04.2010
comment
Я понимаю. Случайно у вас есть место, где я могу получить более подробную информацию о .NET? Спасибо! - person devoured elysium; 29.04.2010
comment
Я бы порекомендовал CLR через C # от Джеффа Рихтера и C # in Depth от Джона Скита. Что касается блогов, то хороши блоги Wintellect, блоги msdn и т. Д. - person Josh; 29.04.2010
comment
Делает ли интерфейс IEquatable<T> что-то большее, чем напоминание разработчику о включении члена public bool Equals(T other) в класс или структуру? Наличие или отсутствие интерфейса не имеет значения во время выполнения. Кажется, что перегрузка Equals - это все, что нужно. - person mikemay; 21.06.2020

Согласно MSDN:

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

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

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

person CodexArcanum    schedule 29.04.2010
comment
Структуры обязательно должны реализовывать iEquatable (of theirOwnType), если они собираются использовать в качестве ключей в Dictionary или аналогичной коллекции; это значительно повысит производительность. Ненаследуемые классы получат небольшое повышение производительности за счет реализации IEquatable (of theirOwnType). Унаследованные классы // не должны реализовывать // IEquatable. - person supercat; 07.12.2010

Дополним сказанное Джошем практическим примером. +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 (), который работает "из коробки" для всех моих производных классов.

person this. __curious_geek    schedule 29.04.2010
comment
Еще один вопрос. В чем преимущество использования obj в качестве EntityBase вместо (EntityBase) obj? Просто вопрос стиля или есть вообще какое-то преимущество? - person devoured elysium; 29.04.2010
comment
в случае obj как EntityBase - если obj не относится к типу EntityBase, он передаст значение null и продолжится без каких-либо ошибок или исключений, но в случае (EntityBase) obj он принудительно попытается преобразовать obj в EntityBase, и если объект не относится к типу EntityBase, он выдаст исключение InvalidCastException. И да, поскольку это применимо только к ссылочным типам. - person this. __curious_geek; 29.04.2010
comment
Я понимаю ответ (общее равно - так строго типизировано, нет необходимости преобразовывать в объект и т. Д.). Но у меня есть вопрос относительно следующего утверждения: ››› Таким образом, у меня есть повторно используемый метод Equals (), который работает «из коробки» для всех моих производных классов. Iequatable ‹T› ', просто переопределите' equals ', это может работать и для производных типов, верно? просто хочу убедиться, каковы ваши намерения :) - person Dreamer; 23.02.2014
comment
Ссылка Джоша на блог Джареда Пэра, кажется, предполагает, что вам также необходимо переопределить GetHashCode. Разве это не так? - person Amicable; 07.04.2014
comment
Я действительно не понимаю той дополнительной ценности, которую предоставляет ваша реализация. Можете ли вы прояснить проблему, которую решает ваш абстрактный базовый класс? - person Mert Akcakaya; 06.01.2015
comment
@Amicable - да, всякий раз, когда вы переопределяете Object.Equals (Object), вы также должны переопределить GetHashCode, чтобы контейнеры работали. - person namford; 08.05.2015

Если мы вызываем object.Equals, это вынуждает к дорогостоящему боксу по типам значений. Это нежелательно в сценариях, чувствительных к производительности. Решение - использовать IEquatable<T>.

public interface IEquatable<T>
{
  bool Equals (T other);
}

Идея IEquatable<T> в том, что он дает тот же результат, что и object.Equals, но быстрее. Ограничение where T : IEquatable<T> должно использоваться с универсальными типами, как показано ниже.

public class Test<T> where T : IEquatable<T>
{
  public bool IsEqual (T a, T b)
  {
    return a.Equals (b); // No boxing with generic T
  }
}

в противном случае он привязывается к slower object.Equals().

person Seyedraouf Modarresi    schedule 02.05.2020