如何可靠地比较两个PropertyInfos或方法?

时间:2011-01-09 17:50:11

标签: c# reflection

方法也是如此:

我得到了两个PropertyInfo实例或方法,这些实例是通过GetProperty()GetMember()等从(或者来自MemberExpression)的类中提取出来的。

我想确定它们是否实际上指的是相同的属性或相同的方法

(propertyOne == propertyTwo)

(methodOne == methodTwo)

显然,这实际上不起作用,您可能正在查看相同的属性,但它可能是从类层次结构的不同级别提取的(在这种情况下通常为propertyOne != propertyTwo

当然,我可以查看DeclaringType,并重新请求该属性,但是当你开始考虑

时,这开始变得有点混乱
  • 在接口上声明并在类
  • 上实现的属性/方法
  • 在基类(虚拟)上声明并在派生类上重写的属性/方法
  • 在基类上声明的属性/方法,用'new'覆盖(在IL世界中,这不是什么特别的事情)

在一天结束时,我只想在两个属性或两个方法之间进行智能相等检查,我80%确定上述要点并未涵盖所有边缘情况,虽然我可以坐下来,写一堆测试并开始玩,我很清楚我对这些概念实际实现的低级知识不是优秀的,而我是希望这是一个已经回答过的话题,我只是在搜索时很糟糕。

最好的答案会给我一些实现上述目标的方法,解释已经处理的边缘案例及其原因: - )


澄清

从字面上看,我想确保它们是相同的属性,这里有一些例子

public interface IFoo
{
     string Bar { get; set; }
}

public class Foo : IFoo
{
     string Bar { get; set; }
}

typeof(IFoo).GetProperty("Bar")

typeof(Foo).GetProperty("Bar")

将返回两个不相等的属性信息:

public class BaseClass
{
     public string SomeProperty { get; set ; }
}

public class DerivedClass : BaseClass { }


typeof(BaseClass).GetMethod("SomeProperty")

typeof(DerivedClass).GetProperty("SomeProperty")

我实际上无法记住这两个现在是否会返回相同的对象,但在我的世界中它们是平等的。

类似地:

public class BaseClass
{
    public virtual SomeMethod() { }
}

public class DerivedClass
{
    public override SomeMethod() { }
}

typeof(BaseClass).GetMethod("SomeMethod")

typeof(DerivedClass).GetProperty("SomeMethod")

同样,这些不匹配 - 但我希望它们(我知道它们不是特别相同,但在我的域中它们是因为它们引用相同的原始属性)

我可以在结构上做到这一点,但这将是'错误'。

进一步说明

您如何申请隐藏其他财产的财产?似乎我之前的一个假设是无效的,默认情况下GetProperty("name")的默认实现将默认引用当前级别。

BindingFlags.DeclaringType似乎最终会返回null!

4 个答案:

答案 0 :(得分:3)

看一下PropertyInfo / IFoo示例中的Foo个对象,我们可以得出以下结论:

  1. 没有直接的方法来查看最初声明属性的类/接口。
  2. 因此,为了检查属性是否实际上是在祖先类中声明的,我们需要迭代祖先并查看它们是否也存在。
  3. 接口也一样,我们需要调用Type.GetInterfaces并从那里开始工作。不要忘记接口可以实现其他接口,因此必须是递归的。
  4. 所以让我们对它有所了解。首先,要涵盖继承的属性:

    PropertyInfo GetRootProperty(PropertyInfo pi)
    {
        var type = pi.DeclaringType;
    
        while (true) {
            type = type.BaseType;
    
            if (type == null) {
                return pi;
            }
    
            var flags = BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Instance |
                        BindingFlags.Public | BindingFlags.Static;
            var inheritedProperty = type.GetProperty(pi.Name, flags);
    
            if (inheritedProperty == null) {
                return pi;
            }
    
            pi = inheritedProperty;
        }
    }
    

    现在,要覆盖在接口中声明的属性(使用DFS搜索):

    PropertyInfo GetImplementedProperty(PropertyInfo pi)
    {
        var type = pi.DeclaringType;
        var interfaces = type.GetInterfaces();
    
        if (interfaces.Length == 0) {
            return pi;
        }
    
        var flags = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public;
        var query = from iface in interfaces
                    let implementedProperty = iface.GetProperty(pi.Name, flags)
                    where implementedProperty != pi
                    select implementedProperty;
    
        return query.DefaultIfEmpty(pi).First();
    }
    

    将这些联系在一起:

    PropertyInfo GetSourceProperty(PropertyInfo pi)
    {
        var inherited = this.GetRootProperty(pi);
        if (inherited != pi) {
            return inherited;
        }
    
        var implemented = this.GetImplementedProperty(pi);
        if (implemented != pi) {
            return implemented;
        }
    
        return pi;
    }
    

    这应该有效。它没有考虑具有相同名称但索引参数的类型和/或数量不同的索引属性,因此留下作为读者的谚语练习。

    免责声明:我甚至没有编译它(现在没时间运行测试)。它旨在作为“答案”的起点,因为到目前为止还没有。

答案 1 :(得分:2)

我不确定你需要什么,但我想在这种情况下你的相等定义是“如果调用两个方法信息调用相同的方法”?如果是这样,那么你真的需要在比较中指定一个类型,例如考虑这个:

public interface IFoo
{
  void AMethod();
}

public interface IBar
{
  void AMethod();
}

public class FooBar : IFoo, IBar
{
  void AMethod();
}

显然typeof(IFoo).GetMethod(“AMethod”)和typeof(IBar).GetMethod(“AMethod”)不相等,它们甚至不相关,它们确实调用相同的FooBar实例上的方法。所以你可能想要的是一个带有三个参数的比较方法:

bool WillInvokeSameMethodOnType(MethodInfo method1, MethodInfo method2, Type type)

查看FakeItEasy中的MethodInfoManager类,它可能是你想要的:http://code.google.com/p/fakeiteasy/source/browse/Source/FakeItEasy/Core/MethodInfoManager.cs?r=8888fefbc508fb02d5435a3e33774500bec498b3

答案 2 :(得分:2)

所以,这是一个艰难的cookie,在我讨论枯燥的细节之前,我会说这个,我最终选择进行结构比较,因为 唯一的地方会掉下来,是一个成员隐藏另一个成员在C#中使用'new'关键字 - 决定这个系统中的一个小问题,与其他多种伤害相比,这个问题的正确解决方案最终会导致它出错了( 出错了,相信我)。

我接受了上面的答案,因为它接近于解决问题 - 它唯一的问题是仍然结构性质(我可以通过类型/名称/参数实现这一点)检查)

虽然存在一些差异,主要是有必要查看界面地图而不是调用GetProperty - 这是一个非常重要的细节 - 这是我修改后的方法,在某些情况下会失败。

private PropertyInfo GetImplementedProperty(PropertyInfo pi)
    {
        var type = pi.DeclaringType;
        var interfaces = type.GetInterfaces();

        for(int interfaceIndex = 0; interfaceIndex < interfaces.Length; interfaceIndex++)
        {
            var iface = interfaces[interfaceIndex];
            var interfaceMethods = type.GetInterfaceMap(iface).TargetMethods;

            MethodInfo matchingMethod = null;
            for (int x = 0; x < interfaceMethods.Length; x++)
            {
                if (pi.GetGetMethod().LooseCompare(interfaceMethods[x]) || pi.GetSetMethod().LooseCompare(interfaceMethods[x]))
                {
                    matchingMethod = type.GetInterfaceMap(iface).InterfaceMethods[x];
                    break; 
                }
            }
            if (matchingMethod == null) continue;

            var interfacePi = from i in interfaces
                              from property in i.GetProperties()
                              where property.GetGetMethod().LooseCompare(matchingMethod) || property.GetSetMethod().LooseCompare(matchingMethod)
                              select property;

            return interfacePi.First();
        }

        return pi;
    } 

我最后放弃检查一名成员是否隐藏了另一名成员,然后进行了以下黑客攻击:

private PropertyInfo GetRootProperty(PropertyInfo pi)
    {
        if ((pi.GetGetMethod().Attributes & MethodAttributes.Virtual) != MethodAttributes.Virtual) { return pi; }

        var type = pi.DeclaringType;

        while (true)
        {
            type = type.BaseType;

            if (type == null)
            {
                return pi;
            }

            var flags = BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Instance |
                        BindingFlags.Public | BindingFlags.Static;

            var inheritedProperty = type.GetProperty(pi.Name, flags);

            if (inheritedProperty == null)
            {
                return pi;
            }

            pi = inheritedProperty;
        }
    }

我在这里假设一个使用'new'关键字的属性/方法也不会使用virtual关键字,因为看起来'new'无论如何都是一个边缘情况,这是不太可能的。

这是我在决定让我的测试通过之前所得到的,我很满意。 (在我决定选择结构检查之前......)我希望这对任何在将来偶然发现的人都有用。

答案 3 :(得分:1)

检查要比较的两个MemberInfo的声明类型似乎更简单。在基本/子类关系的情况下,如果声明类型相同,它们应表示相同的声明。在接口的情况下,如果声明接口类型在另一个接口的列表中,它们应该是相同的:

Type type1 = methodInfo1.DeclaringType;
Type type2 = methodInfo2.DeclaringType;

bool same = type1 == type2 || 
    type1.IsInterface && type2.GetInterfaces.Contains(type1) ||
    type2.IsInterface && type1.GetInterfaces.Contains(type2);

关于接口需要注意的一点是'接口映射' - Type.GetInterfaceMap,这意味着接口中声明的方法在实现类中可能没有相同的名称,而当前的方法似乎没有帐户。

相关问题