Я хочу понять сценарии, где 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» и «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 предназначен для реализации самого объекта (сравниваемого на предмет равенства).
EqualityComparer<T>
вместо реализации интерфейса ", потому чтоEqualityComparer<T>
тестирует равенство, используяIEquatable<T>