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

4

22

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

Vigilante answered 13/11, 2008 at 21:18 Comment(0)
F
23

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.

Fishbein answered 14/11, 2008 at 0:57 Comment(2)
I'm pretty sure you need to also include BindingFlags.ExactBinding in your call to BaseType.GetMethod(). Otherwise, it will report that bool Equals(Foo other) hides Object.Equals(object)Uncompromising
The problem with this answer is that the line var [ prop = typeof(ChildClass).GetProperty("TempProperty"); ] will throw and error if your type is the implementor. You will get an "Ambiguous match found." And if you do a .GetProperties() instead you will notice that Shadowed property will appear twice. So the only way to make this work is to possible cycle through the properties by index and test them and then keep track of duplicated names and decide which one to use.Freakish
W
6

Doesn't look like reflection will give this to you by default so you'll have to roll your own:

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

Not sure how this will work with indexed properties, however!

Warmonger answered 13/11, 2008 at 23:13 Comment(2)
This looks promising. What limitations do you see with this approach?Philipson
I case of new property has different type from the hidden property, i removed self.PropertyType inPropertyInfo baseProperty = baseType.GetProperty(self.Name, self.PropertyType);Annals
M
0

I never did what you are trying to do but MethodInfo.GetBaseDefinition() method seems to be what you are looking for.

It returns the MethodInfo this method is overriding.

From MSDN :

If a given method is specified with the new keyword (as in newslot as described in Type Members), the given method is returned.

Montevideo answered 13/11, 2008 at 22:32 Comment(1)
Note: This will work for this precise example. It will not work for instance if the virtual keyword is emitted.Jamboree
J
-1

Correction, if you are using VB the property you are looking for is "IsHideBySig". This will be false in the case that the "new" keyword was used to define a method/property.

In the C# case, both instances are outputted as "hidebysig". Thanks for pointing that out Greg. I didn't realize I only tested this in VB. Here's sample VB code that will repro this behavior.

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
Jamboree answered 13/11, 2008 at 21:22 Comment(2)
Have you tried this? It isn't correct. It always returns true in C#, irrespective of whether it is virtual, new, overridden, etc.Fishbein
@Greg - do you know another answer? Was that you who had the other post but deleted it? I've read the docs and your right - this isn't language agnostic and also should return "true" not "false" for c#.Philipson

© 2022 - 2024 — McMap. All rights reserved.