【问题标题】:Compare PropertyInfo from Type.GetProperties() and lambda expressions比较 Type.GetProperties() 中的 PropertyInfo 和 lambda 表达式
【发布时间】:2012-04-11 20:38:56
【问题描述】:

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

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

我想为此提供一个简单的流式 API,因此如果给定对象在除 IdName 之外的每个属性上都相等,则像 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 设置为BaseDerived,具体取决于实际对象的类型。

我不知道如何解决它。我可以检查 lambda 中参数的类型,但在下一步中我想允许像 Ignore(x=&gt;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


【解决方案1】:

FindProperty 告诉您反射的TypeBase 的原因是因为这是 lambda 用于调用的类。

你可能知道这个:)

你能用这个代替 Type 的 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 实际上直接使用 Base 方法,并且您会看到本质上不同的 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()

【讨论】:

  • 这看起来不错,但我真的不知道,为什么 lambda 会使用 Base?没有演员表,lambdaExpression.Parameters[0].TypeDerived。你能解释一下为什么会这样吗? (解释性链接或一些关键字就足够了;-))
  • lambda参数Type是声明的参数的Type,但实际的方法调用是使用基类型进行调用(方法在基类型)。
  • @xavier 请参阅我的回答中的附加信息 [另外,如果此解决方案有效并且 6 人支持它 - 甚至有人给它加了星标,为什么不喜欢我的回答 :( 我不明白 SO有时]
  • 请给我一分钟,我正在编码和测试它;-)
  • @PrestonGuillot 我完全理解这一点 - 我只是抱怨我的努力很少能在 SO cred 中获利:)。如果所有喜欢这个问题的人也喜欢这个答案,那就太棒了:) [手动]
【解决方案2】:

不知道这是否有帮助,但我注意到两个 PropertyInfo 实例的 MetaDataToken 属性值是相等的,如果两个实例引用相同的逻辑属性,而不管其中一个的 ReflectedType。即两个PropertyInfo实例的Name、PropertyType、DeclaringType和index参数都是相等的。

【讨论】:

  • 这很有趣!根据msdn,MetadataTokenModule结合,唯一标识元素。谢谢!
猜你喜欢
  • 2012-09-11
  • 1970-01-01
  • 2010-11-28
  • 2021-09-27
  • 2021-04-02
  • 2011-04-24
  • 1970-01-01
  • 1970-01-01
  • 2015-04-15
相关资源
最近更新 更多