Какая связь между GetHashCode и Equals в реализации IEqualityComparer?

У меня есть класс A, который наследуется от класса B и реализует IEqualityComparer<A>. Это означает, что класс A предоставляет собственную реализацию методов Equals и GetHashCode. Все идет нормально. Проблема в том, что я не понимаю, почему код ведет себя следующим образом:

отладчик достигнет точки останова реализации Equals A только в том случае, если реализация GetHashCode A возвращает this.GetHashCode() вместо obj.GetHashCode(), где «obj» является параметром, определяемым сигнатурой GetHashCode (в моем случае это переменная типа A).

Интуитивно я подумал, что должен вернуть хэш-код полученного объекта, но при этом компилятор игнорирует реализацию Equals экземпляра.

Почему так происходит?

Демонстрация кода:

public class A : B, IEqualityComparer<A>
{
    public bool Equals(A x, A y)
    {
        //my implementation...
    }

    public int GetHashCode(A obj)
    {
        //return obj.GetHashCode(); -> this makes my Equals implementation above be ignored! Why?
        return this.GetHashCode(); -> my Equals implementation is used
    }
}

person Veverke    schedule 08.04.2014    source источник
comment
Можете ли вы опубликовать код, объясняющий проблему, трудно угадать правильно   -  person Sriram Sakthivel    schedule 08.04.2014


Ответы (2)


Похоже, вы используете неправильный интерфейс. IEqualityComparer<> обычно используется для класса, который сравнивает экземпляры других типов.

Ваш тип должен просто реализовывать IEquatable<A> и переопределять Equals(object) и GetHashCode(). Обратите внимание на подписи.

Нравится:

public class A : B, IEquatable<A>
{
    public bool Equals(A other)
    {
       if (other == null || GetType() != other.GetType())
           return false;

       //your implementation
    }

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

    public override int GetHashCode()
    {
        //your implementation
    }
}

Затем вы можете делать такие вещи, как someEnumerableOfA.Distinct(), и метод Linq будет использовать вашу реализацию.

Другой вариант - сделать:

public class A : B // no interfaces
{
}

public class AEqualComparer : IEqualityComparer<A>
{
    public bool Equals(A x, A y)
    {
       //your implementation
    }

    public int GetHashCode(A x)
    {
        //your implementation
    }
}

Для этого другого варианта вам понадобится someEnumerableOfA.Distinct(new AEqualComparer ()).

person Jeppe Stig Nielsen    schedule 08.04.2014
comment
Причина, по которой я выбрал IEqualityComparer, заключается в том, что мое решение основывается на вызове IEnumerable ‹›. Distinct ‹› (IEqualityComparer ‹›). Другими словами, если я хочу вернуть отдельные элементы моей коллекции, почему я не могу использовать здесь Distinct и, таким образом, должен предоставить реализацию IEqualityComparer? - person Veverke; 08.04.2014
comment
OP реализует правильный интерфейс, но не так. Для получения дополнительной информации обратитесь к связанной дублирующей ветке. - person Sriram Sakthivel; 08.04.2014
comment
@Veverke, если вы переопределите Equals и GetHashCode, вам не нужно будет реализовывать или предоставлять IEqualityComparer<T>. - person Jodrell; 08.04.2014
comment
@SriramSakthivel Не согласен. У вас не должно быть класса A реализация IEqualityComparer<A>. Это неправильное использование. Посмотрите на String класс. Реализует IEquatable<String>. Однако есть и другие классы, например StringComparer, которые реализуют IEqualityComparer<String> (но не IEqualityComparer<StringComparer>). - person Jeppe Stig Nielsen; 08.04.2014
comment
@Jodrell: А как будет мой вызов IEnumerable для Distinct? variable.Distinct (this) - где текущий класс переопределяет Equals и GetHashCode? - person Veverke; 08.04.2014
comment
@JeppeStigNielsen Я знаю это; вы правы в том, что неправильно используете интерфейс, но это не должно вызывать никаких проблем, поскольку подпись отличается. как вы сказали, должен быть класс компаратора, предназначенный для простого сравнения. - person Sriram Sakthivel; 08.04.2014
comment
@Jodrell: примечание: у этого есть обратная сторона: нужно преобразовывать объект в мой собственный тип ... - person Veverke; 08.04.2014
comment
@SriramSakthivel Совершенно верно. Это похоже на то, как если я создаю класс Car, я могу или не могу выбрать Car внутреннее равенство, сопоставимое с другими Car, например, проверка совпадения номеров регистрационных знаков. Но тогда я мог бы сделать еще class ByColorCarEqualityComparer : IEqualityComparer<Car> или class ByBrandAndModelIDCarEqualityComparer : IEqualityComparer<Car>. Но создавать автомобиль, целью которого является сравнение автомобилей по некоторым критериям, сбивает с толку. Машины предназначены для вождения. Это нечто иное, чем сравнивающие машины. - person Jeppe Stig Nielsen; 08.04.2014
comment
Опять же, я полностью с вами согласен. Я хочу сказать, что технически нет ничего неправильного, но это неправильный дизайн, который вносит путаницу и т. Д., Но ... Это будет работать, я говорю, и предлагаемый дубликат говорит, почему он не работает. - person Sriram Sakthivel; 08.04.2014

Реализация IEqualityComparer<T> не override базовая реализация GetHashCode и Equals.

Реализация IEqualityComparer<T> позволяет вам предоставить экземпляр разработчика в качестве компаратора равенства для T. Это общий параметр для нескольких расширений linq и универсальных конструкторов коллекций.

Переопределение Equals и GetHashCode влияет на способ проверки экземпляров класса на равенство. Использование других имплементаций, вызывающих Equals и GetHashCode, таких как базовые операторы = и !=, расширения linq и общие конструкторы коллекций, для которых вы не предоставляете альтернативу IEqualityComparer<T>.

Эти концепции похожи, но служат разным целям, они не являются частично взаимозаменяемыми.


Позвольте мне расширить пример,

public class A
{
    public string Value1 { get; set; }
    public int Value2 { get; set; }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;
            hash = (hash * 23) + 
                StringComparer.Ordinal.GetHashCode(this.Value1);
            hash = (hash * 23) + this.Value2;
            return hash;
        }
    }

    public override bool Equals(object obj)
    {
        var a = obj as A;
        if (a == null)
        {
            return false;
        }

        if (a.Value2 != this.Value2)
        {
            return false;
        }

        return StringComparer.Ordinal.Equals(
            a.Value1,
            this.Value1);
    }
}

Эта реализация A правильно переопределяет Equals и GetHashCode, этого изменения достаточно, чтобы гарантировать, что после вызова расширения linq

var distinct = aSequneceOfA.Distinct();

distinct не будет содержать экземпляров с одинаковыми Value2 и обычно сопоставимыми Value1. Для этого не требуется никакой другой реализации интерфейса.


Теперь предположим, что в какой-то ситуации мне не понравилось это порядковое сравнение для Value1, возможно, мне нужна некоторая нечувствительность к регистру. Я мог бы реализовать новый компаратор равенства.

public class AComparerInsensitive : IEqualityComparer<A>
{
    public bool Equals(A x, A y)
    {
        if (x == null)
        {
            return y == null;
        }

        if (y == null)
        {
            return false;
        }

        if (x.Value2 != y.Value2)
        {
            return false;
        }

        return StringComparer.CurrentCultureIgnoreCase.Equals(
            x.Value1,
            y.Value1)
    }

    public int GetHashCode(A a)
    {
        if (a == null)
        {
            return 0;
        }

        unchecked
        {
            int hash = 17;
            hash = (hash * 23) + 
                StringComparer.CurrentCultureIgnoreCase.GetHashCode(
                    a.Value1);
            hash = (hash * 23) + a.Value2;
            return hash;
        }
    }
}

Это позволило бы мне вызвать альтернативную перегрузку Distinct,

var insensitivelyDistinct = aSequneceOfA.Distinct(
    new AComparerInsensitive());

Эта перегрузка различных элементов As переопределяет Equals и GetHashCode и использует AComparerInsensitive для выполнения сравнения.

person Jodrell    schedule 08.04.2014
comment
@Veverke Я надеюсь, что мой расширенный ответ проясняет ситуацию. - person Jodrell; 08.04.2014