从MemberExpression中获取对象?

44

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

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

如何提取对foo的引用?

6个回答

51

我遇到了同样的问题,但稍微有点复杂,Darin Dimitrov的答案给了我一个好的开始。尽管这是一个“旧”的问题,但我将在此处发布我的结果。


情况一:根对象是实例成员

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

情况二:根对象是静态类成员

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

从那里开始的"升序"阶段与之前相同。


当然还有更多情况(例如以命名参数作为根对象),但我希望现在已经传达了基本思想,所以我将在此结束。


非常全面的解释与简单的答案相比!+1 - IAbstract

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

很好,我刚用这个工具让一些交互测试更加严格了。 - neontapir

2
有一种更简单的解决方案:
var pExpression = ((MemberExpression)expr.Body);
var bindingObject = Expression.Lambda(((MemberExpression)pExpression.Expression)).Compile().DynamicInvoke();

兄弟,你是MVP! - user2102929

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

-1
这有点棘手,因为涉及到一个闭包类。另外要注意的是,Expression属性的名称对我来说有些令人困惑;可以将其想象成类似于"Parent"的命名方式。
// () => closure.owner.Property
Expression<Func<string>> exp = () => owner.Property;

// closure.owner.Property
var memberExp = (MemberExpression)exp.Body;

// closure.owner
var closureMemberExp = (MemberExpression)memberExp.Expression;

// owner
var closureMember = closureMemberExp.Member;

// closure
var closureValueExp = (ConstantExpression)closureMemberExp.Expression;

// value of closure
var closureValue = closureValueExp.Value;

// value of owner
var ownerValue = closureMember.GetValue(closureValue);

Assert.AreEqual(owner, ownerValue);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接