Сравнение всех свойств объекта с использованием деревьев выражений - PullRequest
2 голосов
/ 07 января 2011

Я пытаюсь написать простой генератор, который использует дерево выражений для динамической генерации метода, который сравнивает все свойства экземпляра типа со свойствами другого экземпляра этого типа. Это прекрасно работает для большинства свойств, таких как int и string, но не работает для DateTime? (и, вероятно, других типов значений, допускающих значение NULL).

Метод:

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]' нельзя использовать для параметра типа 'System.Object' метода 'Boolean Equals (System.Object)'

ОК, значит, обнаружена перегрузка Equals, ожидающая object. Так почему я не могу передать DateTime? в него, поскольку он конвертируется в object? Если я смотрю на Nullable<T>, он действительно имеет переопределение Equals(object o).

PS : Я понимаю, что это еще не правильный генератор, так как он не может работать со null значениями, но я вернусь к этому:)

ОБНОВЛЕНИЕ : ответ Ираклиса сработал для этой конкретной проблемы, но в итоге я выбрал гораздо более простой подход, который, на мой взгляд, достаточно: просто используйте Expression.Equal. Я думаю, что это покрывает 99% моих дел (не уверен, сможет ли он обработать переопределение Equals без переопределения ==, но это нормально).

Ответы [ 2 ]

2 голосов
/ 07 января 2011

это может сработать, если вы проверите, что типы обнуляются с помощью этого кода:

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

Пример кода от здесь
А если они являются Nullable, то вы можетеЗвоните

Nullable.Equals<T>(T? n1, T? n2);
0 голосов
/ 07 ноября 2015

После поиска в Интернете чего-то, что я могу использовать, я решил реализовать это и сам.Я не использовал деревья выражений.Вместо этого я использую отражение для сканирования всех свойств и использую 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.Это работает очень хорошо для меня, я надеюсь, что другие найдут это полезным.

...