欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C# 表达式树讲解(一)

程序员文章站 2023-04-04 14:54:50
一、前言 一直想写一篇Dpper的定制化扩展的文章,但是里面会设计到对Lambda表达式的解析,而解析Lambda表达式,就必须要知道表达式树的相关知识点。我希望能通过对各个模块的知识点或者运用能够多一点的讲解,能够帮助到园友了解得更多。虽然讲解得不全面,如果能成为打开这块的一把钥匙,也是蜗牛比较欣 ......

一、前言

一直想写一篇dpper的定制化扩展的文章,但是里面会设计到对lambda表达式的解析,而解析lambda表达式,就必须要知道表达式树的相关知识点。我希望能通过对各个模块的知识点或者运用能够多一点的讲解,能够帮助到园友了解得更多。虽然讲解得不全面,如果能成为打开这块的一把钥匙,也是蜗牛比较欣慰的。

二、表达树理解

表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,它将我们原来可以直接由代码编写的逻辑以表达式的方式存储在树状的结构里,从而可以在运行时去解析这个树,然后执行,实现动态的编辑和执行代码。在.net 里面的linq to sql就是对表达式树的解析。

这里先讲解下表达式和表达式树,表达式相信大家都知道,比如x+5或者5,都可以算是表达式,而表达式树里面的树指的二叉树,也就是表达式的集合,c#中的expression类就是表达式类。对于一棵表达式树,其叶子节点都是参数或者常数,非叶子节点都是运算符或者控制符。

2.1、表达式的创建

lambda表达式方法:

expression<func<int, int,bool>> fun = (x, y) => x < y

这种方法创建出的表达式根节点类型为expressiontype.lambda,type类型为返回值类型typeof(bool)

组装法(通过 api 创建表达式树):

parameterexpression numparam = expression.parameter(typeof(int), "num");
constantexpression five = expression.constant(5, typeof(int));
binaryexpression numlessthanfive = expression.lessthan(numparam, five);
expression<func<int, bool>> lambda1 =
    expression.lambda<func<int, bool>>(
        numlessthanfive,
        new parameterexpression[] { numparam });

我们先创建了两个参数表达式num和5,然后用lessthan组装在一起,最终的表达式为“num<5”,expr的节点类型为lessthan,type类型为typeof(bool)

我们先看看表达式树里面的构造

首先expression<tdelegate>的功能是将强类型lambda表达式表示为表达式树形式的数据结构,他的父类是lambdaexpression,比较他们代码可知,lambda表达式的主体,名称和参数全部保存在lambdaexpression里面。

expression<tdelegate>与lambdaexpression代码截图:

lambdaexpression里面的body就是我们的表达式。

c#表达式给我们提供了丰富的表达式类,进入到lambdaexpression类里面

方法返回类型以“expression”结尾的,基本上都是一个表达式类。

每个表达式代表的定义和创建方法,可以参照微软官方文档

下面是平常使用最多的表达式

constantexpression:常量表达式

parameterexpression:参数表达式

unaryexpression:一元运算符表达式

binaryexpression:二元运算符表达式

typebinaryexpression:is运算符表达式

conditionalexpression:条件表达式

memberexpression:访问字段或属性表达式

methodcallexpression:调用成员函数表达式

expression<tdelegate>:委托表达式

2.2、表达式的解析

表达式树解析

通过lambdaexpression类我们可以知道,表达式树包含:参数[parameters],表达式树类型[nodetype],表达式[body],返回类型[returntype],lambda表达式的委托[compile]以及lambda表达式名称[name],如图所示:

表达式解析:

所有的表达式都包含:左节点【left】,右节点【right】,类型【nodetype】,不同的表达式还会有其他属性,这里的左右节点依旧是表达式。

下图是binaryexpression表达式截图

表达式树和表达式里面的类型nodetype是一个枚举,一共有85个类型,有兴趣的朋友可以去了解下。

常用的类型如下:

expressiontype.and:c#中类似于&

expressiontype.andalso:c#中类似于&&

expressiontype.or:c#中类似于|

expressiontype.orelse:c#中类似于||

expressiontype.equal:c#中类似于==

expressiontype.notequal:c#中类似于!=

expressiontype.greaterthan:c#中类似于>

expressiontype.greaterthanorequal:c#中类似于>=

expressiontype.lessthan:c#中类似于<

expressiontype.lessthanorequal:c#中类似于<=

expressiontype.add:c#中类似于+

expressiontype.addchecked:c#中类似于+

expressiontype.subtract:c#中类似于-

expressiontype.subtractchecked:c#中类似于-

expressiontype.divide:c#中类似于/

expressiontype.multiply:c#中类似于*

expressiontype.multiplychecked:c#中类似于*

2.3、编译表达式树

在表达式创建那,我们组合创建了一个lambda表达式,那么应该怎么使用它呢?在“表达式的解析”里面,lambdaexpression类和expression<tdelegate>类都有一个compile的方法,学名是lambda表达式的委托,其实就是lambda表达式编译函数的委托,所以我们只需要调用他,得到的结果就是一个函数方法。

代码修改如下:

parameterexpression numparam = expression.parameter(typeof(int), "num");
constantexpression five = expression.constant(5, typeof(int));
binaryexpression numlessthanfive = expression.lessthan(numparam, five);
expression<func<int, bool>> lambda1 =
    expression.lambda<func<int, bool>>(
        numlessthanfive,
        new parameterexpression[] { numparam });

console.writeline($"lambda的内容:{lambda1.tostring()}");

//表达式的编译
var func = lambda1.compile();
console.writeline($"lambda的运行结果:{func(6)}");

运行结果

三、总结

这里我们对表达式做了基本的讲解,相信大家对lambda表达式有了初步的了解,下面我们将继续讲解对一个表达式树的遍历。