Lambda Expressions in LINQ
在第12章,我提到可以用lambda表达式定义内联的委托定义。在如下表达式中:
customer => customer.FirstName == "Donna" |
左边的操作数,customer,是输入参数。右边的操作数是lambda表达式,检查客户的名字属性是否等于"Donna"。因此,对于给定的客户对象,你再检查它的名字是否为Donna。
这个lambda表达式会被传入Where方法并对在客户列表中的每一个客户执行这个比较操作。
使用扩展方法定义的查询被称为基于方法的查询(method-based queries)。虽然查询和方法的语法不同,它们的语义相同,编译器会把它们转变为相同的IL代码。你可以根据自己的喜好使用其中之一。
让我们以一个简单的查询开始,如示例13-8所示。
示例13-8:一个简单的基于方法的查询
using System; namespace SimpleLamda string[] names = { "Jesse", "Donald", "Douglas" }; } |
语句names.Where是
System.Linq.Enumerable.Where(names,n=>n.StartsWith("D")); |
的一个缩写。
Where是一个扩展方法,因此你可以把对象(names)作为第一个参数传入。通过包含名空间System.Linq,你可以直接对names对象调用Where而不是通过Enumerable。
其次,dNames的类型是Ienumberable<string>;我们通过关键字var来使用编译器新的功能对其进行推断(infer)。当然这样做不会损害类型安全,因为通过推断var被编译为类型Ienumerable<string>。
因此你可以把:
var dNames = names.Where(n => n.StartsWith("D")); |
这行代码理解为"从集合names中找出以字母D开头的成员,然后填充到IEnumerable集合中"。
因为方法的语法和C#编译器如何处理查询更接近,值得花一些时间来看看一个更复杂的查询是如何描述的,从而增长对LINQ的理解。让我们把示例13-3翻译成一个基于方法的查询来看看它是怎样的(参见示例13-9)。
示例13-9:使用方法语法的复杂查询
namespace Programming_CSharp // 客户地址类 // 主程序 var result = customers.Join(addresses, foreach (var ca in result) // 使用相同数据创建客户列表 |
LINQ中的Lambda表达式(2)
示例13-9:使用方法语法的复杂查询(续例)
// 使用相同数据创建客户列表 Output: |
在示例13-3中,查询使用了查询的语法:
var result = |
它被翻译为以下方法的语法:
var result = customers.Join(addresses, |
lambda表达式需要一些时间来适应。以OrderBy子句开始;你可以把它读作"通过以下方式来排序:对于每一个客户地址,获得客户的姓氏。"你把整个语句读作:"从客户开始,和地址通过以下方式连接:连接客户的名字和姓氏,获取地址的名称,对两者进行连接,然后对于每一个结果记录创建一个客户地址对象,这个对象的客户和地址由取出来的客户和地址赋值;然后首先通过每个客户的姓氏排序,再接着根据每个地址的街道名称按降序排列。"
主要的数据源即客户集合,仍然是主要的目标对象。扩展方法Join()作用于它来执行连接操作。它的第一个参数是第二个数据源地址。接下来的两个参数是每个数据源的连接条件域。最后一个参数是连接条件的结果,实际上是查询的选择子句。
查询表达式的OrderBy子句表明你想将客户姓氏按升序排列,然后将它们的街道地址按降序排列。在方法语法中必须通过使用OrderBy和ThenBy方法指明这个顺序。
也可以只调用一系列的OrderBy方法,但是这些方法必须逆序调用。也就是说你必须在查询的OrderBy序列中首先对最后一个域调用这个方法,最后才对第一个域调用这个方法。在本例中,你须要首先调用对街道的排序,然后才能调用对名称的排序:
var result = customers.Join(addresses, |
从结果可以看出,两个例子的输出是一样的。因此你可以根据自己的喜好选择其中一个。
提示:Ian Griffiths,地球上最聪明的C#程序员之一,(他的blog在IanG On Tap上,(http://www.interact-sw.co.uk/iangblog/)阐述了以下的观点,我也将会在第15章演示这个观点, 但是我想在这里先表明:"你可以在许多不同的源上使用完全相同的这两个语法,但是行为并不总是相同的。一个lambda表达式的意义随着传给它的函数的原型不同而不同。在这些例子中,它是委托的一个简洁的语法。但是如果你对一个SQL数据源使用相同的查询格式,lambda表达式将会被转变为另外的东西。"
所有的LINQ扩展方法--连接(Join)、选择(Select)、Where,以及其他--具有多种实现,每个实现面向不同的目标类型。这里我们学习的是在IEnumerable上操作的方法。与在IQueryable上操作的方法有微妙的不同。它们接受表达式而不是接受连接、映射、Where及其他子句的委托。这些是非常神奇的技术,使得C#源代码能够转换为相应的SQL查询。