追根溯源之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表达式编译成了表达式树。

三、总结

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpxzwp.html