Как получить дочернее объявление типа из выражения? - PullRequest
12 голосов
/ 27 февраля 2012

У меня есть иерархия класса Parent / Child, где Parent абстрактно объявляет строковое свойство, а класс Child реализует его:

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

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

Когда я использую выражение, которое явно (или неявно) использует класс ChildЯ ожидаю, что DeclaringType для MemberInfo в выражениях будет 'Child', но вместо этого это Parent:

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

Утверждение не выполнено, поскольку DeclaringType является Parent.

Есть ли что-то, что я могу сделать вобъявить мое выражение или использовать его, чтобы раскрыть фактическое использование типа Child?

ПРИМЕЧАНИЕ: GetMemberInfo () выше в качестве метода расширения (я даже забыл, что мы написали это!):

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

Ответы [ 3 ]

10 голосов
/ 27 февраля 2012

Нет - это точное представление того, что испускается компилятором C #. Переопределение фактически игнорируется при поиске члена - компилятор заботится только о типе, который первоначально объявил член. Вы можете убедиться в этом сами, скомпилировав код, а затем посмотрев на IL. Этот метод:

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

компилируется в этот 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

Одна мелочь: компилятор VB не работает так же, поэтому этот метод:

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

компилируется как:

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
3 голосов
/ 27 февраля 2012

Мое решение, основанное на информации из @JonSkeet и @CodeInChaos, заключается не в том, чтобы рассматривать не только PropertyInfo в выражении, но и тип компонента Member MemberExpression:

/// <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);
}
1 голос
/ 27 февраля 2012

Если вам не нужен метод статического типа, над которым вы работаете, а скорее последнее переопределение, тогда это возможно.Я не тестировал, но что-то похожее на следующее должно выполнить эту работу:

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

Где вы передаете MemberInfo в качестве первого параметра, а тип времени выполнения объекта - в качестве второго.Обратите внимание, что это, вероятно, медленно, поэтому вы можете добавить кеширование.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...