views:

72

answers:

2

Consider this interesting set of types:

class A     { public virtual     int MyProperty { get; set; } }
class B : A { public override    int MyProperty { get; set; } }
class C : B { public new virtual int MyProperty { get; set; } }
class D : C { public override    int MyProperty { get; set; } }
class E : D { public new         int MyProperty { get; set; } }

I see three different properties here, with five implementations hiding or overriding each other.

I'm trying to get the set of property declarations for type E:

A.MyProperty
C.MyProperty
E.MyProperty

But my code below gives me the set of property implementations:

A.MyProperty
B.MyProperty
C.MyProperty
D.MyProperty
E.MyProperty

What do I need to do to get the property declarations?

Or is there any chance that B.MyProperty will ever return a value other than A.MyProperty for any instance of E?

If my approach is heading in the wrong direction: How do I get all property members of a type including any hidden ones, but not including those that will never have different values?


void GetProperties(Type type)
{
    if (type.BaseType != null)
    {
        GetProperties(type.BaseType);
    }

    foreach (var item in type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public))
    {
        Console.WriteLine("{0}.{1}", type.Name, item.Name);
    }
}

Desired outputs:

typeof(A)       typeof(B)       typeof(C)       typeof(D)       typeof(E)
------------    ------------    ------------    ------------    ------------
A.MyProperty    A.MyProperty    A.MyProperty    A.MyProperty    A.MyProperty
                                C.MyProperty    C.MyProperty    C.MyProperty
                                                                E.MyProperty
+2  A: 

This may get you started down the path that you want:

    static void GetProperties(Type type)
    {
        if (type.BaseType != null)
        {
            GetProperties(type.BaseType);
        }

        foreach (var item in type.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public))
        {
            MethodInfo method = item.GetGetMethod();
            MethodInfo baseMethod = method.GetBaseDefinition();

            System.Diagnostics.Debug.WriteLine(string.Format("{0} {1}.{2} {3}.{4}", type.Name, method.DeclaringType.Name, method.Name, baseMethod.DeclaringType, baseMethod.Name));

            if (baseMethod.DeclaringType == type)
            {
                Console.WriteLine("{0} {1}", type.Name, item.Name);
            }
        }
    }

This code outputs the following:

A MyProperty

C MyProperty

E MyProperty

Note that this code depends on using the MethodInfo of the get method associated with the property. If you happen to have set-only properties, then you'll need to do some extra checks to handle that case.

Dr. Wily's Apprentice
`GetBaseDefinition` is the key I was looking for. Thanks!
dtb
A: 

Each of these is both a declaration and implementation (as none of them are abstract, so they all define method bodies). The keywords override and new are little more than hints to the runtime about which implementation to pick given the context of an instance. With reflection, you're bypassing normal inheritance tracing and invoking a specific implementation.

Given that, you can still figure out which of these will be the "root" call made from various points in the hierarchy. Recall that properties in .NET are pretty much syntactic sugar for a specific getter and setter method structure and a backing field, accessed as if the accessors were the field itself. Thus, a PropertyInfo exposes GetGetMethod() and GetSetMethod() which will return MethodInfo objects. You should only need one of the two as they'll both have the inheritance qualifiers given to the property, but make sure that your property has a get or a set at all levels of the hierarchy or this will blow up.

Now, MethodInfo exposes several properties regarding accessibility and inheritance. Of importance to you is the IsHideBySig property. If this returns true, then this method, as declared on the current type, is hiding an identical signature on its parent using the new keyword. So, in your code, you want to look at the members, and test two things; whether the type has a base type other than Object (or an abstract type you specify), and whether IsHideBySig on the getter or setter is true. If either of those is true, this is a "root" implementation for any type between the current type and the next most derived type with a similar "root".

So, your foreach would end up looking something like this:

foreach (var item in type.GetProperties(
   BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public)
      .Where(pi=>pi.GetGetMethod().IsHideBySig || pi.ReflectedType.BaseType == typeof(Object))
{
        Console.WriteLine("{0} {1}", type.Name, item.Name);
}

Now remember that these "roots" are not the ones that will be called, unless their children explicitly call them using the base identifier, and this can happen regardless of whether the current property hides or overrides its parent. The difference is in which implementation will be invoked by the runtime when an instance is cast as one of its ancestor types. In that case, the implementation chosen is that of the most derived type in the inheritance chain between cast and actual types, starting from the cast type, which does not hide its parent. If the most direct descendant of the cast type hides its parent, the cast type's implementation is used regardless of what that implementation does to its parent.

In your hierarchy, those would be B, D and E. If you declared a new E and invoked MyProperty, you'd get E's implementation. Then, if you passed it to a method that takes any A and accesses MyProperty, it would use B's implementation. If you declared an E and treated it as a C, it would use D's implementation. If you created a C and treated it as such, you'd get C's implementation (because a C is not a D), but if you treated it as an A you'd get B's implementation.

KeithS
That sounds interesting, but the code doesn't produce the desired results in its current form.
dtb
BTW you nailed exactly what I want: get the list of "roots" and invoke the most derived implementations.
dtb