我一直在用
public static bool IsSameAsProperty(PropertyInfo first, PropertyInfo second) =>
first.DeclaringType == second.DeclaringType && first.Name == second.Name;
为了确定反射的属性信息是否与某个属性匹配,我抓住了一个基类。
当我尝试引用接口中定义的属性时,这种方法已经开始分崩离析。
例如,假设以下多接口继承方案:
interface IAnimal : { bool IsHungry { get; } }
interface IDog : IAnimal { }
abstract class Animal : IAnimal { public bool IsHungry { get; set; } }
class Dog : Animal, IDog { }
如果我正在创建属性表达式,则以下所有内容都是有效的:
Expression<Func<object, bool>> propertyExpression;
propertyExpression = (IAnimal animal) => animal.IsHungry
propertyExpression = (Animal animal) => animal.IsHungry
propertyExpression = (IDog dog) => dog.IsHungry
propertyExpression = (Dog dog) => dog.IsHungry
由于这些类型中的每一个都定义或继承了属性IsHungry
,因此所有这些表达式都是有效的。有人甚至认为他们都指的是同一个属性(虽然我可以理解接口和实例声明之间的细微差别)。
我的问题是我想要某种方式以编程方式检测所有这些属性“来自”共享接口IAnimal
并且是兼容的。不幸的是,我的测试返回false
,因为:
IDog.IsHungry
有DeclaringType == typeof(IAnimal)
而Dog.IsHungry
有DeclaringType == typeof(Animal)
我想不出一种简单的方法来比较接口和具体类型的属性表达式,而不需要采用简单的Name
比较(很容易出现误报) - 但我想不出任何不涉及枚举所有内容的东西两种类型继承的接口,并查找具有两个集合中的属性名称的任何内容。
问:我们可以创建一个函数,在比较从上述4个属性表达式中产生的任何PropertyInfo时返回true。 (例如,检测它们都表示/实现相同的基本接口属性?)
在使用new
关键字隐藏继承属性的情况下,这可能会导致误报,但是:
public static bool IsSameAsProperty(PropertyInfo first, PropertyInfo second) =>
first == second || // If default equals implementation returns true, no doubt
first.Name == second.Name && (
first.DeclaringType == second.DeclaringType ||
first.DeclaringType.IsAssignableFrom(second.DeclaringType) ||
second.DeclaringType.IsAssignableFrom(first.DeclaringType));
我想我可以更具体一点,检查是否first.DeclaringType.IsInterface
,反之亦然,但仍然可以明确地实现该接口并使用具有相同名称的新属性隐藏其属性,因此额外的检查不会使这个“更安全”。
不确定我是否可以判断一个PropertyInfo
实例是否代表另一个的接口实现。
我想出的解决方案将getter / setter MethodInfo
与InterfaceMapping
进行了比较。它通过了我能想到的所有测试,但我并不是100%确信没有奇怪的角落情况,这没有抓住。
public static bool IsSameAsProperty(PropertyInfo first, PropertyInfo second)
{
if (first.DeclaringType == second.DeclaringType && first.Name == second.Name)
return true;
bool firstIsSecond = second.DeclaringType.IsAssignableFrom(first.DeclaringType);
bool secondIsFirst = first.DeclaringType.IsAssignableFrom(second.DeclaringType);
if (firstIsSecond || secondIsFirst)
{
PropertyInfo baseProp = firstIsSecond ? second : first;
PropertyInfo derivedProp = firstIsSecond ? first : second;
MethodInfo baseMethod, implMethod;
if (baseProp.GetMethod != null && derivedProp.GetMethod != null)
{
baseMethod = baseProp.GetMethod;
implMethod = derivedProp.GetMethod;
}
else if (baseProp.SetMethod != null && derivedProp.SetMethod != null)
{
baseMethod = baseProp.SetMethod;
implMethod = derivedProp.SetMethod;
}
else
{
return false;
}
// Is it somehow possible to create a situation where both get and set exist
// and the set method to be an implementation while the get method is not?
if (baseMethod.DeclaringType.IsInterface)
return IsInterfaceImplementation(implMethod, baseMethod);
else
return IsOverride(implMethod, baseMethod);
}
return false;
}
private static bool IsInterfaceImplementation(MethodInfo implMethod, MethodInfo interfaceMethod)
{
InterfaceMapping interfaceMap = implMethod.DeclaringType.GetInterfaceMap(interfaceMethod.DeclaringType);
int index = Array.IndexOf(interfaceMap.InterfaceMethods, interfaceMethod);
// I don't think this can ever be the case?
if (index == -1)
return false;
MethodInfo targetMethod = interfaceMap.TargetMethods[index];
return implMethod == targetMethod || IsOverride(implMethod, targetMethod);
}
private static bool IsOverride(MethodInfo implMethod, MethodInfo baseMethod)
{
return implMethod.GetBaseDefinition() == baseMethod.GetBaseDefinition();
}