Search code examples
c#equalsgethashcode

Is there a way to reduce amount of boilerplate code in Equals and GetHashCode?


I frequently have to override Equals and GetHashCode methods for the purpose of unit testing. After this my classes begin to look like this:

public class TestItem
{
    public bool BoolValue { get; set; }

    public DateTime DateTimeValue { get; set; }

    public double DoubleValue { get; set; }

    public long LongValue { get; set; }

    public string StringValue { get; set; }

    public SomeEnumType EnumValue { get; set; }

    public decimal? NullableDecimal { get; set; }

    public override bool Equals(object obj)
    {
        var other = obj as TestItem;

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

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

        return this.BoolValue == other.BoolValue
            && this.DateTimeValue == other.DateTimeValue
            && this.DoubleValue == other.DoubleValue // that's not a good way, but it's ok for demo
            && this.EnumValue == other.EnumValue
            && this.LongValue == other.LongValue
            && this.StringValue == other.StringValue
            && this.EnumValue == other.EnumValue
            && this.NullableDecimal == other.NullableDecimal;
    }

    public override int GetHashCode()
    {
        return this.BoolValue.GetHashCode()
            ^ this.DateTimeValue.GetHashCode()
            ^ this.DoubleValue.GetHashCode()
            ^ this.EnumValue.GetHashCode()
            ^ this.LongValue.GetHashCode()
            ^ this.NullableDecimal.GetHashCode()
            ^ (this.StringValue != null ? this.StringValue.GetHashCode() : 0);
    }
}

While it's not hard to do it, time after time it gets boring and error prone to maintain list of same fields in Equals and GetHashCode. Is there any way to list filelds used for equality checking and hash code function only once? Equals and GetHashCode should be implemented in terms of this setup list.

In my imagination configuration and usage of such setup list may look like

public class TestItem
{
    // same properties as before

    private static readonly EqualityFieldsSetup Setup = new EqualityFieldsSetup<TestItem>()
        .Add(o => o.BoolValue)
        .Add(o => o.DateTimeValue)
        // ... and so on
        // or even .Add(o => o.SomeFunction())

    public override bool Equals(object obj)
    {
        return Setup.Equals(this, obj);
    }

    public override int GetHashCode()
    {
        return Setup.GetHashCode(this);
    }
}

There's a way to auto implement hashCode and equals in java, project lombok for example. I wonder is there anything serving the purpose of reducing boilerplate code readily available for C#.


Solution

  • I've done some research and found several components that were not quite what I wanted:

    And also a couple of related discussions:

    So far idea of having explicitly configured list of members seemed unique. And I implemented my own library https://github.com/msugakov/YetAnotherEqualityComparer. It's better than the code suggested by TylerOhlsen in that it does not box extracted members and it uses EqualityComparer<T> to compare members.

    Now the code looks like:

    public class TestItem
    {
        private static readonly MemberEqualityComparer<TestItem> Comparer = new MemberEqualityComparer<TestItem>()
            .Add(o => o.BoolValue)
            .Add(o => o.DateTimeValue)
            .Add(o => o.DoubleValue) // IEqualityComparer<double> can (and should) be specified here
            .Add(o => o.EnumValue)
            .Add(o => o.LongValue)
            .Add(o => o.StringValue)
            .Add(o => o.NullableDecimal);
    
        // property list is the same
    
        public override bool Equals(object obj)
        {
            return Comparer.Equals(this, obj);
        }
    
        public override int GetHashCode()
        {
            return Comparer.GetHashCode(this);
        }
    }
    

    Also the MemberEqualityComparer implements IEqualityComparer<T> and follows its semantics: it can successfully compare default(T) which may be null for reference types and Nullables.

    UPDATE: There are tools that can solve the same problem of creating member based IEqualityComparer<T> but also these can provide composite IComparer<T>!