LINQ:.NET Language-Integrated查詢
Don Box、 Anders Hejlsberg
2007 年 2 月
適用於:
Visual Studio Code名稱 「Orcas」
.Net Framework 3.5
總結:新增至.NET Framework的一般用途查詢設施會套用至所有資訊來源,而不只是關聯式或 XML 資料。 此設施稱為 .NET Language-Integrated 查詢 (LINQ) 。 (32 個列印頁面)
目錄
.NET Language-Integrated查詢
使用標準查詢運算子消費者入門
支援 LINQ 專案的語言功能
更多標準查詢運算子
查詢語法
LINQ to SQL:SQL 整合
LINQ to XML:XML 整合
總結
.NET Language-Integrated查詢
在 200 年之後,產業在物件導向 (OO) 程式設計技術的演進中達到穩定點。 程式設計人員現在會接受授與的功能,例如類別、物件和方法。 在查看目前和新一代的技術時,在程式設計技術中發現下一個重大挑戰是減少存取和整合未使用 OO 技術原生定義的資訊複雜度。 非 OO 資訊的兩個最常見來源是關係資料庫和 XML。
使用 LINQ 專案,而不是將關聯式或 XML 特定功能新增至程式設計語言和執行時間,而是將一般用途的查詢設備新增至套用至所有資訊來源的.NET Framework,而不只是關聯式或 XML 資料。 此設施稱為 .NET Language-Integrated 查詢 (LINQ) 。
我們使用 語言整合查詢 一詞,指出查詢是開發人員主要程式設計語言的整合功能 (,例如 Visual C#、Visual Basic) 。 語言整合式查詢可讓 查詢運算式 受益于豐富的中繼資料、編譯時間語法檢查、靜態類型,以及先前僅供命令式程式碼使用的 IntelliSense。 語言整合式查詢也允許單一通用宣告式查詢設施套用到所有記憶體內部資訊,而不只是來自外部來源的資訊。
.NET Language-Integrated查詢會定義一組一般用途 的標準查詢運算子 ,允許在任何 中以直接但宣告方式表示周遊、篩選和投影作業。以 NET 為基礎的程式設計語言。 標準查詢運算子允許查詢套用至任何IEnumerable < T >資訊來源。 LINQ 可讓協力廠商使用適用于目標網域或技術的新領域特定運算子來增強一組標準查詢運算子。 更重要的是,協力廠商也可以自由地將標準查詢運算子取代為自己的實作,以提供其他服務,例如遠端評估、查詢轉譯、優化等等。 藉由遵守 LINQ 模式的慣例,這類實作會享有與標準查詢運算子相同的語言整合和工具支援。
查詢架構的擴充性用於 LINQ 專案本身,以提供可處理 XML 和 SQL 資料的實作。 透過 XML 的查詢運算子 (LINQ to XML) 使用有效率、容易使用的記憶體內部 XML 設施,以主機程式設計語言提供 XPath/XQuery 功能。 關聯式資料的查詢運算子 (LINQ to SQL) 建置在將 SQL 架構定義整合到 Common Language Runtime (CLR) 類型系統中。 這項整合提供對關聯式資料的強型別,同時保留關聯式模型的表達能力,以及直接在基礎存放區中查詢評估的效能。
使用標準查詢運算子消費者入門
若要查看工作的語言整合式查詢,我們將從使用標準查詢運算子來處理陣列內容的簡單 C# 3.0 程式開始:
using System;
using System.Linq;
using System.Collections.Generic;
class app {
static void Main() {
string[] names = { "Burke", "Connor", "Frank",
"Everett", "Albert", "George",
"Harris", "David" };
IEnumerable<string> query = from s in names
where s.Length == 5
orderby s
select s.ToUpper();
foreach (string item in query)
Console.WriteLine(item);
}
}
如果您要編譯並執行此程式,您會看到此為輸出:
BURKE
DAVID
FRANK
To understand how language-integrated query works, we need to dissect the
first statement of our program.
IEnumerable<string> query = from s in names
where s.Length == 5
orderby s
select s.ToUpper();
區域變數 查詢 會使用 查詢運算式初始化。 查詢運算式會藉由從標準查詢運算子或網域特定運算子套用一或多個查詢運算子,在一或多個資訊來源上運作。 此運算式使用三個標準查詢運算子: Where、 OrderBy和 Select。
Visual Basic 9.0 也支援 LINQ。 以下是以 Visual Basic 9.0 撰寫的上述語句:
Dim query As IEnumerable(Of String) = From s in names _
Where s.Length = 5 _
Order By s _
Select s.ToUpper()
這裡顯示的 C# 和 Visual Basic 語句都會使用查詢運算式。 就像 foreach 語句一樣,查詢運算式是方便您手動撰寫的程式碼宣告式速記。 上述語句語意上與 C# 所示的下列明確語法相同:
IEnumerable<string> query = names
.Where(s => s.Length == 5)
.OrderBy(s => s)
.Select(s => s.ToUpper());
這種形式的查詢稱為 以方法為基礎的 查詢。 Where、OrderBy和Select運算子的引數稱為Lambda 運算式,也就是類似委派的程式碼片段。 它們可讓標準查詢運算子個別定義為方法,並使用點標記法一起串起來。 這些方法會形成可延伸查詢語言的基礎。
支援 LINQ 專案的語言功能
LINQ 完全建置在一般用途語言功能上,其中部分是 C# 3.0 和 Visual Basic 9.0 的新功能。 這些功能各自具有公用程式,但這些功能共同提供可延伸的方式來定義查詢和可查詢的 API。 在本節中,我們會探索這些語言功能,以及它們如何參與更直接且宣告式的查詢樣式。
Lambda 運算式和運算式樹狀架構
許多查詢運算子可讓使用者提供執行篩選、投影或索引鍵擷取的函式。 查詢設施是以 Lambda 運算式的概念為基礎,為開發人員提供方便的方式來撰寫函式,以作為後續評估的引數傳遞。 Lambda 運算式類似于 CLR 委派,且必須遵守委派類型所定義的方法簽章。 為了說明這一點,我們可以使用 Func 委派類型,將上述語句擴充為對等但更明確的表單:
Func<string, bool> filter = s => s.Length == 5;
Func<string, string> extract = s => s;
Func<string, string> project = s => s.ToUpper();
IEnumerable<string> query = names.Where(filter)
.OrderBy(extract)
.Select(project);
Lambda 運算式是 C# 2.0 中匿名方法的自然演進。 例如,我們可以使用如下的匿名方法撰寫上述範例:
Func<string, bool> filter = delegate (string s) {
return s.Length == 5;
};
Func<string, string> extract = delegate (string s) {
return s;
};
Func<string, string> project = delegate (string s) {
return s.ToUpper();
};
IEnumerable<string> query = names.Where(filter)
.OrderBy(extract)
.Select(project);
一般而言,開發人員可以使用具名方法、匿名方法或 Lambda 運算式搭配查詢運算子。 Lambda 運算式的優點是提供最直接且精簡的撰寫語法。 更重要的是,Lambda 運算式可以編譯為程式碼或資料,這可讓優化工具、翻譯工具和評估工具在執行時間處理 Lambda 運算式。
命名空間 System.Linq.Expressions會定義辨別的泛型型別Expression < T >,這表示指定 Lambda 運算式需要運算式樹狀結構,而不是傳統的 IL 型方法主體。 運算式樹狀結構是 Lambda 運算式的有效記憶體內資料標記法,並讓運算式的結構透明且明確。
判斷編譯器是否會發出可執行檔 IL 或運算式樹狀結構,取決於 Lambda 運算式的使用方式。 當 Lambda 運算式指派給類型為委派的變數、欄位或參數時,編譯器會發出與匿名方法相同的 IL。 當 Lambda 運算式指派給類型為某些委派類型T > 的變數 <、欄位或參數時,編譯器會改為發出運算式樹狀結構。
例如,請考慮下列兩個變數宣告:
Func<int, bool> f = n => n < 5;
Expression<Func<int, bool>> e = n => n < 5;
變數 f 是直接可執行之委派的參考:
bool isSmall = f(2); // isSmall is now true
變數 e 是運算式樹狀結構的參考,不是直接可執行檔:
bool isSmall = e(2); // compile error, expressions == data
不同于實際上不透明程式碼的委派,我們可以與運算式樹狀結構互動,就像程式中的其他任何資料結構一樣。
Expression<Func<int, bool>> filter = n => n < 5;
BinaryExpression body = (BinaryExpression)filter.Body;
ParameterExpression left = (ParameterExpression)body.Left;
ConstantExpression right = (ConstantExpression)body.Right;
Console.WriteLine("{0} {1} {2}",
left.Name, body.NodeType, right.Value);
上述範例會在執行時間分解運算式樹狀結構,並列印下列字串:
n LessThan 5
在執行時間將運算式視為資料的能力,對於啟用協力廠商程式庫的生態系統非常重要,這些程式庫會利用平臺的基底查詢抽象概念。 LINQ to SQL資料存取實作會利用這項功能,將運算式樹狀架構轉譯為適用于存放區中評估的 T-SQL 語句。
擴充方法
Lambda 運算式是查詢架構的其中一個重要部分。 擴充方法 是另一個。 擴充方法結合了動態語言中熱門的「無子類型」彈性,以及靜態類型語言的效能和編譯時間驗證。 使用擴充方法時,協力廠商可能會使用新方法增強型別的公用合約,同時仍允許個別類型作者提供這些方法自己的特製化實作。
擴充方法會在靜態類別中定義為靜態方法,但會以 CLR 中繼資料中的 [System.Runtime.CompilerServices.Extension] 屬性標示。 建議語言提供擴充方法的直接語法。 在 C# 中,擴充方法是由 這個 修飾詞表示,此修飾詞必須套用至擴充方法的第一個參數。 讓我們看看最簡單的查詢運算子定義, 其中:
namespace System.Linq {
using System;
using System.Collections.Generic;
public static class Enumerable {
public static IEnumerable<T> Where<T>(
this IEnumerable<T> source,
Func<T, bool> predicate) {
foreach (T item in source)
if (predicate(item))
yield return item;
}
}
}
擴充方法的第一個參數類型會指出延伸模組所套用的類型。 在上述範例中,Where擴充方法會擴充IEnumerable < T >類型。 由於 Where是靜態方法,因此我們可以直接叫用它,就像任何其他靜態方法一樣:
IEnumerable<string> query = Enumerable.Where(names,
s => s.Length < 6);
不過,讓擴充方法成為唯一的方法,是也可以使用實例語法叫用它們:
IEnumerable<string> query = names.Where(s => s.Length < 6);
擴充方法會在編譯時期根據範圍中的擴充方法進行解析。 使用 C# 中的 using 語句或 Visual Basic 中的 Import 語句匯入命名空間時,來自該命名空間的靜態類別所定義的所有擴充方法都會進入範圍。
標準查詢運算子會定義為 System.Linq.Enumerable類型的擴充方法。 檢查標準查詢運算子時,您會發現其中一些運算子全都以IEnumerable < T >介面來定義。 這表示每個IEnumerable < T >相容資訊來源只要在 C# 中新增下列 using 語句即可取得標準查詢運算子:
using System.Linq; // makes query operators visible
想要取代特定類型之標準查詢運算子的使用者,可以在具有相容簽章的特定類型上定義自己的相同名稱方法,或定義擴充特定類型的新同名擴充方法。 想要完全略過標準查詢運算子的使用者,可以直接將System.Linq放入範圍,並撰寫自己的IEnumerable < T >擴充方法。
擴充方法會根據解析提供最低優先順序,而且只有在目標型別和其基底類型上沒有適當的相符專案時,才會使用。 這可讓使用者定義型別提供自己的查詢運算子,其優先順序高於標準運算子。 例如,請考慮下列自訂集合:
public class MySequence : IEnumerable<int> {
public IEnumerator<int> GetEnumerator() {
for (int i = 1; i <= 10; i++)
yield return i;
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
public IEnumerable<int> Where(Func<int, bool> filter) {
for (int i = 1; i <= 10; i++)
if (filter(i))
yield return i;
}
}
假設此類別定義,下列程式會使用 MySequence.Where 實作 ,而不是擴充方法,因為實例方法的優先順序高於擴充方法:
MySequence s = new MySequence();
foreach (int item in s.Where(n => n > 3))
Console.WriteLine(item);
OfType運算子是少數標準查詢運算子之一,不會擴充IEnumerable < T >型資訊來源。 讓我們看看 OfType 查詢運算子:
public static IEnumerable<T> OfType<T>(this IEnumerable source) {
foreach (object item in source)
if (item is T)
yield return (T)item;
}
OfType不僅接受以 IEnumerable < T >為基礎的來源,還接受針對存在於 .NET Framework 1.0 版的非參數化IEnumerable介面所撰寫的來源。 OfType運算子可讓使用者將標準查詢運算子套用至傳統 .NET 集合,如下所示:
// "classic" cannot be used directly with query operators
IEnumerable classic = new OlderCollectionType();
// "modern" can be used directly with query operators
IEnumerable<object> modern = classic.OfType<object>();
在此範例中,變數 modern
會產生與 傳統相同的值序列。 不過,其類型與新式IEnumerable < T >程式碼相容,包括標準查詢運算子。
OfType運算子也適用于較新的資訊來源,因為它允許根據類型篩選來源的值。 產生新序列時, OfType 只會省略與類型引數不相容之原始序列的成員。 請考慮從異質陣列擷取字串的簡單程式:
object[] vals = { 1, "Hello", true, "World", 9.1 };
IEnumerable<string> justStrings = vals.OfType<string>();
當我們在foreach語句中列舉justStrings變數時,我們將取得兩個字串的序列:「Hello」 和 「World」。
延後查詢評估
觀察讀取器可能已注意到標準 Where 運算子是使用 C# 2.0 中引進的 yield 建構來實作。 這個實作技術對於傳回值序列的所有標準運算子而言很常見。 使用 yield有一個有趣的優點,就是查詢在逐一查看之前,不會實際評估,不論是使用foreach語句,還是手動使用基礎GetEnumerator和MoveNext方法。 此延後評估可讓查詢保留為可多次評估的IEnumerable < T >型值,每次產生可能不同的結果。
對於許多應用程式而言,這是所需的行為。 對於想要快取查詢評估結果的應用程式,會提供兩個運算子ToList和ToArray,以強制立即評估查詢,並傳回List < T >或包含查詢評估結果的陣列。
若要查看延後查詢評估的運作方式,請考慮透過陣列執行簡單查詢的程式:
// declare a variable containing some strings
string[] names = { "Allen", "Arthur", "Bennett" };
// declare a variable that represents a query
IEnumerable<string> ayes = names.Where(s => s[0] == 'A');
// evaluate the query
foreach (string item in ayes)
Console.WriteLine(item);
// modify the original information source
names[0] = "Bob";
// evaluate the query again, this time no "Allen"
foreach (string item in ayes)
Console.WriteLine(item);
每次反覆運算變數 時 ,都會評估查詢。 若要指出需要快取的結果複本,我們只要將 ToList 或 ToArray 運算子附加至查詢,如下所示:
// declare a variable containing some strings
string[] names = { "Allen", "Arthur", "Bennett" };
// declare a variable that represents the result
// of an immediate query evaluation
string[] ayes = names.Where(s => s[0] == 'A').ToArray();
// iterate over the cached query results
foreach (string item in ayes)
Console.WriteLine(item);
// modifying the original source has no effect on ayes
names[0] = "Bob";
// iterate over result again, which still contains "Allen"
foreach (string item in ayes)
Console.WriteLine(item);
ToArray和ToList都會強制立即查詢評估。 對於傳回單一值 (的標準查詢運算子也是如此,例如: First、 ElementAt、 Sum、 Average、 All、 Any) 。
IQueryable < T > 介面
使用運算式樹狀架構實作查詢功能的資料來源通常需要相同的延後執行模型,例如LINQ to SQL。 這些資料來源可以受益于實作IQueryable < T >介面,而 LINQ 模式所需的所有查詢運算子都是使用運算式樹狀架構來實作。 每個IQueryable < T >都有運算式樹狀結構形式的「執行查詢所需的程式碼」標記法。 所有延後查詢運算子都會傳回新的IQueryable < T >,以呼叫該查詢運算子的表示來增強該運算式樹狀結構。 因此,當它變成評估查詢的時間時,通常是因為已列舉IQueryable < T >,資料來源就可以處理運算式樹狀結構,代表一個批次中的整個查詢。 例如,許多查詢運算子呼叫所取得的複雜LINQ to SQL查詢,可能會導致只有單一 SQL 查詢傳送至資料庫。
藉由實作IQueryable < T >
介面,資料來源實作者重複使用這項延遲功能的優點很明顯。 另一方面,對於撰寫查詢的用戶端而言,擁有遠端資訊來源的通用類型是很好的優點。 它不僅允許他們撰寫可用於不同資料來源的多型查詢,也會開啟撰寫跨網域的查詢的可能性。
初始化複合值
Lambda 運算式和擴充方法提供查詢所需的一切,只要篩選出一連串值的成員即可。 大部分的查詢運算式也會對這些成員執行投影,有效地將原始序列的成員轉換成其值和類型可能與原始成員不同的成員。 為了支援撰寫這些轉換,LINQ 依賴稱為 物件初始化運算式 的新建構來建立結構化型別的新實例。 在本檔的其餘部分,我們將假設已定義下列類型:
public class Person {
string name;
int age;
bool canCode;
public string Name {
get { return name; } set { name = value; }
}
public int Age {
get { return age; } set { age = value; }
}
public bool CanCode {
get { return canCode; } set { canCode = value; }
}
}
物件初始化運算式可讓我們根據類型的公用欄位和屬性輕鬆地建構值。 例如,若要建立 Person類型的新值,我們可以撰寫此語句:
Person value = new Person {
Name = "Chris Smith", Age = 31, CanCode = false
};
語意上,此語句相當於下列語句序列:
Person value = new Person();
value.Name = "Chris Smith";
value.Age = 31;
value.CanCode = false;
物件初始化運算式是語言整合式查詢的重要功能,因為它們允許在內容中建構新的結構化值,其中只允許運算式 (,例如 lambda 運算式和運算式樹狀結構) 。 例如,請考慮此查詢運算式,針對輸入序列中的每個值建立新的 Person 值:
IEnumerable<Person> query = names.Select(s => new Person {
Name = s, Age = 21, CanCode = s.Length == 5
});
物件初始化語法也方便初始化結構化值的陣列。 例如,請考慮使用個別物件初始化運算式初始化的這個陣列變數:
static Person[] people = {
new Person { Name="Allen Frances", Age=11, CanCode=false },
new Person { Name="Burke Madison", Age=50, CanCode=true },
new Person { Name="Connor Morgan", Age=59, CanCode=false },
new Person { Name="David Charles", Age=33, CanCode=true },
new Person { Name="Everett Frank", Age=16, CanCode=true },
};
結構化值和類型
LINQ 專案支援以資料為中心的程式設計樣式,其中某些類型主要存在於結構化值上提供靜態「形狀」,而不是同時具有狀態和行為的完整自訂物件。 以此內部部署為邏輯結論,通常所有開發人員關心的是值的結構,而且該圖形的具名類型需求很少使用。 這會導致引進 匿名型 別,以允許使用其初始化來定義「內嵌」新結構。
在 C# 中,匿名型別的語法類似于物件初始化語法,但省略類型的名稱。 例如,請考慮下列兩個語句:
object v1 = new Person {
Name = "Brian Smith", Age = 31, CanCode = false
};
object v2 = new { // note the omission of type name
Name = "Brian Smith", Age = 31, CanCode = false
};
變數 v1 和 v2 都指向 CLR 類型有三個公用屬性 Name、 Age和 CanCode的記憶體內建物件。 v2 的變數與匿名型別的實例不同。 在 CLR 詞彙中,匿名型別與任何其他類型都不同。 讓匿名型別特別的是,它們在您的程式設計語言中沒有有意義的名稱。 建立匿名型別實例的唯一方式是使用上述語法。
為了允許變數參考匿名型別的實例,但仍受益于靜態類型,C# 引進了隱含型別區域變數:var關鍵字可用來取代區域變數宣告的類型名稱。 例如,請考慮此合法的 C# 3.0 程式:
var s = "Bob";
var n = 32;
var b = true;
var關鍵字會告知編譯器從用來初始化變數的運算式靜態類型推斷變數的類型。 在此範例中, s、 n和 b 的類型分別是 string、 int和 bool。 此程式與下列程式相同:
string s = "Bob";
int n = 32;
bool b = true;
var關鍵字是變數的便利性,其類型具有有意義的名稱,但對於參考匿名型別實例的變數而言是必要的。
var value = new {
Name = " Brian Smith", Age = 31, CanCode = false
};
在上述範例中,變數 值 是匿名型別,其定義相當於下列虛擬 C#:
internal class ??? {
string _Name;
int _Age;
bool _CanCode;
public string Name {
get { return _Name; } set { _Name = value; }
}
public int Age{
get { return _Age; } set { _Age = value; }
}
public bool CanCode {
get { return _CanCode; } set { _CanCode = value; }
}
public bool Equals(object obj) { ... }
public bool GetHashCode() { ... }
}
無法跨元件界限共用匿名型別;不過,編譯器可確保每個元件內的指定屬性名稱/類型組最多有一個匿名型別。
由於匿名型別通常用於投影中,以選取現有結構化值的一或多個成員,因此我們可以直接參考匿名型別初始化中另一個值的欄位或屬性。 這會導致新的匿名型別取得名稱、類型和值都從參考的屬性或欄位複製。
例如,請考慮此範例,藉由結合其他值的屬性來建立新的結構化值:
var bob = new Person { Name = "Bob", Age = 51, CanCode = true };
var jane = new { Age = 29, FirstName = "Jane" };
var couple = new {
Husband = new { bob.Name, bob.Age },
Wife = new { Name = jane.FirstName, jane.Age }
};
int ha = couple.Husband.Age; // ha == 51
string wn = couple.Wife.Name; // wn == "Jane"
上述欄位或屬性的參考只是撰寫下列更明確格式的便利語法:
var couple = new {
Husband = new { Name = bob.Name, Age = bob.Age },
Wife = new { Name = jane.FirstName, Age = jane.Age }
};
在這兩種情況下,兩個變數都會從bob和jane取得自己的Name和Age屬性複本。
匿名型別最常用於查詢的 select 子句中。 例如,思考一下下列查詢:
var query = people.Select(p => new {
p.Name, BadCoder = p.Age == 11
});
foreach (var item in query)
Console.WriteLine("{0} is a {1} coder",
item.Name,
item.BadCoder ? "bad" : "good");
在此範例中,我們可以針對 Person 類型建立新的投影,以完全符合處理常式代碼所需的圖形,但仍提供靜態類型的優點。
更多標準查詢運算子
在上述的基本查詢功能之上,有數個運算子提供實用的操作順序和撰寫查詢的方式,讓使用者在標準查詢運算子的便利架構內控制結果。
排序和群組
一般而言,查詢評估會產生一連串的值,這些值會以基礎資訊來源內建的順序產生。 為了讓開發人員明確控制產生這些值的順序,系統會定義標準查詢運算子來控制順序。 這些運算子最基本的是 OrderBy 運算子。
OrderBy 和OrderByDescending運算子可以套用至任何資訊來源,並允許使用者提供金鑰擷取函式,以產生用來排序結果的值。 OrderBy 和 OrderByDescending 也接受選擇性比較函式,可用來對索引鍵強制執行部分順序。 讓我們看看基本範例:
string[] names = { "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };
// unity sort
var s1 = names.OrderBy(s => s);
var s2 = names.OrderByDescending(s => s);
// sort by length
var s3 = names.OrderBy(s => s.Length);
var s4 = names.OrderByDescending(s => s.Length);
前兩個查詢運算式會產生以根據字串比較排序來源成員為基礎的新序列。 第二個查詢會產生以根據每個字串長度排序來源成員為基礎的新序列。
若要允許多個排序準則,OrderBy和OrderByDescending都會傳回OrderedSequence < T >,而不是泛型IEnumerable < T >。 兩個運算子只會在OrderedSequence < T >上定義,也就是ThenBy和ThenByDescending,它會套用額外的 (次級) 排序準則。 ThenBy/ThenByDescending本身會傳回OrderedSequence < T >,允許套用任意數目的ThenBy/ThenByDescending運算子:
string[] names = { "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };
var s1 = names.OrderBy(s => s.Length).ThenBy(s => s);
評估此範例中 s1 所參考的查詢會產生下列值序列:
"Burke", "David", "Frank",
"Albert", "Connor", "George", "Harris",
"Everett"
除了 OrderBy 系列運算子之外,標準查詢運算子也包含 Reverse 運算子。 反向 只會列舉序列,並以反向順序產生相同的值。 不同于 OrderBy, Reverse 不會在判斷順序時考慮實際值本身,而是只依賴基礎來源所產生的值順序。
OrderBy運算子會對值序列施加排序次序。 標準查詢運算子也包含 GroupBy 運算子,它會根據索引鍵擷取函式對值序列強制執行分割。 GroupBy運算子會傳回一連串的 IGrouping值,每個遇到的相異索引鍵值各有一個。 IGrouping是IEnumerable,另外包含用來擷取其內容的金鑰:
public interface IGrouping<K, T> : IEnumerable<T> {
public K Key { get; }
}
GroupBy最簡單的應用程式看起來像這樣:
string[] names = { "Albert", "Burke", "Connor", "David",
"Everett", "Frank", "George", "Harris"};
// group by length
var groups = names.GroupBy(s => s.Length);
foreach (IGrouping<int, string> group in groups) {
Console.WriteLine("Strings of length {0}", group.Key);
foreach (string value in group)
Console.WriteLine(" {0}", value);
}
執行時,此程式會列印出下列內容:
Strings of length 6
Albert
Connor
George
Harris
Strings of length 5
Burke
David
Frank
Strings of length 7
Everett
la Select, GroupBy 可讓您提供用來填入群組成員的投影函式。
string[] names = { "Albert", "Burke", "Connor", "David",
"Everett", "Frank", "George", "Harris"};
// group by length
var groups = names.GroupBy(s => s.Length, s => s[0]);
foreach (IGrouping<int, char> group in groups) {
Console.WriteLine("Strings of length {0}", group.Key);
foreach (char value in group)
Console.WriteLine(" {0}", value);
}
此變化會列印下列內容:
Strings of length 6
A
C
G
H
Strings of length 5
B
D
F
Strings of length 7
E
注意 在此範例中,投影類型不需要與來源相同。 在此情況下,我們建立了一組整數到字串序列中的字元。
匯總運算子
定義數個標準查詢運算子,以將值序列匯總成單一值。 最常見的匯總運算子是 Aggregate,其定義如下:
public static U Aggregate<T, U>(this IEnumerable<T> source,
U seed, Func<U, T, U> func) {
U result = seed;
foreach (T element in source)
result = func(result, element);
return result;
}
Aggregate運算子可讓您輕鬆地對值序列執行計算。 匯總 的運作方式是針對基礎序列的每個成員呼叫 Lambda 運算式一次。 每次 Aggregate 呼叫 Lambda 運算式時,它會同時傳遞序列的成員和匯總值, (初始值是 匯總) 的種子參數。 Lambda 運算式的結果會取代先前的匯總值, 而 Aggregate 會傳回 Lambda 運算式的最終結果。
例如,此程式會使用 Aggregate 來累積字串陣列的總字元計數:
string[] names = { "Albert", "Burke", "Connor", "David",
"Everett", "Frank", "George", "Harris"};
int count = names.Aggregate(0, (c, s) => c + s.Length);
// count == 46
除了一般用途 匯總 運算子之外,標準查詢運算子也包含一般用途 Count 運算子和四個數值匯總運算子, (Min、 Max、 Sum和 Average) ,以簡化這些常見的匯總作業。 數值匯總函式會處理數數值型別的序列 (,例如 int、 double、double、 decimal) 或任意值的序列,只要函式是提供將序列的成員投影成數數值型別即可。
此程式說明剛才描述的 Sum 運算子兩種形式:
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
string[] names = { "Albert", "Burke", "Connor", "David",
"Everett", "Frank", "George", "Harris"};
int total1 = numbers.Sum(); // total1 == 55
int total2 = names.Sum(s => s.Length); // total2 == 46
注意 第二 個 Sum 語句相當於使用 Aggregate的上一個範例。
選取與 SelectMany
Select運算子需要轉換函式為來源序列中的每個值產生一個值。 如果您的轉換函式傳回序列本身的值,則取用者必須手動周遊子序列。 例如,假設此程式會使用現有的 String.Split 方法,將字串分成標記:
string[] text = { "Albert was here",
"Burke slept late",
"Connor is happy" };
var tokens = text.Select(s => s.Split(' '));
foreach (string[] line in tokens)
foreach (string token in line)
Console.Write("{0}.", token);
執行時,此程式會列印出下列文字:
Albert.was.here.Burke.slept.late.Connor.is.happy.
在理想情況下,我們希望我們的查詢傳回聯合的權杖序列,而不會將中繼 字串[] 公開給取用者。 為了達成此目的,我們使用 SelectMany 運算子,而不是 Select 運算子。 SelectMany運算子的運作方式與Select運算子類似。 其差異在於,轉換函式預期會傳回一個序列,然後由 SelectMany 運算子展開。 以下是使用 SelectMany重寫的程式:
string[] text = { "Albert was here",
"Burke slept late",
"Connor is happy" };
var tokens = text.SelectMany(s => s.Split(' '));
foreach (string token in tokens)
Console.Write("{0}.", token);
使用 SelectMany 會導致每個中繼序列展開為一般評估的一部分。
SelectMany 非常適合用來結合兩個資訊來源:
string[] names = { "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };
var query = names.SelectMany(n =>
people.Where(p => n.Equals(p.Name))
);
在傳遞至 SelectMany的 Lambda 運算式中,巢狀查詢會套用至不同的來源,但具有從外部來源傳入的參數範圍
n
。 因此 ,人員。其中 會針對每個 n呼叫一次,結果序列會由 SelectMany 扁平化,以供最終輸出使用。 結果是名稱出現在 名稱 陣列中的所有人員序列。
聯結運算子
在物件導向程式中,彼此相關的物件通常會與容易巡覽的物件參考連結。 對外部資訊來源而言,這通常不會保留 true,其中資料項目通常沒有選項,而是以符號方式「指向」彼此,而識別碼或其他可唯一識別所指向實體的資料。 聯結的概念是指將序列的元素與它們「與另一個序列相符」的專案結合在一起的作業。
使用 SelectMany 的上一個範例實際上確實會執行此作業,將字串與名稱為這些字串的人員相符。 不過,針對此特定用途,SelectMany方法並不非常有效率,它會針對每個名稱和每個名稱元素迴圈查看人員的所有元素。 藉由將這兩個資訊來源和它們相符的「索引鍵」結合在一個方法呼叫中, 聯 結運算子就能執行更好的工作:
string[] names = { "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };
var query = names.Join(people, n => n, p => p.Name, (n,p) => p);
這是一些口語,但瞭解片段如何結合在一起: 聯 結方法會在「外部」資料來源上呼叫, 名稱。 第一個引數是「內部」資料來源, 也就是人員。 第二個和第三個引數是 Lambda 運算式,分別從外部和內部來源的專案擷取索引鍵。 這些索引鍵是 Join 方法用來比對專案的內容。 在這裡,我們想要名稱本身符合人員的 Name 屬性。 最後的 Lambda 運算式接著負責產生結果序列的元素:它會以每個相符元素 n 和 p配對呼叫,並用來塑造結果。 在此情況下,我們選擇捨棄 n 並傳回 p。 最終結果是Name位於名稱清單中的人員人員元素清單。
聯結功能更強大的Cousin 是 GroupJoin運算子。 GroupJoin 與 Join 不同之處在于使用結果成形 Lambda 運算式的方式:不要使用每個外部和內部元素個別配對來叫用,而是針對每個專用項目呼叫一次,其中所有符合該專用項目的內部元素序列。 若要使該具體化:
string[] names = { "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };
var query = names.GroupJoin(people, n => n, p => p.Name,
(n, matching) =>
new { Name = n, Count = matching.Count() }
);
此呼叫會產生一連串您開始使用的名稱,並搭配具有該名稱的人員數目。 因此, GroupJoin 運算子可讓您根據專用項目的整個「相符專案集」來根據結果。
查詢語法
C# 中現有的 foreach 語句會針對 .NET Frameworks IEnumerable/IEnumerator 方法提供反復專案的宣告式語法。 foreach語句絕對是選擇性的,但它已證明是非常方便且熱門的語言機制。
以這個前置詞為基礎,查詢運算式會使用最常見查詢運算子的宣告式語法來簡化查詢:Where、Join、GroupJoin、Select、SelectMany、GroupBy、OrderBy、OrderBy、OrderByDescending、ThenByDescending和Cast。
讓我們從查看我們開始使用這份檔的簡單查詢開始:
IEnumerable<string> query = names
.Where(s => s.Length == 5)
.OrderBy(s => s)
.Select(s => s.ToUpper());
使用查詢運算式,我們可以重寫類似以下的確切語句:
IEnumerable<string> query = from s in names
where s.Length == 5
orderby s
select s.ToUpper();
如同 C# 中的 foreach 語句,查詢運算式更精簡且更容易閱讀,但完全是選擇性的。 每個可撰寫為查詢運算式的運算式都有對應的 (,但使用點標記法) 語法。
讓我們從查看查詢運算式的基本結構開始。 C# 中的每個語法查詢運算式都會以 from 子句開頭,並以 select 或 group 子句結尾。 初始 from 子句後面可以接著零個或多個 from、 let、 where、 join 和 orderby 子句。 每個 from 子句都是一個產生器,會在序列上引進範圍變數;每個 let 子句都會提供運算式結果的名稱;和每個 where 子句都是篩選準則,可排除結果中的專案。 每個 聯結 子句都會將新的資料來源與上述子句的結果相互關聯。 orderby子句會指定結果的順序:
query-expression ::= from-clause query-body
query-body ::=
query-body-clause* final-query-clause query-continuation?
query-body-clause ::=
(from-clause
| join-clause
| let-clause
| where-clause
| orderby-clause)
from-clause ::=from itemName in srcExpr
join-clause ::=join itemName in srcExpr on keyExpr equals keyExpr
(into itemName)?
let-clause ::=let itemName = selExpr
where-clause ::= where predExpr
orderby-clause ::= orderby (keyExpr (ascending | descending)?)*
final-query-clause ::=
(select-clause | groupby-clause)
select-clause ::= select selExpr
groupby-clause ::= group selExpr by keyExprquery-continuation ::= intoitemName query-body
例如,請考慮下列兩個查詢運算式:
var query1 = from p in people
where p.Age > 20
orderby p.Age descending, p.Name
select new {
p.Name, Senior = p.Age > 30, p.CanCode
};
var query2 = from p in people
where p.Age > 20
orderby p.Age descending, p.Name
group new {
p.Name, Senior = p.Age > 30, p.CanCode
} by p.CanCode;
編譯器會將這些查詢運算式視為使用下列明確點標記法來撰寫:
var query1 = people.Where(p => p.Age > 20)
.OrderByDescending(p => p.Age)
.ThenBy(p => p.Name)
.Select(p => new {
p.Name,
Senior = p.Age > 30,
p.CanCode
});
var query2 = people.Where(p => p.Age > 20)
.OrderByDescending(p => p.Age)
.ThenBy(p => p.Name)
.GroupBy(p => p.CanCode,
p => new {
p.Name,
Senior = p.Age > 30,
p.CanCode
});
查詢運算式會進行機械式轉譯,以呼叫具有特定名稱的方法。 因此,選擇的確切查詢運算子 實 作取決於所查詢的變數類型,以及範圍中的擴充方法。
到目前為止顯示的查詢運算式只會使用一個產生器。 使用多個產生器時,會在其前置任務的內容中評估每個後續產生器。 例如,請考慮對查詢進行稍微修改:
var query = from s1 in names
where s1.Length == 5
from s2 in names
where s1 == s2
select s1 + " " + s2;
針對此輸入陣列執行時:
string[] names = { "Burke", "Connor", "Frank", "Everett",
"Albert", "George", "Harris", "David" };
我們得到下列結果:
Burke Burke
Frank Frank
David David
上述查詢運算式會展開至這個點標記法運算式:
var query = names.Where(s1 => s1.Length == 5)
.SelectMany(s1 => names, (s1,s2) => new {s1,s2})
.Where($1 => $1.s1 == $1.s2)
.Select($1 => $1.s1 + " " + $1.s2);
注意 這個版本的 SelectMany 會採用額外的 Lambda 運算式,用來根據外部和內部序列的專案來產生結果。 在此 Lambda 運算式中,會以匿名型別收集兩個範圍變數。 編譯器會發明變數名稱 $1 ,以表示後續 Lambda 運算式中的匿名型別。
特殊種類的產生器是 join 子句,它會根據指定的索引鍵,引進另一個來源的專案,以符合上述子句的專案。 聯結子句可能會逐一產生相符的專案,但如果使用into子句指定,則會將相符的專案指定為群組:
var query = from n in names
join p in people on n equals p.Name into matching
select new { Name = n, Count = matching.Count() };
這並不意外,此查詢會直接擴充到我們之前看到的查詢:
var query = names.GroupJoin(people, n => n, p => p.Name,
(n, matching) =>
new { Name = n, Count = matching.Count() }
);
在後續查詢中,將一個查詢的結果視為產生器通常很有用。 為了支援這項功能,查詢運算式會使用 into 關鍵字,在 select 或 group 子句之後擷取新的查詢運算式。 這稱為 查詢接續。
into關鍵字特別適用于後續處理group
by子句的結果。 例如,請考慮此程式:
var query = from item in names
orderby item
group item by item.Length into lengthGroups
orderby lengthGroups.Key descending
select lengthGroups;
foreach (var group in query) {
Console.WriteLine("Strings of length {0}", group.Key);
foreach (var val in group)
Console.WriteLine(" {0}", val);
}
此程式會輸出下列內容:
Strings of length 7
Everett
Strings of length 6
Albert
Connor
George
Harris
Strings of length 5
Burke
David
Frank
本節說明 C# 如何實作查詢運算式。 其他語言可以選擇支援具有明確語法的其他查詢運算子,或完全沒有查詢運算式。
請務必注意,查詢語法不一定要硬式連接到標準查詢運算子。 這是純語法功能,可藉由使用適當的名稱和簽章來實作基礎方法,以符合 查詢模式 的任何專案。 上述標準查詢運算子會使用擴充方法來增強IEnumerable < T >介面。 只要開發人員確定其符合查詢模式,只要直接實作必要的方法,或將它們新增為擴充方法,即可利用查詢語法。
透過布建兩個啟用 LINQ 的API,也就是LINQ to SQL,以實作 SQL 型資料存取的 LINQ 模式,以及 LINQ to XML允許透過 XML 資料的 LINQ 查詢,在 LINQ 專案中利用此擴充性。 下列各節將說明這兩者。
LINQ to SQL:SQL 整合
.NET Language-Integrated 查詢可用來查詢關聯式資料存放區,而不需要離開本機程式設計語言的語法或編譯時間環境。 此設施程式碼命名LINQ to SQL會利用 SQL 架構資訊與 CLR 中繼資料的整合。 此整合會將 SQL 資料表和檢視定義編譯成可從任何語言存取的 CLR 類型。
LINQ to SQL定義兩個核心屬性[Table]和[Column],指出哪些 CLR 類型和屬性對應至外部 SQL 資料。 [Table]屬性可以套用至類別,並將 CLR 類型與具名 SQL 資料表或檢視產生關聯。 [Column]屬性可以套用至任何欄位或屬性,並將成員與具名 SQL 資料行產生關聯。 這兩個屬性都會參數化,以允許保留 SQL 特定的中繼資料。 例如,請考慮這個簡單的 SQL 架構定義:
create table People (
Name nvarchar(32) primary key not null,
Age int not null,
CanCode bit not null
)
create table Orders (
OrderID nvarchar(32) primary key not null,
Customer nvarchar(32) not null,
Amount int
)
CLR 對等專案看起來像這樣:
[Table(Name="People")]
public class Person {
[Column(DbType="nvarchar(32) not null", Id=true)]
public string Name;
[Column]
public int Age;
[Column]
public bool CanCode;
}
[Table(Name="Orders")]
public class Order {
[Column(DbType="nvarchar(32) not null", Id=true)]
public string OrderID;
[Column(DbType="nvarchar(32) not null")]
public string Customer;
[Column]
public int? Amount;
}
注意這個範例中,可為 Null 的資料行會對應至 CLR (可為 Null 的型別,第一次出現在 .NET Framework) 2.0 版中,而對於沒有 CLR 類型的 1:1 對應,SQL 類型 (例如Nvarchar、char、text) ,原始 SQL 類型會保留在 CLR 中繼資料中。
若要對關係存放區發出查詢,LINQ 模式的LINQ to SQL實作會將查詢從運算式樹狀結構形式轉譯成 SQL 運算式,並 ADO.NET 適用于遠端評估的DbCommand物件。 例如,請考慮這個簡單的查詢:
// establish a query context over ADO.NET sql connection
DataContext context = new DataContext(
"Initial Catalog=petdb;Integrated Security=sspi");
// grab variables that represent the remote tables that
// correspond to the Person and Order CLR types
Table<Person> custs = context.GetTable<Person>();
Table<Order> orders = context.GetTable<Order>();
// build the query
var query = from c in custs
from o in orders
where o.Customer == c.Name
select new {
c.Name,
o.OrderID,
o.Amount,
c.Age
};
// execute the query
foreach (var item in query)
Console.WriteLine("{0} {1} {2} {3}",
item.Name, item.OrderID,
item.Amount, item.Age);
DataCoNtext類型提供輕量型翻譯工具,可將標準查詢運算子轉譯為 SQL。 DataCoNtext 會使用現有的 ADO.NET IDbConnection 來存取存放區,而且可以使用已建立的 ADO.NET 連線物件或可用來建立存放區的連接字串進行初始化。
GetTable方法提供IEnumerable相容變數,可用於查詢運算式來表示遠端資料表或檢視表。 對 GetTable 的呼叫不會造成資料庫的任何互動,而是代表使用查詢運算式與遠端資料表或檢視互動 的可能性 。 在上述範例中,除非程式在 C# 中使用 foreach 語句,否則查詢不會傳輸至存放區。 當程式第一次逐一查看查詢時, DataCoNtext 機器會將運算式樹狀結構轉譯成下列傳送至存放區的 SQL 語句:
SELECT [t0].[Age], [t1].[Amount],
[t0].[Name], [t1].[OrderID]
FROM [Customers] AS [t0], [Orders] AS [t1]
WHERE [t1].[Customer] = [t0].[Name]
請務必注意,藉由將查詢功能直接建置至本機程式設計語言,開發人員就能取得關係模型的完整功能,而不需要以靜態方式將關聯性模擬到 CLR 類型。 這表示,完整的物件/關聯式對應也可以針對想要該功能的使用者利用這個核心查詢功能。 LINQ to SQL提供物件關聯式對應功能,開發人員可以定義和巡覽物件之間的關聯性。 您可以使用對應將 Orders 視為 Customer 類別的屬性,因此您不需要明確聯結,才能將兩者系結在一起。 外部對應檔案可讓對應與物件模型分開,以提供更豐富的對應功能。
LINQ to XML:XML 整合
.NET Language-Integrated XML 查詢 (LINQ to XML) 允許使用標準查詢運算子以及樹狀結構特定運算子來查詢 XML 資料,以提供類似 XPath 的子系、上階和同層級導覽。 它為 XML 提供有效率的記憶體內部標記法,可與現有的 System.Xml 讀取器/寫入器基礎結構整合,而且比 W3C DOM 更容易使用。 有三種類型可以執行大部分的 XML 與查詢整合的工作: XName、 XElement 和 XAttribute。
XName 提供容易使用的方式,可處理命名空間限定識別碼 (QNames) 做為元素和屬性名稱。 XName 會以透明方式處理識別碼的有效 Atom 化,並允許在需要 QName 時使用符號或純字串。
XML 元素和屬性分別使用 XElement 和 XAttribute 來表示。 XElement 和 XAttribute 支援一般建構語法,可讓開發人員使用自然語法撰寫 XML 運算式:
var e = new XElement("Person",
new XAttribute("CanCode", true),
new XElement("Name", "Loren David"),
new XElement("Age", 31));
var s = e.ToString();
這會對應至下列 XML:
<Person CanCode="true">
<Name>Loren David</Name>
<Age>31</Age>
</Person>
請注意,建立 XML 運算式不需要以 DOM 為基礎的處理站模式,而且 ToString 實作會產生文字 XML。 XML 元素也可以從現有的 XmlReader 或字串常值建構:
var e2 = XElement.Load(xmlReader);
var e1 = XElement.Parse(
@"<Person CanCode='true'>
<Name>Loren David</Name>
<Age>31</Age>
</Person>");
XElement 也支援使用現有的 XmlWriter 類型發出 XML。
XElement 會與查詢運算子搭配使用,讓開發人員能夠針對非 XML 資訊撰寫查詢,並在 select 子句主體中建構 XElements 來產生 XML 結果:
var query = from p in people
where p.CanCode
select new XElement("Person",
new XAttribute("Age", p.Age),
p.Name);
此查詢會傳回 XElements序列。 為了允許從這類查詢的結果建置 XElements , XElement 建構函式可讓元素序列直接傳遞為引數:
var x = new XElement("People",
from p in people
where p.CanCode
select
new XElement("Person",
new XAttribute("Age", p.Age),
p.Name));
此 XML 運算式會產生下列 XML:
<People>
<Person Age="11">Allen Frances</Person>
<Person Age="59">Connor Morgan</Person>
</People>
上述語句具有直接轉譯至 Visual Basic。 不過,Visual Basic 9.0 也支援使用 XML 常值,允許直接從 Visual Basic 使用宣告式 XML 語法來表示查詢運算式。 上述範例可以使用 Visual Basic 語句來建構:
Dim x = _
<People>
<%= From p In people __
Where p.CanCode _
Select <Person Age=<%= p.Age %>>p.Name</Person> _
%>
</People>
到目前為止,這些範例已示範如何使用語言整合式查詢 來建構 新的 XML 值。 XElement和XAttribute類型也會簡化從 XML 結構擷取資訊。 XElement 提供存取子方法,允許將查詢運算式套用至傳統的 XPath 軸。 例如,下列查詢只會從上述 XElement 擷取名稱:
IEnumerable<string> justNames =
from e in x.Descendants("Person")
select e.Value;
//justNames = ["Allen Frances", "Connor Morgan"]
若要從 XML 擷取結構化值,我們只需要在 select 子句中使用物件初始化運算式:
IEnumerable<Person> persons =
from e in x.Descendants("Person")
select new Person {
Name = e.Value,
Age = (int)e.Attribute("Age")
};
請注意, XAttribute 和 XElement 都支援明確轉換,以將文字值擷取為基本類型。 若要處理遺漏的資料,我們可以直接轉換成可為 Null 的類型:
IEnumerable<Person> persons =
from e in x.Descendants("Person")
select new Person {
Name = e.Value,
Age = (int?)e.Attribute("Age") ?? 21
};
在此情況下,當Age屬性遺失時,我們會使用預設值21。
Visual Basic 9.0 提供XElement的 Elements、Attribute和Descendants存取子方法的直接語言支援,允許使用更精簡且直接的語法來存取 XML 軸屬性。 我們可以使用這項功能來撰寫上述 C# 語句,如下所示:
Dim persons = _
From e In x...<Person> _
Select new Person { _
.Name = e.Value, _
.Age = IIF(e.@Age, 21) _
}
在 Visual Basic 中,x... <Person >會取得名稱為Person之x子系集合中的所有專案,而運算式e. @Age會尋找名稱為 Age.
的所有XAttributes:Value屬性會取得集合中的第一個屬性,並在該屬性上呼叫Value屬性。
總結
.NET Language-Integrated查詢會將查詢功能新增至 CLR 及其目的語言。 查詢設施是以 Lambda 運算式和運算式樹狀結構為基礎,允許述詞、投影和索引鍵擷取運算式當做不透明的可執行程式碼,或作為適用于下游處理或轉譯的透明記憶體內資料。 LINQ 專案定義的標準查詢運算子會處理任何IEnumerable < T >型資訊來源,並與 ADO.NET (LINQ to SQL) 和System.Xml整合 (LINQ to XML) ,以允許關聯式和 XML 資料獲得語言整合式查詢的優點。
簡單來說,標準查詢運算子
運算子 | 描述 |
---|---|
Where | 以述詞函式為基礎的限制運算子 |
Select/SelectMany | 根據選取器函式的投影運算子 |
Take/Skip/ TakeWhile/SkipWhile | 根據位置或述詞函式的資料分割運算子 |
Join/GroupJoin | 以索引鍵選取器函式為基礎的聯結運算子 |
Concat | 串連運算子 |
OrderBy/ThenBy/OrderByDescending/ThenByDescending | 根據選擇性索引鍵選取器和比較子函式,以遞增或遞減順序排序運算子 |
Reverse | 排序運算子會反轉序列的順序 |
群組依據 | 根據選擇性索引鍵選取器和比較子函式分組運算子 |
Distinct | Set 運算子移除重複專案 |
等位/交集 | 設定傳回集合等位或交集的運算子 |
Except | 傳回集合差異的 Set 運算子 |
AsEnumerable | 將運算子轉換為IEnumerable < T > |
ToArray/ToList | 將運算子轉換成陣列或清單 < T > |
ToDictionary/ToLookup | 根據索引鍵選取器函式,將運算子轉換成字典 < K、T >或查閱 < K、T > (多字典) |
OfType/Cast | 根據篩選依據或轉換成類型引數,將運算子轉換成IEnumerable < T > |
SequenceEqual | 相等運算子檢查配對專案相等 |
First/FirstOrDefault/Last/LastOrDefault/Single/SingleOrDefault | 根據選擇性述詞函式傳回 initial/final/only 元素的專案運算子 |
ElementAt/ElementAtOrDefault | 根據位置傳回元素的元素運算子 |
DefaultIfEmpty | 以預設值單一序列取代空序列的元素運算子 |
範圍 | 傳回範圍中數位的產生運算子 |
Repeat | 產生運算子傳回指定值的多個出現次數 |
空白 | 傳回空序列的產生運算子 |
任何/所有 | 述詞函式是否存在或通用滿意度的數量檢查 |
包含 | 指定專案是否存在的數量詞檢查 |
Count/LongCount | 根據選擇性述詞函數計算元素的匯總運算子 |
Sum/Min/Max/Average | 根據選擇性選取器函數匯總運算子 |
Aggregate | 根據累積函式和選擇性種子累積多個值的匯總運算子 |