表达式树(Expression Tree)

表达式树是不可执行的代码,它只是用于表示一种树状的数据结构,树上的每一个节点都表示为某种表达式类型,大概有25种表达式类型,它们都派生自Expression类。创建表达式树具体有两个优势:

1.对表达式树的代码进行编辑修改,使表达式树中的代码变成动态代码,根据不同的数据库修改树上的代码逻辑从而达到动态切换数据库查询语句的目的,用表达式树可以动态构建针对不同数据库的查询语句。

2.完成类似反射访问未知对象的属性,通过动态构造表达式树,生成委托。

三种方式创建表达式树

Expression(表达式类)

此类可以称为表示表达式的类型,或也称为一棵表达式树。因为当你每创建一个表示表达式的实例时,都可以将该类型实例看成是一棵表达式树。每种表示表达式的类型都有一个具体的类型,如Expression的Variable()方法创建的是ParameterExpression类型的表达式,Expression的Add()方法创建的则是BinaryExpression类型的表达式。无论哪种表示表达式的类型都是从Expression派生。

//使用Expression的静态方法创建表达式
ParameterExpression variable = Expression.Variable ( typeof ( int ) , "x" ); // x

LambdaExpression(Lambda表达式类)

LambdaExpression是Expression的子类,此类型表示一个Lambda表达式类型。

//使用Expression的静态方法创建表达式
LambdaExpression lambda = Expression.Lambda ( Expression.Variable ( typeof ( int ) , "x" ) ); //()=>x

Expression<TDelegate>(表达式委托类)

此类从LambdaExpression派生,所以也可以使用此类来创建一个表示Lambda表达式的类型,这种方式只能提供一个Lambda表达式,不能提供Lambda语句。

//直接将Lambda表达式表示为一个LambdaExpression
Expression<Func<int,int>> Info =  ( x ) => x; //linq查询方法中的参数就是类似于这种将lambda表达式隐式转换为了lambda表达式树类型

执行表达式

假设创建了一个表示加法运算的Lambda表达式对象,现在想要执行这个表示Lambda表达式的对象,此时就要用到Expression<TDelegate>类,因为此类从LambdaExpression派生,LambdaExpression提供了一个叫做Compile()的方法可以将表示Lambda表达式的对象(LambdaExpression)解析为一个委托,然后你就可以执行Lambda表达式。而要执行表示表达式的对象,也必然需要将其封装到Lambda表达式中,否则不可能执行。封装任何表示表达式的对象都是通过Expression的Lambda<TDelegate>(Expression expr)方法,该方法是非泛型版本Expression.Lambda ( Expression expr )的重载,返回一个Expression<TDelegate>类型的实例。其中TDelegate是一个委托类型,你可以根据封装的表示lambda表达式的对象的代码逻辑来确定需要为Lambda定义一个什么样的委托,可以使用内置的Fun或Action委托表示该LambdaExpression。比如Expression<Func<TDelegate>> | Expression<Action<TDelegate>> | Expression<Action>。只要记住:要执行一个表示表达式的对象就需要使用Expression的Lambda<TDelegate>(Expression expr)方法对其进行封装,使其处于Lambda方法体中以便可以被执行。

,); //执行
Console.WriteLine ( r ); // print 3

表达式树

无论使用哪种方式创建表达式对象,编译器都会自动为表达式生成一棵树结构,然后将表达式主体的代码体拆分成单一的表达式并作为主体表达式的子节点。变量、参数、运算符都会被拆分成一个单一的表达式,如果被拆分的表达式含有多个子表达式,则子表达式将作为表达式的子节并以此类推。上面第三个例子中创建了一个表示Lambda的表达式,该表达式接收并返回一个int类型的变量,其树结构如下:

;

上面例子中创建了一个表达式( x , y ) => x != y && x!=0,代码体是x != y && x!=0,&&是一个表达式,它含有左右两个操作数的子表达式,所以它会被拆分,左边x != y是一个表达式,右边 x!=0也是一个表达式,左右两边都含有子表达式,所以会继续拆分,直到无法拆分为止,结构如下:

Expression的子类

树的每个子节点是一个具体的表达式,它们都有自己的表达式类型,这些类型从Expression派生。

UnaryExpression; //一元运算表达式
BinaryExpression; //二元运算表达式
ConstantExpression; //常量表达式
ParameterExpression; //变量、变量参数表达式
GotoExpression; //跳转语句表达式,如:return。continue、break
BlockExpression; //块语句表达式
ConditionalExpression; //条件语句表达式
LoopExpression;  //循环语句表达式
SwitchExpression; //选择语句表达式
IndexExpression; //访问数组索引表达式
MethodCallExpression; //调用方法表达式
LambdaExpression;  //Lambda表达式
TypeBinaryExpression; //类型检查表达式
NewArrayExpression;  // 创建数组表达式
DefaultExpression; //默认值表达式
DynamicExpression; //动态类型表达式
TryExpression; //try语句表达式
MemberExpression; //类成员表达式
InvocationExpression; //执行Lambda并传递实参的表达式
NewExpression; //调用无参构造函数表达式
MemberInitExpression; //调用带参构造函数表达式,可初始化成员
ListInitExpression; //集合初始化器表达式

ExpressionType枚举

表示乘法运算和表示加法运算的表达式都是属于二元运算,所以这两种表达式都是BinaryExpression类型,但如果需要确定这两个表达式具体的行为,就需要使用Expression.NodeType属性,此属性是一个枚举数,用以获取表达式属于什么种类(根据其行为判定)。

Add
//加法运算,如 a + b, ,不进行溢出检查,针对数值操作数。
AddAssign
//加法复合赋值运算,如 ( a += b), ,不进行溢出检查,针对数值操作数。
AddAssignChecked
//加法复合赋值运算,如 ( a += b), ,进行溢出检查,针对数值操作数。
AddChecked
//加法运算,如 ( a + b), ,进行溢出检查,针对数值操作数。
And
//按位或逻辑 AND 操作,如 ( a & b) 在 C# 和 (a And b) 在 Visual Basic 中。
AndAlso
//在条件 AND 仅当第一个操作数的计算结果为才计算第二个操作数的操作 true。 它对应于 ( a && b) 在 C# 和 (a AndAlso b) 在 Visual Basic 中。
AndAssign
//按位或逻辑 AND 复合赋值运算,如 ( a &= b) C# 中。
ArrayIndex
//索引操作在一维数组中,如 array [ index ] 在 C# 或 array(index) 在 Visual Basic 中。
ArrayLength
//获取一维数组的长度,如操作 array.Length。
Assign
//赋值运算,如 (a = b )。
Block
//表达式的块。
Call
//某个方法调用,如在 obj.sampleMethod ( ) 表达式。
Coalesce
//一个表示空合并操作,如节点 ( a ?? b) 在 C# 或 If(a, b) 在 Visual Basic 中。
Conditional
//条件运算,如 a > b? a : b 在 C# 或 If(a > b, a, b) 在 Visual Basic 中。
Constant
//常量的值。
Convert
//强制转换或转换操作中,如 ( SampleType)obj C# 中或 CType(obj, SampleType) 在 Visual Basic 中。 对于数值的转换,如果转换后的值对于目标类型来说太大不引发异常。
ConvertChecked
//强制转换或转换操作中,如 ( SampleType)obj C# 中或 CType(obj, SampleType) 在 Visual Basic 中。 对于数值的转换,如果转换后的值不符合目标类型是引发异常。
DebugInfo
//调试信息。
Decrement
//一元递减操作,如 ( a - 1) C# 和 Visual Basic 中。 该对象 a 不应就地修改。
Default
//默认值。
Divide
//除法运算,如 ( a / b), ,针对数值操作数。
DivideAssign
//除的复合赋值运算,如 ( a /= b), ,针对数值操作数。
Dynamic
//动态操作。
Equal
//一个表示相等比较,如节点 ( a == b) 在 C# 或 (a = b) 在 Visual Basic 中。
ExclusiveOr
//按位或逻辑 XOR 操作,如 ( a ^ b) 在 C# 或 (a Xor b) 在 Visual Basic 中。
ExclusiveOrAssign
//按位或逻辑 XOR 复合赋值运算,如 ( a ^= b) C# 中。
Extension
//扩展表达式。
Goto
//一个"转到"表达式,如 goto Label 在 C# 或 GoTo Label 在 Visual Basic 中。
GreaterThan
//"大于"比较,如 ( a > b)。
GreaterThanOrEqual
//"大于或等于"比较,如 ( a >= b)。
Increment
//一元递增操作,如 ( a + 1) C# 和 Visual Basic 中。 该对象 a 不应就地修改。
Index
//索引操作或访问不采用参数的属性的操作。
Invoke
//操作调用的委托或 lambda 表达式,如 sampleDelegate.Invoke ( )。
IsFalse
//一个 false 条件值。
IsTrue
//一个 true 条件值。
Label
//标签。
Lambda
//Lambda 表达式,如 a => a + a 在 C# 或 Function(a) a + a 在 Visual Basic 中。
LeftShift
//按位左移运算,如 ( a << b)。
LeftShiftAssign
//按位左移复合赋值运算,如 ( a <<= b)。
LessThan
//"小于"比较,如 ( a<b)。
LessThanOrEqual
//"小于或等于"比较,如 ( a <= b)。
ListInit
//创建一个新的操作的 IEnumerable 对象,并对其进行初始化从列表中的元素,如 new List<SampleType>(){ a, b, c } 在 C# 或 Dim sampleList = { a, b, c } 在 Visual Basic 中。
Loop
//一个循环,如 for 或 while。
MemberAccess
//从一个字段或属性,如读取操作 obj.SampleProperty。
MemberInit
//运算,创建一个新的对象并初始化一个或多个成员,如 new Point { X = 1, Y = 2 } 在 C# 或 New Point With {.X = 1, .Y = 2} 在 Visual Basic 中。
Modulo
//算术余数运算,如 ( a % b) 在 C# 或 (a Mod b) 在 Visual Basic 中。
ModuloAssign
//算术余数复合赋值运算,如 ( a %= b) C# 中。
Multiply
//乘法运算,如 ( a* b ), ,不进行溢出检查,针对数值操作数。
MultiplyAssign
//乘法复合赋值运算,如 ( a *= b), ,不进行溢出检查,针对数值操作数。
MultiplyAssignChecked
//乘法复合赋值运算,如 ( a *= b), ,,进行溢出检查,针对数值操作数。
MultiplyChecked
//乘法运算,如 ( a* b ), ,,进行溢出检查,针对数值操作数。
Negate
//算术求反运算,如 (-a)。 该对象 a 不应就地修改。
NegateChecked
//算术求反运算,如 (-a), ,,进行溢出检查。 该对象 a 不应就地修改。
New
//调用构造函数以创建新的对象,如操作 new SampleType()。
NewArrayBounds
//创建一个新数组,其中每个维度的下限指定,如操作 new SampleType[dim1, dim2] 在 C# 或 New SampleType(dim1, dim2) 在 Visual Basic 中。
NewArrayInit
//操作,创建一个新的一维数组并对其进行初始化从列表中的元素,如 new SampleType[]{a, b, c} 在 C# 或 New SampleType(){a, b, c} 在 Visual Basic 中。
Not
//按位求补或逻辑求反运算。 在 C# 中,则等同于 (~a) 整型和 (!a) 布尔值。 在 Visual Basic 中,则等同于 (Not a)。 该对象 a 不应就地修改。
NotEqual
//不相等比较,如 (a != b) 在 C# 或 (a <> b) 在 Visual Basic 中。
OnesComplement
//一个二进制反码运算,如 (~a) C# 中。
Or
//按位或逻辑 OR 操作,如 (a | b) 在 C# 或 (a Or b) 在 Visual Basic 中。
OrAssign
//按位或逻辑 OR 复合赋值运算,如 (a |= b) C# 中。
OrElse
//短路条件 OR 操作,如 (a || b) 在 C# 或 (a OrElse b) 在 Visual Basic 中。
Parameter
//对参数或变量的表达式的上下文中定义的引用。 有关更多信息,请参见ParameterExpression。
PostDecrementAssign
//一元后缀递减,如 (a--)。 该对象 a 应就地修改。
PostIncrementAssign
//一元后缀递增,如 (a++)。 该对象 a 应就地修改。
Power
//如引发数字进行幂运算的数学运算 (a ^ b) 在 Visual Basic 中。
PowerAssign
//如引发数字进行幂运算的复合赋值运算 (a ^= b) 在 Visual Basic 中。
PreDecrementAssign
//一元前缀递减,如 (--a)。 该对象 a 应就地修改。
PreIncrementAssign
//一元前缀递增,如 (++a)。 该对象 a 应就地修改。
Quote
//具有类型的常量值的表达式 Expression。 一个 Quote 节点可以包含对它所代表的表达式的上下文中定义的参数的引用。
RightShift
//按位右移运算,如 (a >> b)。
RightShiftAssign
//按位右移复合赋值运算,如 (a >>= b)。
RuntimeVariables
//运行时变量的列表。 有关详细信息,请参阅RuntimeVariablesExpression。
Subtract
//减法运算,如 (a - b), ,不进行溢出检查,针对数值操作数。
SubtractAssign
//减法复合赋值运算,如 (a -= b), ,不进行溢出检查,针对数值操作数。
SubtractAssignChecked
//减法复合赋值运算,如 (a -= b), ,,进行溢出检查,针对数值操作数。
SubtractChecked
//算术减法运算,如 (a - b), ,,进行溢出检查,针对数值操作数。
Switch
//一个切换操作,如 switch 在 C# 或 Select Case 在 Visual Basic 中。
Throw
//引发异常,如操作 throw new Exception()。
Try
//一个 try-catch 表达式。
TypeAs
//显式引用或装箱转换在其中 null 如果转换失败,如提供 (obj as SampleType) 在 C# 或 TryCast(obj, SampleType) 在 Visual Basic 中。
TypeEqual
//确切类型测试。
TypeIs
//一种类型测试,如 obj is SampleType 在 C# 或 TypeOf obj is SampleType 在 Visual Basic 中。
UnaryPlus
//一元正运算,如 (+a)。 预定义的一元正运算的结果是操作数的值,但用户定义的实现可能有不寻常的结果。
Unbox
//取消装箱值类型的操作,如 unbox 和 unbox.any MSIL 中的说明。

/*节点的NodeType用以描述表达式的行为*/

表达式类型的转换

所有表达式类型都从Expression类派生,当创建一棵表达式树时,如果创建的是一个Lambda表达式,那么得到的是一个Expression<TDelegate>实例,Expression<TDelegate>的Body属性存储Lambda封装的表达式,比如Expression<Func<int,int>> info=(x)=>x,其Body返回x,x是一个ParameterExpression,但Body被表示为表达式基类Expression,其实际存储的是ParameterExpression:

;
Console.WriteLine ( additionExpressionInfo.Body.GetType().Name );  //BinaryExpression
var add = additionExpressionInfo.Body as BinaryExpression; //可以将父类转子类,因为Body虽然是Expression类型,但实际存储的是子类实例

何时使用Expression<TDelegate>?

现在假设你要创建一个表示调用string的EndWith()方法的表达式,如果使用Expression的静态方法,你得创建一堆子表达式,然后将它们合成为一个表示方法调用的表达式,再将方法表达式、方法需要的参数表达式全部封装到LambdaExpression中,以便可以执行

var x = Expression.Parameter ( typeof ( string ) , "x" );
var y = Expression.Parameter ( typeof ( string ) , "y" );
var methodInfo = typeof ( string ).GetMethod ( "StartsWith" , new Type [ ] { typeof ( string ) } );
var call = Expression.Call ( x , methodInfo , y );
var lambda = Expression.Lambda<Func<string , string , bool>> ( call , new [ ] { x , y } );
Console.WriteLine ( lambda.ToString ( ) ); // (x,y)=>x.StartsWith(y)
bool IsTrue = lambda.Compile ( ) ( "ABC" , "A" );
Console.WriteLine ( IsTrue ); //print true

一个简单的方法调用需要写出一堆臃肿难堪的代码?像这样简单的运算完全可以直接使用Expression<TDelegate>类型,因为你只需要创建一个Lambda表达式,一个Lambda表达式本身就可以被看成是一个Expression<TDelegate>,这样你完全不需要使用Expression的静态方法创建那么多表达式,一切交给Lambda表达式即可:

//产生与上面代码完全一样的表达式树
Expression<Func<string , string , bool>> lambdaExpr = ( str1 , str2 ) => str1.StartsWith ( str2 );
IsTrue=lambdaExpr.Compile ( ) ( "ABC" , "A" );
Console.WriteLine ( IsTrue ); //print true

前面说过Expression<TDelegate>只能表示一个Lambda表达式,不能表示Lambda语句。也即类似一元、二元运算的表达式完全可以使用Lambda来创建一个Expression<TDelegate>,而像条件判断、循环等语句表达式就不能使用Lambda表达式来创建,它们只能是Lambda语句,而Lambda语句不被视为Expression<TDelegate>,此时才需要考虑使用Expression的静态方法来构造更复杂的表达式逻辑。

LambdaExpression的方法

Body
;
;
;
 ,  ) );  ,  ); // true

Expression的静态方法

Constant ( )
 ) );  ) ) ) ) ) ) ,  ) ) ,  ) ) ,
     ) ) ,
     ) ) ,
     ) ) ,
     ) ) ,
          ) ) ,
              ) ) ,
          ) ) ,
              ) )
);

);  ) ) ,
     )
            ) ,
            ;
 ) ,  )   ) ,  ) );
var expr1 = Expression.Lambda<Func<int>> ( add );
var expr2 = Expression.Quote ( Expression.Lambda ( add ) );
Console.WriteLine(expr1.Compile ( ) ( ) ); //print 2
Console.WriteLine(expr2 ); // print  ()=>1+1

经过以上的逐步分析,现在可以创建一个稍微复杂一点的例子了,下面演示了创建一个块语句表达式,在块中创建了双重循环表达式,通过调用Lambda方法提取一个委托来执行表达式树:

 ) ) ,
     ) ) ,  ) ) ,
                //内层循环
                Expression.Loop (
                    Expression.IfThenElse (
                        Expression.LessThanOrEqual ( y , x ) , // if y <= x
                        //为真时执行
                        Expression.Block (
                            new [ ] { result } ,
                            Expression.Assign ( result , Expression.Multiply ( x , y ) ) ,
                            Expression.Call ( null , typeof ( Console ).GetMethod ( "Write" , new Type [ ] { typeof ( int ) } ) , y ) ,
                            Expression.Call ( null , typeof ( Console ).GetMethod ( "Write" , new Type [ ] { typeof ( string ) } ) , Expression.Constant ( "×" ) ) ,
                            Expression.Call ( null , typeof ( Console ).GetMethod ( "Write" , new Type [ ] { typeof ( int ) } ) , x ) ,
                            Expression.Call ( null , typeof ( Console ).GetMethod ( "Write" , new Type [ ] { typeof ( string ) } ) , Expression.Constant ( "=" ) ) ,
                            Expression.Call ( null , typeof ( Console ).GetMethod ( "Write" , new Type [ ] { typeof ( int ) } ) , result ) ,
                            Expression.Call ( null , typeof ( Console ).GetMethod ( "Write" , new Type [ ] { typeof ( string ) } ) , Expression.Constant ( "\t" ) ) ,
                            Expression.PostIncrementAssign ( y ) // y++
                        ) ,
                        //为假时退出内层循环
                        Expression.Break ( innerBreak )
                    ) ,
                    innerBreak
                ) ,//内层循环end
                Expression.Call ( null , typeof ( Console ).GetMethod ( "WriteLine" , new Type [ ] { typeof ( string ) } ) , Expression.Constant ( "" ) ) ,
                Expression.PostIncrementAssign ( x ) // x++
            ) ,
            //为假时执行
            Expression.Break ( outerBreak )
            )
    , outerBreak )
);

Expression.Lambda<Action> ( block ).Compile ( ) ( );

Linq查询中的表达式树

为什么有时候Linq查询需要表达式树?

Linq To SQL

Queryable类为IQueryable<T>实现了一系列的扩展方法用于Linq To SQL的查询,这些扩展方法大部分都返回一个IQueryable<T>类型,IQueryable <T>有一个Expression类型的属性叫做Expression,当使用Linq To SQL执行Linq查询时会调用那些扩展方法,而扩展方法都要求一个Expression<T>类型的参数,扩展方法接收这个作为参数的表达式树后将它作为IQueryable <T>集合的Expression属性来使用,IQueryable <T>还有一个叫做IQueryProvider(Linq查询提供程序)的属性,IQueryProvider类拿到Expression表达式树后会对其进行解析以便生成纯SQL语句,再将SQL字符串发送到数据库以便执行。所以从此处可以看出来,表达式树具有很高的灵活性,它并非可执行的C#代码,但通过Expression提供的一系列属性和方法,使用ExpressionVisitor(对表达式树进行访问和修改的类)你可以对其进行解析,根据需求的不同从而动态修改表达式树中的表达式以便生成可在非C#环境执行的代码。

Linq To Object

Enumerable类为IEnumerable<T>实现了一系列的扩展方法用于Linq To Object的查询,这些扩展方法大部分都返回一个IEnumerable<T>类型,IEnumerable<T>没有需要接收表达式树作为参数的扩展方法,因为Linq To Object是直接查询内存中的数据,不需要转化为SQL语句,所以根本用不上表达式树。

C# - 学习总目录

参考:http://www.infoq.com/cn/articles/dot-net-expression-tree

最新文章

  1. Ubuntu菜鸟入门(四)—— 搜狗输入法
  2. 【小白的CFD之旅】04 任务
  3. 网络婚礼之AFNetWorking3.0
  4. WordPress 添加面包屑导航
  5. UIView 的autoresizingMask属性
  6. 【国内独家首发】iPhone4 iOS7不完美越狱教程新鲜出炉
  7. thymeleaf的初次使用(带参请求以及调用带参js方法)
  8. java怎么连接sql server,需要注意的几点
  9. Python--三元运算与lambda表达式
  10. Extjs的架构设计思考,单页面应用 or 多页面?
  11. ECMAScript6-let和const命令
  12. MongoDB的安装和使用指南
  13. windows安装多个python及pip版本
  14. js之正则的坑
  15. phantomjs的安装和使用链接
  16. shell脚本选择LOG里面特定的行,生成新文件并rsync上传
  17. Android NDK定位.so文件crash代码位置
  18. R语言plot函数参数合集
  19. NetCore入门篇:(九)Net Core项目使用Session及用Redis做分布式
  20. CodeIgniter在nginx下404 not found

热门文章

  1. Zabbix 3.4.7针对一些主机设置期间维护
  2. php 表单提交大量数据发生丢失的解决方法
  3. .NET平台下,初步认识AutoMapper
  4. Jenkins pipeline:pipeline 语法详解
  5. 【jq】prop和attr的区别
  6. yum makecache
  7. eclipse 创建springboot项目
  8. future builder
  9. JQ面向对象的放大镜
  10. MySQL 中触发器的应用