反射如何告诉我属性使用'new'关键字隐藏继承成员?

22

所以如果我有:

public class ChildClass : BaseClass
{
    public new virtual string TempProperty { get; set; }
}

public class BaseClass
{
    public virtual string TempProperty { get; set; }
}

如何使用反射来查看ChildClass隐藏了TempProperty的基本实现?

我希望答案不受C#和VB.NET的影响。

4个回答

23

我们将不得不根据属性的方法来处理,而不是属性本身,因为实际上被覆盖的是属性的get/set方法,而不是属性本身。我将使用get方法,因为永远不应该没有get方法的属性,尽管完整的解决方案应该检查是否缺少get方法。

在许多情况下查看发出的IL(中间语言),基本属性的“get”方法将具有元数据标记(这是来自C#编译器的,其他编译器可能不发出hidebysig,具体取决于它们的方法隐藏语义,在这种情况下,该方法将被按名称隐藏):

non-virtual : .method public hidebysig specialname instance
virtual     : .method public hidebysig specialname newslot virtual instance 

派生的内容将具有以下标记:

override    : .method public hidebysig specialname virtual instance 
new         : .method public hidebysig specialname instance
new virtual : .method public hidebysig specialname newslot virtual instance 
所以从这里我们可以看出,仅从方法的元数据标记中无法判断它是否是new,因为非虚基方法与非虚new方法具有相同的标记,而虚基方法与new virtual方法具有相同的标记。
我们可以说的是,如果该方法具有virtual标记但没有newslot标记,则它会覆盖一个基本方法而不是遮蔽它。
var prop = typeof(ChildClass).GetProperty("TempProperty");
var getMethod = prop.GetGetMethod();
if ((getMethod.Attributes & MethodAttributes.Virtual) != 0 &&
    (getMethod.Attributes & MethodAttributes.NewSlot) == 0)
{
    // the property's 'get' method is an override
}

假设我们发现“get”方法不是一个覆盖方法,那么我们想知道基类中是否有一个属性被其遮蔽。问题在于,由于该方法位于不同的方法表格槽中,它实际上与被其遮蔽的方法没有任何直接关系。因此,我们实际上想问的是,“基类型是否有任何满足遮蔽条件的方法”,这取决于方法是hidebysig还是按名称隐藏。

对于前者,我们需要检查基类是否有与签名完全匹配的方法,而对于后者,我们需要检查是否有具有相同名称的方法,因此继续上面的代码:

else 
{
    if (getMethod.IsHideBySig)
    {
        var flags = getMethod.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic;
        flags |= getMethod.IsStatic ? BindingFlags.Static : BindingFlags.Instance;
        var paramTypes = getMethod.GetParameters().Select(p => p.ParameterType).ToArray();
        if (getMethod.DeclaringType.BaseType.GetMethod(getMethod.Name, flags, null, paramTypes, null) != null)
        {
            // the property's 'get' method shadows by signature
        }
    }
    else
    {
        var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;
        if (getMethod.DeclaringType.BaseType.GetMethods(flags).Any(m => m.Name == getMethod.Name))
        {
            // the property's 'get' method shadows by name
        }
    }
}

我认为这个答案已经非常接近了,但我仍然认为它并不完全正确。首先,我不是完全熟悉按名称隐藏的方法,因为C#不支持它,而这几乎是我所使用的全部内容,所以我可能在这里的代码中有错误,表明实例方法可能会遮盖静态方法。我也不知道大小写敏感的问题(例如,在VB中,如果两个具有相同签名且都是"hidebysig"的方法分别称为Foofoo,是否可以遮蔽 - 在C #中,答案是否定的,但如果VB的答案是肯定的,那么这意味着这个问题的答案实际上是不确定的)。

嗯,我不确定所有这些内容能够提供多少帮助,除了说明这实际上比我想象的要难得多(或者我可能错过了一些非常明显的东西,如果是这样,我想知道!)。但是希望它包含了足够的内容,能够帮助您实现您想要做的事情。


我非常确定你需要在调用BaseType.GetMethod()时还包括BindingFlags.ExactBinding。否则,它会报告bool Equals(Foo other)隐藏了Object.Equals(object)。 - RobSiklos
这个答案的问题在于,如果你的类型是实现者,那么行 var [ prop = typeof(ChildClass).GetProperty("TempProperty"); ] 将会抛出一个错误。你会得到一个“找到多个匹配项”的错误。如果你使用 .GetProperties(),你会发现 Shadowed 属性会出现两次。所以唯一让它工作的方法是可能通过索引循环遍历属性,测试它们,然后跟踪重复的名称并决定使用哪一个。 - jrandomuser

6

默认情况下,似乎反射并不能自动为您提供这个功能,因此您需要自己编写代码:

public static bool IsHidingMember( this PropertyInfo self )
{
    Type baseType = self.DeclaringType.BaseType;
    PropertyInfo baseProperty = baseType.GetProperty( self.Name, self.PropertyType );

    if ( baseProperty == null )
    {
        return false;
    }

    if ( baseProperty.DeclaringType == self.DeclaringType )
    {
        return false;
    }

    var baseMethodDefinition = baseProperty.GetGetMethod().GetBaseDefinition();
    var thisMethodDefinition = self.GetGetMethod().GetBaseDefinition();

    return baseMethodDefinition.DeclaringType != thisMethodDefinition.DeclaringType;
}

不确定这对于索引属性会如何工作,然而!


这看起来很有前途。你认为这种方法有哪些限制? - Dane O'Connor
如果“new”属性的类型与隐藏属性不同,则我会在“PropertyInfo baseProperty = baseType.GetProperty(self.Name, self.PropertyType);”中删除“self.PropertyType”。 - Rzassar

0
我从未尝试过你正在尝试的事情,但是MethodInfo.GetBaseDefinition()方法似乎是你要找的。
它返回此方法覆盖的MethodInfo。
来自MSDN:
如果给定方法使用new关键字指定(如Type Members中所述的newslot),则返回给定方法。

注意:这将适用于此精确示例。如果省略虚拟关键字,则不起作用。 - JaredPar

-1

更正一下,如果你使用的是VB语言,你要找的属性是"IsHideBySig"。在定义方法/属性时使用了"new"关键字,则此属性值为false。

在C#中,这两种情况都会输出为“hidebysig”。感谢Greg指出这一点。我没有意识到我只测试了VB。以下是一个可以重现此行为的VB示例代码。

Module Module1

    Class Foo
        Public Function SomeFunc() As Integer
            Return 42
        End Function
    End Class

    Class Bar
        Inherits Foo
        Public Shadows Function SomeFunc() As Integer
            Return 36
        End Function
    End Class

    Sub Main()
        Dim type = GetType(Bar)
        Dim func = type.GetMethod("SomeFunc")
        Stop
    End Sub

End Module

你试过这个吗?它是不正确的。在C#中,无论它是虚拟的、新的、重写的等等,它总是返回true。 - Greg Beech
@Greg - 你知道另一个答案吗?是你发了另一篇帖子但删除了吗?我已经阅读了文档,你是对的 - 这不是语言无关的,并且对于c#应该返回“true”,而不是“false”。 - Dane O'Connor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接