追根溯源之Linq与表达式树
一、什么是表达式树?
首先来看下官方定义(以下摘录自巨硬官方文档)
表达式树表示树状数据结构中的代码,其中每个节点都是表达式,例如,方法调用或诸如的二进制操作x < y。
您可以编译和运行由表达式树表示的代码。这样就可以对可执行代码进行动态修改,在各种数据库中执行LINQ查询以及创建动态查询。有关LINQ中的表达式树的更多信息,请参见如何使用表达式树构建动态查询(C#)。
在动态语言运行时(DLR)中还使用了表达式树,以提供动态语言和.NET之间的互操作性,并使编译器编写程序可以发出表达式树而不是Microsoft中间语言(MSIL)。有关DLR的更多信息,请参见《动态语言运行时概述》。
您可以让C#或Visual Basic编译器根据匿名lambda表达式为您创建一个表达式树,或者您可以使用System.Linq.Expressions命名空间手动创建表达式树。
从上面我们可以提取一些关键信息——它是一种树型结构、表达式树可以被编译成可执行代码然后运行、DLR使用了表达式树、可以用表达式树来达到和直接写MSIL一样的效果、C#编译器能够根据匿名Lambda表达式静态生成构建表达式树的代码、你可以手动编写构建表达式树的代码。
其实第一个关键信息就是表达式树的全部,后面的所有功能都是在这之上衍生出来的,所以用我的话来回答,什么是表达式树?表达式树就是一种树形数据结构,在这个结构上包含了代码逻辑所必须的信息,用这些信息我们可以用来做很多事,例如,生成MSIL代码,生成SQL语句等等,这也是Linq To Anything的基础。
二、Linq
Linq(语言集成查询),在.Neter中经常用到的技术,你虽然在开发中经常用到,但你有没有了解过到它到底是怎么运作的呢?我们来扒一扒。
1.Linq To Entity
首先,Linq的链式调用,是靠扩展方法实现的,Linq主要扩展了IEnumerable<T>和IQueryable<T>两大接口。我们看下针对IEnumerable<T>的扩展。
public static class Enumerable
{
//所有针对IEnumerable<TSource>的扩展方法
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate)
//省略......
}
观察可以发现,针对IEnumerable的扩展方法,貌似跟Expression没有半毛钱关系。是的,半分钱关系都没有。这样做其实是为了性能考虑,因为这些查询实际上是从MSIL翻译成机器代码本地执行,我何必要先解析表达式树,然后翻译成MSIL,再到机器代码呢?这也是所谓的Linq To Entity
2.Linq To Other
对IQueryable<T>的扩展如下:
public static class Queryable
{
//所有针对IEnumerable<TSource>的扩展方法
public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
//省略......
}
观察可以发现,在Where扩展方法中有一个Expression<Func<TSource, bool>>类型的参数。这就是一个表达式树,确切的说是一个Lambda表达式树,这个Lamdbda表达式树包含了必要的信息,在对source上调用了这个方法,并传入一个Lambda表达式树之后,source内部会被把传入的表达式树添加到之前的表达式树节点上,然后返回一个新的IQueryable<TSource>实例,其中内部的表达式树已经包含了你刚传入的表达式节点,然后你可以在此之上继续调用扩展方法,当在调用诸如First()、ToList()、Count()等之类的方法之后,将会导致内部的表达式树被一个解析器解析,然后根据解析出来的结果,去查数据库、去检索JSON文件、去检索XML文件或是调用外部服务等,最后生成数据到内存,构造成一个List实例给你。至于内部的细节到底是什么,有时间再写。
3.问题
细心的朋友可能注意到,上节提到的一个Expression<Func<TSource, bool>>类型的参数,这个是怎么构造出来的呢?我们平时开发的时候好像从没有构造过啊。其实文章开头就有提到,
您可以让C#或Visual Basic编译器根据匿名lambda表达式为您创建一个表达式树,或者您可以使用System.Linq.Expressions命名空间手动创建表达式树。
发现没,这个脏活其实是由编译器帮我们干了,我们来验证一下。新建.Net Core控制台程序如下:
static void Main(string[] args)
{
List<int> datas = new List<int> { 1, 2, 3, 4, 5, 6 };
var res = datas.AsQueryable().Where(x => x > 3).ToList();
}
使用Debug模式编译,然后用一个你喜欢的反编译工具(PS:反编译一般指把中间语言代码变成高级语言代码,而反汇编一般指把机器代码变成汇编语言代码)反编译生成的程序集,这里我使用的是DNSPY。
如果使用的是DNSPY,记得把“反编译表达式树”选项关掉。
内容如下:
// Token: 0x02000002 RID: 2
internal class Program
{
// Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250
private static void Main(string[] args)
{
List<int> datas = new List<int>
{
1,
2,
3,
4,
5,
6
};
IQueryable<int> source = datas.AsQueryable<int>();
ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "x");
List<int> res = source.Where(Expression.Lambda<Func<int, bool>>(Expression.GreaterThan(parameterExpression, Expression.Constant(3, typeof(int))), new ParameterExpression[]
{
parameterExpression
})).ToList<int>();
}
}
可以发现,编译器帮我们把Lambda表达式编译成了表达式树。
三、总结
总的来说,表达式树是Linq中不可或缺的一环,为了方便人们使用表达式树,编译器也做了许多工作,从而避免用户手动构造表达式树,因此选用了Lambda表达式这种用户熟悉的形式给用户使用,但同时,也提高了理解门槛。
四、题外话
为了减少重复劳动,我编写了一个动态构建查询的类库,基于.NetStandard,支持静态排序,动态排序,多重排序,模糊查询,分页查询,能适用大多数的后台管理应用开发场景。原理其实就是动态构建表达式树。GitHub上有文档,Nuget上搜索EazyPageQuery,记得勾选“包括预发行版”~
Github:https://github.com/HekunX/EazyPageQuery
追根溯源之Linq与表达式树的更多相关文章
- C#学习笔记(九):LINQ和表达式树
LINQ LINQ:语言集成查询(Language Integrated Query)是一组用于c#和Visual Basic语言的扩展.它允许编写C#或者Visual Basic代码以查询数据库相同 ...
- C#3.0新特性:隐式类型、扩展方法、自动实现属性,对象/集合初始值设定、匿名类型、Lambda,Linq,表达式树、可选参数与命名参数
一.隐式类型var 从 Visual C# 3.0 开始,在方法范围中声明的变量可以具有隐式类型var.隐式类型可以替代任何类型,编译器自动推断类型. 1.var类型的局部变量必须赋予初始值,包括匿名 ...
- C#复习笔记(4)--C#3:革新写代码的方式(Lambda表达式和表达式树)
Lambda表达式和表达式树 先放一张委托转换的进化图 看一看到lambda简化了委托的使用. lambda可以隐式的转换成委托或者表达式树.转换成委托的话如下面的代码: Func<string ...
- 【C#表达式树 开篇】 Expression Tree - 动态语言
.NET 3.5中新增的表达式树(Expression Tree)特性,第一次在.NET平台中引入了"逻辑即数据"的概念.也就是说,我们可以在代码里使用高级语言的形式编写一段逻辑, ...
- LinQ实战学习笔记(三) 序列,查询操作符,查询表达式,表达式树
序列 延迟查询执行 查询操作符 查询表达式 表达式树 (一) 序列 先上一段代码, 这段代码使用扩展方法实现下面的要求: 取进程列表,进行过滤(取大于10M的进程) 列表进行排序(按内存占用) 只保留 ...
- C# - LINQ 表达式树
表达式树(Expression Tree) 表达式树是不可执行的代码,它只是用于表示一种树状的数据结构,树上的每一个节点都表示为某种表达式类型,大概有25种表达式类型,它们都派生自Expression ...
- LINQ Expresstion Tree 表达式树
Expression trees represent code in a tree-like data structure, where each node is an expression, for ...
- LINQ to Objects系列(4)表达式树
为了进一步加深对Lambda表达式的理解,我们需要掌握一个新的知识,Lambda表达式树,可能听名字看起来很高深和难以理解,但实际上理解起来并没有想象中那么难,这篇文章我想分以下几点进行总结. 1,表 ...
- 表达式树在LINQ动态查询
动态构建表达式树,最佳实践版,很实用! public class FilterCollection : Collection<IList<Filter>> { public F ...
随机推荐
- WPF启动流程-自己手写Main函数
WPF一般默认提供一个MainWindow窗体,并在App.Xaml中使用StartupUri标记启动该窗体.以下通过手写实现WPF的启动. 首先先介绍一下VS默认提供的App.Xaml的结构,如下图 ...
- java进阶(23)--List接口
一.基本概念 1.有序可重复 2.有下标 3.包含ArraList.LinkedList.Vector 二.List中特有的方法 且常用 (以下军均为数组通用方法) 1.void add(int ...
- Harmony OS 开发避坑指南——源码下载和编译
Harmony OS 开发避坑指南--源码下载和编译 本文介绍了如何下载鸿蒙系统源码,如何一次性配置可以编译三个目标平台(Hi3516,Hi3518和Hi3861)的编译环境,以及如何将源码编译为三个 ...
- CentOS7设置内网时间同步
1.yum 安装 NTP服务器 [root@master ~]# yum -y install ntp 2.启动ntpd服务 [root@master ~]# systemctl start ntpd ...
- 092 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 02 static关键字 02 static关键字(中)
092 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 ...
- Python实现的数据结构与算法之队列详解
本文实例讲述了Python实现的数据结构与算法之队列.分享给大家供大家参考.具体分析如下: 一.概述 队列(Queue)是一种先进先出(FIFO)的线性数据结构,插入操作在队尾(rear)进行,删除操 ...
- Selenium截屏 图片未加载的问题解决--【懒加载】
需求: 截屏后转PDF. 问题: selenium截屏后,图片未加载 如下图: 原因: 网站使用了懒加载技术:只有在浏览器中纵向滚动条滚动到指定的位置时,页面的元素才会被动态加载. 什么是图片懒加载? ...
- NOI 2011 【阿狸的打字机】
之前讲了[AC自动姬],今天我终于把这题给刚下来了...嗯,来给大家讲一讲. 题目描述: 打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母.经阿狸研究发现,这个打字机是这样工 ...
- 初始python模块
Python语言中,模块分为三类. 第一类:内置模块,也叫做标准库.此类模块就是python解释器给你提供的,比如我们之前见过的 time模块,os模块.标准库的模块非常多(200多个,每个模块又有很 ...
- DM9000裸机驱动程序设计
对于任何一个硬件模块的设计,首先第一步都是要先了解硬件本身后,再开始程序的软件设计.而由于DM9000的芯片文档内容很多,要驱动好网卡,需要很长时间,特别对于新手比较困难,所以可以参考linux内核代 ...