EqualityComparer‹T›.Default не возвращает производный EqualityComparer

У меня есть класс Person, и я создал класс сравнения равенства, производный от EqualityComparer ‹ Person >. Тем не менее, EqualityComparer по умолчанию не вызывает функцию Equals моего компаратора равенства

Согласно MSDN EqualityComparer ‹ T > свойство .Default:

Свойство Default проверяет, реализует ли тип T интерфейс System.IEquatable, и, если да, возвращает EqualityComparer, использующий эту реализацию. В противном случае он возвращает EqualityComparer, который использует переопределения Object.Equals и Object.GetHashCode, предоставленные T.

В приведенном ниже (упрощенном) примере класс Person не реализует реализацию System.IEquatable ‹ Person >. Поэтому я ожидаю, что PersonComparer.Default вернет экземпляр PersonComparer.

Однако PersonComparer.Equals не вызывается. Выходные данные отладки отсутствуют, а возвращаемое значение ложно.

public class Person
{
    public string Name { get; set; }
}

public class PersonComparer : EqualityComparer<Person>
{
    public override bool Equals(Person x, Person y)
    {
        Debug.WriteLine("PersonComparer.Equals called");
        return true;
    }

    public override int GetHashCode(Person obj)
    {
        Debug.WriteLine("PersonComparer.GetHasCode called");
        return obj.Name.GetHashCode();
    }
}

public static void Main()
{
    Person x = new Person() { Name = "x" };
    Person y = new Person() { Name = "x" };
    bool b1 = PersonComparer.Default.Equals(x, y);
}

Вопрос: что я делаю не так?

На случай, если вы спросите, почему я не хочу реализовывать IEquatable ‹ Person >.

Моя проблема сравнима со сравнением строк. Иногда вы хотите, чтобы две строки были равны, если они являются абсолютно одинаковыми строками, иногда вы хотите игнорировать регистр, а иногда вы хотите обрабатывать символы как óò и т. д., как если бы они были символом o.

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

Я хочу проверить это в модульном тесте: если вы что-то поместите в него, вы должны получить ключ, который можно использовать для извлечения элемента. Увы, база данных возвращает не того же человека, а производный от него класс (по крайней мере, при использовании EF 6). Поэтому я не могу использовать обычный IEquatable, который должен возвращать false, если объекты не одного типа. Вот почему я хотел использовать специальный компаратор, который объявляет два класса Person равными, если они имеют одинаковые значения свойств, даже если они оба являются разными производными классами от Person. Очень сопоставим как компаратор строк, который принимает O и o и ó равными


person Harald Coppoolse    schedule 26.11.2015    source источник
comment
Процитированная документация дает вам ответ - она ​​использует компаратор, который использует методы Equals(object) и GetHashCode для Person. Как вы думаете, почему он будет использовать PersonComparer?   -  person Lee    schedule 26.11.2015
comment
Вам просто нужно позвонить new PersonComparer() вместо PersonComparer.Default..   -  person Dennis_E    schedule 26.11.2015
comment
Иногда вам нужно, чтобы две строки были равны, если они точно такие же строки, иногда вы хотите…. Да, и когда у вас есть куча иногда, у вас может быть значение по умолчанию, которое Person должно делать само по себе и которое, следовательно, будет использовать EqualityComparer<Person>.Default.   -  person Jon Hanna    schedule 26.11.2015
comment
Поэтому я не могу использовать обычный IEquatable, который должен возвращать false, если объекты не одного типа. Почему вы определили это именно так?   -  person Jon Hanna    schedule 26.11.2015


Ответы (3)


Вы кое-что перепутали, но это неудивительно, поскольку все должны признать, что .NET Framework имеет слишком много возможностей для сравнения равенства.

Вы должны реализовать IEqualityComparer<T> только в том случае, если вы хотите указать специальную логику сравнения для конкретных ситуаций, например, с Dictionary<TKey, TValue>.

EqualityComparer<T> сбивает с толку переопределяемый тип в .NET; однако не предполагается, что вы переопределите его, и в этом нет никакого смысла. Он предоставляет компаратор по умолчанию для универсальных типов и вызовет вашу реализацию IEquatable<T>, если вы используете T в List<T> (Contains, IndexOf и т. д.) или когда T является ключом словаря, и вы не передавали никаких пользовательских IEqualityComparer в Словарь.

person György Kőszeg    schedule 26.11.2015

Давайте перечитаем цитату, которую вы добавили:

Свойство Default проверяет, реализует ли тип T интерфейс System.IEquatable, и, если да, возвращает EqualityComparer, использующий эту реализацию.

Итак, свойство Default ищет реализация IEqutable<T>, которую не предоставляет ваш Person.

Если объект не реализует IEquatable<T>, то:

В противном случае он возвращает EqualityComparer, который использует переопределения Object.Equals и Object.GetHashCode, предоставляемые T.

Что показывает вам, почему именно object.Equals и object.GetHashCode вызываются. У вас есть два варианта: либо использовать new PersonComparer().Equals(), либо реализовать IEquatable<Person> в вашем типе (если такая единственная реализация возможна).

person Yuval Itzchakov    schedule 26.11.2015
comment
Вы имеете в виду, что фраза использует переопределения ... by T, не означает, что вызывается EqualityComparer.Equals(T x, T y)? Почему тогда они существуют? - person Harald Coppoolse; 26.11.2015
comment
@HaraldDutch Переопределение object.Equals и object.GetHashCode будет существовать только в том случае, если вы переопределите IEquatable<T>. Не уверен, почему документация выражает это таким образом, но в основном это означает, что для этих двух методов будет использоваться реализация по умолчанию object. - person Yuval Itzchakov; 26.11.2015
comment
@HaraldDutch Реализация CreateComparer внутри EqualityComparer показывает, что если она не соответствует определенным критериям, возвращается ObjectEqualityComparer. Что в конечном итоге приведет к использованию object.Equals, если тип не имеет пользовательской реализации Equals. - person Yuval Itzchakov; 26.11.2015

Это связано с тем, что свойство Default объекта EqualityComparer<T> возвращает не PersonComparer, а ObjectEqualityComparer<T>. И этот ObjectEqualityComparer<T>, как вы указали в документации, сравнивается с использованием Equals на Person.

См. фактический источник. В строке 89 он возвращает ObjectEqualityComparer<T>.

На самом деле в вашем коде нет ничего плохого, как вы можете видеть, когда пытаетесь запустить свой код на экземпляре PersonComparer:

bool b1 = new PersonComparer().Equals(x, y);
person Patrick Hofman    schedule 26.11.2015
comment
Я создал два переопределения и не реализовал IEquatable. Почему тогда мои переопределения не вызываются? - person Harald Coppoolse; 26.11.2015
comment
Потому что это не один и тот же объект. PersonComparer != ObjectEqualityComparer<Person>. Как говорится в документации. - person Patrick Hofman; 26.11.2015
comment
Согласен, но я ожидал, что ObjectEqualtyComparer вызовет PersonComparer.Equals(Person, Person) - person Harald Coppoolse; 26.11.2015