LINQ 查詢語法與方法語法的比較 (C#)
在 LINQ 簡介文件中,大部分的查詢都是使用 C# 3.0 引進的宣告式查詢語法,以查詢運算式的形式撰寫。 不過,.NET Common Language Runtime (CLR) 本身並沒有查詢語法的概念。 因此,在編譯期間,查詢運算式會轉譯為 CLR 可以了解的項目:即方法呼叫。 這些方法稱為「標準查詢運算子」(Standard Query Operator),而且名稱為 Where、Select、GroupBy、Join、Max、Average 等。您可以使用方法語法 (而不是查詢語法) 直接呼叫它們。
一般而言,因為查詢語法通常比較簡單易懂,所以建議您使用查詢語法,不過,方法語法與查詢語法之間並沒有語意上的差異。 此外,部分查詢 (例如擷取與所指定條件相符的項目數,或擷取來源序列中值最大的項目) 只可以用方法呼叫形式表示。 System.Linq 命名空間 (Namespace) 中標準查詢運算子的相關參考文件一般是使用方法語法。 因此,即使是撰寫 LINQ 查詢的新手,熟悉如何在查詢和查詢運算式本身中使用方法語法還是十分有用的。
標準查詢運算子擴充方法
下列範例顯示簡單的「查詢運算式」(Query Expression),以及在語意上相等,以「方法架構查詢」(Method-Based Query) 撰寫的對等查詢。
class QueryVMethodSyntax
{
static void Main()
{
int[] numbers = { 5, 10, 8, 3, 6, 12};
//Query syntax:
IEnumerable<int> numQuery1 =
from num in numbers
where num % 2 == 0
orderby num
select num;
//Method syntax:
IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);
foreach (int i in numQuery1)
{
Console.Write(i + " ");
}
Console.WriteLine(System.Environment.NewLine);
foreach (int i in numQuery2)
{
Console.Write(i + " ");
}
// Keep the console open in debug mode.
Console.WriteLine(System.Environment.NewLine);
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
/*
Output:
6 8 10 12
6 8 10 12
*/
這兩個範例的輸出是相同的。 您可以看到在這兩種形式中查詢變數的型別都是相同的:IEnumerable<T>。
為了了解方法架構查詢,讓我們更仔細地看看它。 在運算式右邊,您會發現 where 子句現在是以 numbers 物件上的執行個體方法 (instance method) 表示,這個物件的型別就是剛剛的 IEnumerable<int>。 如果您熟悉泛型 IEnumerable<T> 介面,就會知道這個介面並沒有 Where 方法。 不過,如果在 Visual Studio IDE 中叫用 (Invoke) IntelliSense 完成清單,就會不只看到 Where 方法,還會看到其他許多方法,例如 Select、SelectMany、Join 和 Orderby)。 這些都是標準查詢運算子。
雖然看起來像是已將 IEnumerable<T> 重新定義為包含這些額外方法,但是實際上並非如此。 標準查詢運算子是以稱為「擴充方法」(Extension Method) 的一種新方法來實作。 擴充方法會「擴充」現有的型別,而您可以把它們當做型別上的執行個體方法一樣呼叫。 標準查詢運算子會擴充 IEnumerable<T>,這就是為何您可以寫 numbers.Where(...) 的原因。
若要開始使用 LINQ,在擴充方法方面您只需要知道如何使用正確的 using 指示詞,將擴充方法帶入應用程式的範圍內。 這在 HOW TO:建立 LINQ 專案中會有額外說明。 對應用程式而言,擴充方法和一般執行個體方法是相同的。
如需擴充方法的詳細資訊,請參閱擴充方法 (C# 程式設計手冊)。 如需標準查詢運算子的詳細資訊,請參閱標準查詢運算子概觀。 部分 LINQ 提供者 (Provider) (例如 LINQ to SQL 和 LINQ to XML) 除了針對 IEnumerable<T> 之外,還會針對其他型別實作自己的標準查詢運算子和其他擴充方法。
Lambda 運算式
在上一個範例中,您會發現條件運算式 (num % 2 == 0) 是以 Where 方法的內嵌 (Inline) 引數傳遞:Where(num => num % 2 == 0). 。這個內嵌運算式稱為 Lambda 運算式。 用這個方式撰寫程式碼很方便,原因是不需要撰寫冗長的匿名方法 (Anonymous Method)、泛型委派或運算式樹狀架構。 在 C# 中,=> 是 Lambda 運算子,意思為「移至」。 運算子左邊的 num 是輸入變數,對應至查詢運算式中的 num。 編譯器 (Compiler) 因為知道 numbers 是泛型 IEnumerable<T> 型別,所以可以推斷 num 的型別。 Lambda 主體與在查詢語法或其他任何 C# 運算式或陳述式中表示的運算式相同,可以包含方法呼叫和其他複雜邏輯。 而「傳回值」就是運算式結果。
開始使用 LINQ 時,並不需要廣泛使用 Lambda。 不過,某些查詢只能以方法語法表示,而其中有些又需要使用 Lambda 運算式。 在更熟悉 Lambda 之後,您會發現它們是您 LINQ 工具箱中強大而有彈性的工具。 如需詳細資訊,請參閱 Lambda 運算式 (C# 程式設計手冊)。
查詢撰寫性
在上一個程式碼範例中,請注意 OrderBy 方法是在 Where 的呼叫中使用點運算子來呼叫。 Where 會產生篩選後的序列,然後 Orderby 就會將該序列進行排序。 因為查詢會傳回 IEnumerable,所以撰寫查詢時,必須以方法語法將方法呼叫鏈結在一起。 這就是當您使用查詢語法撰寫查詢時,編譯器在幕後執行的作業。 而且,因為查詢變數不會儲存查詢的結果,所以您隨時可以修改查詢變數或將它當做新查詢的基礎,即使已經執行查詢變數也一樣。