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

185

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

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

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

пожрал Элизиум
источник
3
Очень хорошее объяснение здесь: blogs.msdn.com/b/jaredpar/archive/2009/01/15/…
nawfal,
возможный дубликат Понимания IEquatable
nawfal

Ответы:

214

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

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

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

мистифицировать
источник
Есть ли приведение между ссылочными типами? Я всегда думал, что приведение - это просто «заявления», которые вы делаете компилятору, когда назначаете несколько неочевидных приведений от одного вида объекта к другому. Это после того, как вы скомпилируете, что код даже не узнает, что там был приведен код.
пожрала Элизиум
7
Это верно в C ++, но не в языках .NET, которые обеспечивают безопасность типов. Существует приведение во время выполнения, и если приведение не удается, генерируется исключение. Таким образом, есть небольшой штраф за время исполнения, чтобы заплатить за кастинг. Компилятор может оптимизировать апкаст-апы, например, object o = (object) "string"; Но по унынию - строка s = (string) o; - должно произойти во время выполнения.
Джош
1
Понимаю. Случайно у вас есть место, где я могу получить такую ​​"более глубокую" информацию о .NET? Спасибо!
пожрала Элизиум
7
Я бы порекомендовал CLR через C # Джеффа Рихтера и C # in Depth Джона Скита. Что касается блогов, то блоги Wintellect хороши, блоги msdn и т. Д.
Josh
Имеет ли IEquatable<T>интерфейс сделать что - нибудь больше , чем напомнить разработчик включить public bool Equals(T other) элемент в классе или структуры? Наличие или отсутствие интерфейса не имеет значения во время выполнения. EqualsКажется, что перегрузка - это все, что необходимо.
Mikemay
48

Согласно MSDN :

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

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

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

CodexArcanum
источник
9
Структуры должны обязательно реализовывать iEquatable (ofOwnType), если они будут использоваться в качестве ключей в Словаре или подобной коллекции; это обеспечит значительный прирост производительности. Не наследуемые классы получат небольшое повышение производительности за счет реализации IEquatable (ofOwnType). Наследуемые классы // не должны // реализовывать IEquatable.
суперкат
30

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

этот. __curious_geek
источник
Еще один вопрос. В чем преимущество использования «obj as EntityBase» вместо (EntityBase) obj? Просто вопрос стиля или есть какое-то преимущество?
пожрала Элизиум
22
в случае «obj as EntityBase» - если объект obj не имеет типа EntityBase, он передаст «null» и продолжит работу без каких-либо ошибок или исключений, но в случае «(EntityBase) obj» он принудительно попытается привести объект obj в EntityBase, и если объект не относится к типу EntityBase, он выдаст InvalidCastException. И да, «как» может применяться только к ссылочным типам.
это __curious_geek
1
Похоже, что ссылка Джоша на блог Джареда Пэра предполагает, что вам также необходимо переопределить GetHashCode. Разве это не так?
Дружелюбный
3
Я действительно не понимаю, какую дополнительную ценность обеспечивает ваша реализация. Можете ли вы прояснить проблему, которую решает ваш абстрактный базовый класс?
Мерт Акчакая
1
@Amicable - да, всякий раз, когда вы переопределяете Object.Equals (Object), вы также должны переопределять GetHashCode, чтобы контейнеры работали.
Нэмфорд
0

Если мы позвоним 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().

Сейедрауф Модарреси
источник