Сравните PropertyInfo из Type.GetProperties ()и лямбда-выражений

При создании своего тестового фреймворка я обнаружил странную проблему.

Я хочу создать статический класс, который позволял бы мне сравнивать объекты одного типа по их свойствам, но с возможностью игнорировать некоторые из них.

Я хочу иметь для этого простой свободный API, поэтому вызов типа TestEqualityComparer.Equals(first.Ignore(x=>x.Id).Ignore(y=>y.Name), second);вернет true, если заданные объекты равны по всем свойствам, кроме Idи Name(, они не будут проверяться на равенство ).

Вот мой код. Конечно, это тривиальный пример (с некоторыми очевидными отсутствующими перегрузками методов ), но я хотел извлечь как можно более простой код. Реальный сценарий немного сложнее, поэтому я не хочу менять подход.

Метод FindPropertyявляется практически копией -вставки из библиотеки AutoMapper .

Объектная оболочка для Fluent API:

public class TestEqualityHelper
{
    public List IgnoredProps = new List();
    public T Value;
}

Fluent stuff:

public static class FluentExtension
{
    //Extension method to speak fluently. It finds the property mentioned
    // in 'ignore' parameter and adds it to the list.
    public static TestEqualityHelper Ignore(this T value,
         Expression> ignore)
    {
        var eh = new TestEqualityHelper { Value = value };

        //Mind the magic here!
        var member = FindProperty(ignore);
        eh.IgnoredProps.Add((PropertyInfo)member);
        return eh;
    }

    //Extract the MemberInfo from the given lambda
    private static MemberInfo FindProperty(LambdaExpression lambdaExpression)
    {
        Expression expressionToCheck = lambdaExpression;

        var done = false;

        while (!done)
        {
            switch (expressionToCheck.NodeType)
            {
                case ExpressionType.Convert:
                    expressionToCheck 
                        = ((UnaryExpression)expressionToCheck).Operand;
                    break;
                case ExpressionType.Lambda:
                    expressionToCheck
                        = ((LambdaExpression)expressionToCheck).Body;
                    break;
                case ExpressionType.MemberAccess:
                    var memberExpression 
                        = (MemberExpression)expressionToCheck;

                    if (memberExpression.Expression.NodeType 
                          != ExpressionType.Parameter &&
                        memberExpression.Expression.NodeType 
                          != ExpressionType.Convert)
                    {
                        throw new Exception("Something went wrong");
                    }

                    return memberExpression.Member;
                default:
                    done = true;
                    break;
            }
        }

        throw new Exception("Something went wrong");
    }
}

Фактический компаратор:

public static class TestEqualityComparer
{
    public static bool MyEquals(TestEqualityHelper a, T b)
    {
        return DoMyEquals(a.Value, b, a.IgnoredProps);
    }

    private static bool DoMyEquals(T a, T b,
        IEnumerable ignoredProperties)
    {
        var t = typeof(T);
        IEnumerable props;

        if (ignoredProperties != null && ignoredProperties.Any())
        {
            //THE PROBLEM IS HERE!
            props =
                t.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                   .Except(ignoredProperties);
        }
        else
        {
            props = 
                t.GetProperties(BindingFlags.Instance | BindingFlags.Public);
        }
        return props.All(f => f.GetValue(a, null).Equals(f.GetValue(b, null)));
    }
}

Вот, собственно, и все.

А вот два тестовых фрагмента, первый работает, второй не работает:

//These are the simple objects we'll compare
public class Base
{
    public decimal Id { get; set; }
    public string Name { get; set; }
}
public class Derived : Base
{    }

[TestMethod]
public void ListUsers()
{
   //TRUE
   var f = new Base { Id = 5, Name = "asdas" };
   var s = new Base { Id = 6, Name = "asdas" };
   Assert.IsTrue(TestEqualityComparer.MyEquals(f.Ignore(x => x.Id), s));

   //FALSE
   var f2 = new Derived { Id = 5, Name = "asdas" };
   var s2 = new Derived { Id = 6, Name = "asdas" };
   Assert.IsTrue(TestEqualityComparer.MyEquals(f2.Ignore(x => x.Id), s2));
}

Проблема с методом Exceptв DoMyEquals.

Свойства, возвращаемые FindProperty, не совпадают со свойствами, возвращаемыми Type.GetProperties. Разница, которую я замечаю, заключается в PropertyInfo.ReflectedType.

  • независимо от типа моих объектов, FindPropertyговорит мне, что отраженный тип — Base. Свойства

  • , возвращаемые Type.GetProperties, имеют для ReflectedTypeзначение Baseили Derived, в зависимости от типа фактических объектов.

Я не знаю, как это решить. Я мог бы проверить тип параметра в лямбде, но на следующем шаге я хочу разрешить конструкции типа Ignore(x=>x.Some.Deep.Property), так что это, вероятно, не подойдет.

Будем признательны за любые предложения о том, как сравнивать PropertyInfoили как правильно извлекать их из лямбда-выражений.

11
задан Piotr Zierhoffer 11 April 2012 в 20:38
поделиться