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

Posted

技术标签:

【中文标题】如何在 C# 中快速检查两个数据传输对象是不是具有相同的属性?【英文标题】:How to quickly check if two data transfer objects have equal properties in C#?如何在 C# 中快速检查两个数据传输对象是否具有相同的属性? 【发布时间】:2010-11-02 11:05:25 【问题描述】:

我有这些数据传输对象:

public class Report 

    public int Id  get; set; 
    public int ProjectId  get; set; 
    //and so on for many, many properties.

我不想写

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;

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

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

【问题讨论】:

我在想这个。在我看来,最好的方法是通过 IDE 工具。看起来 Eclipse 有一个-eclipsezone.com/eclipse/forums/t92613.rhtml。我想知道 VS.NET 是否有类似的东西? @RichardOD:ReSharper 可以在 VS.NET 中做到这一点。 【参考方案1】:

一些反思怎么样,也许使用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();
        
    


编辑:更新以处理字段:

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();
            
        
    

【讨论】:

哇,真是太棒了。比纯反射版本好得多。 为什么不使用 Expression.Constant(true) 初始化 body 以避免在循环中出现? @ASpirin - if 仅在创建 Expression 时使用,这是您执行一次然后缓存/重用的操作。我宁愿这样做,也不愿在表达式中添加其他内容.... +1。通过表达式生成代码非常强大,但在我看来没有得到充分利用。 这段代码的第一个版本应该是 typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public) 而不是 typeof(T).GetProperties()【参考方案2】:

最初回复于 (question 1831747)

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

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

它处理了所有那些在你使用自己的 equals 方法时很少想到的讨厌的边缘情况(即,你不能将你自己的对象与 null 进行比较,除非你先将它装箱到一个对象中还有更多与 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);

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

【讨论】:

一种可能的改进是让相等测试生成器允许使用字段属性来指示哪些字段封装了 identity 以及哪些字段封装了 value。一个相当常见的模式是通过持有一个永远不会暴露给任何可能改变它的东西的引用来封装一个可变类值。即使在其类型上调用 Equals 会测试引用相等性,也应该测试此类字段的值相等性。【参考方案3】:

我已将 Marc 的代码扩展为成熟的 IEqualityComparer 实现供我自己使用,并认为这可能对其他人有用:

/// <summary>
/// An <see cref="IEqualityComparerT"/> that compares the values of each public property.
/// </summary>
/// <typeparam name="T"> The type to compare. </typeparam>
public class PropertyEqualityComparer<T> : IEqualityComparer<T>

    // http://***.com/questions/986572/hows-to-quick-check-if-data-transfer-two-objects-have-equal-properties-in-c/986617#986617

    static class EqualityCache
    
        internal static readonly Func<T, T, bool> Compare;
        static EqualityCache()
        
            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();
        
    

    /// <inheritdoc/>
    public bool Equals(T x, T y)
    
        return EqualityCache.Compare(x, y);
    

    static class HashCodeCache
    
        internal static readonly Func<T, int> Hasher;
        static HashCodeCache()
        
            var props = typeof(T).GetProperties();
            if (props.Length == 0)
            
                Hasher = delegate  return 0; ;
                return;
            
            var x = Expression.Parameter(typeof(T), "x");

            Expression body = null;
            for (int i = 0; i < props.Length; i++)
            
                var prop = Expression.Property(x, props[i]);
                var type = props[i].PropertyType;
                var isNull = type.IsValueType ? (Expression)Expression.Constant(false, typeof(bool)) : Expression.Equal(prop, Expression.Constant(null, type));
                var hashCodeFunc = type.GetMethod("GetHashCode", BindingFlags.Instance | BindingFlags.Public);
                var getHashCode = Expression.Call(prop, hashCodeFunc);
                var hashCode = Expression.Condition(isNull, Expression.Constant(0, typeof(int)), getHashCode);

                if (body == null)
                
                    body = hashCode;
                
                else
                
                    body = Expression.ExclusiveOr(Expression.Multiply(body, Expression.Constant(typeof(T).AssemblyQualifiedName.GetHashCode(), typeof(int))), hashCode);
                
            
            Hasher = Expression.Lambda<Func<T, int>>(body, x).Compile();
        
    

    /// <inheritdoc/>
    public int GetHashCode(T obj)
    
        return HashCodeCache.Hasher(obj);
    

【讨论】:

【参考方案4】:

不幸的是,您将不得不编写方法来比较字段值。 System.ValueType 是为使用反射和比较 struct 的字段值而构建的,但由于性能缓慢,即使这样做也是不可取的。最好的办法是重写Equals 方法,同时实现IEquatable&lt;T&gt; 接口以实现强类型Equals 重载。

当您使用它时,您不妨提供一个好的GetHashCode 覆盖以及补充Equals 实现。所有这些步骤都被认为是好的做法。

【讨论】:

【参考方案5】:

您需要使用反射来执行此操作,请点击此链接 --> Comparing object properties in c#

【讨论】:

以上是关于如何在 C# 中快速检查两个数据传输对象是不是具有相同的属性?的主要内容,如果未能解决你的问题,请参考以下文章

如何检查两个对象是不是具有相同的一组属性名称?

如何检查在 C# 的全局类中找到的数组中是不是存在对象

如何在c#中检查对象是不是没有空属性[重复]

如何返回具有通用属性的 C# Web 服务?

如何在 Unity C# 中检查对象是不是面向某个方向

如何检查对象是不是在 JavaScript 中具有特定属性?