Почему нет. Кроме того, (LINQ) правильно сравнивает вещи? (используя IEquatable)
У меня есть две коллекции собственных объектов ссылочного типа, которые я написал для себя свой метод IEquatable.Equals, и я хочу иметь возможность использовать методы LINQ для них.
Итак,
List<CandyType> candy = dataSource.GetListOfCandy();
List<CandyType> lollyPops = dataSource.GetListOfLollyPops();
var candyOtherThanLollyPops = candy.Except( lollyPops );
В соответствии с документацией. Кроме того, если не передавать IEqualityComparer, это должно привести к тому, что EqualityComparer.Default используется для сравнения объектов. И документация для сравнения по умолчанию:
"Свойство Default проверяет, реализует ли тип T общий универсальный интерфейс System.IEquatable, и если это так возвращает EqualityComparer, который использует эту реализацию. В противном случае он возвращает EqualityComparer, который использует переопределения Object.Equals и Object.GetHashCode, предоставленные T."
Итак, поскольку я реализую IEquatable для своего объекта, он должен использовать это и работать. Но это не так. Он не работает, пока я не переопределяю GetHashCode. Фактически, если я устанавливаю точку прерывания, мой метод IEquatable.Equals никогда не будет выполнен. Это заставляет меня думать, что он идет с планом Б в соответствии с его документацией. Я понимаю, что переопределение GetHashCode - это хорошая идея, так или иначе, и я могу заставить это работать, но я расстроен тем, что он ведет себя так, что не соответствует тому, что заявлено в его собственной документации.
Почему он не делает то, что он сказал? Спасибо.
Ответы
Ответ 1
После расследования выясняется, что все не так плохо, как я думал. В основном, когда все реализовано правильно (GetHashCode и т.д.), Документация верна, а поведение корректно. Но если вы попытаетесь сделать что-то вроде реализации IEquatable, то ваш метод Equals никогда не будет вызван (это, вероятно, связано с тем, что GetHashCode не реализован должным образом). Таким образом, хотя документация технически неверна, она ошибочна только в некоторой ситуации, которую вы никогда не захотите делать (если это исследование меня чему-то научило, то, что IEquatable является частью целого набора методов, которые вы должны реализовать атомарно ( по договоренности, а не по правилам, к сожалению)). Хорошие источники по этому поводу:
Есть ли полная ссылка на реализацию IEquatable?
http://msdn.microsoft.com/en-us/library/ms131190.aspx
http://blogs.msdn.com/irenak/archive/2006/07/18/669586.aspx
Ответ 2
Интерфейс IEqualityComparer<T>
имеет следующие два метода:
bool Equals(T x, T y);
int GetHashCode(T obj);
Хорошая реализация этого интерфейса будет таким образом реализовывать оба. Метод расширения Linq За исключением использования хеш-кода, чтобы использовать словарь или задавать внешний вид внутри, чтобы выяснить, какие объекты пропустить, и, следовательно, требует правильной реализации GetHashCode.
К сожалению, когда вы используете EqualityComparer<T>.Default
, этот класс не обеспечивает хорошую реализацию GetHashCode сам по себе и полагается на объект, тип T, чтобы предоставить эту часть, когда обнаруживает, что объект реализует IEquatable<T>
.
Проблема здесь в том, что IEquatable<T>
на самом деле не объявляет GetHashCode
, поэтому гораздо легче забыть реализовать этот метод правильно, в отличие от метода Equals
, который он объявляет.
Итак, у вас есть два варианта:
- Обеспечьте правильную реализацию
IEqualityComparer<T>
, которая реализует как Equals
, так и GetHashCode
- Убедитесь, что в дополнение к реализации
IEquatable<T>
на вашем объекте реализуйте надлежащий GetHashCode
, а также
Ответ 3
Опасность угадывания - это разные классы? Я думаю, что по умолчанию IEquatable работает только с тем же классом. Поэтому он может вернуться к методу Object.Equal.
Ответ 4
Я написал GenericEqualityComparer, который будет использоваться на лету для этих типов методов: "Разные", "Кроме", "Пересечь" и т.д.
Используйте следующим образом:
var results = list1.Except(list2, new GenericEqualityComparer<MYTYPE>((a, b) => a.Id == b.Id // OR SOME OTHER COMPARISON RESOLVING TO BOOLEAN));
Здесь класс:
public class GenericEqualityComparer<T> : EqualityComparer<T>
{
public Func<T, int> HashCodeFunc { get; set; }
public Func<T, T, Boolean> EqualityFunc { get; set; }
public GenericEqualityComparer(Func<T, T, Boolean> equalityFunc)
{
EqualityFunc = equalityFunc;
HashCodeFunc = null;
}
public GenericEqualityComparer(Func<T, T, Boolean> equalityFunc, Func<T, int> hashCodeFunc) : this(equalityFunc)
{
HashCodeFunc = hashCodeFunc;
}
public override bool Equals(T x, T y)
{
return EqualityFunc(x, y);
}
public override int GetHashCode(T obj)
{
if (HashCodeFunc == null)
{
return 1;
}
else
{
return HashCodeFunc(obj);
}
}
}