2012-02-27 9 views
8

Mam klasy hierarchii nadrzędny/dziecko, gdzie dominująca abstrakcyjnie deklaruje właściwości String i klasa Dziecko realizuje go:Jak zdobyć dziecko deklarujące typ z wyrażenia?

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

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

Gdy używam wyrażenia wprost (lub pośrednio) korzysta z klasy podrzędne, spodziewam DeclaringType wyrażenia męska MemberInfo do bycia „dziecko”, ale zamiast tego jest nadrzędny:

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

twierdzenie nie powiedzie się, ponieważ DeclaringType jest nadrzędna.

Czy jest coś, co mogę zrobić, gdybym wyraził moje wyrażenie lub pochłonął je, aby ujawnić rzeczywiste użycie typu Dziecko?

UWAGA: GetMemberInfo() powyżej jako metodę rozszerzenia:

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

klasy 'Child' nie dziedzicz po 'Parent'! – vulkanino

+1

Co to jest metoda GetMemberInfo()? Jeśli jest to rozszerzenie, opublikuj jego implementację. – Ani

+1

"Jeśli obiekt Type, z którego uzyskano ten obiekt MemberInfo, nie zadeklarował tego elementu, właściwość DeclaringType będzie reprezentować jeden z jego typów podstawowych." – vulkanino

Odpowiedz

8

Nie - to jest dokładnym odzwierciedleniem tego, co dostaje emitowane przez kompilator C# (ja nawet zapomniałem, że napisał to!). Nadpisanie jest skutecznie ignorowane podczas szukania elementu - kompilator troszczy się tylko o typ, który pierwotnie został zadeklarowany jako . Możesz to zobaczyć samodzielnie, kompilując kod, a następnie patrząc na IL. Ta metoda:

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

jest kompilowany do tej 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 

jeden punkt ciekawostek: kompilator VB nie działają tak samo, więc ta metoda:

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

jest skompilowany jako:

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 
+1

Wow, dziękuję za szczegółową odpowiedź! Myślę, że to oznacza: "to niemożliwe", więc muszę wrócić do deski kreślarskiej. Moim rzeczywistym celem jest GetCustomAttributes(), ale niektóre niestandardowe atrybuty są dodawane do nadpisywanych właściwości w podklasach, ale korzystając z powyższego, nie mogę ich osiągnąć, ponieważ DeclaringType jest rodzica. – Trinition

+0

@Trinition: Ale można uzyskać typ * target * odniesienia odniesienia, tj. Typ 'child'. Czy to nie wystarczy? Trudno dokładnie wiedzieć, co pokazać, ponieważ twój przykładowy kod jest nieprawidłowy - nie ma takiej metody 'GetMemberInfo' w' Expression '. –

+0

Edytowałem i dodawałem treść GetMemberInfo() powyżej. – Trinition

1

Jeśli nie chcesz stosować metody typu statycznego, nad którym pracujesz, ale raczej ostatnią zmianę, to jest to możliwe. Nie testowałem, ale coś podobnego do poniższego powinien wykonać zadanie:

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

Jeżeli zdasz MemberInfo jako pierwszy param i typu runtime obiektu jako drugi. Zwróć uwagę, że jest to prawdopodobnie powolne, więc możesz dodać buforowanie.

3

Moje rozwiązanie, oparte na informacjach z @JonSkeet i @CodeInChaos to nie patrzeć wyłącznie na PropertyInfo w wyrażeniu, ale także od rodzaju elementu członkowskiego w MemberExpression za:

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