Подтвердить что ты не робот

Почему Equals (объект) выигрывает над Equals (T) при использовании унаследованного объекта в Hashset или других коллекциях?

Мне известно, что при реализации IEquatable<T>.Equals(T) я всегда должен переопределять Equals(object) и GetHashCode().

Однако я не понимаю, почему в некоторых ситуациях Equals(object) выигрывает за общий Equals(T).

Например, почему происходит следующее? Если я объявляю IEquatable<T> для интерфейса и реализую для него конкретный тип X, общий Equals(object) вызывается Hashset<X> при сравнении элементов этого типа друг против друга. Во всех других ситуациях, когда по крайней мере одна из сторон передается в интерфейс, вызывается правильный Equals(T).

Вот пример кода для демонстрации:

public interface IPerson : IEquatable<IPerson> { }

//Simple example implementation of Equals (returns always true)
class Person : IPerson
{
    public bool Equals(IPerson other)
    {
        return true;
    }

    public override bool Equals(object obj)
    {
        return true;
    }

    public override int GetHashCode()
    {
        return 0;
    }
}

private static void doEqualityCompares()
{
    var t1 = new Person();

    var hst = new HashSet<Person>();
    var hsi = new HashSet<IPerson>();

    hst.Add(t1);
    hsi.Add(t1);

    //Direct comparison
    t1.Equals(t1);                  //IEquatable<T>.Equals(T)

    hst.Contains(t1);               //Equals(object) --> why? both sides inherit of IPerson...
    hst.Contains((IPerson)t1);      //IEquatable<T>.Equals(T)

    hsi.Contains(t1);               //IEquatable<T>.Equals(T)
    hsi.Contains((IPerson)t1);      //IEquatable<T>.Equals(T)
}
4b9b3361

Ответ 1

HashSet<T> вызывает EqualityComparer<T>.Default, чтобы получить сопоставитель равенства по умолчанию, когда не предоставляется сопоставитель.

EqualityComparer<T>.Default определяет, реализует ли T IEquatable<T>. Если это так, оно использует это, если нет, оно использует object.Equals и object.GetHashCode.

Объект Person реализует IEquatable<IPerson> не IEquatable<Person>.

Когда у вас есть HashSet<Person>, он заканчивает проверку, если Person является IEquatable<Person>, а его нет, поэтому он использует методы object.

Когда у вас есть HashSet<IPerson>, он проверяет, является ли IPerson IEquatable<IPerson>, что он, поэтому он использует эти методы.


Что касается оставшегося случая, почему строка:

hst.Contains((IPerson)t1);

вызовите метод IEquatable Equals, хотя его вызывали на HashSet<Person>. Здесь вы вызываете Contains на HashSet<Person> и передаете . HashSet<Person>.Contains требует, чтобы параметр был Person; a IPerson не является допустимым аргументом. Тем не менее, HashSet<Person> также является IEnumerable<Person>, а поскольку IEnumerable<T> является ковариантным, это означает, что его можно рассматривать как IEnumerable<IPerson>, который имеет метод расширения Contains (через LINQ), который принимает IPerson как параметр.

IEnumerable.Contains также использует EqualityComparer<T>.Default, чтобы получить свой сопоставитель по равенству, когда ни один не предоставлен. В случае вызова этого метода мы фактически вызываем Contains на IEnumerable<IPerson>, что означает, что EqualityComparer<IPerson>.Default проверяет, является ли IPerson IEquatable<IPerson>, что и есть, так что метод Equals называется.

Ответ 2

Хотя IComparable<in T> является контравариантным относительно T, так что любой тип, реализующий IComparable<Person>, автоматически считается реализацией IComparable<IPerson>, тип IEquatable<T> предназначен для использования с закрытыми типами, особенно структур. Требование, чтобы Object.GetHashCode() было согласуется как с IEquatable<T>.Equals(T), так и с Object.Equals(Object), как правило, подразумевает, что последние два метода должны вести себя одинаково, что, в свою очередь, подразумевает, что один из них должен быть привязан к другому. Хотя существует большая разница в производительности между передачей структуры непосредственно в реализацию IEquatable<T> правильного типа, по сравнению с построением экземпляра структуры типа boxed-heap-object и с копией реализации Equals(Object), данные структуры из что никакая такая производительность не существует с ссылочными типами. Если IEquatable<T>.Equals(T) и Equals(Object) будут эквивалентны, а T является наследуемым ссылочным типом, нет существенной разницы между:

bool Equals(MyType obj)
{
  MyType other = obj as MyType;
  if (other==null || other.GetType() != typeof(this))
    return false;
  ... test whether other matches this
}

bool Equals(MyType other)
{
  if (other==null || other.GetType() != typeof(this))
    return false;
  ... test whether other matches this
}

Последний может сохранить один тип, но это вряд ли позволит сделать достаточную разницу в производительности, чтобы оправдать наличие двух методов.