使用表达式树比较对象的所有属性

时间:2011-01-07 17:13:55

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

我正在尝试编写一个简单的生成器,它使用表达式树动态生成一个方法,该方法将类型实例的所有属性与该类型的另一个实例的属性进行比较。这适用于大多数属性,例如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而不覆盖==,但这没关系。

2 个答案:

答案 0 :(得分:2)

如果您使用此代码检查类型是否为空,则可能会有效:

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

代码示例来自here
如果他们是Nullable,那么你可以打电话

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

答案 1 :(得分:0)

在网上搜索我可以使用的东西之后,我决定自己实现它。我没有使用表达式树。相反,我使用反射来扫描所有属性,并使用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标记要从比较中排除的特定属性。它对我来说非常好用,我希望其他人觉得它很有用。