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查詢。

arrow
arrow

    Jimmy 發表在 痞客邦 留言(0) 人氣()