【问题标题】:Comparing all properties of an object using expression trees使用表达式树比较对象的所有属性
【发布时间】:2011-01-07 17:13:54
【问题描述】:

我正在尝试编写一个简单的生成器,它使用表达式树来动态生成一个方法,该方法将一个类型实例的所有属性与该类型的另一个实例的属性进行比较。这适用于大多数属性,例如 intstring,但不适用于 DateTime?(可能还有其他可为空的值类型)。

方法:

static Delegate GenerateComparer(Type type)
{
  var left = Expression.Parameter(type, "left");
  var right = Expression.Parameter(type, "right");

  Expression result = null;

  foreach (var p in type.GetProperties())
  {
    var leftProperty = Expression.Property(left, p.Name);
    var rightProperty = Expression.Property(right, p.Name);

    var equals = p.PropertyType.GetMethod("Equals", new[] { p.PropertyType });

    var callEqualsOnLeft = Expression.Call(leftProperty, equals, rightProperty);

    result = result != null ? (Expression)Expression.And(result, callEqualsOnLeft) : (Expression)callEqualsOnLeft;
  }

  var method = Expression.Lambda(result, left, right).Compile();

  return method;

}

DateTime? 属性上失败:

“System.Nullable`1[System.DateTime]”类型的表达式不能用于“Boolean Equals(System.Object)”方法的“System.Object”类型参数

好的,所以它找到了 Equals 的重载,它需要 object。那么为什么我不能将DateTime? 传递给它,因为它可以转换为object?如果我查看Nullable<T>,它确实覆盖了Equals(object o)

PS:我意识到这还不是一个合适的生成器,因为它无法处理 null 值,但我会解决的 :)

更新:Iraklis 的回答确实解决了这个特殊问题,但最后我选择了一种我认为就足够了的更简单的方法:只需使用Expression.Equal。我认为这涵盖了我 99% 的案例(不确定它是否可以在不覆盖 == 的情况下处理覆盖 Equals,但没关系)。

【问题讨论】:

    标签: c# .net expression-trees dynamic-method


    【解决方案1】:

    如果您使用以下代码检查类型是否可以为空,它可能会起作用:

    if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)){}  
    

    代码示例来自here
    如果它们是可空的,那么你可以调用

    Nullable.Equals<T>(T? n1, T? n2);
    

    【讨论】:

    • 谢谢,该方法解决了我的问题,但最后我完全采用了不同的方法。
    【解决方案2】:

    在网上搜索了一些我可以使用的东西后,我决定自己也实现它。我没有使用表达式树。相反,我使用反射来扫描所有属性并使用ToString() 来比较它们。如果属性是一个集合,它将比较集合中的每个元素是否相等。

    这里是代码;

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Linq;
    
    namespace Utils
    {
        public class PropertyComparer<T> : IEqualityComparer<T>
        {
            public bool Equals(T x, T y)
            {
                IEnumerable<PropertyInfo> allProperties = typeof(T).GetProperties();
                foreach(PropertyInfo pi in allProperties)
                {
                    if (pi.GetCustomAttributes<EqualityIrrelevantAttribute>().Any())
                    {
                        continue;
                    }
    
                    object xProp = pi.GetValue(x);
                    object yProp = pi.GetValue(y);
    
                    if ((xProp == null) && (yProp == null))
                    {
                        continue;
                    }
                    else if ((xProp == null) || (yProp == null))
                    {
                        return false;
                    }
                    else if (xProp is ICollection)
                    {
                        if (!CollectionsEqual(xProp as ICollection, yProp as ICollection))
                        {
                            return false;
                        }
                    }
    
                    if (xProp.ToString() != yProp.ToString())
                    {
                        return false;
                    }
                }
    
                return true;
            }
    
            bool CollectionsEqual(ICollection left, ICollection right)
            {
                IEnumerator leftEnumerator = left.GetEnumerator();
                IEnumerator rightEnumerator = right.GetEnumerator();
    
                bool leftAdvanced = leftEnumerator.MoveNext();
                bool rightAdvanced = rightEnumerator.MoveNext();
    
                if ((leftAdvanced && !rightAdvanced) || (rightAdvanced && !leftAdvanced))
                {
                    return false;
                }
                else if (!leftAdvanced && !rightAdvanced)
                {
                    return true;
                }
    
                bool compareByClass = false;
                object comparer = null;
                MethodInfo equalsMethod = null;
    
                // Inspect type first
                object peek = leftEnumerator.Current;
                Type valuesType = peek.GetType();
                if (valuesType.IsClass)
                {
                    compareByClass = true;
                    Type comparerType = typeof(PropertyComparer<>).MakeGenericType(new Type[] { valuesType });
                    equalsMethod = comparerType.GetMethod("Equals", new Type[] { valuesType, valuesType });
                    comparer = Activator.CreateInstance(comparerType);
                }
    
    
                leftEnumerator.Reset();
                rightEnumerator.Reset();
    
                while (true)
                {
                    leftAdvanced = leftEnumerator.MoveNext();
                    rightAdvanced = rightEnumerator.MoveNext();
    
                    if ((leftAdvanced && !rightAdvanced) || (rightAdvanced && !leftAdvanced))
                    {
                        return false;
                    }
                    else if (!leftAdvanced && !rightAdvanced)
                    {
                        return true;
                    }
    
                    object leftValue = leftEnumerator.Current;
                    object rightValue = rightEnumerator.Current;
    
                    if (compareByClass)
                    {
                        bool result = (bool)equalsMethod.Invoke(comparer, new object[] { leftValue, rightValue });
                        if (!result)
                        {
                            return false;
                        }
                    }
                    else if (leftEnumerator.Current.ToString() != rightEnumerator.Current.ToString())
                    {
                        return false;
                    }
    
                    // Continue looping
                }
            }
    
            public int GetHashCode(T obj)
            {
                throw new NotImplementedException();
            }
        }
    }
    

    如果一个类的属性本身就是一个类,那么它将创建一个新的比较器来比较该类的属性。您还可以选择使用EqualityIrrelevantAttribute 标记要从比较中排除的特定属性。它对我来说真的很好用,我希望其他人觉得它有用。

    【讨论】:

      猜你喜欢
      • 2011-05-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多