.NET PropertyInfo的平等性

10

我有一些比较两个PropertyInfo对象是否相等的代码,使用Equals()方法。通常情况下这种方法是可行的,但我遇到了一个奇怪的问题:对于同一个属性反射得到的两个PropertyInfo对象,它们却不相等:

PropertyInfo prop1, prop2; // both are public and not static
Console.WriteLine(prop1 == prop2); // false ???
Console.WriteLine(Equals(prop1, prop2)); // false ???
Console.WriteLine(prop1.DeclaringType == prop2.DeclaringType); // true
Console.WriteLine(prop1.ReturnType == prop2.ReturnType); // true
Console.WriteLine(prop1.Name == prop2.Name); // true
Console.WriteLine(prop1.DeclaringType.GetProperties().Contains(prop1)); // true
Console.WriteLine(prop2.DeclaringType.GetProperties().Contains(prop2)); // false ???

似乎 PropertyInfo 实际上没有实现 Equals() 方法,但我认为 .NET 会缓存反射成员,以便始终返回相同的实例。你经常看到 a.GetType() == b.GetType()。PropertyInfos 不是这种情况吗?
其他一些注意事项: - 在 .NET 4、VS2012、x86 构建目标下运行 NUnit 测试时发生了这种奇怪的情况 - 这种比较方式并不适用于我们比较的所有属性,但在一个属性上始终失败。
有人能解释这种行为吗?
编辑:如果有人感兴趣,这里是我编写的用于比较 MemberInfos 的 EqualityComparison 函数:
public class MemberEqualityComparer : EqualityComparer<MemberInfo> {
    public override bool Equals(MemberInfo @this, MemberInfo that) {
        if (@this == that) { return true; }
        if (@this == null || that == null) { return false; }

                        // handles everything except for generics
                    if (@this.MetadataToken != that.MetadataToken
                        || !Equals(@this.Module, that.Module)
                        || this.Equals(@this.DeclaringType, that.DeclaringType))
                    {
                        return false;
                    }

                    bool areEqual;
                    switch (@this.MemberType)
                    {
                        // constructors and methods can be generic independent of their types,
                        // so they are equal if they're generic arguments are equal
                        case MemberTypes.Constructor:
                        case MemberTypes.Method:
                            var thisMethod = @this as MethodBase;
                            var thatMethod = that as MethodBase;
                                                areEqual = thisMethod.GetGenericArguments().SequenceEqual(thatMethod.GetGenericArguments(), 
this);
                            break;
                        // properties, events, and fields cannot be generic independent of their types,
                        // so if we've reached this point without bailing out we just return true.
                        case MemberTypes.Property:
                        case MemberTypes.Event:
                        case MemberTypes.Field:
                            areEqual = true;
                            break;
                        // the system guarantees reference equality for types, so if we've reached this point
                        // without returning true the two are not equal
                        case MemberTypes.TypeInfo:
                        case MemberTypes.NestedType:
                            areEqual = false;
                            break;
                        default:
                            throw new NotImplementedException(@this.MemberType.ToString());
    }

    public override int GetHashCode(MemberInfo memberInfo) {
        if (memberInfo == null) { return 0; }

    var hash = @this.MetadataToken 
        ^ @this.Module.GetHashCode() 
        ^ this.GetHashCode(@this.DeclaringType);
    return hash;
    }
}

你看过反编译的代码吗? - pylover
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Jax
2个回答

16

对象标识仅适用于Type类,而不适用于其他反射类。一种可能可行的比较相等的方法是检查属性是否具有相同的元数据标记并来自同一模块。因此,请尝试以下操作:

bool equal = prop1.MetadataToken == prop2.MetadataToken &&
             prop1.Module.Equals(prop2.Module);

只要符合ECMA 335标准,这就是有意义的。由于你没有发布代码,我无法对其进行测试。所以请尝试一下。


这似乎是有效的,除了泛型类型的属性(除非你想要List<int>.Count == List<string>.Count)。 - ChaseMedallion
1
@ChaseMedallion,加上prop1.DeclaringType == prop2.DeclaringType的连接词怎么样?这样似乎可以涵盖那种情况。 - smartcaveman
@smartcaveman:DeclaringType 很好地处理了属性泛型,因为属性本身不能是泛型(只有声明类型可以)。但是要将此方法扩展到所有成员,您需要进行一些额外的检查来处理泛型方法。请查看我在问题中发布的代码(似乎)包含任意成员的完整检查集。您会注意到顶部的 DeclaringType 检查。 - ChaseMedallion
@ChaseMedallion - 我明白了。当我评论时,我没意识到你是原帖发布者。如果你想让prop2.DeclaringType.GetProperties().Contains(prop2)使用你的 MemberEqualityComparer 并返回 true,你应该查看 Undefault.NET(免责声明:这是我制作的)。 - smartcaveman
我甚至在两个System.RuntimeTypes(它们不是MemberInfos)中遇到了同样的问题。它们具有完全相同的模块和GUID - 一切都相同。但是Equals返回false。 - N73k
我找到答案了。问题出现在System.RuntimeType中。 System.RuntimeType重写了Equals方法,只做一个对象==检查(通过引用检查等式)。这非常糟糕。很可能是人们程序中许多错误的原因。 - N73k

7

我猜他们有一个不同的ReflectedType。例如,继承:

class A {
   public int Foo {get;set;}
}
class B : A {}

现在看一下typeof(A).GetProperty("Foo")typeof(B).GetProperty("Foo")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接