Чим відрізняється IEqualityComparer <T> від IEquatable <T>?


151

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


1
MSDN sez: "Цей інтерфейс дозволяє реалізувати індивідуальне порівняння рівності для колекцій ", що, звичайно, робиться у вибраній відповіді. MSDN також рекомендує успадковувати EqualityComparer<T>замість того, щоб реалізовувати інтерфейс "тому що EqualityComparer<T>тестує рівність за допомогоюIEquatable<T>
radarbob

... вищесказане говорить про те, що я повинен створити спеціальну колекцію для будь-якої Tреалізації IEquatable<T>. Невже колекція на зразок List<T>має якусь тонку помилку в ній інакше?
radarbob


@RamilShavaleev посилання розірвано
ChessMax

Відповіді:


117

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

IEquatable<T>призначений для об'єкта типу, Tщоб він міг порівнювати себе з іншим того ж типу.


1
Однакова різниця між IComparable / IComparer
boctulus

3
Капітан Очевидний
Степан Іваненко

(Відредаговано) IEqualityComparer<T>- це інтерфейс для об'єкта (який, як правило, відрізняється легким класом T), який забезпечує функції порівняння, що працюєT
rwong

61

Вирішуючи, використовувати 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>реалізація, ви можете просто замінити її, якщо вона недостатньо хороша для ваших цілей.


6
Запропонований метод порівняння для тестування подвійної точкової рівності порушений, оскільки будь-яка законна IEqualityComparer<T>реалізація повинна реалізувати відношення еквівалентності, що означає, що у всіх випадках, коли два об'єкти порівнюються рівними третині, вони повинні порівнювати однакові між собою. Будь-який клас, який реалізує IEquatable<T>або IEqualityComparer<T>таким чином суперечить вищесказаному, порушується.
supercat

5
Кращим прикладом можуть бути порівняння між рядками. Має сенс застосовувати метод порівняння рядків, який розглядає рядки як рівні, лише якщо вони містять однакову послідовність байтів, але є й інші корисні методи порівняння, які також складають співвідношення еквівалентності , такі як порівняння з урахуванням регістру. Зауважте, що a, IEqualityComparer<String>що вважає "привіт" рівним і "Hello", і "hElLo", повинно вважати "Hello" і "hElLo" рівними один одному, але для більшості методів порівняння це не буде проблемою.
supercat

@supercat, дякую за цінні відгуки. Цілком ймовірно, що я не обійдусь переглянути свою відповідь у найближчі кілька днів, тому, якщо ви хочете, будь ласка, не соромтесь редагувати, як вважаєте за потрібне.
stakx - більше не вносять

Це саме те, що мені було потрібно і що я роблю. Однак існує потреба в заміні GetHashCode, в якому він поверне помилкові, коли значення будуть іншими. Як ви обробляєте свій GetHashCode?
teapeng

@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()
hyankov

@HristoYankov повинен повернутися obj.Age.GetHashCode. буде редагувати.
nawfal

Поясніть, будь ласка, чому порівняльник повинен робити таке припущення щодо того, з чим порівнюється хеш об'єкта? Ваш порівняльник не знає, як Person обчислює свій хеш, чому б ви це перекрили?
hyankov

@HristoYankov Ваш порівняльник не знає, як Особа обчислює свій хеш - Впевнений, тому ми нікуди прямо не дзвонили 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с. Інший може порівнювати себе з іншими Ts. Зазвичай вам потрібно буде використовувати лише один, а не обидва.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.