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