从 MemberExpression 中获取对象?

发布于 2024-08-08 05:54:29 字数 137 浏览 6 评论 0原文

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

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

如何提取对 foo 的引用?

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

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

How do I pull out a reference to foo?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

清欢 2024-08-15 05:54:29

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


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

从那里开始的“上升”阶段与之前相同。


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

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.

灯角 2024-08-15 05:54:29
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);
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);
失退 2024-08-15 05:54:29

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

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

There is a simpler solution:

var pExpression = ((MemberExpression)expr.Body);
var bindingObject = Expression.Lambda(((MemberExpression)pExpression.Expression)).Compile().DynamicInvoke();
冰雪之触 2024-08-15 05:54:29

谢谢,staks - 你的例子对我帮助很大!所以我想对第一种情况做出一些补充:

要从方法中提取值,应该将 code: 替换

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

为 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

这样就可以从表达式中提取值,例如:

aInstane.MethodCall(anArgument1, anArgument2) or
AType.MethodCall(anArgument1, anArgument2) or
new AType().MethodCall(anArgument1, aInstane.MethodCall(anArgument2, anArgument3))

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))
夜清冷一曲。 2024-08-15 05:54:29

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

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

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;
  }
酷到爆炸 2024-08-15 05:54:29

这里实际上有两件独立的事情要做:

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

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

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

(另请注意:成员所有者的所有者可以是隐藏的闭包实例,问题中给出的示例就是这种情况。 () => foo.Bar 实际上是 < code>() =>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)
      return null; // static member

   return EvaluateExpressionChain(ownerExp);
}

There are really two separate things to do here:

  1. Evaluate the result of an expression chain.
  2. Evaluate the expression chain that results in the owner of the member.

Once we can do 1, then 2 becomes relatively straightforward.

Note the reason this is necessary is that in order to get the owner of the member requires getting the owner of the owner of the member (to ask it for the value of one of its members).

(Also note: the owner of the owner of the member can be a hidden closure instance, which would be the case in the example given in the question. () => foo.Bar is really () => closure.foo.Bar.)

Evaluating an expression chain can be done something like this. Note that it's recursive, since we don't know how long the chain is. (As long as there are members, you need the value of each parent to evaluate one of that parent's members.) This could also be expanded to support other kinds of expression.

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(
         
quot;The exp contained an unsupported subexp type {me.GetType().Name}."
      )
   };

Now using the above, we can just do the following to get the owner of the member:

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)
      return null; // static member

   return EvaluateExpressionChain(ownerExp);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文