在创建测试框架时,我发现了一个奇怪的问题。
我想要创建一个静态类,它允许我通过属性比较同一类型的对象,但可能忽略其中的一些对象。
为此,我希望有一个简单的fluent API,因此,如果给定的对象在除Id和Name以外的每个属性上相等,则像Id这样的调用将返回true (不会检查它们是否相等)。
这是我的密码。当然,这是一个简单的示例(缺少一些明显的重载方法),但我想提取最简单的代码。实际情况要复杂一些,所以我不想改变这种方法。
FindProperty方法几乎是AutoMapper library的复制粘贴。
用于fluent 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中的DoMyEquals方法。
FindProperty返回的属性不等于Type.GetProperties返回的属性。我发现的不同之处在于PropertyInfo.ReflectedType。
不管我的对象的类型如何,Base.
Type.GetProperties返回的,根据实际对象的类型,Type.GetProperties将其ReflectedType设置为Base或Derived。我不知道怎么解决。我可以在lambda中检查参数的类型,但在下一步中,我希望允许像Ignore(x=>x.Some.Deep.Property)这样的构造,所以它可能无法实现。
任何关于如何比较PropertyInfo或如何正确地从lambdas检索它们的建议都将不胜感激。
发布于 2012-04-11 21:14:40
FindProperty告诉您反射Type的原因是Base,因为这是lambda将用于调用的类。
你可能知道这一点:)
而不是类型中的GetProperties(),您可以使用以下
static IEnumerable<PropertyInfo> GetMappedProperties(Type type)
{
return type
.GetProperties()
.Select(p => GetMappedProperty(type, p.Name))
.Where(p => p != null);
}
static PropertyInfo GetMappedProperty(Type type, string name)
{
if (type == null)
return null;
var prop = type.GetProperty(name);
if (prop.DeclaringType == type)
return prop;
else
return GetMappedProperty(type.BaseType, name);
}要更多地解释为什么lambda实际上直接使用基本方法,您可以看到本质上不同的PropertyInfo,可以更好地解释查看IL
请考虑以下代码:
static void Foo()
{
var b = new Base { Id = 4 };
var d = new Derived { Id = 5 };
decimal dm = b.Id;
dm = d.Id;
}这是b.Id的IL
IL_002f: callvirt instance valuetype [mscorlib]System.Decimal ConsoleApplication1.Base::get_Id()以及用于d.Id的IL
IL_0036: callvirt instance valuetype [mscorlib]System.Decimal ConsoleApplication1.Base::get_Id()发布于 2012-04-13 05:35:31
不知道这是否有帮助,但是我注意到两个PropertyInfo实例的PropertyInfo属性值是相等的,如果两个实例都引用相同的逻辑属性,而不管两者的ReflectedType是什么。也就是说,两个PropertyType实例的名称、DeclaringType和索引参数都是相等的。
https://stackoverflow.com/questions/10113584
复制相似问题