Объяснение поведения вложенных десятичных дробей в реализации ValueType GetHashCode / Equals - PullRequest
0 голосов
/ 14 сентября 2018

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

При устранении проблемы получится что-то вроде следующего:

public readonly struct TwoDecimals
{
    public readonly decimal First;
    public readonly decimal Second;
    public TwoDecimals(decimal first, decimal second)
    {
        this.First = first;
        this.Second = second;
    }
}

public void Main()
{
    var withoutTrailingZero = new TwoDecimals(42m, 42m);
    var withTrailingZero = new TwoDecimals(42.0m, 42m);
    var equal = withoutTrailingZero.Equals(withTrailingZero); // true
    var equalHashCodes = withoutTrailingZero.GetHashCode() == withTrailingZero.GetHashCode(); // false!
}

Это проблема,потому что если Equals истинно, GetHashCode определенно должно вернуть то же значение.

Я не переопределяю ни Equals, ни GetHashCode, поэтому пользовательская структура должна получить значение по умолчанию ValueTypeреализация, которая может зависеть от платформы.Но читая в справочном источнике здесь Я вижу комментарий с этим утверждением:

Наш алгоритм возврата хеш-кода немного сложен.Мы ищем первое нестатическое поле и получаем его [sic] хэш-код.

Очевидно, что это не так, поскольку 42.0m и 42m имеют один и тот же хэш-код, несмотря на то, что имеют разные биты.

Кажется, что вместо этого он по какой-то причине возвращается к использованию битов.К сожалению, этот метод extern, и я не уверен, как найти источник / a.

Источник ссылки имеет реализацию Equals, которая использует отражение для сравнениякаждое поле структуры.Но сначала он делает эту проверку

// if there are no GC references in this object we can avoid reflection 
// and do a fast memcmp
if (CanCompareBits(this))
    return FastEqualsCheck(thisObj, obj);

FastEqualsCheck, предположительно, просто сравнивает биты.В моем случае, это не должно использовать эту быструю проверку, потому что биты определенно отличаются, но я подозреваю, что что-то подобное может происходить в реализации для GetHashCode.


Я также пробовал аналогичныеслучаи использования double s (0.0d и -0.0d, которые имеют разные биты, но с одинаковым хеш-кодом и считаются равными) и float s.Поведение все еще было не таким, как ожидалось, но по-другому!На этот раз хеш-коды снова были другими (и не такими, как предлагается в этом комментарии к коду, равными хеш-коду первого поля).Но на этот раз метод Equals возвращает false, поэтому, по крайней мере, он соответствует.

...