How to get the child declaring type from an expression?
Asked Answered
I

3

14

I have a Parent / Child class hierarchy where the Parent abstractly declares a string property and the Child class implements it:

abstract class Parent
{
   public abstract string Value { get; }
}

class Child : Parent
{
   public override string Value { get { return null; } }
}

When I use an expression that explicitly (or implicitly) uses the Child class, I expect the Expressions's MemberInfo's DeclaringType to be 'Child', but instead it is Parent:

Child child = new Child();
Expression<Func<string>> expression = (() => child.Value);
MemberInfo memberInfo = expression.GetMemberInfo();
Assert.AreEqual(typeof(Child), memberInfo.DeclaringType); // FAILS!

The assertion fails because the DeclaringType is Parent.

Is there something I can do in declaring my expression or consuming it to reveal the actual use of the Child type?

NOTE: GetMemberInfo() above as an extension method (I even forgot we had written this!):

public static class TypeExtensions
{
    /// <summary>
    /// Gets the member info represented by an expression.
    /// </summary>
    /// <param name="expression">The member expression.</param>
    /// <returns>The member info represeted by the expression.</returns>
    public static MemberInfo GetMemberInfo(this Expression expression)
    {
        var lambda = (LambdaExpression)expression;

        MemberExpression memberExpression;
        if (lambda.Body is UnaryExpression)
        {
            var unaryExpression = (UnaryExpression)lambda.Body;
            memberExpression = (MemberExpression)unaryExpression.Operand;
        }
        else memberExpression = (MemberExpression)lambda.Body;

        return memberExpression.Member;
    }
}
Infusorian answered 27/2, 2012 at 14:23 Comment(6)
class Child does not inherit from Parent!Uric
What's this GetMemberInfo() method? If it's an extension, please post its implementation.Archle
"If the Type object from which this MemberInfo object was obtained did not declare this member, the DeclaringType property will represent one of its base types."Uric
vulkanino, that was a typo on my part. Thanks for catching it!Infusorian
Ani, yes, it's a local extension method. I forgot it was an extension method myself until I looked! Here it is:Infusorian
You asked for the declaring type and that's what you got. The member was declared by the base class. It was overridden by the derived class, but an override does not count as declaring a new "method slot"; rather it specializes an existing slot.Deliquescence
S
12

No - this is an accurate representation of what gets emitted by the C# compiler. The override is effectively ignored when looking for the member - the compiler only cares about the type that originally declared the member. You can see this for yourself by compiling code and then looking at the IL. This method:

static void Main()
{
    Child c = new Child();
    string x = c.Value;
}

is compiled into this IL:

IL_0000:  nop
IL_0001:  newobj     instance void Child::.ctor()
IL_0006:  stloc.0
IL_0007:  ldloc.0
IL_0008:  callvirt   instance string Parent::get_Value()
IL_000d:  stloc.1
IL_000e:  ret

One point of trivia: the VB compiler doesn't work the same way, so this method:

Public Shared Sub Main(Args As String())
    Dim x As Child = New Child()
    Dim y As String = x.Value
End Sub

is compiled as:

IL_0000:  newobj     instance void [lib]Child::.ctor()
IL_0005:  stloc.0
IL_0006:  ldloc.0
IL_0007:  callvirt   instance string [lib]Child::get_Value()
IL_000c:  stloc.1
IL_000d:  ret
Servetnick answered 27/2, 2012 at 14:24 Comment(5)
Wow, thank you for the detailed response! I guess this means: "it's impossible," so I need to head back to the drawing board. My actual goal is to GetCustomAttributes(), but some of the custom attributes are added to the overridden properties in subclasses but using the above I can't reach them since the DeclaringType is the Parent.Infusorian
@Trinition: But you could get the type of the target of the member reference, i.e. the type of child. Wouldn't that be good enough? It's hard to know exactly what to show you as your sample code is invalid - there's no such GetMemberInfo method on Expression<TDelegate>.Servetnick
@Trinition: Right, well once you've got a MemberExpression, you can just use memberExpression.Expression.Type.Servetnick
Even if the VB.NET compiler works differently, expressions in VB.NET have the same behavior as in C# -- Dim expr As Expression(Of Func(Of Child, String)) = Function(x) x.Value : CType(expr.Body, MemberExpression).Member <> GetType(Child).GetMember("Value")(0).Thadeus
@ZevSpitz I just made a fiddle (dotnetfiddle.net/YxumrX) proving that they in fact do not have the same behavior. Did you run this on a specific runtime version? Or did I miss something?Fiona
I
6

My solution, based on information from @JonSkeet and @CodeInChaos is to not look purely at the PropertyInfo in the Expression, but also the Type of the MemberExpression's Member component:

/// <summary>
/// Extracts the PropertyInfo for the propertybeing accessed in the given expression.
/// </summary>
/// <remarks>
/// If possible, the actual owning type of the property is used, rather than the declaring class (so if "x" in "() => x.Foo" is a subclass overriding "Foo", then x's PropertyInfo for "Foo" is returned rather than the declaring base class's PropertyInfo for "Foo").
/// </remarks>
/// <typeparam name="T"></typeparam>
/// <param name="propertyExpression"></param>
/// <returns></returns>
internal static PropertyInfo ExtractPropertyInfo<T>(Expression<Func<T>> propertyExpression)
{
    if (propertyExpression == null)
    {
        throw new ArgumentNullException("propertyExpression");
    }

    var memberExpression = propertyExpression.Body as MemberExpression;
    if (memberExpression == null)
    {
        throw new ArgumentException(string.Format("Expression not a MemberExpresssion: {0}", propertyExpression), "propertyExpression");
    }

    var property = memberExpression.Member as PropertyInfo;
    if (property == null)
    {
        throw new ArgumentException(string.Format("Expression not a Property: {0}", propertyExpression), "propertyExpression");
    }

    var getMethod = property.GetGetMethod(true);
    if (getMethod.IsStatic)
    {
        throw new ArgumentException(string.Format("Expression cannot be static: {0}", propertyExpression), "propertyExpression");
    }

    Type realType = memberExpression.Expression.Type;
    if(realType == null) throw new ArgumentException(string.Format("Expression has no DeclaringType: {0}", propertyExpression), "propertyExpression");

    return realType.GetProperty(property.Name);
}
Infusorian answered 27/2, 2012 at 15:50 Comment(1)
This works, thanks. I as able to get the DisplayNameAttribute of the child class adapting this.Bucksaw
V
1

If you don't want the method of the static type you work on, but rather the latest override, then it is possible. I didn't test, but something similar to the following should do the job:

public bool FindOverride(MethodInfo baseMethod, Type type)
{
    if(baseMethod==null)
      throw new ArgumentNullException("baseMethod");
    if(type==null)
      throw new ArgumentNullException("type");
    if(!type.IsSubclassOf(baseMethod.ReflectedType))
        throw new ArgumentException(string.Format("Type must be subtype of {0}",baseMethod.DeclaringType));
    while(true)
    {
        var methods=type.GetMethods(BindingFlags.Instance|
                                    BindingFlags.DeclaredOnly|
                                    BindingFlags.Public|
                                    BindingFlags.NonPublic);
        var method=methods.FirstOrDefault(m=>m.GetBaseDefinition()==baseMethod))
        if(method!=null)
          return method;
        type=type.BaseType;
    }
}

Where you pass the MemberInfo as the first param, and the runtime type of the object as second. Note that this is likely slow, so you might want to add some caching.

Voletta answered 27/2, 2012 at 15:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.