为了进一步加深对Lambda表达式的理解,我们需要掌握一个新的知识,Lambda表达式树,可能听名字看起来很高深和难以理解,但实际上理解起来并没有想象中那么难,这篇文章我想分以下几点进行总结。
1,表达式树的语法
2,将代码转换到数据
3,探索表达式树
4,将数据转换到代码
5,IQueryable<T>和表达式树
6,为什么要将LINQ to SQL查询表达式转换成表达式树?
7,IQueryable<T>和IEnumerable<T>
8,总结
表达式树的语法
//利用Lambda表达式定义一个Func委托 Func<int, int, int> function = (a, b) => a + b;
变量function指向两个数字相加的原生可执行的代码,上面这个Lambda表达式等价于下面这个方法。
public int function(int a, int b) { return a + b; }
这个Lambda表达式和这个方法都可以这样调用。
int c = function(1,2); //结果为:3
将代码转换到数据
表达式树不是一段可执行代码,而是一种数据结构。那么怎么将Lambda表达式转换成表达式树呢?
我们可以使用命名空间System.Linq.Expressions下的Expression类来实现这个需求。
例如我们先创建一个表达式树,如下代码。
//创建表达式树 Expression<Func<int, int, int>> expression = (a, b) => a + b;
这样,我们就创建一个类型为Expression<T>的表达式树,标识expression不是可执行代码;它是一个名叫表达式树的数据结构。我们可以使用工具ExpressionTreeVisualizer来浏览表达式树,如下图。
探索表达式树
Expression<TDelegate>类有四个属性:
- Body: 得到表达式的主体。
- Parameters: 得到lambda表达式的参数.
- NodeType: 获取树的节点的ExpressionType。共45种不同值,包含所有表达式节点各种可能的类型,例如返回常量,例如返回参数,例如取两个值的小值(<),例如取两个值的大值(>),例如将值相加(+),等等。
- Type: 获取表达式的一个静态类型。在这个例子里,表达式的类型是Func<int, int, int>。
那么怎么查看表达式树中的参数名称呢?从上图中我们可以看出,参数是一个ReadOnlyCollection集合,所以我们可以通过索引来访问。如下代码。
//访问表达式树的参数 Console.WriteLine("参数1:{0},参数2:{1}",expression.Parameters[0],expression.Parameters[1]);
接下来,怎么查看表达式树的Body体呢?在这个例子里是(a+b)。代码如下。
//访问表达式树的Body BinaryExpression body = expression.Body as BinaryExpression; ParameterExpression left = body.Left as ParameterExpression; ParameterExpression right = body.Right as ParameterExpression; Console.WriteLine(expression.Body); Console.WriteLine(" 表达式左边部分: " + "{0}{4} 节点类型: {1}{4} 表达式右边部分: {2}{4} 类型: {3}{4}", left.Name, body.NodeType, right.Name, body.Type, Environment.NewLine);
输出结果为:
通过探索表达式树,我们可以分析表达式的各个部分发现它的组成。你可以看见,我们的表达式的所有元素都展示为像节点这样的数据结构。表达式树是代码转换成的数据。
将数据转换到代码
我们可以将代码转换为数据,那么我们也可将数据转换为代码。下面的代码说明了如果将数据(表达式树数据结构)转换为代码。
//将数据(表达式树)转换为代码 int result = expression.Compile()(1,2); Console.WriteLine(result); //输出结果为:3
可以发现,程序输出结果与Lambda表达式执行结果一样。
IQueryable<T>和表达式树
现在至少你有一个抽象的概念理解表达式树,现在是时候回来理解其在LINQ中的关键作用了,尤其是在LINQ to SQL中。花点时间考虑这个标准的LINQ to SQL查询表达式:
var query = from c in db.Customers where c.City == "Nantes" select new { c.City, c.CompanyName };
你可能知道,这里LINQ表达式返回的变量query是IQueryable类型。这里是IQueryable类型的定义:
public interface IQueryable : IEnumerable { Type ElementType { get; } Expression Expression { get; } IQueryProvider Provider { get; } }
你可以看见,IQueryable包含一个类型为Expression的属性,Expression是Expression<T>的基类。IQueryable的实例被设计成拥有一个相关的表达式树。它是一个等同于查询表达式中的可执行代码的数据结构。
为什么要将LINQ to SQL查询表达式转换成表达式树?
现在我们知道,表达式树是一个用来表示可执行代码的数据结构。那我们为什么要将LINQ to SQL查询表达式转换成表达式树呢?
一个LINQ to SQL查询不是在C#程序里执行的,而是被转换成SQL语句,通过网络发送,最后在数据库服务器上执行的。也就是说,下面这个LINQ查询不是在C#程序里执行的。
var query = from c in db.Customers where c.City == "Nantes" select new { c.City, c.CompanyName };
它是被转换成SQL语句后在数据库服务器上运行的。转换后的SQL语句如下代码。
SELECT [t0].[City], [t0].[CompanyName] FROM [dbo].[Customers] AS [t0] WHERE [t0].[City] = @p0
现在也许可以回答上面的问题。可以用一句话总结:表达式树是为了更方面地将查询表达式转换成字符串(这里指的是SQL语句)并交给其它程序(这里一般指数据库服务器)执行。
IQueryable<T>和IEnumerable<T>
我们知道,LINQ to Objects通常返回IEnumerable<T>,而LINQ to SQL返回的是IQueryable<T>。那为什么它们返回的类型会不一样呢?
我们先来看它们的定义,也许我们可以从它们的定义中找到问题的答案。
IEnumberable<T>的定义如下:
public interface IEnumerable<T> : IEnumerable { IEnumerator<T> GetEnumerator(); }
IQueryable<T>的定义如下:
public interface IQueryable : IEnumerable { Type ElementType { get; } Expression Expression { get; } //表达式树 IQueryProvider Provider { get; } }
可以看出,IQueryable<T>包含一个Expression表达式树的定义而IEnumberable<T>却没有,这同时也揭示了一个现象,表达式树通常用在LINQ to SQL查询中,而LINQ to Objects中却很少使用。
那为什么LINQ to Objects中很少使用表达式树呢?是因为LINQ to Objects查询通常在.net程序中就可以完成,不需要将其转换成字符串(或SQL语句)发送到其它程序中执行。
那么针对这两种返回类型,我们该怎么选择呢?这里有两条原则可以参考:
- 如果查询表达式可以在本程序里执行的,那么使用Enumberable<T>就可以完成任务。
- 如果查询表达式需要被转换成字符串并发送到其它程序中执行的,那么就应该使用IQueryable<T>和表达式树。
总结
通过以上内容的学习,发现表达式树并没有想象中那么难以理解,关于表达式树我想用以一几句通俗易懂的话总结。
- 表达式树是一种用来表示可执行代码(一般指Lambda查询表达式)的树形数据结构。
- 表达式树在LINQ to SQL中使用得非常多(因为LINQ to SQL查询返回IQueryable<T>类型),通过表达式树,LINQ to SQL查询表达式更方便地被解析成SQL语句并发送到数据库服务器上执行。
- 一条最佳实践原则:LINQ查询如果在程序内执行的不需要表达式树,当代码在程序外部执行时则需要使用表达式树。