从 MemberExpression 中获取对象?

问题描述 投票:0回答:6

所以,假设我在 C# 中有以下表达式:

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

如何提取对 foo 的引用?

c# lambda expression-trees
6个回答
52
投票

我也遇到了同样的问题,但更复杂一些,达林·季米特洛夫的回答给了我一个良好的开端。我将在这里发布我的结果,尽管事实上这是一个“老”问题。


情况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);

此后的“上升”阶段与之前相同。


当然还有更多的情况(例如命名参数作为根对象),但我希望到目前为止,我已经了解了基本的想法,所以我就在这里结束。


43
投票
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);

3
投票

有一个更简单的解决方案:

var pExpression = ((MemberExpression)expr.Body);
var bindingObject = Expression.Lambda(((MemberExpression)pExpression.Expression)).Compile().DynamicInvoke();

0
投票

谢谢,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))

0
投票

这是我在单元测试中使用的:

 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
投票

这里确实有两件不同的事情要做:

  1. 评估表达式链的结果。
  2. 评估导致该成员所有者的表达链。

一旦我们能做到 1,那么 2 就变得相对简单。

请注意,这是必要的原因是,为了获取该成员的所有者,需要获取该成员的所有者的所有者(询问其成员之一的值)。

(另请注意:成员所有者的所有者可以是隐藏的闭包实例,问题中给出的示例就是这种情况。

() => foo.Bar
确实是
() => closure.foo.Bar
。)

评估表达式链可以像这样完成。请注意,它是递归的,因为我们不知道链有多长。这也可以扩展到支持其他类型的表达。

public static object? EvaluateExpressionChain(
   this Expression me
) =>
   me switch {
      // just a value by itself
      ConstantExpression constantExp => constantExp.Value,

      // a member value of something else
      // requires recursion to evaluate unless it's a static member
      MemberExpression memberExp => memberExp.Expression is { } parentExp

         // recursively evaluate parent and then get the value of its member
         ? memberExp.Member.GetValue(EvaluateExpressionChain(parentExp))

         // a static member; can just get its value
         : memberExp.Member.GetValue(),

      // something else was in the expression that we haven't accounted for
      _ => throw new InvalidOperationException(
         $"The exp contained an unsupported subexp type {me.GetType().Name}."
      )
   };

现在使用上面的内容,我们可以执行以下操作来获取成员的所有者:

public static object? GetMemberOwner(
   this LambdaExpression me
) {
   var bodyExp = me.Body;

   if (bodyExp is not MemberExpression memberExp)
      throw new InvalidOperationException("Lambda did not contain a member exp.");

   if (memberExp.Expression is not { } ownerExp)
      throw new InvalidOperationException("The member exp did not have a parent exp.");

   return EvaluateExpressionChain(ownerExp);
}
© www.soinside.com 2019 - 2024. All rights reserved.