2

我正在尝试编写一个简单的生成器,它使用表达式树来动态生成一个方法,该方法将一个类型实例的所有属性与该类型的另一个实例的属性进行比较。这适用于大多数属性,例如int,但对于(以及可能其他可为空的值类型)string失败。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。那么为什么我不能将 a 传递给DateTime?它,因为它可以转换为object?如果我看一下Nullable<T>,它确实有Equals(object o).

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

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

4

2 回答 2

2

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

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

代码示例来自这里
如果它们是 Nullable 那么你可以调用

Nullable.Equals<T>(T? n1, T? n2);
于 2011-01-07T17:49:09.153 回答
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。它对我来说非常有用,我希望其他人觉得它有用。

于 2015-11-06T22:24:29.713 回答