答案是:不,这是不是一个错误。 所不同的是在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
。 文档说这个:
获取用于获取此成员的类对象。
不要假设有一个在库中的缺陷,除非你确实知道你在做什么,你已经彻底测试的问题。
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
实例。 我不能告诉你为什么,他们已经做出了这个决定(我有我的理论),你就必须从他们那里听到它。
为什么你不只是比较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; }
}
我比较DeclaringType
和Name
。 这个报告说,从两个不同的通用类型的“相同”的属性是不同的(例如, List<int>.Count
和List<string>.Count
)。 比较MetadataToken
和Module
将报告这两个属性是相同的。
在一开始,这似乎有道理两个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
的给定的例子。