LINQ中的Lambda表達式(Lambda Expressions in LINQ)
用lambda表達式定義內聯的委託定義。在如下表達式中:
customer => customer.FirstName == "Donna"
左邊的操作數,customer,是輸入參數。右邊的操作數是lambda表達式,檢查客戶的名字屬性是否等於"Donna"。因此,對於給定的客戶對象,你再檢查它的名字是否為Donna。
這個lambda表達式會被傳入Where方法並對在客戶列表中的每一個客戶執行這個比較操作。
使用擴展方法定義的查詢被稱為基於方法的查詢(method-based queries)。雖然查詢和方法的語法不同,它們的語義相同,編譯器會把它們轉變為相同的IL代碼。你可以根據自己的喜好使用其中之一。
讓我們以一個簡單的查詢開始,如示例13-8所示。
示例13-8:一個簡單的基於方法的查詢
using System; using System.Linq; namespace SimpleLamda { class Program { static void Main(string[] args) { string[] names = { "Jesse", "Donald", "Douglas" }; var dNames = names.Where(n => n.StartsWith("D")); foreach (string foundName in dNames) { Console.WriteLine ("Found: " + foundName); } } } }
Output:
Found: Donald
Found: Douglas
語句names.Where是System.Linq.Enumerable.Where(names,n=>n.StartsWith("D"));的一個縮寫。
Where是一個擴展方法,因此你可以把對象(names)作為第一個參數傳入。通過包含名空間System.Linq,你可以直接對names對象調用Where而不是通過Enumerable。
其次,dNames的類型是Ienumberable;我們通過關鍵字var來使用編譯器新的功能對其進行推斷(infer)。當然這樣做不會損害類型安全,因為通過推斷var被編譯為類型Ienumerable。
因此你可以把:
var dNames = names.Where(n => n.StartsWith("D"));
這行代碼理解為"從集合names中找出以字母D開頭的成員,然後填充到IEnumerable集合中"。
因為方法的語法和C#編譯器如何處理查詢更接近,值得花一些時間來看看一個更複雜的查詢是如何描述的,從而增長對LINQ的理解。讓我們把示例13-3翻譯成一個基於方法的查詢來看看它是怎樣的(參見示例13-9)。
示例13-9:使用方法語法的複雜查詢
namespace Programming_CSharp { //簡單客戶類 public class Customer { //和示例13-1相同 } //客戶地址類 public class Address { //和示例13-3相同 } //主程序 public class Tester { static void Main() { List customers = CreateCustomerList(); List addresses = CreateAddressList(); var result = customers.Join ( addresses, customer => string.Format("{0} {1}", customer.FirstName, customer.LastName ),address => address.Name,(customer, address) => new { Customer = customer, Address = address }) .OrderBy(ca => ca.Customer.LastName) .ThenByDescending(ca => ca.Address.Street); foreach (var ca in result) { Console.WriteLine(string.Format("{0}\nAddress: {1}", ca.Customer, ca.Address)); } } //使用相同數據創建客戶列表 private static List CreateCustomerList() { //和示例13-3相同 } }
示例13-9:使用方法語法的複雜查詢(續例)
//使用相同數據創建客戶列表 private static List CreateAddressList() { //和示例13-3相同 } } } Output: Janet Gates Email: janet1@adventure-works.com Address: 800 Interchange Blvd., Austin Janet Gates Email: janet1@adventure-works.com Address: 165 North Main, Austin Orlando Gee Email: orlando0@adventure-works.com Address: 2251 Elliot Avenue, Seattle Keith Harris Email: keith0@adventure-works.com Address: 7943 Walnut Ave, Renton Keith Harris Email: keith0@adventure-works.com Address: 3207 S Grady Way, Renton
在示例13-3中,查詢使用了查詢的語法:
var result = from customer in customers join address in addresses on string.Format("{0} {1}", customer.FirstName, customer.LastName) equals address.Name orderby customer.LastName, address.Street descending select new { Customer = customer, Address = address.Street };
它被翻譯為以下方法的語法:
var result = customers.Join(addresses, customer => string.Format("{0} {1}", customer.FirstName, customer.LastName), address => address.Name, (customer, address) => new { Customer = customer, Address = address }) .OrderBy(ca => ca.Customer.LastName) .ThenByDescending(ca => ca.Address.Street);
lambda表達式需要一些時間來適應。以OrderBy子句開始;你可以把它讀作"通過以下方式來排序:對於每一個客戶地址,獲得客戶的姓氏。"你把整個語句讀作:"從客戶開始,和地址通過以下方式連接:連接客戶的名字和姓氏,獲取地址的名稱,對兩者進行連接,然後對於每一個結果記錄創建一個客戶地址對象,這個對象的客戶和地址由取出來的客戶和地址賦值;然後首先通過每個客戶的姓氏排序,再接著根據每個地址的街道名稱按降序排列。"
主要的數據源即客戶集合,仍然是主要的目標對象。擴展方法Join()作用於它來執行連接操作。它的第一個參數是第二個數據源地址。接下來的兩個參數是每個數據源的連接條件域。最後一個參數是連接條件的結果,實際上是查詢的選擇子句。
查詢表達式的OrderBy子句表明你想將客戶姓氏按升序排列,然後將它們的街道地址按降序排列。在方法語法中必須通過使用OrderBy和ThenBy方法指明這個順序。
也可以只調用一系列的OrderBy方法,但是這些方法必須逆序調用。也就是說你必須在查詢的OrderBy序列中首先對最後一個域調用這個方法,最後才對第一個域調用這個方法。在本例中,你須要首先調用對街道的排序,然後才能調用對名稱的排序:
var result = customers.Join(addresses, customer => string.Format("{0} {1}", customer.FirstName, customer.LastName), address => address.Name, (customer, address) => new { Customer = customer, Address = address }) .OrderByDescending(ca => ca.Address.Street) .OrderBy(ca => ca.Customer.LastName);
從結果可以看出,兩個例子的輸出是一樣的。因此你可以根據自己的喜好選擇其中一個。
提示: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查詢。