答案是:不,这不是错误。 区别在于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
。文档说明了这一点:
获取用于获取此成员的类对象。
答案 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)
我比较DeclaringType
和Name
。这报告来自两种不同泛型类型的“相同”属性是不同的(例如,List<int>.Count
和List<string>.Count
)。比较MetadataToken
和Module
会报告这两个属性是相同的。
答案 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;
}
该方法仅针对Public
和Instance
成员编写。其他一些讨论主题建议使用AreEqual1(...)
或AreEqual2(...)
方法,但对于给定的示例,它们会返回false
。