在创建我的测试框架时,我发现了一个奇怪的问题。比较Type.GetProperties()和lambda表达式中的PropertyInfo
我想创建一个静态类,允许我通过它们的属性比较相同类型的对象,但有可能会忽略它们中的一些。
我要对此有一个简单流畅的API,所以如果给定对象是上除了Id
和Name
每个属性(他们不会被检查是否相等)等于像TestEqualityComparer.Equals(first.Ignore(x=>x.Id).Ignore(y=>y.Name), second);
通话将返回true。
这里是我的代码。当然这是一个微不足道的例子(某些显然重载的方法缺失),但我想提取最简单的代码。真实案例情况稍微复杂一点,所以我不想改变方法。
方法FindProperty
几乎是从AutoMapper library复制粘贴。为流畅的API
对象包装:
public class TestEqualityHelper<T>
{
public List<PropertyInfo> IgnoredProps = new List<PropertyInfo>();
public T Value;
}
流利的东西:
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<T> Ignore<T>(this T value,
Expression<Func<T, object>> ignore)
{
var eh = new TestEqualityHelper<T> { 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<T>(TestEqualityHelper<T> a, T b)
{
return DoMyEquals(a.Value, b, a.IgnoredProps);
}
private static bool DoMyEquals<T>(T a, T b,
IEnumerable<PropertyInfo> ignoredProperties)
{
var t = typeof(T);
IEnumerable<PropertyInfo> 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));
}
的问题是,在DoMyEquals
的Except
方法。
FindProperty
返回的属性不等于Type.GetProperties
返回的属性。我发现的差异在PropertyInfo.ReflectedType
。
不管我对象的类型,
FindProperty
告诉我,反射型为Base
。通过Type.GetProperties
返回属性都有自己的一套
ReflectedType
到Base
或Derived
,根据实际对象的类型。
我不知道如何解决它。我可以在lambda中检查参数的类型,但在下一步中,我想允许像Ignore(x=>x.Some.Deep.Property)
这样的结构,所以它可能不会。
任何有关如何比较PropertyInfo
或如何从lambdas正确检索他们的建议,将不胜感激。
你有没有试过玩GetProperties中的BindingFlags.FlattenHierarchy值?看看它是否改变了什么? – 2012-04-11 20:50:05
那里没有运气,但谢谢你的建议。我**认为** BindingFlags只能改变_which_成员返回,但他们不会影响他们自己的属性。我相信这个解决方案会在FindProperty中发挥作用。 – 2012-04-11 21:00:37
在获得成员运行GetProperty的类型(也可以通过表达式获取)和成员名称之后,可能会向FindProperty添加第二个冒险步骤?这是一个黑客攻击,但它可能会起作用。 – 2012-04-11 21:04:37