掌握表达式树

  • Hartmut Wilms
  • 张海龙

2008 年 2 月 12 日

话题:.NET函数式编程语言 & 开发

对于熟悉函数式编程语言的开发者而言,可能不需要再解释为什么说“表达式树(即 Expression Tree)是非常有用的”,但对其他人来讲,表达式树则是 C# 3.0 或 VB 9.0 的所有新特性中最引人注目的一个概念。

Charlie Calvert讲解了表达式树的基础,他从语法描述开始,并以同 Visual Studio 2008 一起发行的示例程序——ExpressionTressVisualizer 为例,形象地展示了如何使用表达式树。

表达式树与 Lambda 表达式相关联,Lambda 表达式是可在行内实现预期代理和匿名代理的内容的一种方式。Ian Griffiths在他关于表达式树文章中,给出了一个非常不错的Lambda 表达式介绍,以及 Lambda 与表达式树的关系。正如 Ian 所指出的,“Lambda 除了语法不同以外,并没有比我已掌握的匿名方法提供更多的东西,但是,它却使一些匿名方法无法实现的事情变成了可能。”

Func<int, bool> nonExprLambda = x => (x & 1) == 0;

Expression<Func<int, bool>> exprLambda = x => (x & 1) == 0;

[...]

第二行更有趣,它变成 Func 的代理,将 Func 作为一种名为 Expression 的泛型的类型参数,然后按照相同的方式进行初始化,因此,你可能会认为两者是在完成同样的事情,但是,它却能让编译器知道了 Expression 类型,这就导致了不一样的行为;前者会直接将 Lambda 表达式编译为 IL,而后者则是通过对表达式进行评估后,生成一个可以创建并代表此表达式的对象树的 IL。

Charlie 仔细钻研了 Expression 的细节:

Expression<TDelegate>类共有四个属性:

  • Body:获取表达式的主体;

  • Parameters:获取 Lambda 表达示的参数;
  • NodeType:得到树中某些节点的表达式类型(ExpressionType),这是一个有 45 种不同值的枚举类型,代表表达式节点的所有可能类型,如返回常数、也可能返回参数、或者返回一个值是否小于另外一个(<),或者返回一个值是否大于另外一个(>),或者返回两个值的和(+)等等;
  • Type:得到表达式的静态类型,在本例中,表达式的类型是Func<int, int, int>。

在 C# 3.0 和 VB 9.0 大部分新特性中,表达式树在“LINQ,尤其是 LINQ to SQL”中扮演了一个非常重要的角色:

var query = from c in db.Customers

where c.City == "Nantes"

 select new { c.City, c.CompanyName };

LINQ 表达式会返回一个 IQueryable 类型的值,IQueryable 值包含有一个表达式树,由它来代表这个 LINQ 查询。当创建这个查询时,SQL 语句并没有被发送到数据库中,只有当你在代码中对这个 IQueryable 对象进行 Iterate 操作时,这个 SQL 语句才会被创建并执行,这个概念就是延迟执行

Charlie Calvert 对这种方法的使用进行了说明:

因为查询是以一种高度抽象的数据结构封装后传送给编译器的,所以,编译器可以以它期望的任一方式自由地进行翻译。查询的执行次序和途径并非强制指定的,相反,它可以对表达树进行分析,以发现你所期望的结果,然后再决定如何处理;至少在理论上,它可以独立地考虑很多因素,诸如当前的网络流量、数据库的负载、它当前已有的可用结果集等等;虽然在 LINQ to SQL 中,它实际上并没有去考虑所有这些因素,但在理论上它可以考虑更多它关心的因素;此外,它还可以对表达式树进行分析,并将 LINQ to SQL 的输出结果转换成截然不同的形式,然后传递给你手写的自定义代码。

Marlon Grech展示了如何应用表达式树,以及如何创建表达式解析器。Octavio Hernández 提供了一个表达式树的表达式类层次汇总表。

查看英文原文Get a Grasp on Expression Trees

.NET函数式编程语言 & 开发