Getting the object out of a MemberExpression?

前端 未结 5 1778
野性不改
野性不改 2020-11-30 23:33

So, lets say I have the following expression in C#:

Expression> expr = () => foo.Bar;

How do I pull out a refer

相关标签:
5条回答
  • 2020-11-30 23:56

    I had the same problem, but somewhat more complex, and Darin Dimitrov's answer gave me a good start. I'll post my results here, despite the fact that this is an "old" question .


    Case 1: The root object is an instance member

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

    ... is equivalent to the following expression tree:

    .                                    +====================+
    .                                    |  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
    

    The only place in this expression tree where you actually get an object reference is from the ConstantExpression: it allows you to get a reference to this. The basic idea to get any object reference in this tree is thus as follows:

    1. Descend into the expression tree along the .Expression axes until you reach a ConstantExpression node.

    2. Grab that node's .Value property. This is the root object reference (ie. this in the above example).

    3. Using reflection and the MemberInfo nodes from the expression tree, get object references and work your way back "up" the expression tree.

    Here's some code that demonstrates this:

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

    Case 2: The root object is a static class member

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

    ... results in a different expression tree. Note to the null reference at the lower left:

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

    Here, you cannot stop the "descend" phase by waiting for a ConstantExpression. Instead, you stop descending when you reach a null reference. Next, you retrieve the root object reference as follows:

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

    The "ascend" phase from there onwards is the same as before.


    There are certainly more cases (such as named parameters as the root object), but I hope that by now, I've got the basic idea across, so I'll cut off here.

    0 讨论(0)
  • 2020-11-30 23:58

    Thanks, staks - your example helped me a lot! So i'd like to contribute with some addition to the first case:

    To extract values from methods, one should replace code:

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

    with code:

        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
    

    that way one could extract values from expressions like:

    aInstane.MethodCall(anArgument1, anArgument2) or
    AType.MethodCall(anArgument1, anArgument2) or
    new AType().MethodCall(anArgument1, aInstane.MethodCall(anArgument2, anArgument3))
    
    0 讨论(0)
  • 2020-12-01 00:02

    There is a simpler solution:

    var pExpression = ((MemberExpression)expr.Body);
    var bindingObject = Expression.Lambda(((MemberExpression)pExpression.Expression)).Compile().DynamicInvoke();
    
    0 讨论(0)
  • 2020-12-01 00:06
    Expression<Func<string>> expr = () => foo.Bar;
    var me = (MemberExpression)((MemberExpression)expr.Body).Expression;
    var ce = (ConstantExpression)me.Expression;
    var fieldInfo = ce.Value.GetType().GetField(me.Member.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
    var value = (Foo)fieldInfo.GetValue(ce.Value);
    
    0 讨论(0)
  • 2020-12-01 00:08

    This is what I use in unit tests:

     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;
      }
    
    0 讨论(0)
提交回复
热议问题