Search code examples
c#vb.netreflectioninheritanceoverriding

How does reflection tell me when a property is hiding an inherited member with the 'new' keyword?


So if I have:

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

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

How can I use reflection to see that ChildClass is hiding the Base implementation of TempProperty?

I'd like the answer to be agnostic between c# and vb.net


Solution

  • We'll have to deal in terms of the methods of the property here rather than the property itself, because it is the get/set methods of the property that actually get overridden rather than the property itself. I'll use the get method as you should never have a property without one, though a complete solution should check for the lack of one.

    Looking at the IL emitted in a number of cases, the 'get' method of the base property will have the metadata tokens (this is from the C# compiler; others may not emit the hidebysig depending on their method hiding semantics, in which case the method would be hide-by-name):

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

    The derived one will have the following tokens:

    override    : .method public hidebysig specialname virtual instance 
    new         : .method public hidebysig specialname instance
    new virtual : .method public hidebysig specialname newslot virtual instance 
    

    So we can see from this that it isn't possible to tell purely from the method's metadata tokens whether it is new because the non-virtual base method has the same tokens as the non-virtual new method, and the virtual base method has the same tokens as the new virtual method.

    What we can say is that if the method has the virtual token but not the newslot token then it overrides a base method rather than shadows it, i.e.

    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
    }
    

    Assuming, then, that we find the 'get' method is not an override, we want to know whether there is a property in the base class that it is shadowing. The problem is that because the method is in a different method table slot, it doesn't actually have any direct relationship to the method it is shadowing. So what we're actually saying is "does the base type have any method which meets the criteria for shadowing", which varies depending on whether the method is hidebysig or hide-by-name.

    For the former we need to check whether the base class has any method which matches the signature exactly, whereas for the latter we need to check whether it has any method with the same name, so continuing the code from above:

    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
            }
        }
    }
    

    I think this is most of the way there, but I still don't think it's exactly right. For a start I'm not totally familiar with hiding by name as C# doesn't support it and that's pretty much all I use, so I may be wrong in the code here that indicates an instance method could shadow a static one. I also don't know about the case sensitivity issue (e.g. in VB could a method called Foo shadow a method called foo if they both had the same signature and were both hidebysig - in C# the answer is no but if the answer is yes in VB then it means the answer to this question is actually nondeterministic).

    Well, I'm not sure how much help all this is, other than to illustrate that it's actually a much harder problem than I thought it would be (or I've missed something really obvious in which case I'd like to know!). But hopefully it's got sufficient content that it helps you achieve what you're trying to do.