生成表达式树
C# 编译器创建了迄今为止你看到的所有表达式树。 你创建了一个 Lambda 表达式,将其分配给一个类型为 Expression<Func<T>>
或某种相似类型的变量。 很多情况下,需要在运行时在内存中生成一个表达式。
表达式树是不可变的。 不可变意味着必须以从叶到根的方式生成表达式树。 用于生成表达式树的 API 体现了这一点:用于生成节点的方法将其所有子级用作自变量。 让我们通过几个示例来了解相关技巧。
创建节点
你将使用在这些部分中一直使用的加法表达式开始:
Expression<Func<int>> sum = () => 1 + 2;
若要构造该表达式树,需要先构造叶节点。 叶节点是常量。 使用 Constant 方法创建节点:
var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));
接下来,将生成加法表达式:
var addition = Expression.Add(one, two);
生成加法表达式后,就可以创建 Lambda 表达式:
var lambda = Expression.Lambda(addition);
此 Lambda 表达式不包含任何自变量。 在本节的后续部分,你将了解如何将自变量映射到参数并生成更复杂的表达式。
对于此类表达式,可以将所有调用合并到单个语句中:
var lambda2 = Expression.Lambda(
Expression.Add(
Expression.Constant(1, typeof(int)),
Expression.Constant(2, typeof(int))
)
);
生成树
上一节介绍了在内存中生成表达式树的基础知识。 更复杂的树通常意味着更多的节点类型,并且树中有更多的节点。 让我们再浏览一个示例,了解通常在创建表达式树时创建的其他两个节点类型:自变量节点和方法调用节点。 生成一个表达式树以创建此表达式:
Expression<Func<double, double, double>> distanceCalc =
(x, y) => Math.Sqrt(x * x + y * y);
首先,创建 x
和 y
的参数表达式:
var xParameter = Expression.Parameter(typeof(double), "x");
var yParameter = Expression.Parameter(typeof(double), "y");
按照你所看到的模式创建乘法和加法表达式:
var xSquared = Expression.Multiply(xParameter, xParameter);
var ySquared = Expression.Multiply(yParameter, yParameter);
var sum = Expression.Add(xSquared, ySquared);
接下来,需要为调用 Math.Sqrt
创建方法调用表达式。
var sqrtMethod = typeof(Math).GetMethod("Sqrt", new[] { typeof(double) }) ?? throw new InvalidOperationException("Math.Sqrt not found!");
var distance = Expression.Call(sqrtMethod, sum);
如果未找到方法,GetMethod
调用可能会返回 null
。 最有可能的是因为方法名称拼写错误。 否则,这可能意味着没有加载所需的程序集。 最后,将方法调用放入 Lambda 表达式,并确保定义 Lambda 表达式的自变量:
var distanceLambda = Expression.Lambda(
distance,
xParameter,
yParameter);
在这个更复杂的示例中,你看到了创建表达式树通常使用的其他几种技巧。
首先,在使用它们之前,需要创建表示参数或局部变量的对象。 创建这些对象后,可以在表达式树中任何需要的位置使用它们。
其次,需要使用反射 API 的一个子集来创建 System.Reflection.MethodInfo 对象,以便创建表达式树以访问该方法。 必须仅限于 .NET Core 平台上提供的反射 API 的子集。 同样,这些技术将扩展到其他表达式树。
深入了解代码生成
不仅限于使用这些 API 可以生成的代码。 但是,要生成的表达式树越复杂,代码就越难以管理和阅读。
让我们生成一个与此代码等效的表达式树:
Func<int, int> factorialFunc = (n) =>
{
var res = 1;
while (n > 1)
{
res = res * n;
n--;
}
return res;
};
前面的代码没有生成表达式树,只生成了委托。 使用 Expression
类不能生成语句 lambda。 下面是生成相同的功能所需的代码。 没有用于生成 while
循环的 API,需要生成一个包含条件测试的循环和一个用于中断循环的标签目标。
var nArgument = Expression.Parameter(typeof(int), "n");
var result = Expression.Variable(typeof(int), "result");
// Creating a label that represents the return value
LabelTarget label = Expression.Label(typeof(int));
var initializeResult = Expression.Assign(result, Expression.Constant(1));
// This is the inner block that performs the multiplication,
// and decrements the value of 'n'
var block = Expression.Block(
Expression.Assign(result,
Expression.Multiply(result, nArgument)),
Expression.PostDecrementAssign(nArgument)
);
// Creating a method body.
BlockExpression body = Expression.Block(
new[] { result },
initializeResult,
Expression.Loop(
Expression.IfThenElse(
Expression.GreaterThan(nArgument, Expression.Constant(1)),
block,
Expression.Break(label, result)
),
label
)
);
用于生成阶乘函数的表达式树的代码相对更长、更复杂,它充满了标签和 break 语句以及在日常编码任务中想要避免的其他元素。
本部分编写了用于访问此表达式树中所有节点的代码,并编写了在此示例中创建的节点的相关信息。 可以在 dotnet/docs GitHub 存储库查看或下载示例代码。 生成并运行这些示例,自行动手试验。
将代码构造映射到表达式
下面的代码示例展示了使用 API 表示 Lambda 表达式 num => num < 5
的表达式树。
// Manually build the expression tree for
// the lambda expression num => num < 5.
ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
ConstantExpression five = Expression.Constant(5, typeof(int));
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
Expression<Func<int, bool>> lambda1 =
Expression.Lambda<Func<int, bool>>(
numLessThanFive,
new ParameterExpression[] { numParam });
表达式树 API 还支持赋值表达式和控制流表达式,比如循环、条件块和 try-catch
块等。 相对于通过 C# 编译器和 Lambda 表达式创建表达式树,还可利用 API 创建更加复杂的表达式树。 下列示例展示如何创建计算数字阶乘的表达式树。
// Creating a parameter expression.
ParameterExpression value = Expression.Parameter(typeof(int), "value");
// Creating an expression to hold a local variable.
ParameterExpression result = Expression.Parameter(typeof(int), "result");
// Creating a label to jump to from a loop.
LabelTarget label = Expression.Label(typeof(int));
// Creating a method body.
BlockExpression block = Expression.Block(
// Adding a local variable.
new[] { result },
// Assigning a constant to a local variable: result = 1
Expression.Assign(result, Expression.Constant(1)),
// Adding a loop.
Expression.Loop(
// Adding a conditional block into the loop.
Expression.IfThenElse(
// Condition: value > 1
Expression.GreaterThan(value, Expression.Constant(1)),
// If true: result *= value --
Expression.MultiplyAssign(result,
Expression.PostDecrementAssign(value)),
// If false, exit the loop and go to the label.
Expression.Break(label, result)
),
// Label to jump to.
label
)
);
// Compile and execute an expression tree.
int factorial = Expression.Lambda<Func<int, int>>(block, value).Compile()(5);
Console.WriteLine(factorial);
// Prints 120.
有关详细信息,请参阅在 Visual Studio 2010 中使用表达式树生成动态方法,该方法也适用于 Visual Studio 的更高版本。