【发布时间】:2012-04-11 20:38:56
【问题描述】:
在创建我的测试框架时,我发现了一个奇怪的问题。
我想创建一个静态类,它允许我通过属性比较相同类型的对象,但有可能忽略其中的一些。
我想为此提供一个简单的流式 API,因此如果给定对象在除 Id 和 Name 之外的每个属性上都相等,则像 TestEqualityComparer.Equals(first.Ignore(x=>x.Id).Ignore(y=>y.Name), second); 这样的调用将返回 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)));
}
}
基本上就是这样。
这里有两个测试sn-ps,第一个有效,第二个失败:
//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 或如何从 lambda 中正确检索它们的建议将不胜感激。
【问题讨论】:
-
您是否尝试过使用 GetProperties 中的 BindingFlags.FlattenHierarchy 值?看看它是否改变了什么?
-
运气不好,但感谢您的建议。我认为 BindingFlags 只能改变返回哪些 成员,但不会影响自己的属性。我相信解决方案可以通过 FindProperty 发挥作用。
-
也许在您获得 Member 之后向 FindProperty 添加第二个 hacky 步骤 - 对具有成员名称的类型(您也可以通过表达式获得)运行 GetProperty?这是一个 hack,但它可能会起作用。
-
嗯,这是一个很好的解决方案,但如果没有进一步的组合,它将无法与 x.Nested.Properties 一起使用。我认为@payo 解决方案(我们将 FindProperty 单独放置)更具可扩展性。是的,我很容易改变主意 ;-) 还是谢谢你! :)
-
另见相关:stackoverflow.com/questions/6658669/…。我认为它有一个更好的解决方案,可以为您提供实际的
PropertyInfo。
标签: c# reflection equality