Skip to content

GetHashCode returns different hash for Equal units #1017

Closed
@dschuermans

Description

@dschuermans

Describe the bug
.GetHashCode() returns different hash for 2 units for which .Equals() return true

To Reproduce

Length inCm = Length.FromCentimeters(100);
Length inM = Length.FromMeters(1);

Assert.AreEqual(inCm, inM);
Assert.AreNotEqual(inCm.GetHashCode(), inM.GetHashCode());

Expected behavior
The GetHashCode() should return the same value because the .Equals() returns true

Additional context
From the remarks:

Two objects that are equal return hash codes that are equal. However, the reverse is not true: equal hash codes do not imply object equality, because different (unequal) objects can have identical hash codes.

I've peeked in the sources and I think the issue is, that the value from which the unit is constructed is used when calculating the hashcode:

/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>A hash code for the current Length.</returns>
public override int GetHashCode()
{
return new { Info.Name, Value, Unit }.GetHashCode();
}

Shouldn't this be normalized to the unit's base unit or something along those lines?

Reason I'm asking is because I'm doing IEquatable implementations on my objects and in the .Equals(...) method, I'm comparing Units to a max of 5 decimals while in the GetHashCode() method I'm simply using the GetHashCode() method from the unit.

During the PR someone pointed out that for the GetHashCode(), the unit should also be rounded using the same logic as in the .Equals(...) method:

        /// <inheritdoc />
        public bool Equals(ResultAnalysis1DInternalForces other)
        {
            if (ReferenceEquals(null, other))
            {
                return false;
            }

            if (ReferenceEquals(this, other))
            {
                return true;
            }

            return base.Equals(other)
                && Equals(Member, other.Member)
                && Equals(MemberRib, other.MemberRib)
                && Index == other.Index
                && Section.UnitsNetEquals(other.Section)
                && N.UnitsNetEquals(other.N)
                && Vy.UnitsNetEquals(other.Vy)
                && Vz.UnitsNetEquals(other.Vz)
                && Mx.UnitsNetEquals(other.Mx)
                && My.UnitsNetEquals(other.My)
                && Mz.UnitsNetEquals(other.Mz);
        }

        /// <inheritdoc />
        public override bool Equals(object obj)
        {
            return ReferenceEquals(this, obj) || obj is ResultAnalysis1DInternalForces other && Equals(other);
        }

        /// <inheritdoc />
        public override int GetHashCode()
        {
            unchecked
            {
                int hashCode = base.GetHashCode();
                hashCode = (hashCode * 397) ^ (Member != null ? Member.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (MemberRib != null ? MemberRib.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ Index;
                hashCode = (hashCode * 397) ^ Section.GetHashCode();
                hashCode = (hashCode * 397) ^ N.GetHashCode();
                hashCode = (hashCode * 397) ^ Vy.GetHashCode();
                hashCode = (hashCode * 397) ^ Vz.GetHashCode();
                hashCode = (hashCode * 397) ^ Mx.GetHashCode();
                hashCode = (hashCode * 397) ^ Mz.GetHashCode();
                return hashCode;
            }
        }
        public static bool UnitsNetEquals<TUnit>(this TUnit value, TUnit other)
        {
            if (value == null && other == null)
            {
                return true;
            }

            if (value != null && other != null && value is IQuantity thisUnit && other is IQuantity otherUnit)
            {
                try
                {
                    double thisValue = thisUnit.Value;
                    double otherValueInThisUnits = otherUnit.As(thisUnit.Unit);

                    return Comparison.Equals(thisValue, otherValueInThisUnits, ComparingConstants.DoubleComparisionDelta, ComparisonType.Absolute);
                }
                catch (ArgumentException e)
                {
                    return false;
                }
            }

            return EqualityComparer<TUnit>.Default.Equals(value, other);
        }

Metadata

Metadata

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions