Получение объекта из MemberExpression?

Итак, скажем, у меня есть следующее выражение на C#:

Expression<Func<string>> expr = () => foo.Bar;

Как вытащить ссылку на foo?


person Brian Genisio    schedule 23.10.2009    source источник


Ответы (5)


У меня была та же проблема, но несколько более сложная, и ответ Дарина Димитрова дал мне хорошее начало. Я опубликую здесь свои результаты, несмотря на то, что это «старый» вопрос.


Случай 1: корневой объект является членом экземпляра

    this.textBox.Text    // where 'this' has type 'Form'

... эквивалентно следующему дереву выражений:

.                                    +====================+
.                                    |  MemberExpression  |
.                                    +====================+
#                                      |                |
#                          .Expression |                | .Member
#                                      v                v
.                    +------------------+              +------------+
.                    | MemberExpression |              | MemberInfo |
.                    +------------------+              +------------+
#                      |              |                 .Name = "Text"
#          .Expression |              | .Member         .MemberType = Property
#                      v              v
.   +--------------------+          +------------+
.   | ConstantExpression |          | MemberInfo |
.   +--------------------+          +------------+
#    .Value = this                   .Name = "textBox"
#    .Type  = typeof(Form)           .MemberType = Field

Единственное место в этом дереве выражений, где вы фактически получаете ссылку на объект, это ConstantExpression: это позволяет вам получить ссылку на this. Таким образом, основная идея получения любой ссылки на объект в этом дереве заключается в следующем:

  1. Спускайтесь в дерево выражений по осям .Expression, пока не достигнете узла ConstantExpression.

  2. Возьмите свойство .Value этого узла. Это ссылка на корневой объект (например, this в приведенном выше примере).

  3. Используя отражение и узлы MemberInfo из дерева выражений, получите ссылки на объекты и вернитесь «вверх» по дереву выражений.

Вот код, демонстрирующий это:

Expression expr = ...;   // <-- initially set to the expression tree's root

var memberInfos = new Stack<MemberInfo>();

// "descend" toward's the root object reference:
while (expr is MemberExpression)
{
    var memberExpr = expr as MemberExpression;
    memberInfos.Push(memberExpr.Member);
    expr = memberExpr.Expression
}

// fetch the root object reference:
var constExpr = expr as ConstantExpression;
var objReference = constExpr.Value;

// "ascend" back whence we came from and resolve object references along the way:
while (memberInfos.Count > 0)  // or some other break condition
{
    var mi = memberInfos.Pop();
    if (mi.MemberType == MemberTypes.Property)
    {
        objReference = objReference.GetType()
                                   .GetProperty(mi.Name)
                                   .GetValue(objReference, null);
    }
    else if (mi.MemberType == MemberTypes.Field)
    {
        objReference = objReference.GetType()
                                   .GetField(mi.Name)
                                   .GetValue(objReference);
    }
}

Случай 2: корневой объект является статическим членом класса

    Form.textBox.Text    // where 'textBox' is a static member of type 'Form'

... приводит к другому дереву выражений. Примечание к нулевой ссылке в левом нижнем углу:

.                                    +====================+
.                                    |  MemberExpression  |
.                                    +====================+
#                                      |                |
#                          .Expression |                | .Member
#                                      v                v
.                    +------------------+              +------------+
.                    | MemberExpression |              | MemberInfo |
.                    +------------------+              +------------+
#                      |              |                 .Name = "Text"
#          .Expression |              | .Member         .MemberType = Property
#                      v              v
.                     null          +------------+
.                                   | MemberInfo |
.                                   +------------+
#                                   .Name = "textBox"
#                                   .MemberType = Field
#                                   .DeclaringType = typeof(Form)

Здесь вы не можете остановить фазу «спуска», дождавшись ConstantExpression. Вместо этого вы прекращаете спуск, когда достигаете нулевой ссылки. Затем вы получаете ссылку на корневой объект следующим образом:

var mi = memberInfos.Pop();
objReference = mi.DeclaringType
                 .GetField(member.Name, BindingFlags.Static)  // or .GetProperty!
                 .GetValue(null);

Фаза «восхождения» оттуда и далее такая же, как и раньше.


Конечно, есть и другие случаи (например, именованные параметры в качестве корневого объекта), но я надеюсь, что к настоящему моменту у меня есть основная идея, поэтому я остановлюсь здесь.

person stakx - no longer contributing    schedule 17.10.2010
comment
Очень исчерпывающее объяснение по сравнению с просто ответом! +1 - person IAbstract; 01.05.2014

Есть более простое решение:

var pExpression = ((MemberExpression)expr.Body);
var bindingObject = Expression.Lambda(((MemberExpression)pExpression.Expression)).Compile().DynamicInvoke();
person Denis Shishkanov    schedule 19.06.2018
comment
Чувак, ты MVP! - person user2102929; 24.05.2019

Спасибо, staks - твой пример мне очень помог! Поэтому я хотел бы внести свой вклад в дополнение к первому случаю:

Чтобы извлечь значения из методов, следует заменить код:

// fetch the root object reference:
var constExpr = expr as ConstantExpression;
var objReference = constExpr.Value;

с кодом:

    var newExpression = expr as NewExpression;
    if (newExpression != null)
    {                
        return newExpression.Constructor.Invoke(newExpression.Arguments.Select(GetObjectValue).ToArray());
    }

    var methodCallExpr = expr as MethodCallExpression;
    if (methodCallExpr != null)
    {
        var value = methodCallExpr.Method.Invoke(methodCallExpr.Object == null
                                                                 ? null
                                                                 : GetObjectValue(methodCallExpr.Object),
methodCallExpr.Arguments.Select(GetObjectValue).ToArray());
                    return value;
    }

    // fetch the root object reference:
    var constExpr = expr as ConstantExpression;
    if (constExpr == null)
    {
         return null;
    }
    var objReference = constExpr.Value;
    // ... the rest remains unchanged

таким образом можно было бы извлечь значения из таких выражений, как:

aInstane.MethodCall(anArgument1, anArgument2) or
AType.MethodCall(anArgument1, anArgument2) or
new AType().MethodCall(anArgument1, aInstane.MethodCall(anArgument2, anArgument3))
person Andrey Ilnitsky    schedule 27.11.2012

Это то, что я использую в модульных тестах:

 internal static INotifyPropertyChanged SubModel < T, TProperty > (T model, Expression < Func < T, TProperty >> pickProperty) where T: INotifyPropertyChanged {
   MemberExpression memberExpression = (MemberExpression) pickProperty.Body;
   ParameterExpression parameterExpression = pickProperty.Parameters[0];
   Expression mem = memberExpression.Expression;
   var delegateType = typeof(Func < , > ).MakeGenericType(typeof(T), mem.Type);
   LambdaExpression lambdaExpression = Expression.Lambda(delegateType, mem, parameterExpression);
   object subModel = lambdaExpression.Compile().DynamicInvoke(model);
   return subModel as INotifyPropertyChanged ? ? model;
  }
person pub    schedule 02.06.2014

person    schedule
comment
Хорошо, просто использовал это, чтобы сделать некоторые тесты взаимодействия более строгими. - person neontapir; 18.07.2011