撰寫 C# LINQ 查詢以查詢資料

在介紹 Language Integrated Query (LINQ) 的文件中,大多數查詢都是使用 LINQ 宣告式查詢語法撰寫。 不過,編譯程式碼時,必須將查詢語法轉譯成 .NET Common Language Runtime (CLR) 的方法呼叫。 這些方法呼叫會叫用標準查詢運算子,而其具有 WhereSelectGroupByJoinMaxAverage 這類名稱。 您可以使用方法語法來直接呼叫它們,而不是使用查詢語法。

查詢語法和方法語法在語意上相同,但查詢語法通常更簡單且更容易閱讀。 某些查詢必須以方法呼叫形式表示。 例如,您必須使用方法呼叫,來表示可擷取符合所指定條件的項目數的查詢。 您也必須針對擷取來源序列中具有最大值的項目的查詢,使用方法呼叫。 System.Linq 命名空間中標準查詢運算子的參考文件一般會使用方法語法。 您應該熟悉如何在查詢和在查詢運算式本身中使用方法語法。

標準查詢運算子擴充方法

下列範例示範簡單「查詢運算式」以及撰寫為「方法查詢」的語意對等查詢。

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 + " ");
}

這兩個範例的輸出完全相同。 您可以看到查詢變數的類型在兩種形式中都相同:IEnumerable<T>

若要了解方法查詢,讓我們更深入進行探討。 在運算式的右側,請注意 where 子句現在會在 numbers 物件 (其類型為 IEnumerable<int>) 上被表示為執行個體方法。 如果您熟悉泛型 IEnumerable<T> 介面,就會知道它沒有 Where 方法。 不過,如果您在 Visual Studio IDE 中叫用 IntelliSense 完成清單,您不只會看到 Where 方法,還會看到許多其他方法 (例如 SelectSelectManyJoinOrderby)。 這些方法會實作標準查詢運算子。

Screenshot showing all the standard query operators in Intellisense.

雖然 IEnumerable<T> 看起來似乎包含其他方法,但事實上並沒有。 標準查詢運算子會實作為擴充方法。 擴充方法會「擴充」現有類型,其呼叫方式就像它們是類型上的執行個體方法一樣。 標準查詢運算子可擴充 IEnumerable<T>,而且這是您可以撰寫 numbers.Where(...) 的原因。

若要使用擴充方法,您可以使用 using 指示詞將它們帶入範圍。 從您應用程式的觀點來看,擴充方法和一般執行個體方法都相同。

如需擴充方法的詳細資訊,請參閱擴充方法。 如需標準查詢運算子的詳細資訊,請參閱標準查詢運算子概觀 (C#)。 有一些 LINQ 提供者 (例如 Entity Framework 和 LINQ to XML) 會為 IEnumerable<T> 以外的其他類型,實作自己的標準查詢運算子和擴充方法。

Lambda 運算式

在上述範例中,請注意,條件運算式 (num % 2 == 0) 會傳遞為 Enumerable.Where 方法的內嵌引數︰Where(num => num % 2 == 0). 這個內嵌運算式稱為 Lambda 運算式。 這是一種撰寫程式碼的便捷方法,否則必須以更繁瑣的形式編寫程式碼。 運算子左側的 num 是輸入變數,其對應到查詢運算式中的 num。 編譯器可以推斷 num 類型,因為它知道 numbers 是泛型 IEnumerable<T> 類型。 Lambda 的主體就與查詢語法或任何其他 C# 運算式或陳述式中的運算式相同。 它可以包含方法呼叫和其他複雜的邏輯。 傳回值就是運算式結果。 某些查詢只能以方法語法表示,而其中一些需要 Lambda 運算式。 Lambda 運算式是 LINQ 工具箱中功能強大且彈性的工具。

查詢的編寫性

在上述程式碼範例中,在 Where 呼叫上使用點運算子來叫用 Enumerable.OrderBy 方法。 Where 會產生篩選的序列,然後 Orderby 會排序 Where 所產生的序列。 因為查詢會傳回 IEnumerable,所以您可以將方法呼叫鏈結在一起,以在方法語法中撰寫它們。 當您使用查詢語法撰寫查詢時,編譯器會執行此撰寫。 因為查詢變數不會儲存查詢的結果,所以您隨時都可以修改它或使用它做為新查詢的基礎 (即使在執行它之後也一樣)。

下列範例會使用先前列出的每種方法,來示範一些簡單 LINQ 查詢。

注意

這些查詢是在簡單記憶體內部集合上運作,但,基本語法與用於 LINQ to Entities 和 LINQ to XML 的語法完全相同。

範例 - 查詢語法

您可以使用查詢語法來撰寫大部分的查詢,以建立查詢運算式。 下列範例示範三個查詢運算式。 第一個查詢運算式示範如何使用 where 子句套用條件來篩選或限制結果。 它會傳回值大於 7 或小於 3 的來源序列中的所有項目。 第二個運算式示範如何排序傳回的結果。 第三個運算式示範如何根據索引鍵來分組結果。 此查詢會根據單字的第一個字母來傳回兩個群組。

List<int> numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];

// The query variables can also be implicitly typed by using var

// Query #1.
IEnumerable<int> filteringQuery =
    from num in numbers
    where num is < 3 or > 7
    select num;

// Query #2.
IEnumerable<int> orderingQuery =
    from num in numbers
    where num is < 3 or > 7
    orderby num ascending
    select num;

// Query #3.
string[] groupingQuery = ["carrots", "cabbage", "broccoli", "beans", "barley"];
IEnumerable<IGrouping<char, string>> queryFoodGroups =
    from item in groupingQuery
    group item by item[0];

查詢類型是 IEnumerable<T>。 使用 var 可以撰寫所有這些查詢,如下列範例所示︰

var query = from num in numbers...

在每個上述範例中,除非您逐一查看 foreach 陳述式或其他陳述式中的查詢變數,否則不會實際執行查詢。

範例 - 方法語法

某些查詢作業必須以方法呼叫形式表示。 最常見的此類方法是傳回單一數值的方法,例如 SumMaxMinAverage 等等。 這些方法必須一律在任何查詢中最後呼叫,因為它們會傳回單一值且不能用作額外查詢作業的來源。 下列範例示範查詢運算式中的方法呼叫:

List<int> numbers1 = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];
List<int> numbers2 = [15, 14, 11, 13, 19, 18, 16, 17, 12, 10];

// Query #4.
double average = numbers1.Average();

// Query #5.
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);

如果方法具有 System.ActionSystem.Func<TResult> 參數,則這些引數會以 Lambda 運算式的形式提供,如下列範例所示:

// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);

在先前的查詢中,只會立即執行 Query #4,因為它會傳回單一值,而不是泛型 IEnumerable<T> 集合。 方法本身會使用 foreach 或類似的程式碼來計算其值。

搭配使用隱含類型與 `var``,可以撰寫每個先前的查詢,如下列範例所示:

// var is used for convenience in these queries
double average = numbers1.Average();
var concatenationQuery = numbers1.Concat(numbers2);
var largeNumbersQuery = numbers2.Where(c => c > 15);

範例 - 混合查詢和方法語法

這個範例示範如何在查詢子句結果上使用方法語法。 只需要用括號括住查詢運算式,然後套用點運算子並呼叫方法。 在下列範例中,查詢 #7 會傳回其值介於 3 與 7 之間的數字計數。 不過,一般而言,最好使用第二個變數來儲存方法呼叫的結果。 如此一來,查詢就比較不容易與查詢結果混淆。

// Query #7.

// Using a query expression with method syntax
var numCount1 = (
    from num in numbers1
    where num is > 3 and < 7
    select num
).Count();

// Better: Create a new variable to store
// the method call result
IEnumerable<int> numbersQuery =
    from num in numbers1
    where num is > 3 and < 7
    select num;

var numCount2 = numbersQuery.Count();

因為查詢 #7 會傳回單一值,而不是集合,所以會立即執行查詢。

搭配使用隱含類型與 var,可以撰寫前一個查詢,如下列所示︰

var numCount = (from num in numbers...

它可以撰寫於方法語法中,如下所示:

var numCount = numbers.Count(n => n is > 3 and < 7);

它可以使用明確類型進行撰寫,如下所示:

int numCount = numbers.Count(n => n is > 3 and < 7);

另請參閱