首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >比较来自Type.GetProperties()和lambda表达式的Type.GetProperties

比较来自Type.GetProperties()和lambda表达式的Type.GetProperties
EN

Stack Overflow用户
提问于 2012-04-11 20:38:56
回答 2查看 2.8K关注 0票数 11

在创建测试框架时,我发现了一个奇怪的问题。

我想要创建一个静态类,它允许我通过属性比较同一类型的对象,但可能忽略其中的一些对象。

为此,我希望有一个简单的fluent API,因此,如果给定的对象在除IdName以外的每个属性上相等,则像Id这样的调用将返回true (不会检查它们是否相等)。

这是我的密码。当然,这是一个简单的示例(缺少一些明显的重载方法),但我想提取最简单的代码。实际情况要复杂一些,所以我不想改变这种方法。

FindProperty方法几乎是AutoMapper library的复制粘贴。

用于fluent API的对象包装:

代码语言:javascript
复制
public class TestEqualityHelper<T>
{
    public List<PropertyInfo> IgnoredProps = new List<PropertyInfo>();
    public T Value;
}

流利的东西:

代码语言:javascript
复制
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");
    }
}

实际比较者:

代码语言:javascript
复制
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)));
    }
}

基本上就是这样。

下面是两个测试片段,第一个有效,第二个失败:

代码语言:javascript
复制
//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.

  • properties告诉我反射的类型是Type.GetProperties返回的,根据实际对象的类型,Type.GetProperties将其ReflectedType设置为BaseDerived

我不知道怎么解决。我可以在lambda中检查参数的类型,但在下一步中,我希望允许像Ignore(x=>x.Some.Deep.Property)这样的构造,所以它可能无法实现。

任何关于如何比较PropertyInfo或如何正确地从lambdas检索它们的建议都将不胜感激。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2012-04-11 21:14:40

FindProperty告诉您反射Type的原因是Base,因为这是lambda将用于调用的类。

你可能知道这一点:)

而不是类型中的GetProperties(),您可以使用以下

代码语言:javascript
复制
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

请考虑以下代码:

代码语言:javascript
复制
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

代码语言:javascript
复制
IL_002f: callvirt instance valuetype [mscorlib]System.Decimal ConsoleApplication1.Base::get_Id()

以及用于d.Id的IL

代码语言:javascript
复制
IL_0036: callvirt instance valuetype [mscorlib]System.Decimal ConsoleApplication1.Base::get_Id()
票数 5
EN

Stack Overflow用户

发布于 2012-04-13 05:35:31

不知道这是否有帮助,但是我注意到两个PropertyInfo实例的PropertyInfo属性值是相等的,如果两个实例都引用相同的逻辑属性,而不管两者的ReflectedType是什么。也就是说,两个PropertyType实例的名称、DeclaringType和索引参数都是相等的。

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/10113584

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档