这是.NET中反射的错误吗?(Is this a bug in .Net reflection?)

2019-09-23 15:21发布

答案是:不,这是不是一个错误。 所不同的是在ReflectedType 。

因此,这里真正的问题是:有没有比较两个的方式PropertyInfo对象,相同的属性,但不同类型的反射,使得它返回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
    {
    }
}

这里的输出是:

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


罪魁祸首: PropertyInfo.ReflectedType

我发现这两个对象之间的差异......是在ReflectedType 。 文档说这个:

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

Answer 1:

不要假设有一个在库中的缺陷,除非你确实知道你在做什么,你已经彻底测试的问题。

PropertyInfo对象没有平等的概念。 当然,他们可以代表相同的结果,但他们超载的==操作符 ,所以你不能想当然地认为他们应该 。 由于他们不这样做,它只是简单地做一个参考比较,你猜怎么着,他们指的是两个不同的对象,因此!=

在另一方面, Type对象也做不超载的==操作符,但它似乎与比较两个实例==操作符会工作。 为什么? 由于类型实例实际上是实现为单身人士,这是一个实现细节。 所以给出了两个引用相同的类型,他们会因为你实际上是比较引用同一个实例比较符合市场预期。

不要指望每个对象调用框架方法时将取得同样的方式,你将永远得到。 没有多少在使用单例模式的框架。 检查才这样做的所有相关文件和其他来源。


重温这一点,我已经了解到,作为.NET 4中, Equals()方法和==操作符已经为类型实现。 不幸的是,文件没有解释他们的行为很多,但使用的工具,如.net反射揭示了一些有趣的信息。

据的反射器,是在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 - > PropertyInfo - > MemberInfo - > Object ), Equals()调用基实现一路攀升到Object ,因此在不影响一个对象引用相等比较。

==操作符专门检查,以确保没有发生PropertyInfo对象是RuntimePropertyInfo对象。 而据我所知,每一个PropertyInfo对象,你会得到使用反射(在这里显示的用例)将返回一个RuntimePropertyInfo

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



Answer 2:

为什么你不只是比较MetadataToken和模块。

据的文档该组合唯一地标识。

MemberInfo.MetadataToken
其中,与模块组合,唯一地识别元数据元素的值。

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; }
}


Answer 3:

我比较DeclaringTypeName 。 这个报告说,从两个不同的通用类型的“相同”的属性是不同的(例如, List<int>.CountList<string>.Count )。 比较MetadataTokenModule将报告这两个属性是相同的。



Answer 4:

在一开始,这似乎有道理两个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的给定的例子。



文章来源: Is this a bug in .Net reflection?