那么,什么是一个表达式树?
与一般情况下C#编译得到的IL不同,一个Lambda表达式会被编译为一个表现代码逻辑的对象模型。于是,例如数据库提供者(provider)便可以观察这个对象模型,理解我们编写代码的目的,在必要时便可以将这种目的转化为其他形式(如T-SQL)。
我们不妨独立观察先前的Lambda表达式:
cust => cust.Region == "North";
如果要观察编译器做的事情,我们需要如平时般编译示例代码(Lambda表达式),然后在强大(且免费)的Reflector中查看——不过在此之前,我们要将.NET 3.5 (C# 3.0)优化选项关闭。

然后观察反编译器的输出内容,就会发现一些原本应该由我们自己编写的C#代码:
![]()
请注意,虽然编译器直接访问了MemberInfo对象,并且使用了非法的变量名——所以你无法直接让这些输出编译通过,它只是用来参考的。有趣的是,事 实上C#语言规范中并没有指明编译器是如何将代码转化为表达式树的,因此,使用编译结果作为参考实现,是为数不多的可用于研究表达式树的方法之一。
为了研究表达式树的手动编写方法,我们需要把相等性判断(==)和成员访问(.)视为接受运算对象的普通方法:
cust => Equal(Property(cust,"Region"),"North");
我们现在可以构建一个相同的,接受Customer类型作为参数,判断其Region属性是否为字符串“North”,并返回一个布尔值的表达式树。
ParameterExpression cust = Expression.Parameter(
typeof(Customer), "cust");
// compare (equality) the Region property of the// parameter against the string constant "North"
BinaryExpression body = Expression.Equal(
Expression.Property(cust, "Region"),
Expression.Constant("North", typeof(string)));
// formalise this as a lambda
Expression<Func<Customer, bool>> filter =
Expression.Lambda<Func<Customer, bool>>(body, cust);
最后的“Lambda”语句是将表达式树标准化为一个完全独立的单元,并包含一系列(这个例子中只有一个)用于表达式树内部的参数。如果我们传入一个Lambda内不存在的参数,则会抛出一个异常。
一个表达式树,其实只是一个表现我们意图的不可变的对象模型:
- parameter: Customer: “cust”
- body (binary)
- method: equals
- left (member)
- member: “Region”
- expression: “cust” parameter
- right (constant)
- string: “North”
在获得一个完整的表达式树之后,我们可以将其交由LINQ提供者处理(此时它就会被真正当作一颗“树”来处理了),或者把它编译为一个委托——即动态生成所需的IL:
Func<Customer, bool> filterFunc = filter.Compile();
现在我们向委托对象中传入一个Customer实例并返回一个布尔值。
这看上去有些麻烦,但已经远比使用Reflection.Emit的方式来的简单了,尤其在一些较为复杂的情况下(如装箱)。
重要:将一个表达式树编译为委托对象涉及到动态代码的生成。如果想要获得非常好的的性能,你应该仅编译一次——将其储存起来(如放在一个字段中)并复用多次。