【已更新最新开发文章,点击查看详细】

很多情况下,可能需要在运行时在内存中生成一个表达式。

让我们通过几个示例来了解相关技巧。

创建节点
我们将使用在这些部分中一直使用的加法表达式:
Expression<Func<int>> sum = () => 1 + 2;

叶节点是常量,因此可以使用 Expression.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);

在本节的后续部分,你将了解如何将实参映射到形参并生成更复杂的表达式。

对于此类简单的表达式,可以将所有调用合并到单个语句中:

var lambda = 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) });
var distance = Expression.Call(sqrtMethod, sum);

最后,将方法调用放入 lambda 表达式,并确保定义 lambda 表达式的参数:

var distanceLambda = Expression.Lambda(distance,xParameter, yParameter);

在这个更复杂的示例中,你看到了创建表达式树通常使用的其他几种技巧。

创建这些对象后,可以在表达式树中任何需要的位置使用它们。

同样,这些技术将扩展到其他表达式树。

深度生成代码

但是,要生成的表达式树越复杂,代码就越难以管理和阅读。

让我们生成一个与此代码等效的表达式树:

Func<int, int> factorialFunc = (n) =>
{
    var res = 1;
    while (n > 1)
    {
        res = res * n;
        n--;
    }
    return res;
};

它很复杂,这是因为没有用于生成 while循环的 API,而是需要生成一个包含条件测试的循环和一个用于中断循环的标签目标。

 1 var nArgument = Expression.Parameter(typeof(int), "n");
 2 var result = Expression.Variable(typeof(int), "result");
 3 
 4 // 创建一个表示返回值的标签
 5 LabelTarget label = Expression.Label(typeof(int));
 6 
 7 var initializeResult = Expression.Assign(result, Expression.Constant(1));
 8 
 9 // 这是执行乘法运算的内部块,
10 // 并减小“n”的值
11 var block = Expression.Block(
12     Expression.Assign(result,
13         Expression.Multiply(result, nArgument)),
14     Expression.PostDecrementAssign(nArgument)
15 );
16 
17 // 创建一个方法体
18 BlockExpression body = Expression.Block(
19     new[] { result },
20     initializeResult,
21     Expression.Loop(
22         Expression.IfThenElse(
23             Expression.GreaterThan(nArgument, Expression.Constant(1)),
24             block,
25             Expression.Break(label, result)
26         ),
27         label
28     )
29 );
检查 API

这种平衡意味着许多控件结构不是由其 C# 构造表示,而是由表示基础逻辑的构造表示,这些基础逻辑由编译器从这些较高级别的构造生成。

(例如,无法生成 async 表达式,并且无法直接创建新 ?. 运算符。)

【已更新最新开发文章,点击查看详细】

相关文章: