LINQ与DLR的Expression tree(4):创建静态类型的LINQ表达式树节点
系列的前三篇介绍过LINQ与DLR中的expression tree的概况后,我们对LINQ与DLR所使用的AST的概况和关系应该有一定认识了。既然LINQ Expression tree现在是DLR tree的子集,而DLR能够使用DLR tree来支持动态类型的语言,那么LINQ Expression tree能不能同样支持动态类型方式的呢?系列接下来的文章就会围绕这个主题,描绘一个从使用LINQ Expression tree过渡到使用DLR tree的过程。
本篇,我们先来详细看看要自己手工创建一棵LINQ Expression tree大概是个什么样子。
System.Linq.Expressions.Expression类上的工厂方法
在第一篇介绍LINQ Expression tree的时候提到过,创建节点主要是通过调用Expression类上的静态工厂方法,而不是直接使用具体的Expression类的构造器来完成的。下面直接用代码来演示C#中的一些基本表达式结构是如何用Expression tree来表示的,并在代码最后的Main()方法里演示一个稍微复杂一点的表达式的构造。
由于代码比较长,下面将分段把代码贴出来。如果用IE浏览提示“脚本可能失去响应”,请不要担心,并选择继续执行脚本。本文的完整代码可以通过附件下载。
常量表达式部分:
没什么值得解释的,就是通过Expression.Constant()来表示一个常量。注意一个常量可以是C#里的字面量,如0、1、2.0、"abc"、null等,也可以是任意别的语义上符合“常量”概念的对象。
- // By RednaxelaFX, 2008-09-07
- using System;
- using System.Collections.Generic;
- using System.Linq.Expressions;
- static class ExpressionTreeSamples {
- #region Constant
- // Represents a constant (such as a literal)
- static void Constant( ) {
- // Expression<Func<int>> con = ( ) => 1;
- Expression<Func<int>> con = Expression.Lambda<Func<int>>(
- Expression.Constant(
- 1, // value
- typeof( int ) // type
- ),
- new ParameterExpression[ ] { }
- );
- }
- #endregion
算术表达式部分:
这部分代码演示了+、-、*、/、%、幂、一元-、一元+等运算符在Expression tree中的对应物。需要说明的地方有五点:
1、.NET中算术运算可以抛出OverflowException来表示运算结果溢出(overflow)了,也就是超过了数据类型所能表示的范围。用户可以选择不理会这些异常(unchecked)或者关注这些异常(checked)。C#中默认是unchecked。可以对一个表达式或者一个语句块指定checked与unchecked。与此相应,Expression tree API中也对算术运算(和类型转换运算)提供了checked与unchecked的两个版本。
2、在第二个Add()的例子里,原本的lambda表达式只接受一个参数(y),但在表达式内容中使用了一个自由变量(x)。这个时候如果是由C#编译器来编译,就会自动生成一个私有内部类(这里用CompilerGeneratedDisplayClass来模拟),并将自由变量x变为其成员变量。主要是想通过这个例子来演示自己如何通过创建类似的类来模拟lambda表达式对自由变量的捕获(也就是闭包的功能)。
3、Divide()与DivideDouble()实际上演示的都是Expression.Divide()的使用。特意用两个例子来演示是为了说明无论是整型除法还是浮点数除法,Expression.Divide()都能正确处理。Expression tree中的其它算术表达式也是同理。
4、C#中没有幂运算符,所以Power()中的lambda表达式是用VB.NET来写的;VB.NET有幂运算符,“^”,不过实际上也是映射到Math.Pow()上的调用而已。
5、UnaryPlus()中演示的lambda表达式实际被C#编译器编译时是不会生成对Expression.UnaryPlus()的调用的,因为这个运算符作用在int上并没有什么意义,只是直接返回其操作数的值而已。但是Expression tree API允许用户指定在创建这些算术运算表达式节点时使用什么方法来执行运算,当用户选择使用默认以外的方法时,这个表达式还是可能有不同的意义的。例如这个Expression.UnaryPlus有两个版本的重载:
默认方法:
- public static UnaryExpression UnaryPlus( Expression expression )
用户指定方法:
- public static UnaryExpression UnaryPlus( Expression expression, MethodInfo method )
采用第二个版本时,根据以下规则判断使用什么方法来实现一元加:
- 如果 method 不为 null 引用(在 Visual Basic 中为 Nothing),并且它表示带一个参数的非 void static(在 Visual Basic 中为 Shared)方法,则它是节点的实现方法。
- 如果 expression.Type 为定义一元正运算符的用户定义的类型,则表示此运算符的 MethodInfo 为实现方法。
- 否则,如果 expression.Type 为数值类型,则实现方法为 null 引用(在 Visual Basic 中为 Nothing)。
- #region Arithmetic
- // "+" operator
- static void Add( ) {
- // Expression<Func<int, int, int>> add = ( x, y ) => x + y;
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, int>> add = Expression.Lambda<Func<int, int, int>>(
- Expression.Add(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- // simulates a compiler generated closure class
- private class CompilerGeneratedDisplayClass {
- public int x;
- }
- static void Add( int x ) {
- // Expression<Func<int, int>> add = y => x + y;
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- var display = new CompilerGeneratedDisplayClass( );
- display.x = x;
- Expression<Func<int, int>> add = Expression.Lambda<Func<int, int>>(
- Expression.Add(
- Expression.Field(
- Expression.Constant( display ),
- typeof( CompilerGeneratedDisplayClass ).GetField( "x" )
- ),
- y
- ),
- new ParameterExpression[ ] { y }
- );
- }
- // "+" operator, checked
- static void AddChecked( ) {
- // Expression<Func<int, int, int>> add = ( x, y ) => checked( x + y );
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, int>> add = Expression.Lambda<Func<int, int, int>>(
- Expression.AddChecked(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- // "-" operator
- static void Subtract( ) {
- // Expression<Func<int, int, int>> sub = ( x, y ) => x - y;
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, int>> sub = Expression.Lambda<Func<int, int, int>>(
- Expression.Subtract(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- // "-" operator, checked
- static void SubtractChecked( ) {
- // Expression<Func<int, int, int>> sub = ( x, y ) => checked( x - y );
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, int>> sub = Expression.Lambda<Func<int, int, int>>(
- Expression.SubtractChecked(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- // "*" operator
- static void Multiply( ) {
- // Expression<Func<int, int, int>> mul = ( x, y ) => x * y;
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, int>> mul = Expression.Lambda<Func<int, int, int>>(
- Expression.Multiply(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- // "*" operator, checked
- static void MultiplyChecked( ) {
- // Expression<Func<int, int, int>> mul = ( x, y ) => checked( x * y );
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, int>> mul = Expression.Lambda<Func<int, int, int>>(
- Expression.MultiplyChecked(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- // "/" operator (demonstrates integral division)
- static void Divide( ) {
- // Expression<Func<int, int, int>> div = ( x, y ) => x / y;
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, int>> div = Expression.Lambda<Func<int, int, int>>(
- Expression.Divide(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- // // "+" operator (demonstrates floating-point division)
- static void DivideDouble( ) {
- // Note that this expression tree has exactly the same shape as the one
- // in Divide(). The only difference is the type of parameters and result.
- // The expression tree can find correct implementation of the divide operator
- // in both cases.
- // Expression<Func<double, double, double>> div = ( x, y ) => x / y;
- ParameterExpression x = Expression.Parameter( typeof( double ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( double ), "y" );
- Expression<Func<double, double, double>> div = Expression.Lambda<Func<double, double, double>>(
- Expression.Divide(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- // "%" operator
- static void Modulo( ) {
- // Expression<Func<int, int, int>> mod = ( x, y ) => x % y;
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, int>> mod = Expression.Lambda<Func<int, int, int>>(
- Expression.Modulo(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- // exponentiation operator (not available in C#)
- static void Power( ) {
- // There's no "raise to the power" operator in C#, but there is one
- // in VB.NET, the "^" operator.
- // So this sample is in VB9:
- // Dim pow As Expression(Of Func(Of Integer, Integer, Integer)) _
- // = Function( x, y ) x ^ y
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, int>> pow = Expression.Lambda<Func<int, int, int>>(
- Expression.Power(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- // unary "-" operator
- static void Negate( ) {
- // Expression<Func<int, int>> neg = x => -x;
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- Expression<Func<int, int>> neg = Expression.Lambda<Func<int, int>>(
- Expression.Negate(
- x // expression
- ),
- new ParameterExpression[ ] { x }
- );
- }
- // unary "-" operator, checked
- static void NegateChecked( ) {
- // Expression<Func<int, int>> neg = x => checked( -x );
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- Expression<Func<int, int>> neg = Expression.Lambda<Func<int, int>>(
- Expression.NegateChecked(
- x // expression
- ),
- new ParameterExpression[ ] { x }
- );
- }
- // unary "+" operator
- static void UnaryPlus( ) {
- // Note that C# compiler will optimize this by removing the unary plus
- //Expression<Func<int, int>> unary = x => +x;
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- Expression<Func<int, int>> unaryPlus = Expression.Lambda<Func<int, int>>(
- Expression.UnaryPlus(
- x // expression
- ),
- new ParameterExpression[ ] { x }
- );
- }
- #endregion
按位运算表达式部分:
有两点需要说明:
1、C#中(以及.NET Framework的基础类库(BCL)中没有逻辑右移运算符“>>>”,而Java、JavaScript等语言中是存在这个运算符的。
2、千万要注意,Expression.And()与Expression.Or()是表示按位运算用的,而Expression.AndAlso()与Expression.OrElse()才是表示逻辑运算的。
3、Expression.Not()在文档中写的是表示按位反转运算,实际上取决于Not()的操作数表达式的类型,它既可以是按位反转运算(操作数类型为整数类型时)也可以是逻辑否运算(操作数类型为布尔类型时)。
- #region Bitwise
- // "<<" operator
- static void LeftShift( ) {
- // Expression<Func<int, int, int>> lshift = ( x, y ) => x << y;
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, int>> lshift = Expression.Lambda<Func<int, int, int>>(
- Expression.LeftShift(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- // ">>" operator
- static void RightShift( ) {
- // Expression<Func<int, int, int>> rshift = ( x, y ) => x >> y;
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, int>> expression = Expression.Lambda<Func<int, int, int>>(
- Expression.RightShift(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- // Note that C# doesn't have a logical right shift operator ">>>",
- // neither is there one in the expression tree
- }
- // "&" operator
- static void And( ) {
- // Note that And() is for bitwise and, and AndAlso() is for logical and.
- // Expression<Func<int, int, int>> and = ( x, y ) => x & y;
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, int>> and = Expression.Lambda<Func<int, int, int>>(
- Expression.And(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- // "|" operator
- static void Or( ) {
- // Note that Or() is for bitwise or, and OrElse() is for logical or.
- // Expression<Func<int, int, int>> or = ( x, y ) => x | y;
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, int>> or = Expression.Lambda<Func<int, int, int>>(
- Expression.Or(
- x,
- y
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- // "~" operator
- static void BitwiseNot( ) {
- // Expression<Func<int, int>> not = i => ~i;
- ParameterExpression i = Expression.Parameter( typeof( int ), "i" );
- Expression<Func<int, int>> not = Expression.Lambda<Func<int, int>>(
- Expression.Not(
- i // expression
- ),
- new ParameterExpression[ ] { i }
- );
- }
- // "^" operator
- static void ExclusiveOr( ) {
- // Expression<Func<int, int, int>> xor = ( x, y ) => x ^ y;
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, int>> xor = Expression.Lambda<Func<int, int, int>>(
- Expression.ExclusiveOr(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- #endregion
条件表达式部分:
也就是C-like语言中常见的三元运算符“? :”。注意这个对应的不是if-else语句,而是条件表达式——C-like语言中表达式有值,语句不一定有(即便有值也被忽略了);条件表达式的两个分支的值的类型必须匹配。
- #region Conditional
- // "? :" operator
- static void Condition( ) {
- // Expression<Func<bool, int, int, int>> cond = ( c, x, y ) => c ? x : y;
- ParameterExpression c = Expression.Parameter( typeof( bool ), "c" );
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<bool, int, int, int>> cond = Expression.Lambda<Func<bool, int, int, int>>(
- Expression.Condition(
- c, // test
- x, // if true
- y // if false
- ),
- new ParameterExpression[ ] { c, x, y }
- );
- }
- #endregion
相等性表达式部分:
也就是等于大于小于等比较大小的部分。比较直观,不多说。
- #region Equality
- // "==" operator
- static void Equal( ) {
- // Expression<Func<int, int, bool>> eq = ( x, y ) => x == y;
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, bool>> eq = Expression.Lambda<Func<int, int, bool>>(
- Expression.Equal(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- // "!=" operator
- static void NotEqual( ) {
- // Expression<Func<int, int, bool>> neq = ( x, y ) => x != y;
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, bool>> neq = Expression.Lambda<Func<int, int, bool>>(
- Expression.NotEqual(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- // Note that the lambda above isn't equivalent to the following:
- // Expression<Func<int, int, bool>> neq2 = ( x, y ) => !( x == y );
- }
- // ">" operator
- static void GreaterThan( ) {
- // Expression<Func<int, int, bool>> gt = ( x, y ) => x > y;
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, bool>> gt = Expression.Lambda<Func<int, int, bool>>(
- Expression.GreaterThan(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- // ">=" operator
- static void GreaterThanOrEqual( ) {
- // Expression<Func<int, int, bool>> ge = ( x, y ) => x >= y;
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, bool>> ge = Expression.Lambda<Func<int, int, bool>>(
- Expression.GreaterThanOrEqual(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- // "<" operator
- static void LessThan( ) {
- // Expression<Func<int, int, bool>> lt = ( x, y ) => x < y;
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, bool>> lt = Expression.Lambda<Func<int, int, bool>>(
- Expression.LessThan(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- // "<=" operator
- static void LessThanOrEqual( ) {
- // Expression<Func<int, int, bool>> le = ( x, y ) => x <= y;
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( int ), "y" );
- Expression<Func<int, int, bool>> le = Expression.Lambda<Func<int, int, bool>>(
- Expression.LessThanOrEqual(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- #endregion
关系表达式部分:
基本上只要注意好与前面按位运算的And()、Or()区分开就行,Not()是按位运算与逻辑运算共用的。另外需要记住的是,Expression.AndAlso()与Expression.OrElse()的默认语义与C#/C++中的&&、||一样,是短路表达式——会根据对左操作数求值的结果决定是否对右操作数求值。
- #region Relational
- // "&&" operator
- static void AndAlso( ) {
- // Note that And() is for bitwise and, and AndAlso() is for logical and.
- // Note also that the shortcut semantics is implemented with AndAlso().
- // Expression<Func<bool, bool, bool>> and = ( x, y ) => x && y;
- ParameterExpression x = Expression.Parameter( typeof( bool ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( bool ), "y" );
- Expression<Func<bool, bool, bool>> and = Expression.Lambda<Func<bool, bool, bool>>(
- Expression.AndAlso(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- // "||" operator
- static void OrElse( ) {
- // Note that Or() is for bitwise or, and OrElse() is for logical or.
- // Note also that the shortcut semantics is implemented with OrElse().
- // Expression<Func<bool, bool, bool>> or = ( x, y ) => x || y;
- ParameterExpression x = Expression.Parameter( typeof( bool ), "x" );
- ParameterExpression y = Expression.Parameter( typeof( bool ), "y" );
- Expression<Func<bool, bool, bool>> or = Expression.Lambda<Func<bool, bool, bool>>(
- Expression.OrElse(
- x, // left
- y // right
- ),
- new ParameterExpression[ ] { x, y }
- );
- }
- // "!" operator
- static void LogicalNot( ) {
- // Expression<Func<bool, bool>> not = b => !b;
- ParameterExpression b = Expression.Parameter( typeof( bool ), "b" );
- Expression<Func<bool, bool>> not = Expression.Lambda<Func<bool, bool>>(
- Expression.Not(
- b // expression
- ),
- new ParameterExpression[ ] { b }
- );
- }
- #endregion
类型转换表达式部分:
注意点就在于.NET中的值类型(value type)只能用C-style的类型转换,所以也只能对应Expression.Convert();而引用类型(reference type)既可以用C-style的也可以用as运算符来做类型转换,可以根据需要选用Expression.Convert()或者Expression.TypeAs()。
对了,对C#不熟悉的人可能对??运算符感到陌生。这个运算符的语义是:当左操作数不为null时,该表达式的值为左操作数的值;反之则为右操作数的值。
- #region Type Conversion
- // C-style conversion
- static void Convert( ) {
- // Expression<Func<int, short>> conv = x => ( short ) x;
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- Expression<Func<int, short>> conv = Expression.Lambda<Func<int, short>>(
- Expression.Convert(
- x, // expression
- typeof( short ) // type
- ),
- new ParameterExpression[ ] { x }
- );
- }
- // C-style conversion, checked
- static void ConvertChecked( ) {
- // Expression<Func<int, short>> conv = x => checked( ( short ) x );
- ParameterExpression x = Expression.Parameter( typeof( int ), "x" );
- Expression<Func<int, short>> conv = Expression.Lambda<Func<int, short>>(
- Expression.ConvertChecked(
- x, // expression
- typeof( short ) // type
- ),
- new ParameterExpression[ ] { x }
- );
- }
- // "as" operator
- static void TypeAs( ) {
- // Expression<Func<string, object>> typeAs = x => x as object;
- ParameterExpression x = Expression.Parameter( typeof( string ), "x" );
- Expression<Func<string, object>> typeAs = Expression.Lambda<Func<string, object>>(
- Expression.TypeAs(
- x, // expression
- typeof( object ) // type
- ),
- new ParameterExpression[ ] { x }
- );
- }
- // "is" operator
- static void TypeIs( ) {
- // Expression<Func<string, bool>> typeIs = x => x is object;
- ParameterExpression x = Expression.Parameter( typeof( string ), "x" );
- Expression<Func<string, bool>> typeIs = Expression.Lambda<Func<string, bool>>(
- Expression.TypeIs(
- x, // expression
- typeof( object ) // type
- ),
- new ParameterExpression[ ] { x }
- );
- }
- // "??" operator
- static void Coalesce( ) {
- // Expression<Func<int?, int>> coal = x => x ?? 1;
- ParameterExpression x = Expression.Parameter( typeof( int? ), "x" );
- Expression<Func<int?, int>> coal = Expression.Lambda<Func<int?, int>>(
- Expression.Coalesce(
- x, // left
- Expression.Constant( 1, typeof( int ) ) // right
- ),
- new ParameterExpression[ ] { x }
- );
- }
- #endregion
成员表达式部分:
这里的“成员”主要是指域(field)和属性(property)了。
在演示Expression.Field()的时候我用了前面模拟编译器生成的内部类,只是图个方便而已,因为实在想不到.NET基础类库里(特别是System命名空间里)有什么类型会包含共有域并且该域不是常量的。Int32.MaxValue是一个公有域,但同时也是个常量,C#编译器一编译就把lambda表达式里的域访问优化成一个常量了。所以干脆用一个自定义的类来演示域访问表达式。
Expression tree API里除了Expression.Field()和Expression.Property(),还有一个Expression.PropertyOrField()工厂方法。但我没想出来在什么情况下无法得知一个名字到底是成员域还是成员属性,所以没写这个对应的例子。
- #region Member
- // Accessing a field
- static void Field( ) {
- // Reusing the CompilerGeneratedDisplayClass class for demo.
- // Expression<Func<CompilerGeneratedDisplayClass, int>> field = c => c.x;
- ParameterExpression c = Expression.Parameter(
- typeof( CompilerGeneratedDisplayClass ), "c" );
- Expression<Func<CompilerGeneratedDisplayClass, int>> field =
- Expression.Lambda<Func<CompilerGeneratedDisplayClass, int>>(
- Expression.Field(
- c,
- "x"
- ),
- new ParameterExpression[ ] { c }
- );
- }
- // Accessing a static field
- static void StaticField( ) {
- // Expression<Func<string>> field = () => string.Empty;
- Expression<Func<string>> field =
- Expression.Lambda<Func<string>>(
- Expression.Field(
- null, // static field
- typeof( string ),
- "Empty"
- ),
- new ParameterExpression[ 0 ]
- );
- }
- // Calling a property
- static void Property( ) {
- // Expression<Func<string, int>> prop = s => s.Length;
- ParameterExpression s = Expression.Parameter( typeof( string ), "s" );
- Expression<Func<string, int>> prop = Expression.Lambda<Func<string, int>>(
- Expression.Property(
- s, // expression
- typeof( string ).GetProperty( "Length" ) // property
- ),
- new ParameterExpression[ ] { s }
- );
- }
- #endregion
方法/委托调用表达式部分:
有两点需要注意:
1、Expression.Call()对应的是方法调用,而Expression.Invoke()对应的是委托(delegate)的调用。它们在.NET中最大的不同可以说是:一个委托背后的方法可能是某个类上的方法,也可能是一个通过LCG(lightweight code generation)生成的方法;后者是不与任何类相关联的,所以调用机制会有点区别。在Expression tree API中这个区别就体现为工厂方法的不同。
2、Expression.Call()既对应成员方法的调用,也对应静态方法的调用。调用静态方法时把instance参数设为null。
- #region Invocation
- // Calling a static method
- static void Call( ) {
- // Note that to call a static method, use Expression.Call(),
- // and set "instance" to null
- // Expression<Func<string, int>> scall = s => int.Parse( s );
- ParameterExpression s = Expression.Parameter( typeof( string ), "s" );
- Expression<Func<string, int>> scall = Expression.Lambda<Func<string, int>>(
- Expression.Call(
- null, // instance
- typeof( int ).GetMethod( // method
- "Parse", new Type[ ] { typeof( string ) } ),
- new Expression[ ] { s } // arguments
- ),
- new ParameterExpression[ ] { s }
- );
- }
- // Calling a member method
- static void CallMember( ) {
- // Note that to call a member method, use Expression.Call(),
- // and set "instance" to the expression for the instance.
- // Expression<Func<string, string>> mcall = s => s.ToUpper( );
- ParameterExpression s = Expression.Parameter( typeof( string ), "s" );
- Expression<Func<string, string>> mcall = Expression.Lambda<Func<string, string>>(
- Expression.Call(
- s, // instance
- typeof( string ).GetMethod( // method
- "ToUpper", new Type[ ] { } ),
- new Expression[ ] { } // arguments
- ),
- new ParameterExpression[ ] { s }
- );
- }
- // Invoking a delegate
- static void Invoke( ) {
- // Note that invoking a delegate is different from calling a method.
- // Use Expression.Invoke() instead of Expression.Call().
- // Expression<Func<Func<int>, int>> invoc = f => f( );
- ParameterExpression f = Expression.Parameter( typeof( Func<int> ), "f" );
- Expression<Func<Func<int>, int>> invoc = Expression.Lambda<Func<Func<int>, int>>(
- Expression.Invoke(
- f, // expression
- new Expression[ ] { } // arguments
- ),
- new ParameterExpression[ ] { f }
- );
- }
- #endregion
数组表达式部分:
就是对数组下标和数组长度的特殊处理,没什么特别需要注意的。
- #region Array
- // Array index expression ("[]" operator on arrays)
- static void ArrayIndex( ) {
- // Expression<Func<int[ ], int, int>> aryIdx = ( a, i ) => a[ i ];
- ParameterExpression a = Expression.Parameter( typeof( int[ ] ), "a" );
- ParameterExpression i = Expression.Parameter( typeof( int ), "i" );
- Expression<Func<int[ ], int, int>> aryIdx = Expression.Lambda<Func<int[ ], int, int>>(
- Expression.ArrayIndex(
- a, // array
- i // index
- ),
- new ParameterExpression[ ] { a, i }
- );
- }
- // Array length expression (".Length" property on arrays)
- static void ArrayLength( ) {
- // Expression<Func<int[ ], int>> aryLen = a => a.Length;
- ParameterExpression a = Expression.Parameter( typeof( int[ ] ), "a" );
- Expression<Func<int[ ], int>> aryLen = Expression.Lambda<Func<int[ ], int>>(
- Expression.ArrayLength(
- a // array
- ),
- new ParameterExpression[ ] { a }
- );
- }
- #endregion
新建对象表达式部分:
基本上就是对应“new”运算符相关的表达式了。这部分有两点需要注意:
1、与数组相关的两个初始化方法:
Expression.NewArrayBounds()接受的参数是数组的元素类型和数组的尺寸。尺寸可以是一维或多维的,取决于指定数组尺寸的表达式的个数。下面的例子里演示的是一个二维数组。注意到它是一个矩形数组而不是一个“数组的数组”(array-of-array,或者叫jagged array)。
Expression.NewArrayInit()方法对应的是一维数组的初始化器,例如new int[] { 1, 2, 3 };它接受的参数是数组的元素类型和由初始化表达式组成的数组。要注意,这个方法只能用于初始化一维数组,而无法初始化秩为2或更高的数组。这个限制在LINQv2里仍然存在。
2、Expression.ListInit()并不只是能创建和初始化System.Collections.Generic.List<T>类型的对象。任何能用C# 3.0的列表初始化器表示的新建对象表达式都能用Expression.ListInit()表示,例如这个:
- var hashset = new HashSet<string> {
- "Alpha", "Beta", "Charlie"
- };
要理解这个初始化器实际上被编译器转换为对默认构造器与一系列Add()方法的调用。在使用Expression.ListInit()时这些实际调用必须手工指定。下面的例子很明显:先选择了默认构造器并用一个Expression.New()来调用,然后是一组通过Expression.ElementInit()对Add()方法的调用。能够制定调用的方法自然意味着可以使用Add()以外的方法作为初始化器的内容。
- #region New
- // Creating a new object instance
- static void New( ) {
- // Expression<Func<object>> n = ( ) => new object( );
- Expression<Func<object>> newObj = Expression.Lambda<Func<object>>(
- Expression.New(
- typeof( object ).GetConstructor( new Type[ ] { } ), // constructor
- new Expression[ ] { } // arguments
- ),
- new ParameterExpression[ ] { }
- );
- }
- // Creating a new array with specified bounds
- static void NewArrayBounds( ) {
- // Expression<Func<int[ , ]>> n = ( ) => new int[ 1, 2 ];
- Expression<Func<int[ , ]>> newArr = Expression.Lambda<Func<int[ , ]>>(
- Expression.NewArrayBounds(
- typeof( int ), // type
- new Expression[ ] { // bounds
- Expression.Constant( 1, typeof( int ) ),
- Expression.Constant( 2, typeof( int ) )
- }
- ),
- new ParameterExpression[ ] { }
- );
- }
- // Creating a new array with initializers
- static void NewArrayInit( ) {
- // Expression<Func<int[ ]>> n = ( ) => new int[ ] { };
- Expression<Func<int[ ]>> newArrInit = Expression.Lambda<Func<int[ ]>>(
- Expression.NewArrayInit(
- typeof( int ), // type
- new Expression[ ] { } // initializers
- ),
- new ParameterExpression[ ] { }
- );
- }
- // Creating a new list with initializers
- static void ListInit( ) {
- // Expression<Func<List<int>>> n = ( ) => new List<int>( ) { 1, 2 };
- Expression<Func<List<int>>> linit = Expression.Lambda<Func<List<int>>>(
- Expression.ListInit(
- Expression.New( // new expression
- typeof( List<int> ).GetConstructor( new Type[ ] { } ),
- new Expression[ ] { }
- ),
- new ElementInit[ ] { // initializers
- Expression.ElementInit(
- typeof( List<int> ).GetMethod( "Add", new Type[ ] { typeof( int ) } ),
- new Expression[ ] { Expression.Constant( 1, typeof( int ) ) }
- ),
- Expression.ElementInit(
- typeof( List<int> ).GetMethod( "Add", new Type[ ] { typeof( int ) } ),
- new Expression[ ] { Expression.Constant( 2, typeof( int ) ) }
- )
- }
- ),
- new ParameterExpression[ ] { }
- );
- }
- #endregion
成员初始化表达式部分:
对应物是C# 3.0中的成员初始化器;最常见的地方莫过于匿名类型的实例的声明了,例如:
- new { FistName = "John", LastName = "Smith" }
为了演示方便,这里建了一个简单的内部类。
- #region Member Initialization
- // simulates an anonymous type
- private class DummyClass {
- public int Value { get; set; }
- }
- // Creating a new object instance with member initializers
- static void MemberInit( ) {
- // Expression<Func<DummyClass>> memInit = ( ) => new { Value = 2 };
- Expression<Func<DummyClass>> minit = Expression.Lambda<Func<DummyClass>>(
- Expression.MemberInit(
- Expression.New(
- typeof( DummyClass ).GetConstructor( new Type[ ] { } ),
- new Expression[ ] { }
- ),
- new MemberBinding[ ] {
- Expression.Bind(
- typeof( DummyClass ).GetProperty( "Value" ),
- Expression.Constant( 2, typeof( int ) )
- )
- }
- ),
- new ParameterExpression[ ] { }
- );
- }
- #endregion
“引用”表达式部分:
这对于许多C#程序员来说或许不是那么直观的内容,不过如果了解Lisp的话就很好解释。Expression.Quote()与Lisp中的quote特殊形式作用相似,作用是不对其操作数求值,而是将其操作数表达式作为结果的值。听起来有点拗口,我也说不太清楚,或许举个例子会容易理解一些。
假如我有一个lambda表达式,
- Func<int> five = ( ) => 2 + 3;
然后调用five(),应该得到结果5。
但假如C#里有一种神奇的运算符(这里让我用反单引号来表示),能够阻止表达式的求值,那么下面的lambda表达式:
- Func<Expression> fiveExpr = ( ) = `( 2 + 3 );
对fiveExpr()调用后得到的就是表示2+3的BinaryExpression。
这个Expression.Quote()所达到的效果也正是这一点。
- // Quoting an Expression
- static void Quote( ) {
- // There's no equivalent syntax for quoting an Expression in C#.
- // The quote UnaryExpression is used for preventing the evaluation
- // of its operand expression. Semantically, this results in
- // returning its operand as the result of this unary expression,
- // as opposed to returning the evaluated value of its operand.
- //
- // It's rather like the quote special form in Lisp, where
- // <code>(quote datum)</code> evaluates to <code>datum</code>.
- Expression<Func<BinaryExpression>> quote = Expression.Lambda<Func<BinaryExpression>>(
- Expression.Quote(
- Expression.Add(
- Expression.Constant( 2.0 ),
- Expression.Constant( 3.0 )
- )
- ),
- new ParameterExpression[ ] { }
- );
- // Func<BinaryExpression> quteFunc = quote.Compile( );
- // Console.WriteLine( quoteFunc( ) ); // prints (2 + 3)
- //
- // In contrast, a normal Expression.Add() without the quote
- // will return 4 instead:
- //Expression<Func<int>> add = Expression.Lambda<Func<int>>(
- // Expression.Add(
- // Expression.Constant( 2.0 ),
- // Expression.Constant( 3.0 )
- // ),
- // new ParameterExpression[ ] { }
- //);
- //Func<int> addFunc = add.Compile( );
- //Console.WriteLine( addFunc( ) ); // prints 5
- }
未说明的工厂方法:
这几个工厂方法主要是用于自定义的表达式的,没有C#的直观的对应物,所以就不在这里说明了。
- // not listed above:
- //PropertyOrField : MemberExpression
- //MakeBinary : BinaryExpression
- //MakeMemberAccess : MemberExpression
- //MakeUnary : UnaryExpression
Main()方法。演示一个稍微复杂一点的表达式:
这段代码的解释放到代码后。请先阅读代码看看:
- static void Main( string[ ] args ) {
- // Let's make a little complex expression tree sample,
- // equivalent to:
- //Expression<Func<int[ ], int>> complexExpr =
- // array => ( array != null && array.Length != 0 ) ?
- // 5 * ( 10 + array[ 0 ] ) :
- // -1;
- ParameterExpression array = Expression.Parameter( typeof( int[ ] ), "array" );
- Expression<Func<int[ ], int>> complexExpr = Expression.Lambda<Func<int[ ], int>>(
- Expression.Condition(
- Expression.AndAlso(
- Expression.NotEqual(
- array,
- Expression.Constant(
- null
- )
- ),
- Expression.NotEqual(
- Expression.ArrayLength(
- array
- ),
- Expression.Constant(
- 0,
- typeof( int )
- )
- )
- ),
- Expression.Multiply(
- Expression.Constant(
- 5,
- typeof( int )
- ),
- Expression.Add(
- Expression.Constant(
- 10,
- typeof( int )
- ),
- Expression.ArrayIndex(
- array,
- Expression.Constant(
- 0,
- typeof( int )
- )
- )
- )
- ),
- Expression.Constant(
- -1,
- typeof( int )
- )
- ),
- new ParameterExpression[ ] { array }
- );
- // And let's see it in action:
- Func<int[ ], int> func = complexExpr.Compile( );
- int[ ] arrayArg = new[ ] { 2, 3, 4, 5 };
- Console.WriteLine( func( arrayArg ) ); // prints 60
- }
- }
Main()方法里,我们手工构造了一个由基本表达式组装起来的表达式。当然,我是可以把其中的各个子表达式先分别赋值给变量,避免这种巨大的嵌套调用;不过嵌套调用也有好处,那就是能比较直观的与“树形”联系起来。回顾这个lambda表达式:
- Expression<Func<int[ ], int>> complexExpr =
- array => ( array != null && array.Length != 0 ) ?
- 5 * ( 10 + array[ 0 ] ) :
- -1;
它对应的抽象语法树(AST)如下图所示。回忆起AST的特征,注意到用于改变表达式优先顺序的括号已经不需要了:
这个AST与实际对Expression上的工厂方法的调用是一一对应的: 
(图片缩小了的话请点击放大)
不难看出Expression tree与C#的表达式的关系。
Expression tree的Compile()方法的一个注意点
还是先看一个例子。我们要写一个比较奇怪的lambda表达式,并让它变成一棵Expression tree:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Linq.Expressions;
- static class Program {
- static void Main( string[ ] args ) {
- Expression<Func<List<int>, IEnumerable<int>>> filter =
- list => from i in list
- where i % 2 == 0
- select i;
- Console.WriteLine( filter );
- var fooList = new List<int> { 1, 2, 3, 4, 5 };
- var result = filter.Compile( )( fooList );
- foreach ( var i in result ) {
- Console.WriteLine( i );
- }
- }
- }
运行得到:
2
4
注意到那个查询表达式(from...where...select...)会被翻译为扩展方法的调用,所以上面代码里的filter等价于:
- list => list.Where( i => i % 2 == 0 )
也就是说lambda表达式里面还嵌套有lambda表达式。
另外注意到List<T>实现了IEnumerable<T>而没有实现IQueryable<T>,所以这个Where()方法是Enumerable类上的。也就是说filter等价于:
- list => Enumerable.Where<int>( list, i => i % 2 == 0 )
然后让我们把这棵Expression tree改用手工方式创建:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Linq.Expressions;
- static class Program {
- static void Main( string[ ] args ) {
- ParameterExpression list = Expression.Parameter(typeof(List<int>), "list");
- ParameterExpression i = Expression.Parameter(typeof(int), "i");
- Expression<Func<List<int>, IEnumerable<int>>> filter = Expression.Lambda<Func<List<int>, IEnumerable<int>>>(
- Expression.Call(
- typeof( Enumerable ).GetMethods( )
- .First( method => "Where" == method.Name
- && 2 == method.GetParameters( ).Length )
- .MakeGenericMethod( new [ ] { typeof( int ) } ),
- new Expression[ ] {
- list,
- Expression.Lambda<Func<int, bool>>(
- Expression.Equal(
- Expression.Modulo(
- i,
- Expression.Constant(
- 2,
- typeof(int)
- )
- ),
- Expression.Constant(
- 0,
- typeof(int)
- )
- ),
- new [ ] { i }
- )
- }
- ),
- new [ ] { list }
- );
- Console.WriteLine( filter );
- var fooList = new List<int> { 1, 2, 3, 4, 5 };
- var result = filter.Compile( )( fooList );
- foreach ( var item in result )
- Console.WriteLine( item );
- }
- }
留意一下我是如何通过反射来得到Enumerable.Where<TSource>>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)这个方法的MethodInfo的。由于Enumerable.Where()有两个重载,而且都是public static的,我们无法直接调用Type.GetMethod(string)来得到MethodInfo;另外,也无法通过Type.GetMethod(string, Type[])来得到这个MethodInfo,因为它是泛型方法定义的MethodInfo,不方便直接指定类型。结果只好通过参数个数来区分两个Enumerable.Where(),然后创建出Enumerable.Where<int>()的MethodInfo。
注意到,Enumerable.Where<int>()的第二个参数是Func<int, bool>类型的,但我提供的参数数组里的却是Expression<Func<int, bool>>类型的。这样能匹配么?
判断标准很简单,只要参数数组里各项的Expression.Type与实际参数类型匹配就能行。在调用Compile()的时候,即使有嵌套的lambda表达式(转换成Expression tree也就是Expression<TDelegate>)也没问题。
Expression<Func<int, bool>>的Type属性返回的就是Func<int, bool>,与Where()需要的参数类型匹配,OK。
没注意到的话一开始可能会被转晕掉,所以我觉得值得一提。
LINQ Expression tree与静态类型
LINQ Expression tree是静态类型的,上面的例子里我都选用了显式指定类型的版本的API来创建树的节点(特别是Expression.Constant(),其实不指定类型也可以的)。
虽然Expression tree的内容是静态类型的,但仔细观察会发现组装表达式时,参数类型基本上都只要求是Expression或其派生类就行,无法避免在组装时把不匹配的表达式错误的组装到一起。为什么会这样呢?
对编译器有所了解的人应该很清楚,把源代码解析到抽象语法树只是完成了语法分析,之后还需要做语义分析。语义分析就包括了类型检查等工作。表达式的一个重要特征就是可组合性,一个表达式的子表达式可以是任意语法结构的表达式;类型的匹配与否无法通过语法来表现。当我们把一个lambda表达式赋值给一个Expressin<TDelegate>类型的变量时,C#编译器做了类型检查,并且生成了相应的Expression tree。但当我们手工创建Expression tree时,我们既获得了创建并组装节点的权利,也承担起了检查类型的义务——C#编译器只知道那些是表示表达式的对象,而无法进一步帮忙检查其中的内容是否匹配。所以,在创建Expression tree时需要额外小心,并且要注意检查实际内容的类型是否匹配。
一般来说这并不是大问题,因为Expression tree一般是由编译器前端所生成的。在生成这棵树之前,编译器就应该负责完成类型检查等工作。
再谈LINQ Expression tree的局限性
LINQ的Expression tree只能用于表示表达式,而且并不支持C#与VB.NET的所有表达式——与赋值相关的表达式都无法用Expression tree表示。因此虽然有一元加和一元减这两个一元运算符的对应物,却没有同为一元运算符的递增(++)与递减(--)的对应物。同时,Expression tree里面也没有“变量”的概念,ParameterExpression只是用来表示参数,而参数的值在实际调用时绑定好之后就不能再改变。
但这些限制并没有降低LINQ Expression tree理论上所能表示的计算逻辑的范围。
1、引用透明性
由于没有“变量”的概念,通过Expression tree定义的表达式本身无法产生副作用,因而只要一棵Expression tree中不包含对外部的有副作用的方法的调用(Invoke/Call),其中的任何子树重复多次产生的值都是一样的。
这种性质被称为引用透明性(referential transparency)。举例来说,假如原本有这样的一组表达式:
- a = 1
- b = 2
- x = a + b
- y = a - b
- result = x * y - x % y
请把这些表达式中的符号看成“名字到值的绑定”而不是变量。如果有引用透明性,那么上面的表达式的求值就可以使用所谓“代替模型”(substitution model),可以展开为以下形式而不改变原表达式计算出来的结果:
- result = (1 + 2) * (1 - 2) - (1 + 2) % (1 - 2)
同理,当我们实在需要在同一棵Expression tree中多次使用同一个子表达式的计算结果时,只要多次使用同一棵子树即可;既然没有变量来暂时记住中间结果,那么就手动把表达式展开。用Expression tree的例子来说明,那就是:
- // xPlusY = x + y
- // result = xPlusY * xPlusY
- var x = Expression.Parameter( typeof( int ), "x" );
- var y = Expression.Parameter( typeof( int ), "y" );
- var add = Expression.Add( x, y );
- var result = Expression.Lambda<Func<int, int, int>>(
- Expression.Multiply( add, add ),
- new ParameterExpression[ ] { x, y }
- );
- Console.WriteLine( result.Compile( )( 3, 4 ) ); // prints 49
(前面已经介绍过Expression tree的工厂方法,这里为了书写方便就不再显式指明变量类型了。)
能够通过代替模型来求值是许多函数式语言的特征之一。求值顺序不影响计算结果,一般有两种求值顺序:如果先求值再代替,称为应用序(applicative order),也叫做紧迫计算(eager evaluation)或者严格求值(strict evaluation);如果先替换再求值,则称为正则序(normal order),也叫做惰性求值(lazy evaluation)或者延迟求值(delayed evaluation)。
在LINQ Expression tree中,因为无法使用变量,也就难以方便的进行严格求值;上面的例子手工展开了表达式,实际上就是模拟了延迟求值的求值过程,在执行效率上会比严格求值要差一些。这虽然不影响计算的能力,但从性能角度看这么做是有负面影响的。
如果一棵Expression tree中含有对外界的调用,上述的引用透明性就不成立了;我们无法确定一个调用是否含有副作用,所以无法保证不同的求值顺序能得到相同的结果。而这个问题需要特别的注意,因为.NET/CLR的类型系统中没任何有信息能表明一个函数是否有副作用,所以只能悲观的假设包含对外界的调用的Expression tree不满足引用透明性。
2、Lambda表达式
LINQ Expression tree允许定义与调用lambda表达式,甚至可以在表达式之中嵌套的定义lambda表达式。这奠定了Expression tree理论上具有接近完备计算能力的基础。所谓“完备”是指图灵完备(Turing-complete)。无类型的lambda演算是图灵完备的,但在C#中lambda表达式需要指明类型,而有些类型通过C#的类型系统难以表示,因此其表达力会比无类型lambda演算要弱一些。
由于C#的类型系统允许定义递归类型,我们可以利用C#的lambda表达式来写递归的函数。同时,方法体只是一个表达式(而不是语句块)的lambda表达式可以用Expression tree表示,也就是说我们可以用Expression tree来写递归函数。有了递归,原本需要循环的逻辑都能用递归来编写,也就突破了LINQ Expression tree没有循环结构的限制。下一篇就让我们来看看实例,敬请期待 ^ ^

浙公网安备 33010602011771号