在编译器生成的表达式树中使用表达式 [英] Using an Expression in a compiler-generated expression tree
问题描述
我知道我可以使用以下方式创建表达式树
I know I can create expression trees using:
-
工厂方法.
Factory methods.
lambda表达式到Expression
的编译器转换.
Compiler conversion of a lambda expression to an Expression
.
对于复杂的表达式树,我更喜欢2,因为它更简洁.
For complicated expression trees I would prefer 2 because it is more concise.
是否可以使用这种方式引用已经构造的Expressions
?
Is it possible to refer to already constructed Expressions
using this way?
using System;
using System.Linq.Expressions;
public class Test
{
public static Expression<Func<int, int>> Add(Expression expr)
{
#if false
// works
ParameterExpression i = Expression.Parameter(typeof(int));
return Expression.Lambda<Func<int, int>>(Expression.Add(i, expr), i);
#else
// compiler error, can I pass expr here somehow?
return i => i + expr;
#endif
}
public static void Main()
{
Func<int, int> f = Add(Expression.Constant(42)).Compile();
Console.WriteLine(f(1));
}
}
推荐答案
您不能将任意Expression
实例与编译时表达式树混合使用. 可以做的是构造一个替换了特定节点的新表达式树,因此您可以先i => i + marker
然后构造一个新树,将marker
节点替换为您的运行时表达式.这需要编写适当的ExpressionVisitor
:
You can't mix arbitrary Expression
instances with compile-time expression trees. What you can do is construct a new expression tree with specific nodes replaced, so you can have i => i + marker
and then construct a new tree with the marker
node replaced by your runtime expression. This requires writing an appropriate ExpressionVisitor
:
public static class ExpressionExtensions {
public static T AsPlaceholder<T>(this Expression expression) {
throw new InvalidOperationException(
"Expression contains placeholders."
);
}
public static Expression FillPlaceholders(this Expression expression) {
return new PlaceholderExpressionVisitor().Visit(expression);
}
}
class PlaceholderExpressionVisitor : ExpressionVisitor {
protected override Expression VisitMethodCall(MethodCallExpression node) {
if (
node.Method.DeclaringType == typeof(ExpressionExtensions) &&
node.Method.Name == "AsPlaceholder" // in C# 6, we would use nameof()
) {
return Expression.Lambda<Func<Expression>>(node.Arguments[0]).Compile()();
} else {
return base.VisitMethodCall(node);
}
}
}
Add
现在变为:
public static Expression<Func<int, int>> Add(Expression expr) {
Expression<Func<int, int>> add = i => i + expr.AsPlaceholder<int>();
return (Expression<Func<int, int>>) add.FillPlaceholders();
}
有点神秘的表情
Expression.Lambda<Func<Expression>>(node.Arguments[0]).Compile()()
可以通过观察
来解释,观察到编译器将捕获我们要插入到闭包中的表达式,而不管它来自何处,因此方法调用的参数始终是对此闭包的引用,我们需要对该闭包进行求值得到实际的表达.
can be explained by observing that the compiler will capture the expression we're inserting in a closure, regardless of where it came from, so the argument of the method call is always a reference to this closure that we need to evaluate to get at the actual expression.
这会扩展到任意数量的替换表达式,并且需要最少的显式键入.
This scales to an arbitrary number of replacement expressions with a minimum of explicit typing.
这篇关于在编译器生成的表达式树中使用表达式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!