Explanation for nested decimals behaviour in ValueType GetHashCode/Equals implementation

73 Views Asked by At

I was scratching my head over an unexpected failing comparison between two instances of a custom struct. I hope someone can either point me to reference source or documentation, or confirm whether this is a bug!

Distilling the issue gets something like the following:

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!
}

This is a problem, because if Equals is true, GetHashCode should definitely return the same value.

I'm not overriding either of Equals or GetHashCode, so the custom struct should get the default ValueType implementation, which may be platform-specific. But reading in the reference source here I see a comment with this claim:

Our algorithm for returning the hashcode is a little bit complex. We look for the first non-static field and get it's [sic] hashcode.

Evidently that is not true, since 42.0m and 42m have the same hashcode, despite having different bits.

It seems as if, instead, it is falling back to using the bits for some reason. Unfortunately that method is extern and I'm not sure how to find the/a source.

The reference source does have an implementation of Equals, which uses reflection to compare each field of the struct. But first, it does this check

// 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, presumably, just compares the bits. In my case, it must not be using this fast check, because the bits are definitely different, but I suspect that something similar may be happening in the implementation for GetHashCode.


I also tried analogous cases using doubles (0.0d and -0.0d which have different bits but the same hashcode and are considered 'equal') and floats. The behaviour still wasn't as expected, but in a different way! This time, the hashcodes were once again different (and not, as suggested in that code comment, equal to the hashcode of the first field). But this time, the Equals method returns false, so at least it is consistent.

0

There are 0 best solutions below