这是.Net反射中的错误吗?

时间:2012-10-07 03:08:07

标签: c# .net reflection linq-expressions

答案是:不,这不是错误。 区别在于ReflectedType

所以真正的问题是:有没有办法比较两个PropertyInfo对象,对于同一个属性,但是从不同的类型反映出来,以便它返回{{1} }?

原始问题

此代码使用两种不同的方式同一属性生成两个true个对象。它来了,这些属性信息以某种方式比较。我已经失去了一些时间试图弄清楚这一点。

我做错了什么?

PropertyInfo

这里的输出是:

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace TestReflectionError
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.BufferWidth = 200;
            Console.WindowWidth = 200;

            Expression<Func<object>> expr = () => ((ClassA)null).ValueA;
            PropertyInfo pi1 = (((expr as LambdaExpression)
                .Body as UnaryExpression)
                .Operand as MemberExpression)
                .Member as PropertyInfo;

            PropertyInfo pi2 = typeof(ClassB).GetProperties()
                .Where(x => x.Name == "ValueA").Single();

            Console.WriteLine("{0}, {1}, {2}, {3}, {4}", pi1, pi1.DeclaringType, pi1.MemberType, pi1.MetadataToken, pi1.Module);
            Console.WriteLine("{0}, {1}, {2}, {3}, {4}", pi2, pi2.DeclaringType, pi2.MemberType, pi2.MetadataToken, pi2.Module);

            // these two comparisons FAIL
            Console.WriteLine("pi1 == pi2: {0}", pi1 == pi2);
            Console.WriteLine("pi1.Equals(pi2): {0}", pi1.Equals(pi2));

            // this comparison passes
            Console.WriteLine("pi1.DeclaringType == pi2.DeclaringType: {0}", pi1.DeclaringType == pi2.DeclaringType);
            Console.ReadKey();
        }
    }

    class ClassA
    {
        public int ValueA { get; set; }
    }

    class ClassB : ClassA
    {
    }
}


罪魁祸首:PropertyInfo.ReflectedType

我发现这两个对象之间存在差异......它位于Int32 ValueA, TestReflectionError.ClassA, Property, 385875969, TestReflectionError.exe Int32 ValueA, TestReflectionError.ClassA, Property, 385875969, TestReflectionError.exe pi1 == pi2: False pi1.Equals(pi2): False pi1.DeclaringType == pi2.DeclaringType: True 。文档说明了这一点:

  

获取用于获取此成员的类对象。

4 个答案:

答案 0 :(得分:4)

从不假设库中存在错误,除非您确实知道自己在做什么并且已经详尽地测试了该问题。

PropertyInfo个对象没有平等的概念。当然它们可能代表相同的结果,但它们不会超载==运算符,因此您不能认为它们应该。既然他们没有,那只是简单地进行参考比较并猜测是什么,他们指的是两个独立的对象,因此是!=

另一方面,Type对象也不会使==运算符超载,但似乎比较两个实例与==运算符将起作用。为什么?因为类型实例实际上是作为单例实现的,这是一个实现细节。因此,给定对相同类型的两个引用,它们将按预期进行比较,因为您实际上是在比较对同一实例的引用。

在调用框架方法时,不要指望每个对象都会以相同的方式工作。框架中没有太多使用单例的东西。在执行此操作之前,请检查所有相关文档和其他来源。


重新审视这一点,我被告知,从.NET 4开始,已为该类型实现了Equals()方法和==运算符。遗憾的是,文档并没有解释它们的行为,但使用.NET Reflector等工具会发现一些有趣的信息。

根据反射器,mscorlib程序集中方法的实现如下:

[__DynamicallyInvokable]
public override bool Equals(object obj)
{
    return base.Equals(obj);
}

[__DynamicallyInvokable]
public static bool operator ==(PropertyInfo left, PropertyInfo right)
{
    return (object.ReferenceEquals(left, right)
        || ((((left != null) && (right != null)) &&
             (!(left is RuntimePropertyInfo) && !(right is RuntimePropertyInfo)))
        && left.Equals(right)));
}

上下移动继承链(RuntimePropertyInfo - &gt; PropertyInfo - &gt; MemberInfo - &gt; Object),Equals()调用基础实现一直到Object,所以它实际上是对象引用相等比较。

==运算符专门检查以确保PropertyInfo对象都不是RuntimePropertyInfo对象。据我所知,使用反射的每个PropertyInfo对象(在此处显示的用例中)将返回RuntimePropertyInfo

基于此,看起来框架设计者认真使得它(运行时)PropertyInfo对象具有不可比性,即使它们代表相同的属性。您只能检查属性是否引用相同的PropertyInfo实例。我不能告诉你他们为什么做出这个决定(我有我的理论),你必须从他们那里听到。

答案 1 :(得分:2)

为什么不直接比较MetadataToken和Module。

根据组合唯一标识的文档。

MemberInfo.MetadataToken
与Module一起唯一标识元数据元素的值。

static void Main(string[] args)
{
    Console.BufferWidth = 200;
    Console.WindowWidth = 140;

    PropertyInfo pi1 = typeof(ClassA).GetProperties()
        .Where(x => x.Name == "ValueA").Single();
    PropertyInfo pi2 = typeof(ClassB).GetProperties()
        .Where(x => x.Name == "ValueA").Single();
    PropertyInfo pi0 = typeof(ClassA).GetProperties()
        .Where(x => x.Name == "ValueB").Single();
    PropertyInfo pi3 = typeof(ClassB).GetProperties()
        .Where(x => x.Name == "ValueB").Single();
    PropertyInfo pi4 = typeof(ClassC).GetProperties()
        .Where(x => x.Name == "ValueA").Single();
    PropertyInfo pi5 = typeof(ClassC).GetProperties()
        .Where(x => x.Name == "ValueB").Single();


    Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}", pi1, pi1.ReflectedType, pi1.DeclaringType, pi1.MemberType, pi1.MetadataToken, pi1.Module);
    Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}", pi2, pi2.ReflectedType, pi2.DeclaringType, pi2.MemberType, pi2.MetadataToken, pi2.Module);
    Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}", pi0, pi0.ReflectedType, pi0.DeclaringType, pi0.MemberType, pi0.MetadataToken, pi1.Module);
    Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}", pi3, pi3.ReflectedType, pi3.DeclaringType, pi3.MemberType, pi3.MetadataToken, pi3.Module);
    Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}", pi4, pi4.ReflectedType, pi4.DeclaringType, pi4.MemberType, pi4.MetadataToken, pi4.Module);
    Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}", pi5, pi5.ReflectedType, pi5.DeclaringType, pi5.MemberType, pi5.MetadataToken, pi5.Module);

    // these two comparisons FAIL
    Console.WriteLine("pi1 == pi2: {0}", pi1 == pi2);
    Console.WriteLine("pi1.Equals(pi2): {0}", pi1.Equals(pi2));

    // this comparison passes
    Console.WriteLine("pi1.DeclaringType == pi2.DeclaringType: {0}", pi1.DeclaringType == pi2.DeclaringType);


    pi1 = typeof(ClassA).GetProperties()
        .Where(x => x.Name == "ValueB").Single();

    pi2 = typeof(ClassB).GetProperties()
        .Where(x => x.Name == "ValueB").Single();

    Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}", pi1, pi1.ReflectedType, pi1.DeclaringType, pi1.MemberType, pi1.MetadataToken, pi1.Module);
    Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}", pi2, pi2.ReflectedType, pi2.DeclaringType, pi2.MemberType, pi2.MetadataToken, pi2.Module);

    // these two comparisons FAIL
    Console.WriteLine("pi1 == pi2: {0}", pi1 == pi2);
    Console.WriteLine("pi1.Equals(pi2): {0}", pi1.Equals(pi2));


    Console.ReadKey();
}
class ClassA
{
    public int ValueA { get; set; }
    public int ValueB { get; set; }
}
class ClassB : ClassA
{
    public new int ValueB { get; set; } 
}
class ClassC
{
    public int ValueA { get; set; }
    public int ValueB { get; set; }
}

答案 2 :(得分:2)

我比较DeclaringTypeName。这报告来自两种不同泛型类型的“相同”属性是不同的(例如,List<int>.CountList<string>.Count)。比较MetadataTokenModule会报告这两个属性是相同的。

答案 3 :(得分:0)

首先,如果两个MemberInfo在直接访问该成员时返回相同的值(而不是通过反射),那么它们似乎是有意义的。对于FieldInfo,这似乎更合理。但是,对于PropertyInfo,它不是那么清楚,因为属性可以在子类中扩展,并且可以将不同的CustomAttributes添加到成员声明中。这意味着严格考虑访问的值不足以定义相等性。但是,如果这是您想要的平等定义,那么您可能需要考虑AreEqual3(...)方法:

private class Person {
    [CustomAttribute1]
    public virtual String Name { get; set; }
}

private class Person2 : Person {
    [CustomAttribute2]
    public override String Name { get; set; }
}

public static void TestMemberInfoEquality() {
    MemberInfo m1 = ExpressionEx.GetMemberInfo<Person>(p => p.Name);
    MemberInfo m2 = ExpressionEx.GetMemberInfo<Person2>(p => p.Name);
    bool b1 = m1.MetadataToken == m2.MetadataToken; // false
    bool b2 = m1 == m2; // false (because ReflectedType is different)
    bool b3 = m1.DeclaringType == m2.DeclaringType; // false
    bool b4 = AreEqual1(m1, m2); // false
    bool b5 = AreEqual2(m1, m2); // false
    bool b6 = AreEqual3(m1, m2); // true
}

public static bool AreEqual1(MemberInfo m1, MemberInfo m2) {
    return m1.MetadataToken == m2.MetadataToken && m1.Module == m2.Module;
}

public static bool AreEqual2(MemberInfo m1, MemberInfo m2) {
    return m1.DeclaringType == m2.DeclaringType && m1.Name == m2.Name;
}

public static bool AreEqual3(MemberInfo m1, MemberInfo m2) {
    return m1.GetRootDeclaration() == m2.GetRootDeclaration();
}

public static MemberInfo GetRootDeclaration(this MemberInfo mi) {
    Type ty = mi.ReflectedType;
    while (ty != null) {
        MemberInfo[] arr = ty.GetMember(mi.Name, mi.MemberType, BindingFlags.Instance | BindingFlags.Public);
        if (arr == null || arr.Length == 0)
            break;
        mi = arr[0];
        ty = ty.BaseType;
    }
    return mi;
}

该方法仅针对PublicInstance成员编写。其他一些讨论主题建议使用AreEqual1(...)AreEqual2(...)方法,但对于给定的示例,它们会返回false