确定两个不同的PropertyInfo是否来自同一接口?

时间:2019-03-15 17:55:30

标签: c# .net reflection .net-4.5

我一直在使用

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.IsHungryDeclaringType == typeof(Animal)

我想不出一种简单的方法来比较接口和具体类型属性表达式,而不必借助简单的Name比较(这容易导致假阳性)-但是我什么都想不起来无需枚举由这两种类型继承的所有接口,并查找具有两个集合中的属性名称的任何内容。


问:我们是否可以创建一个比较上述4个属性表达式产生的PropertyInfo时返回true的函数。(例如,检测它们是否都表示/实现相同的基本接口属性?)

2 个答案:

答案 0 :(得分:0)

在使用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实例是否代表另一个实例的接口实现。

答案 1 :(得分:0)

我想出的解决方案将获取器/设置器MethodInfoInterfaceMapping进行比较。它通过了我能想到的所有测试,但我不是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();
}