Типы записей: переопределение EqualityContract нарушает совпадение равенства / хэш-кода

Недавно представленные типы записей позволяют переопределить EqualityContract для типа, что позволяет создать ситуацию, когда два объекта равны, но имеют разные хэш-коды, что противоречит рекомендации по GetHashCode переопределению:

Если вы переопределите метод GetHashCode, вы также должны переопределить Equals, и наоборот. Если ваш переопределенный метод Equals возвращает true, когда два объекта проверяются на равенство, ваш переопределенный метод GetHashCode должен возвращать одно и то же значение для двух объектов.

    public record Base(string Foo);

    public record Child(string Foo, string Bar) : Base(Foo)
    {
        protected override Type EqualityContract => typeof(Base);
    }

    var b = new Base("Foo");
    var c = new Child("Foo", "Bar");
    Console.WriteLine(b == c); // True
    Console.WriteLine(b.GetHashCode() == c.GetHashCode()); // False

При удалении лишнего свойства из Child GetHashCode и Equals совпадают.

Очевидно, это можно исправить, переопределив GetHashCode, но мне интересно, почему переопределение EqualityContract не приводит автоматически к GetHashCode возвращаемому значению, совпадающему с реализацией Equals? Или есть другой способ справиться с этим, кроме ручного GetHashCode переопределения.


person Guru Stron    schedule 16.11.2020    source источник


Ответы (1)


Как вы можете видеть в части равенства членов предложения записей,

GetHashCode() возвращает int результат детерминированной функции, объединяющей следующие значения:

  • Для каждого поля экземпляра fieldN в типе записи, который не наследуется, значение System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN), где TN - тип поля, и

  • Если есть базовый тип записи, значение base.GetHashCode (); в противном случае значение System.Collections.Generic.EqualityComparer<System.Type>.Default.GetHashCode(EqualityContract).

Итак, в вашем примере GetHashCode для Base будет

public override int GetHashCode()
{
    return EqualityComparer<Type>.Default.GetHashCode(EqualityContract) + EqualityComparer<string>.Default.GetHashCode(Foo);
}

А для Child

public override int GetHashCode()
{
    return base.GetHashCode() + EqualityComparer<string>.Default.GetHashCode(Bar);
}

Для унаследованной записи EqualityContract не используется для вычисления хэш-кода. Если дополнительное свойство Bar будет удалено, будет использовано значение из Base, и вы получите равенство значений хеш-функции. Итак, требуется переопределение GetHashCode.

Такое поведение наблюдается также с помощью sharplab.io

person Pavel Anikhouski    schedule 16.11.2020
comment
Спасибо. Я прикинул то же самое (кроме поиска предложения). Тем не менее (хотя это не соответствует рекомендациям SO =) Интересно, почему =)) - person Guru Stron; 19.11.2020