到目前为止,你所看到的全部表达式树都是由 C# 编译器建立的。 你所要作的是建立一个 lambda 表达式,将其分配给一个类型为 Expression<Func<T>>
或某种类似类型的变量。 这不是建立表达式树的惟一方法。 不少状况下,可能须要在运行时在内存中生成一个表达式。html
因为这些表达式树是不可变的,因此生成表达式树很复杂。 不可变意味着必须以从叶到根的方式生成表达式树。 用于生成表达式树的 API 体现了这一点:用于生成节点的方法将其全部子级用做参数。 让咱们经过几个示例来了解相关技巧。async
Expression<Func<int>> sum = () => 1 + 2;
若要构造该表达式树,必须构造叶节点。 叶节点是常量,所以可使用 Expression.Constant
方法建立节点:oop
var one = Expression.Constant(1, typeof(int)); var two = Expression.Constant(2, typeof(int));
接下来,将生成加法表达式:测试
var addition = Expression.Add(one, two);
一旦得到了加法表达式,就能够建立 lambda 表达式:spa
var lambda = Expression.Lambda(addition);
这是一个很是简单的 Lambda 表达式,由于它不包含任何参数。 在本节的后续部分,你将了解如何将实参映射到形参并生成更复杂的表达式。code
对于此类简单的表达式,能够将全部调用合并到单个语句中:htm
var lambda = Expression.Lambda( Expression.Add(Expression.Constant(1, typeof(int)), Expression.Constant(2, typeof(int)) ) );
这是在内存中生成表达式树的基础知识。 更复杂的树一般意味着更多的节点类型,而且树中有更多的节点。 让咱们再浏览一个示例,了解一般在建立表达式树时建立的其余两个节点类型:参数节点和方法调用节点。对象
生成一个表达式树以建立此表达式:blog
Expression<Func<double, double, double>> distanceCalc = (x, y) => Math.Sqrt(x * x + y * y);
首先,建立 x
和 y
的参数表达式:ip
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);
在这个更复杂的示例中,你看到了建立表达式树一般使用的其余几种技巧。
首先,在使用它们以前,须要建立表示参数或局部变量的对象。 建立这些对象后,能够在表达式树中任何须要的位置使用它们。
其次,须要使用反射 API 的一个子集来建立 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,而是须要生成一个包含条件测试的循环和一个用于中断循环的标签目标。
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 在 .NET Core 中较难导航,但不要紧。 它们的用途至关复杂:编写在运行时生成代码的代码。 它们必须具备复杂的结构,才能在支持 C# 语言中提供的全部控件结构和尽量减少 API 表面积之间保持平衡。 这种平衡意味着许多控件结构不是由其 C# 构造表示,而是由表示基础逻辑的构造表示,这些基础逻辑由编译器从这些较高级别的构造生成。
另外,此时存在一些不能经过使用 Expression
类方法直接生成的 C# 表达式。 通常来讲,这些将是在 C# 5 和 C# 6 中添加的最新运算符和表达式。 (例如,没法生成 async
表达式,而且没法直接建立新 ?.
运算符。)