简体   繁体   English

有没有办法减少 Equals 和 GetHashCode 中的样板代码量?

[英]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.出于单元测试的目的,我经常不得不重写EqualsGetHashCode方法。 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 .虽然这并不难,但一次又一次地在EqualsGetHashCode中维护相同字段的列表变得无聊且容易出错。 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. Equals 和 GetHashCode 应该根据这个设置列表来实现。

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.有一种方法可以在 java 中自动实现hashCodeequals ,例如项目 lombok I wonder is there anything serving the purpose of reducing boilerplate code readily available for C#.我想知道是否有任何东西可以减少 C# 的样板代码。

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.我认为在 C# 中实现与 Lombok 几乎相同的东西是可能的,但我目前并没有那么雄心勃勃。

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

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 .我实现了自己的库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.它比 TylerOhlsen 建议的代码要好,因为它不会将提取的成员装箱,而是使用EqualityComparer<T>来比较成员。

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. MemberEqualityComparer 还实现了IEqualityComparer<T>并遵循其语义:它可以成功比较对于引用类型和 Nullables 可能为nulldefault(T)

UPDATE: There are tools that can solve the same problem of creating member based IEqualityComparer<T> but also these can provide composite IComparer<T> !更新:一些工具可以解决创建基于成员的IEqualityComparer<T>的相同问题,但这些工具也可以提供复合IComparer<T>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM