В чем разница между IEqualityComparer <T> и IEquatable <T>?

151

Я хочу понять сценарии, где IEqualityComparer<T>и IEquatable<T>должны быть использованы. Документация MSDN для обоих выглядит очень похоже.

Тилак
источник
1
MSDN ОЭЗ: «Этот интерфейс позволяет реализовать индивидуальные сравнения равенства для коллекций. » , Которая, конечно , выводится в выбранном ответе. MSDN также рекомендует наследовать EqualityComparer<T>вместо реализации интерфейса ", потому что EqualityComparer<T>тестирует равенство, используяIEquatable<T>
radarbob
... выше предлагает мне создать собственную коллекцию для любой Tреализации IEquatable<T>. Может ли в коллекции List<T>быть какая-то тонкая ошибка?
Радар Боб
хорошее объяснение anotherchris.net/csharp/…
Рамиль Шавалеев
@RamilShavaleev ссылка не работает
ChessMax

Ответы:

117

IEqualityComparer<T>является интерфейсом для объекта, который выполняет сравнение двух объектов типа T.

IEquatable<T>для объекта типа, Tчтобы он мог сравнивать себя с другим объектом того же типа.

Джастин Нисснер
источник
1
Такая же разница между IComparable / IComparer
boctulus
3
Капитан Очевидность
Степан Иваненко
(Отредактированный) IEqualityComparer<T>- это интерфейс для объекта (который обычно отличается от легкого класса T), который предоставляет функции сравнения, которые работаютT
rwong
62

Решая, использовать ли IEquatable<T>или IEqualityComparer<T>, можно спросить:

Есть ли предпочтительный способ проверки двух экземпляров Tна равенство или есть несколько одинаково допустимых способов?

  • Если существует только один способ проверки двух экземпляров Tна равенство или если предпочтителен один из нескольких методов, тогда IEquatable<T>будет правильным выбором: этот интерфейс должен быть реализован только Tсам по себе, так что один экземпляр Tимеет внутренние знания о как сравнить себя с другим экземпляром T.

  • С другой стороны, если есть несколько одинаково разумных методов сравнения двух Ts на равенство, IEqualityComparer<T>может показаться более подходящим: этот интерфейс предназначен не для реализации Tсам по себе, а для других «внешних» классов. Следовательно, при проверке двух экземпляров Tна равенство, поскольку Tвнутреннее понимание равенства отсутствует, вам придется сделать явный выбор IEqualityComparer<T>экземпляра, который выполняет тест в соответствии с вашими конкретными требованиями.

Пример:

Давайте рассмотрим эти два типа (которые должны иметь семантику значений ):

interface IIntPoint : IEquatable<IIntPoint>
{
    int X { get; }
    int Y { get; }
}

interface IDoublePoint  // does not inherit IEquatable<IDoublePoint>; see below.
{
    double X { get; }
    double Y { get; }
}

Почему только один из этих типов наследуется IEquatable<>, а не другой?

В теории, есть только один разумный способ сравнения двух экземпляров любого типа: они равны , если Xи Yсвойства в обоих случаях одинаковы. В соответствии с этим подходом оба типа должны быть реализованы IEquatable<>, поскольку маловероятно, что существуют другие значимые способы проведения теста на равенство.

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

sealed class DoublePointNearEqualityComparerByTolerance : IEqualityComparer<IDoublePoint>
{
    public DoublePointNearEqualityComparerByTolerance(double tolerance) {  }
    
    public bool Equals(IDoublePoint a, IDoublePoint b)
    {
        return Math.Abs(a.X - b.X) <= tolerance  &&  Math.Abs(a.Y - b.Y) <= tolerance;
    }
    
}

Обратите внимание, что на странице, на которую я ссылался (выше), явно указано, что у этого теста на близкое равенство есть некоторые недостатки. Поскольку это IEqualityComparer<T>реализация, вы можете просто заменить ее, если она не подходит для ваших целей.

Stakx - больше не помогает
источник
6
Предложенный метод сравнения для проверки равенства двух точек нарушен, поскольку любая легитимная IEqualityComparer<T>реализация должна реализовывать отношение эквивалентности, что подразумевает, что во всех случаях, когда два объекта сравниваются равными третьему, они должны сравниваться равными друг другу. Любой класс, который реализует IEquatable<T>или каким- либо IEqualityComparer<T>образом, противоречащим вышеприведенному, нарушается.
Суперкат
5
Лучшим примером будут сравнения строк. Имеет смысл иметь метод сравнения строк, который рассматривает строки как равные, только если они содержат одинаковую последовательность байтов, но есть и другие полезные методы сравнения, которые также составляют отношения эквивалентности , такие как сравнения без учета регистра. Обратите внимание, что a, IEqualityComparer<String>который считает «hello» равным «Hello» и «hElLo», должен считать «Hello» и «hElLo» равными друг другу, но для большинства методов сравнения это не будет проблемой.
Суперкат
@supercat, спасибо за ценный отзыв. Вполне вероятно, что я не буду пересматривать свой ответ в ближайшие несколько дней, поэтому, если хотите, пожалуйста, не стесняйтесь редактировать, как считаете нужным.
stakx - больше не вносит вклад
Это именно то, что мне было нужно и чем я занимаюсь. Однако существует необходимость переопределить GetHashCode, в котором он будет возвращать значение false, если значения отличаются. Как вы справляетесь с вашим GetHashCode?
Чайник
@teapeng: Не уверен, о каком конкретном случае использования вы говорите. Вообще говоря, GetHashCodeдолжен позволить быстро определить, отличаются ли два значения. Правила примерно таковы: (1) GetHashCode всегда должен выдавать один и тот же хэш-код для одного и того же значения. (2) GetHashCode должно быть быстро (быстрее, чем Equals). (3) GetHashCode не должен быть точным (не таким точным, как Equals). Это означает, что он может создать один и тот же хэш-код для разных значений. Чем точнее вы можете сделать это, тем лучше, но, вероятно, важнее сохранить его быстрым.
stakx - больше не участвует
27

Вы уже получили базовое определение того, что они есть . Короче говоря, если вы реализуете IEquatable<T>в классе T, Equalsметод объекта типа Tсообщает вам, равен ли сам объект (проверяемый на равенство) другому экземпляру того же типа T. Принимая во внимание, IEqualityComparer<T>что для проверки равенства любых двух экземпляров T, как правило, выходит за рамки экземпляров T.

Относительно того, для чего они нужны, поначалу сбивает с толку. Из определения должно быть ясно, что следовательно IEquatable<T>(определенный в самом классе T) де-факто должен быть стандарт для представления уникальности его объектов / экземпляров. HashSet<T>, Dictionary<T, U>(Учитывая GetHashCodeперекрывается, а), Containsна и List<T>т.д. использовать этот. Реализация IEqualityComparer<T>на Tне помогает указанных выше общих случаев. Впоследствии, есть небольшая ценность для реализации IEquatable<T>на любом другом классе кроме T. Это:

class MyClass : IEquatable<T>

редко имеет смысл.

С другой стороны

class T : IEquatable<T>
{
    //override ==, !=, GetHashCode and non generic Equals as well

    public bool Equals(T other)
    {
        //....
    }
}

как это должно быть сделано.

IEqualityComparer<T>может быть полезно, когда вам требуется пользовательская проверка равенства, но не как общее правило. Например, в какой- Personто момент в классе вам может потребоваться проверить равенство двух людей в зависимости от их возраста. В этом случае вы можете сделать:

class Person
{
    public int Age;
}

class AgeEqualityTester : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y)
    {
        return x.Age == y.Age;
    }

    public int GetHashCode(Person obj)
    {
        return obj.Age.GetHashCode;
    }
}

Чтобы проверить их, попробуйте

var people = new Person[] { new Person { age = 23 } };
Person p = new Person() { age = 23 };

print people.Contains(p); //false;
print people.Contains(p, new AgeEqualityTester()); //true

Точно так же IEqualityComparer<T>на Tне имеет смысла.

class Person : IEqualityComparer<Person>

Правда, это работает, но не выглядит хорошо для глаз и побеждает логику.

Обычно то, что вам нужно IEquatable<T>. Также в идеале вы можете иметь только один, в IEquatable<T>то время как несколько IEqualityComparer<T>возможно на основе различных критериев.

Символы IEqualityComparer<T>и IEquatable<T>точно аналогичны Comparer<T>и IComparable<T>используются для сравнения, а не для сравнения; хорошая тема здесь, где я написал тот же ответ :)

Навфал
источник
public int GetHashCode(Person obj)должен вернутьсяobj.GetHashCode()
Хянков
@ ХристоЯнков должен вернуться obj.Age.GetHashCode. будет редактировать.
nawfal
Пожалуйста, объясните, почему компаратор должен сделать такое предположение о том, что хеш объекта он сравнивает? Ваш компаратор не знает, как Person вычисляет его хэш, зачем вам это переопределять?
Хянков
@HristoYankov Ваш компаратор не знает, как Person вычисляет его хэш - Конечно, именно поэтому мы нигде напрямую не звонили person.GetHashCode... почему бы вам это переопределить? - Мы переопределяем, потому что весь смысл в IEqualityComparerтом, чтобы иметь другую реализацию сравнения в соответствии с нашими правилами - правилами, которые мы действительно хорошо знаем.
Nawfal
В моем примере мне нужно основывать сравнение на Ageсвойстве, поэтому я звоню Age.GetHashCode. Возраст относится к типу int, но каким бы ни был тип, это контракт с хеш-кодом в .NET different objects can have equal hash but equal objects cant ever have different hashes. Это контракт, в который мы можем слепо верить. Конечно, наши пользовательские компараторы также должны придерживаться этого правила. Если когда-либо вызов someObject.GetHashCodeломает вещи, то это проблема с разработчиками типа someObject, это не наша проблема.
Nawfal
11

IEqualityComparer предназначен для использования, когда равенство двух объектов реализуется извне, например, если вы хотите определить компаратор для двух типов, для которых у вас нет источника, или для случаев, когда равенство между двумя вещами имеет смысл только в некотором ограниченном контексте.

IEquatable предназначен для реализации самого объекта (сравниваемого на предмет равенства).

Крис Шейн
источник
Вы единственный, кто фактически поднял проблему «Включение в равенство» (внешнее использование). плюс 1.
Ройи Намир
4

Один сравнивает два Tс. Другой может сравнивать себя с другими T. Обычно вам нужно использовать только один, а не оба.

Мэтт Болл
источник