如何快速检查两个数据传输对象在C#中是否具有相同的属性?

Mat*_*tin 26 c# equals dto

我有这些数据传输对象:

public class Report 
{
    public int Id { get; set; }
    public int ProjectId { get; set; }
    //and so on for many, many properties.
}
Run Code Online (Sandbox Code Playgroud)

我不想写

public bool areEqual(Report a, Report b)
{
    if (a.Id != b.Id) return false;
    if (a.ProjectId != b.ProjectId) return false;
    //Repeat ad nauseum
    return true;
}
Run Code Online (Sandbox Code Playgroud)

有没有更快的方法来测试两个只有属性的对象是否具有相同的值(每个属性不需要一行代码或一个逻辑表达式?)

切换到结构不是一种选择.

Mar*_*ell 66

一些反思,或许Expression.Compile()用于表现?(注意这里的静态ctor确保我们只编译一次T):

using System;
using System.Linq.Expressions;

public class Report {
    public int Id { get; set; }
    public int ProjectId { get; set; }
    static void Main() {
        Report a = new Report { Id = 1, ProjectId = 13 },
            b = new Report { Id = 1, ProjectId = 13 },
            c = new Report { Id = 1, ProjectId = 12 };
        Console.WriteLine(PropertyCompare.Equal(a, b));
        Console.WriteLine(PropertyCompare.Equal(a, c));
    }
}
static class PropertyCompare {
    public static bool Equal<T>(T x, T y) {
        return Cache<T>.Compare(x, y);
    }
    static class Cache<T> {
        internal static readonly Func<T, T, bool> Compare;
        static Cache() {
            var props = typeof(T).GetProperties();
            if (props.Length == 0) {
                Compare = delegate { return true; };
                return;
            }
            var x = Expression.Parameter(typeof(T), "x");
            var y = Expression.Parameter(typeof(T), "y");

            Expression body = null;
            for (int i = 0; i < props.Length; i++) {
                var propEqual = Expression.Equal(
                    Expression.Property(x, props[i]),
                    Expression.Property(y, props[i]));
                if (body == null) {
                    body = propEqual;
                } else {
                    body = Expression.AndAlso(body, propEqual);
                }
            }
            Compare = Expression.Lambda<Func<T, T, bool>>(body, x, y)
                          .Compile();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:更新以处理字段:

static class MemberCompare
{
    public static bool Equal<T>(T x, T y)
    {
        return Cache<T>.Compare(x, y);
    }
    static class Cache<T>
    {
        internal static readonly Func<T, T, bool> Compare;
        static Cache()
        {
            var members = typeof(T).GetProperties(
                BindingFlags.Instance | BindingFlags.Public)
                .Cast<MemberInfo>().Concat(typeof(T).GetFields(
                BindingFlags.Instance | BindingFlags.Public)
                .Cast<MemberInfo>());
            var x = Expression.Parameter(typeof(T), "x");
            var y = Expression.Parameter(typeof(T), "y");

            Expression body = null;
            foreach(var member in members)
            {
                Expression memberEqual;
                switch (member.MemberType)
                {
                    case MemberTypes.Field:
                        memberEqual = Expression.Equal(
                            Expression.Field(x, (FieldInfo)member),
                            Expression.Field(y, (FieldInfo)member));
                        break;
                    case MemberTypes.Property:
                        memberEqual = Expression.Equal(
                            Expression.Property(x, (PropertyInfo)member),
                            Expression.Property(y, (PropertyInfo)member));
                        break;
                    default:
                        throw new NotSupportedException(
                            member.MemberType.ToString());
                }
                if (body == null)
                {
                    body = memberEqual;
                }
                else
                {
                    body = Expression.AndAlso(body, memberEqual);
                }
            }
            if (body == null)
            {
                Compare = delegate { return true; };
            }
            else
            {
                Compare = Expression.Lambda<Func<T, T, bool>>(body, x, y)
                              .Compile();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 天才!奇迹般有效. (2认同)
  • 哇,真的很甜蜜.纯反射版本要好得多. (2认同)

Mar*_*son 6

最初回答于(问题 1831747

查看我的MemberwiseEqualityComparer,看看它是否符合您的需求。

它真的很容易使用,而且效率也很高。它使用 IL-emit 在第一次运行时生成整个 Equals 和 GetHashCode 函数(对于使用的每种类型一次)。它将使用该类型的默认相等比较器 (EqualityComparer.Default) 比较给定对象的每个字段(私有或公共)。我们已经在生产中使用它一段时间了,它看起来很稳定,但我不做任何保证 =)

它处理了所有那些在滚动自己的 equals 方法时很少想到的麻烦的边缘情况(即,您不能将自己的对象与 null 进行比较,除非您首先将它装箱到一个对象中并且很多更多与空值相关的问题)。

我一直想写一篇关于它的博客文章,但还没有开始。代码有点没有记录,但如果你喜欢它,我可以稍微清理一下。

public override int GetHashCode()
{
    return MemberwiseEqualityComparer<Foo>.Default.GetHashCode(this);
}

public override bool Equals(object obj)
{
    if (obj == null)
        return false;

    return Equals(obj as Foo);
}

public override bool Equals(Foo other)
{
    return MemberwiseEqualityComparer<Foo>.Default.Equals(this, other);
}
Run Code Online (Sandbox Code Playgroud)

MemberwiseEqualityComparer 是在MIT 许可下发布的,这意味着您几乎可以用它做任何您想做的事情,包括在专有解决方案中使用它,而无需稍微改变您的许可。