表达式树使用一种类似树的结构来表示代码,它的每个节点都是一个表达式,比如方法调用和x<y这样的二元运算等。我们可以对表达式树的内容进行编辑和运算,这样能够动态修改可执行代码,以及动态创建查询等。我们可以使用匿名lambda表达式或者C# API来创建表达式树。

   这一系列文章,主要是对C#表达式树的一种总结,基本知识参考MSDN的内容 这部分内容可以直接到MSDN上查看,后面的几篇文章主要分享一下,在工作中碰到的应用到表达式树的部分,谨做为记录和分享。

生成表达式树


通过lambda表达式创建表达式树

    可以通过将lambda表达式赋值给Expression<TDelegate>类型的变量,编译器可以自动生成创建该lambda表达式的表达式树。C#编译器只能从lambda表达式生成表达式树,只能是单行lambda表达式,不能解析多行lambda语句,如下,可以通过一下方式创建lambda表达式 num=>num<5的表达式树:

Expression<Func<int, bool>> lambda = num => num < 5;  

通过API创建表达式树

   使用API创建表达式,需要使用Expression类,这个类包含了创建特定类型表达式树节点的静态工厂方法,比如表示参数的变量ParameterExpression,表示方法调用的MethodExpression。ParameterExpression,MethodExpression以及其他特定的表达式类型都在System.Linq.Expression命名空间里定义,这些类型都派生于Expression抽象类。

  下面的例子是使用API方式创建num=>num<5的lambda表达式对应的表达式树:

ParameterExpression numPara = Expression.Parameter(typeof(int), "num");//参数num
ConstantExpression five = Expression.Constant(5, typeof(int));//常数5

BinaryExpression numLessThanFive = Expression.LessThan(numPara, five);
Expression<Func<int, bool>> lambda1 = Expression.Lambda<Func<int, bool>>(numLessThanFive, new ParameterExpression[] { numPara });

    从.NET Framework 4开始,表达式树API还支持赋值以及流程控制,比如循环,条件块和try ... catch块等。相对于通过lambda表达式创建表达式树,可以利用API创建更加复杂的表达式树,比如下面使用API创建数字阶乘的表达式树:

//参数value
ParameterExpression value = Expression.Parameter(typeof(int), "value");
//本地变量
ParameterExpression result = Expression.Parameter(typeof(int), "result");
//标签,用来跳出循环
LabelTarget label = Expression.Label(typeof(int));
//创建表达式块
BlockExpression block = Expression.Block(
    //添加本地参数result
    new[] { result },
    //result=1 赋值
    Expression.Assign(result, Expression.Constant(1)),
    //循环
    Expression.Loop(
        //循环条件
        Expression.IfThenElse(
            //如果 value>1
            Expression.GreaterThan(value, Expression.Constant(1)),
            //则 result*=value--;
            Expression.MultiplyAssign(result, Expression.PostDecrementAssign(value)),
            //否则跳出loop循环。跳到label的语句执行
            Expression.Break(label, result)
            ),
            label
        )
);
//编译表达式树
Func<int, int> factor = Expression.Lambda<Func<int, int>>(block, value).Compile();
//执行,输出结果120
Console.WriteLine(factor(5));

解析表达式树


  在获取了表达式树之后,如何获取表达式树的每一个部分,这个在有些情况下非常有用,下面这个例子展示了如何获取num=>num<5的各个部分。

Expression<Func<int, bool>> expreTree = num => num < 5;

ParameterExpression param = (ParameterExpression)expreTree.Parameters[0];//num
BinaryExpression operation = (BinaryExpression)expreTree.Body;//<
ParameterExpression left = (ParameterExpression)operation.Left;//num
ConstantExpression right = (ConstantExpression)operation.Right;//5
                                                               //output Decomposed expression: num => num LessThan 5  
Console.WriteLine("Decomposed expression:{0} = > {1} {2} {3}", param.Name, left.Name, operation.NodeType, right.Value);

编译表达式树


  Expression<TDelegate>类型有Compile方法,可以将表达式树编译成对应的TDelegate委托类型,使用方法如下:

// 创建表达式树
Expression<Func<int, bool>> expr = num => num < 5;
// 将表达式树编译成对应委托 
Func<int, bool> result = expr.Compile();
//调用委托方法,输出True
Console.WriteLine(result(4));
//也可以直接编译后调用,输出True
Console.WriteLine(expr.Compile()(4));

再比如,下面例子演示了,创建一个表达式树,然后编译执行:

//创建表达式树的执行逻辑
BinaryExpression be = Expression.Power(Expression.Constant(2D), Expression.Constant(3D));
//创建表达式树
Expression<Func<double>> le = Expression.Lambda<Func<double>>(be);
//编译表达式树
Func<double> compileExpression = le.Compile();
//执行lambda表达式,获得结果8
double result = compileExpression();
Console.WriteLine(result);

表达式树的修改


   表达式树是不可变对象(immutable),跟string类似,不能直接修改,只能复制一个然后重新构造。具体参考MSDN How to modify expression trees (C#).

结语


  本篇全部内容参考MSDN上表达式树部分的内容,如果有基础建议直接看,这里只是个人作为笔记,也是表达式树的最基础部分,后文会介绍表达式树的一些用法。