Я хочу зрозуміти сценарії, де IEqualityComparer<T>
і як IEquatable<T>
слід їх використовувати. Документація MSDN для обох виглядає дуже схоже.
T
реалізації IEquatable<T>
. Невже колекція на зразок List<T>
має якусь тонку помилку в ній інакше?
Я хочу зрозуміти сценарії, де IEqualityComparer<T>
і як IEquatable<T>
слід їх використовувати. Документація MSDN для обох виглядає дуже схоже.
T
реалізації IEquatable<T>
. Невже колекція на зразок List<T>
має якусь тонку помилку в ній інакше?
Відповіді:
IEqualityComparer<T>
є інтерфейсом для об’єкта, який виконує порівняння на двох об'єктах типу T
.
IEquatable<T>
призначений для об'єкта типу, T
щоб він міг порівнювати себе з іншим того ж типу.
IEqualityComparer<T>
- це інтерфейс для об'єкта (який, як правило, відрізняється легким класом T
), який забезпечує функції порівняння, що працюєT
Вирішуючи, використовувати IEquatable<T>
чи використовувати IEqualityComparer<T>
, можна запитати:
Чи є кращий спосіб перевірити два екземпляри
T
рівності, або є кілька однаково справедливих способів?
Якщо є лише один спосіб тестування двох екземплярів T
рівності або якщо кращим є один із декількох методів, то це IEquatable<T>
був би правильний вибір: Цей інтерфейс повинен бути реалізований лише T
сам, так що один екземпляр T
має внутрішні знання про Як порівняти себе з іншим екземпляром T
.
З іншого боку, якщо існує кілька однаково розумних методів порівняння двох T
s для рівності, 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>
реалізація, ви можете просто замінити її, якщо вона недостатньо хороша для ваших цілей.
IEqualityComparer<T>
реалізація повинна реалізувати відношення еквівалентності, що означає, що у всіх випадках, коли два об'єкти порівнюються рівними третині, вони повинні порівнювати однакові між собою. Будь-який клас, який реалізує IEquatable<T>
або IEqualityComparer<T>
таким чином суперечить вищесказаному, порушується.
IEqualityComparer<String>
що вважає "привіт" рівним і "Hello", і "hElLo", повинно вважати "Hello" і "hElLo" рівними один одному, але для більшості методів порівняння це не буде проблемою.
GetHashCode
слід швидко визначити, чи відрізняються дві величини. Правила приблизно такі: (1) GetHashCode
завжди повинен створювати один і той же хеш-код для одного і того ж значення. (2) GetHashCode
має бути швидким (швидшим, ніж Equals
). (3) GetHashCode
не має бути точним (не таким точним, як Equals
). Це означає, що він може створювати один і той же хеш-код для різних значень. Чим точніше ви зможете це зробити, тим краще, але, мабуть, важливіше тримати його швидко.
Ви вже отримали основне визначення того, що вони є . Коротше кажучи, якщо ви реалізуєте 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
. буде редагувати.
person.GetHashCode
.... навіщо ви це перекривали? - Ми перекриваємо, оскільки вся суть IEqualityComparer
полягає в тому, щоб згідно з нашими правилами проводити інше порівняння - правила, які ми справді добре знаємо.
Age
властивості, тому я закликаю Age.GetHashCode
. Вік є тип int
, але що б не було тип договору хеш-коду в .NET, це different objects can have equal hash but equal objects cant ever have different hashes
. Це контракт, в який ми можемо сліпо вірити. Впевнені, що наші власні порівняльники також повинні дотримуватися цього правила. Якщо коли-небудь дзвінок someObject.GetHashCode
порушує речі, то це проблема з реалізаторами типу someObject
, це не наша проблема.
IEqualityComparer призначений для використання, коли рівність двох об'єктів здійснюється зовні, наприклад, якщо ви хочете визначити порівняльник для двох типів, для яких у вас не було джерела, або у випадках, коли рівність між двома речами має сенс лише в деякому обмеженому контексті.
IEquatable - це сам об'єкт (той, який порівнюється за рівність).
Один порівнює два T
с. Інший може порівнювати себе з іншими T
s. Зазвичай вам потрібно буде використовувати лише один, а не обидва.
EqualityComparer<T>
замість того, щоб реалізовувати інтерфейс "тому щоEqualityComparer<T>
тестує рівність за допомогоюIEquatable<T>