小试 Linq Expression


0X00 背景

表达式是当今编程语言中最重要的组成成分。简单的说,表达式就是变量、数值、运算符、函数组合起来,表示一定意义的式子。例如下面这些都是(C#)的表达式:

3 //常数表达式
a //变量或参数表达式
!a //一元逻辑非表达式
a + b //二元加法表达式
Math.Sin(a) //方法调用表达式
new StringBuilder() //new 表达式

Expression Tree是.NET 3.5引入的,System.Linq.Expression命名空间下的Expression类和它的诸多子类就是这一数据结构的实现。每个表达式都可以表示成Expression某个子类的实例。每个Expression子类都按照相应表达式的特点储存自己的子节点。例如BinaryExpression就表示各种二元运算符的表达式。它的Left和Right属性就是参与二元运算的两个运算数。

0X01 目录树的构成

Expression Tree是.NET 3.5引入的,究竟什么是表达式目录树(Expression Tree),它是一种抽象语法树或者说它是一种数据结构,通过解析表达式目录树,我们首先来看看如何构造出一个表达式目录树,最简单的方法莫过于使用Lambda表达式,ConstantExpression是一个常量表达式,BinaryExpression是一个二元表达式,它们都继承于抽象的基类Expression,而泛型的Expression则继承于LambdaExpression。在Expression类中提供了大量的工厂方法,这些方法负责创建以上各种表达式对象,如调用Add()方法将创建一个表示不进行溢出检查的算术加法运算的BinaryExpression对象,如下代码

BinaryExpression body = Expression.Add(Expression.Constant(2),Expression.Constant(3));
            Expression> expression = Expression.Lambda>(body, null);
            Func lambda = expression.Compile();
            Console.WriteLine(lambda());

Add表示计算和,运行结果如下

现在我们知道了表达式目录树的组成,再来看看.NET Framework到底提供了哪些表达式,请看下图

0X02 编译方法Compile

这里我们仍要使用诸多表达式中与众不同的LambdaExpression。LambdaExpression.Compile是将Lambda表达式的表达式树真的编译成一个.NET方法。我们看看这段代码:

ParameterExpression pi = Expression.Parameter(typeof(int), "i");
            LambdaExpression fexp =
                Expression.Lambda(
                    Expression.Add(pi, Expression.Constant(1))
                    , pi);
            Delegate lambda3 = fexp.Compile();
            Console.WriteLine(lambda3.DynamicInvoke(3));

这个表达式树其实就是 i => i + 1。后面的Compile方法生成了一个Delegate。如果我们试着获取这个Delegate的类型会发现他是Func!实际上,这个委托所引用的方法是一个DynamicMethod编译后的结果。DynamicMethod是Reflection.Emit的强大功能,可在运行时动态创建出.NET方法来。我们可以正常地调用这个委托。

Expression.Lambda这个方法还有一个泛型版重载,它可以创建如同C#自己生成一样的强类型LambdaExpression。强类型LambdaExpression的Compile就更好了,能直接生成强类型的委托。改成如下代码

ParameterExpression pi = Expression.Parameter(typeof(int), "i");
var fexp =
    Expression.Lambda>(
        Expression.Add(pi, Expression.Constant(1))
        , pi);
var f = fexp.Compile();
Console.WriteLine(f(3));

0X03 执行表达式目录树

前面已经可以构造出一个表达式目录树了,现在看看如何去执行表达式目录树。我们需要调用Compile方法来创建一个可执行委托,并且调用该委托,这里我们只要简单的调用Compile方法就可以了,事实上在.NET Framework中是调用了一个名为ExpressionCompiler的内部类来做表达式目录树的执行(注意此处的Compiler不等同于编译器的编译)运行结果如图

ParameterExpression paraLeft = Expression.Parameter(typeof(int), "a");
            ParameterExpression paraRight = Expression.Parameter(typeof(int), "b");
            BinaryExpression binaryLeft = Expression.Multiply(paraLeft, paraRight);
            ConstantExpression conRight = Expression.Constant(2, typeof(int));
            BinaryExpression binaryBody = Expression.Add(binaryLeft, conRight);
            Expression> lambda2 = Expression.Lambda>(binaryBody, paraLeft, paraRight);
            Func lambdaresult = lambda2.Compile();
            int result = lambdaresult(2, 3);
            Console.WriteLine(lambda2.ToString());
            Console.WriteLine(result);

0X04 参考

https://www.cnblogs.com/Terrylee/archive/2008/08/01/custom-linq-provider-part-1-expression-tree.html

https://www.cnblogs.com/Ninputer/archive/2009/08/28/expression_tree1.html