所以,假设我在 C# 中有以下表达式:
Expression<Func<string>> expr = () => foo.Bar;
如何提取对 foo 的引用?
我也遇到了同样的问题,但更复杂一些,达林·季米特洛夫的回答给了我一个良好的开端。我将在这里发布我的结果,尽管事实上这是一个“老”问题。
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
的引用。获取此树中任何对象引用的基本思想如下:
沿着
.Expression
轴下降到表达式树中,直到到达 ConstantExpression
节点。获取该节点的
.Value
属性。这是根对象引用(即上例中的 this
)。使用反射和表达式树中的
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);
此后的“上升”阶段与之前相同。
当然还有更多的情况(例如命名参数作为根对象),但我希望到目前为止,我已经了解了基本的想法,所以我就在这里结束。
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);
有一个更简单的解决方案:
var pExpression = ((MemberExpression)expr.Body);
var bindingObject = Expression.Lambda(((MemberExpression)pExpression.Expression)).Compile().DynamicInvoke();
谢谢,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))
这是我在单元测试中使用的:
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,那么 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);
}