5

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#.

Mike
  • 2,468
  • 3
  • 25
  • 36
  • override the tostring with the proper representation of your object state,its hashcode will be based on that input. – terrybozzio Jul 09 '13 at 21:07
  • 1
    @terrybozzio `ToString()` comparison? Never do that. First off, it's costly. A comparison function should be fast. Secondly, it doesn't even cut down the boilerplate, it just moves it somewhere else. – Simon Belanger Jul 09 '13 at 21:11
  • @Mike: I doubt it. Your implementation of GetHashCode above is very specific. I might not be satisfied with that implementation at all. Same (to a lesser extent) with Equals. – 500 - Internal Server Error Jul 09 '13 at 21:14
  • with the proper tostring override is testitem class equals override would be redouced to return obj.ToString() == this.ToString().Since everything supports ToString we dont even have to check for correct type – terrybozzio Jul 09 '13 at 21:15
  • @terrybozzio, but you *still* have to implement an equally cumbersome `.ToString()` method. – Kirk Woll Jul 09 '13 at 21:16
  • more or less but compared to equals = cumbersome and gethashcode = cumbersome?with one you get the 3 – terrybozzio Jul 09 '13 at 21:17
  • If you have that many values in the object, I'd seriously question if you actually really need to do a member for member override. For as long as I've been programming I've never felt the need to override GetHashCode, and if I've overridden equals, it's usually been for a struct where I was overriding the == and != operators as well. – Pharap Jul 09 '13 at 21:20
  • @terrybozzio While your suggestion may seem attractive I would not want to implement `Equals` and `GetHashCode` in terms of `ToString` because I would have to rely on properties types to also override ToString which is not always the case. And yes it can be way slower than doing property-by-property comparison – Mike Jul 09 '13 at 21:22
  • @mike it could be said that your gethashcode implementation is flawed, ideally it should be constant across the lifetime of the object, but you are basing on mutable state – user2509738 Jul 09 '13 at 21:25
  • ok then,what pharap said is the best practice,one is always,always override tostring with your key fields,then in hashcode if u must first try to find a field that will be unique to all instances and return its hashcode,if you dont have that "unique" field then leverraging tostring would be better. – terrybozzio Jul 09 '13 at 21:34
  • @user2509738 Yes, you are right, and XOR may not always be the best choice also. But I think ideal implementation won't change the fact that each field that intended to have an influence on a hash function should be used there. – Mike Jul 09 '13 at 21:36
  • @500-InternalServerError Please, share your opinion on `GetHashCode` http://codereview.stackexchange.com/questions/28372/please-criticize-composite-gethashcode-function! – Mike Jul 11 '13 at 15:17

2 Answers2

2

I think it would be possible to implement pretty much the same thing as Lombok in C#, but I'm not feeling that ambitious at the moment.

I believe this is what you are after though (pretty much exactly as you have described it). This implementation does box all value types into objects, so it's not the most efficient implementation, but it should be good enough for your purpose of unit tests.

public class EqualityFieldsSetup<T>
    where T : class
{
    private List<Func<T, object>> _propertySelectors;

    public EqualityFieldsSetup()
    {
        _propertySelectors = new List<Func<T, object>>();
    }

    public EqualityFieldsSetup<T> Add(Func<T, object> propertySelector)
    {
        _propertySelectors.Add(propertySelector);
        return this;
    }

    public bool Equals(T objA, object other)
    {
        //If both are null, then they are equal 
        //    (a condition I think you missed)
        if (objA == null && other == null)
            return true;

        T objB = other as T;

        if (objB == null)
            return false;

        if (object.ReferenceEquals(objA, objB))
            return true;

        foreach (Func<T, object> propertySelector in _propertySelectors)
        {
            object objAProperty = propertySelector.Invoke(objA);
            object objBProperty = propertySelector.Invoke(objB);

            //If both are null, then they are equal
            //   move on to the next property
            if (objAProperty == null && objBProperty == null)
                continue;

            //Boxing requires the use of Equals() instead of '=='
            if (objAProperty == null && objBProperty != null ||
                !objAProperty.Equals(objBProperty))
                return false;
        }

        return true;
    }

    public int GetHashCode(T obj)
    {
        int hashCode = 0;

        foreach (Func<T, object> propertySelector in _propertySelectors)
        {
            object objProperty = propertySelector.Invoke(obj);

            if (objProperty != null)
                hashCode ^= objProperty.GetHashCode();
        }

        return hashCode;
    }
}
TylerOhlsen
  • 5,485
  • 1
  • 24
  • 39
  • 1
    Great, it does not look that hard to build such tool! The problem here is that you have to cast lambda return values to `object`. And I would suggest using `EquailtyComparer` for comparing member values. – Mike Jul 10 '13 at 08:42
  • 1
    Even though the properties are stored in a variable of type object, the Equals method is still going to use the (possibly overridden) Equals method of the derived type. What I have written here is functionally equivalent to yours above. The only difference is that value types are implicitly boxed into objects. – TylerOhlsen Jul 10 '13 at 14:09
  • 1
    And the casting to object in your lambda can be implicit. Syntactically, this answer works exactly as you had suggested in your question. Functionally, it is equivalent (with an additional step this needs to do to box value types). – TylerOhlsen Jul 10 '13 at 14:13
2

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

Mike
  • 2,468
  • 3
  • 25
  • 36