5

答案是:不,这不是错误。区别在于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. 文档是这样说的:

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

4

4 回答 4

4

为什么不直接比较 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; }
}
于 2012-10-07T19:23:37.870 回答
3

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

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-> PropertyInfo-> MemberInfo-> Object)中上下移动,Equals()一直调用基本实现,Object因此它实际上进行了对象引用相等比较。

操作员专门检查以==确保两个PropertyInfo对象都不是RuntimePropertyInfo对象。据我所知,PropertyInfo您使用反射获得的每个对象(在此处显示的用例中)都将返回一个RuntimePropertyInfo.

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

于 2012-10-07T04:28:39.723 回答
2

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

于 2012-12-21T16:37:11.993 回答
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(...)orAreEqual2(...)方法,但它们返回false给定示例。

于 2017-06-14T17:26:22.470 回答