LINQ:进阶 - LINQ 标准查询操作概述
“標準查詢運算符”是組成語言集成查詢 (LINQ) 模式的方法。大多數這些方法都在序列上運行,其中的序列是一個對象,其類型實現了IEnumerable<T>?接口或?IQueryable<T>?接口。標準查詢運算符提供了包括篩選、投影、聚合、排序等功能在內的查詢功能。 ?
?????各個標準查詢運算符在執行時間上有所不同,具體情況取決于它們是返回單一值還是值序列。返回單一值的方法(例如?Average?和?Sum)會立即執行。返回序列的方法會延遲查詢執行,并返回一個可枚舉的對象。 ?
?????對于在內存中集合上運行的方法(即擴展?IEnumerable<T>?的方法),返回的可枚舉對象將捕獲傳遞到方法的參數。在枚舉該對象時,將使用查詢運算符的邏輯,并返回查詢結果。 ?
?????與之相反,擴展?IQueryable<T>?的方法不會實現任何查詢行為,但會生成一個表示要執行的查詢的表達式樹。查詢處理由源?IQueryable<T>?對象處理。
?
一、按標準執行方式分類
?????標準查詢運算符方法的 LINQ to Objects 實現采用兩種主要方式之一來執行:立即執行和延遲執行。采用延遲執行的查詢運算符可以進一步分為兩類:流式和非流式。
? ? ?
? ? ?1.執行方式
? ? ? ? ? (1)立即: 立即執行意味著在代碼中聲明查詢的位置讀取數據源并執行運算。??返回單個不可枚舉的結果的所有標準查詢運算符都立即執行。
? ? ? ? ? (2)延遲: 延遲執行意味著不在代碼中聲明查詢的位置執行運算。??僅當對查詢變量進行枚舉操作時才執行運算,例如通過使用?foreach?語句。這意味著查詢的執行結果取決于執行查詢而非定義查詢時的數據源內容。如果多次枚舉查詢變量,則每次結果可能都不同。幾乎所有返回類型為?IEnumerable<T>?或?IOrderedEnumerable<TElement>?的標準查詢運算符都以延遲方式執行。?
? ? ? ? ?? 采用延遲執行方式的查詢運算符可以另外分類為流式和非流式。
? ? ? ? ? ①流式運算符不需要在生成元素前讀取所有源數據。在執行時,流式運算符一邊讀取每個源元素,一邊對該源元素執行運算,并在可行時生成元素。流式運算符將持續讀取源元素直到可以生成結果元素。這意味著可能要讀取多個源元素才能生成一個結果元素。
? ? ? ? ? ②非流式運算符必須讀取所有源數據才能生成結果元素。諸如排序和分組等運算屬于此類別。在執行時,非流式查詢運算符讀取所有源數據,將其放入數據結構中,執行運算,然后生成結果元素。
?
二、排列數據
?????排序操作按一個或多個特性對序列的元素進行排序。第一個排序條件對元素執行主要排序。通過指定第二個排序條件,可以對各個主要排序組中的元素進行排序。?
?????下圖演示對一個字符序列執行按字母排序操作的結果。
?
| 方法名 | 說明 | C# 查詢表達式語法 |
| OrderBy | 按升序對值進行排序。 | orderby |
| OrderByDescending | 按降序對值進行排序。 | orderby … descending |
| ThenBy | 按升序執行次要排序。 | orderby …, … |
| ThenByDescending | 按降序執行次要排序。 | orderby …, … descending |
| Reverse | 顛倒集合中的元素的順序。 | X |
下面通過演示使用 orderby 進行升序排序:按字符串長度
var words = new[] { "the", "quick", "brown", "fox", "jumps" };
var query = from word in words
? ? ? ? ? ? ? ? ? ?orderby word.Length
? ? ? ? ? ? ? ? ? ?select word;
foreach (var word in query)
{
? ? Console.WriteLine(word);
}
下面通過演示使用 orderby descending 進行降序排序:按字符串的第一個字母
var words = new[] { "the", "quick", "brown", "fox", "jumps" };
var query = from word in words
? ? ? ? ? ? ? ? ? ?orderby word.Substring(0,1) descending?
? ? ? ? ? ? ? ? ? ?select word;
foreach (var word in query)
{
? ? Console.WriteLine(word);
}
下面通過演示使用 orderby 進行主要和次要排序:先升序按字符串長度(主)、再升序按字符串的第一個字母(次)
var words = new[] { "the", "quick", "brown", "fox", "jumps" };
var query = from word in words
? ? ? ? ? ? ? ? ? ?orderby word.Length, word.Substring(0, 1)
? ? ? ? ? ? ? ? ? ?select word;
foreach (var word in query)
{
? ? Console.WriteLine(word);
}
下面通過演示使用 orderby descending 進行主要和次要排序:先升序按字符串長度(主)、再降序按字符串的第一個字母(次)
var words = new[] { "the", "quick", "brown", "fox", "jumps" };
var query = from word in words
? ? ? ? ? ? ? ? ? ?orderby word.Length, word.Substring(0, 1) descending?
? ? ? ? ? ? ? ? ? ?select word;
foreach (var word in query)
{
? ? Console.WriteLine(word);
}
?
三、Set 操作
LINQ 中的 Set 操作是指根據相同或不同集合中是否存在等效元素來生成結果集的查詢操作。
?
| 方法名 | 說明 | C# 查詢表達式語法 |
Distinct | 從集合移除重復值。 | X |
Except | 返回差集,差集是指位于一個集合但不位于另一個集合的元素。 | X |
Intersect | 返回交集,交集是指同時出現在兩個集合中的元素。 | X |
Union | 返回并集,并集是指位于兩個集合中任一集合的唯一的元素。 | X |
?
圖解 Set 操作
(1)Distinct: 返回的序列包含輸入序列的唯一元素。
(2)Except: 返回的序列只包含位于第一個輸入序列但不位于第二個輸入序列的元素。??
(3)Intersect: 返回的序列包含兩個輸入序列共有的元素。??
(4)Union: 返回的序列包含兩個輸入序列的唯一的元素。?
?
?四、過濾數據
? ? ?篩選指將結果集限制為只包含某些滿足指定條件的元素的操作。它又稱為選擇。
?????下圖演示了對字符序列進行篩選的結果。篩選操作的謂詞指定字符必須為“A”。
?
| 方法名 | 說明 | C# 查詢表達式語法 |
| OfType | 根據值強制轉換為指定類型的能力選擇值。 | X |
| Where | 選擇基于謂詞函數的值。 | where |
五、量詞操作?
限定符運算返回一個?Boolean?值,該值指示序列中是否有一些元素滿足條件或是否所有元素都滿足條件。
?????下圖描述了兩個不同源序列上的兩個不同限定符運算。第一個運算詢問是否有一個或多個元素為字符“A”,結果為?true。第二個運算詢問是否所有元素都為字符“A”,結果為?true。 ?
?
方法名 | 說明 | C# 查詢表達式語法 |
| All | 確定是否序列中的所有元素都滿足條件。 | X |
| Any | 確定序列中是否有元素滿足條件。 | X |
Contains | 確定序列是否包含指定的元素。 | X |
?
六、投影操作
? ? ? 投影是指將對象轉換為一種新形式的操作,該形式通常只包含那些將隨后使用的屬性。通過使用投影,您可以構建依據每個對象生成的新類型。您可以映射屬性,并對該屬性執行數學函數。還可以在不更改原始對象的情況下映射該對象。
?
| 方法名 | 說明 | C# 查詢表達式語法 |
| Select | 映射基于轉換函數的值。 | select |
| SelectMany | 映射基于轉換函數的值序列,然后將它們展平為一個序列。 | 使用多個?from 子句 |
Select:下面的示例使用 select 子句來映射字符串列表中每個字符串的第一個字母
var words = new[] { "the", "quick", "brown", "fox", "jumps" };
var query = from word in words
? ? ? ? ? ? ? ? ? ?select word.Substring(0,1);
foreach (var word in query)
{
? ? Console.WriteLine(word);
}
SelectMany:下面的示例使用多個 from 子句來映射字符串列表中每個字符串中的每個單詞
var phrases = new List<string>() { "an apple a day", "the quick brown fox" };
var query = from phrase in phrases
? ? ? ? ? ? ? ? ? ?from word in phrase.Split(' ')
? ? ? ? ? ? ? ? ? ?select word;
foreach (var word in query)
{
? ?Console.WriteLine(word);
}
Select()?和?SelectMany()?的工作都是依據源值生成一個或多個結果值。Select()?為每個源值生成一個結果值。因此,總體結果是一個與源集合具有相同元素數目的集合。與之相反,SelectMany()?將生成單一總體結果,其中包含來自每個源值的串聯子集合。作為參數傳遞到?SelectMany()?的轉換函數必須為每個源值返回一個可枚舉值序列。然后,SelectMany()?將串聯這些可枚舉序列以創建一個大的序列。
下面兩個插圖演示了這兩個方法的操作之間的概念性區別。在每種情況下,假定選擇器(轉換)函數從每個源值中選擇一個由花卉數據組成的數組。
下圖描述?Select()?如何返回一個與源集合具有相同元素數目的集合。
?
下圖描述?SelectMany()?如何將中間數組序列串聯為一個最終結果值,其中包含每個中間數組中的每個值。
?
示例
下面的示例比較?Select()?和?SelectMany()?的行為。代碼將通過從源集合的每個花卉名稱列表中提取前兩項來創建一個“花束”。在此示例中,轉換函數?Select?使用的“單一值”本身就是一個值集合。這需要額外的?foreach?循環,以便枚舉每個子序列中的每個字符串。
static void Main(string[] args)
? ? ? ? {
? ? ? ? ? ? var bouquets = new List<Bouquet>()
? ? ? ? ? ? {
? ? ? ? ? ? ? ? new Bouquet {Flowers = new List<string> {"sunflower", "daisy", "daffodil", "larkspur"}},
? ? ? ? ? ? ? ? new Bouquet {Flowers = new List<string> {"tulip", "rose", "orchid"}},
? ? ? ? ? ? ? ? new Bouquet {Flowers = new List<string> {"gladiolis", "lily", "snapdragon", "aster", "protea"}},
? ? ? ? ? ? ? ? new Bouquet {Flowers = new List<string> {"larkspur", "lilac", "iris", "dahlia"}}
? ? ? ? ? ? };
? ? ? ? ? ? IEnumerable<List<string>> query1 = bouquets.Select(bq => bq.Flowers);
? ? ? ? ? ? IEnumerable<string> query2 = bouquets.SelectMany(bq => bq.Flowers);
? ? ? ? ? ? Console.WriteLine("query1 - Select():");
? ? ? ? ? ? foreach (IEnumerable<string> collection in query1)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? foreach (var item in collection)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? Console.WriteLine(item);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? Console.WriteLine("\nquery2 - SelectMany():");
? ? ? ? ? ? foreach (var item in query2)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? Console.WriteLine(item);
? ? ? ? ? ? }
? ? ? ? ? ? Console.Read();
? ? ? ? }
? ? ? ? class Bouquet
? ? ? ? {
? ? ? ? ? ? public List<string> Flowers { get; set; }
? ? ? ? }
?
七、劃分數據
LINQ 中的分區指的是在不重新排列元素的情況下,將輸入序列劃分為兩部分,然后返回其中一個部分的操作。
下圖顯示對一個字符序列執行三個不同的分區操作的結果。第一個操作返回序列中的前三個元素。第二個操作跳過前三個元素,返回剩余的元素。第三個操作跳過序列中的前兩個元素,返回接下來的三個元素。 ?
?
運算符名稱 | 說明 | C# 查詢表達式語法 |
Skip | 跳過序列中的指定位置之前的元素。 | X |
SkipWhile | 基于謂詞函數跳過元素,直到某元素不再滿足條件。 | X |
Take | 提取序列中的指定位置之前的元素。 | X |
TakeWhile | 基于謂詞函數提取元素,直到某元素不再滿足條件。 | X |
?
八、聯接操作
將兩個數據源“聯接”就是將一個數據源中的對象與另一個數據源中共享某個通用特性的對象關聯起來。
當查詢所面向的數據源相互之間具有無法直接領會的關系時,聯接就成為一項重要的運算。在面向對象的編程中,這可能意味著在未建模對象之間進行關聯,例如對單向關系進行反向推理。下面是單向關系的一個示例:Customer 類有一個類型為 City 的屬性,但 City 類沒有作為 Customer 對象集合的屬性。如果你具有一個 City 對象列表,并且要查找每個城市中的所有客戶,則可以使用聯接運算完成此項查找。??
LINQ 框架中提供的聯接方法包括?Join?和?GroupJoin。這些方法執行同等聯接,即根據兩個數據源的鍵是否相等來匹配這兩個數據源的聯接。(與此相較,Transact-SQL 支持除“等于”之外的聯接運算符,例如“小于”運算符。)用關系數據庫術語表達,就是說?Join?實現了內部聯接,這種聯接只返回那些在另一個數據集中具有匹配項的對象。GroupJoin?方法在關系數據庫術語中沒有直接的等效項,但它實現了內部聯接和左外部聯接的超集。左外部聯接是這樣一種聯接:它返回第一個(左)數據源的每個元素,即使該元素在另一個數據源中沒有關聯元素。??
下圖顯示了一個概念性視圖,其中包含兩個集合以及這兩個集合中的包含在內部聯接或左外部聯接中的元素。
?
方法名 | 描述 | C# 查詢表達式語法 |
| Join | 根據鍵選擇器函數聯接兩個序列并提取值對。 | join … in … on … equals … |
GroupJoin | 根據鍵選擇器函數聯接兩個序列,并對每個元素的結果匹配項進行分組。 | join … in … on … equals … into … |
?
九、分組數據
分組指將數據放入組中以便每個組中的元素共享公共特性的操作。
下圖顯示了對字符序列進行分組的結果。每個組的鍵是字符。 ?
?
方法名 | 說明 | C# 查詢表達式語法 |
GroupBy | 對共享公共特性的元素進行分組。??每個組都由一個?IGrouping<TKey,?TElement>?對象表示。?? | group … by? - 或 - group … by … into … |
ToLookup | 根據鍵選擇器函數將元素插入到?Lookup<TKey,?TElement>(一個一對多字典)中。 | X |
使用 group by 子句根據列表中的整數是奇數還是偶數進行分組
var numbers = new List<int>() { 35, 44, 200, 84, 3987, 4, 199, 329, 446, 208 };
IEnumerable<IGrouping<bool, int>> query = from number in numbers
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? group number by number % 2 == 0;
foreach (var group in query)
{
? ? Console.WriteLine($"{(group.Key ? "偶數" : "基數")}:");
? ? foreach (var i in group)
? ? {
? ? ? ? Console.WriteLine(i);
? ? }
} ? ?
?
十、生成操作
生成是指創建新的值序列。
?
方法名 | 說明 | C# 查詢表達式語法 |
DefaultIfEmpty | 將空集合替換為具有默認值的單一實例集合。 | X |
| Empty | 返回空集合。 | X |
| Range | 生成包含數字序列的集合。 | X |
Repeat | 生成包含一個重復值的集合。 | X |
十一、等值操作
如果兩個序列的對應元素相等且這兩個序列具有相同數量的元素,則視這兩個序列相等。
?
?
| 方法名 | 說明 | C# 查詢表達式語法 |
| SequenceEqual | 通過成對地比較元素確定兩個序列是否相等。 | X |
?
?十二、元素操作
元素操作從一個序列返回單個特定元素。
?
方法名 | 說明 | C# 查詢表達式語法 |
ElementAt | 返回集合中指定索引處的元素。 | X |
ElementAtOrDefault | 返回集合中指定索引處的元素;如果索引超出范圍,則返回默認值。 | X |
First | 返回集合中的第一個元素或滿足條件的第一個元素。 | X |
FirstOrDefault | 返回集合中的第一個元素或滿足條件的第一個元素。??如果沒有這樣的元素,則返回默認值。?? | X |
| Last | 返回集合中的最后一個元素或滿足條件的最后一個元素。 | X |
LastOrDefault | 返回集合中的最后一個元素或滿足條件的最后一個元素。??如果沒有這樣的元素,則返回默認值。?? | X |
Single | 返回集合中的唯一元素或滿足條件的唯一元素。 | X |
SingleOrDefault | 返回集合中的唯一元素或滿足條件的唯一元素。??如果沒有這樣的元素或集合不是正好包含一個元素,則返回默認值。?? | X |
?
十三、轉換數據類型
轉換方法更改輸入對象的類型。
LINQ 查詢中的轉換運算可用于各種應用程序。下面是一些示例:
(1)Enumerable.AsEnumerable<TSource>?方法可用于隱藏類型的標準查詢運算符的自定義實現。
(2)Enumerable.OfType<TResult>?方法可用于啟用非參數化集合以進行 LINQ 查詢。
(3)Enumerable.ToArray<TSource>、Enumerable.ToDictionary<TSource,?TKey>、Enumerable.ToList<TSource>?和Enumerable.ToLookup<TSource,?TKey>?方法可用于強制立即執行查詢,而非推遲到枚舉查詢時。
?
| 方法名 | 說明 | C# 查詢表達式語法 |
| AsEnumerable | 返回類型為?IEnumerable<T>?的輸入。 | X |
| AsQueryable | 將(泛型)IEnumerable?轉換為(泛型)IQueryable。 | X |
| Cast | 將集合的元素強制轉換為指定類型。 | 使用顯式類型化的范圍變量。?例如: from string str in words |
| OfType | 根據值強制轉換為指定類型的能力篩選值。 | X |
| ToArray | 將集合轉換為數組。?此方法強制執行查詢。 | X |
| ToDictionary | 根據鍵選擇器函數將元素放入?Dictionary<TKey,?TValue>?中。?此方法強制執行查詢。 | X |
| ToList | 將集合轉換為?List<T>。?此方法強制執行查詢。 | X |
| ToLookup | 根據鍵選擇器函數將元素放入?Lookup<TKey,?TElement>(一對多字典)中。?此方法強制執行查詢。 | X |
?
示例:
static void Main(string[] args)
? ? ? ? {
? ? ? ? ? ? var plants = new Plant[]
? ? ? ? ? ? {
? ? ? ? ? ? ? ? new CarnivorousPlant {Name = "Venus Fly Trap", TrapType = "Snap Trap"},
? ? ? ? ? ? ? ? new CarnivorousPlant {Name = "Pitcher Plant", TrapType = "Pitfall Trap"},
? ? ? ? ? ? ? ? new CarnivorousPlant {Name = "Sundew", TrapType = "Flypaper Trap"},
? ? ? ? ? ? ? ? new CarnivorousPlant {Name = "Waterwheel Plant", TrapType = "Snap Trap"}
? ? ? ? ? ? };
? ? ? ? ? ? var query = from CarnivorousPlant plant in plants
? ? ? ? ? ? ? ? ? ? ? ? where plant.TrapType == "Snap Trap"
? ? ? ? ? ? ? ? ? ? ? ? select plant;
? ? ? ? ? ? foreach (var carnivorousPlant in query)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? Console.WriteLine(carnivorousPlant.Name);
? ? ? ? ? ? }
? ? ? ? ? ? Console.Read();
? ? ? ? }
? ? ? ? class Plant
? ? ? ? {
? ? ? ? ? ? public string Name { get; set; }
? ? ? ? }
? ? ? ? class CarnivorousPlant : Plant
? ? ? ? {
? ? ? ? ? ? public string TrapType { get; set; }
? ? ? ? } ? ?
使用顯式類型化的范圍變量將類型強制轉換為子類型,然后才訪問僅在此子類型中提供的成員。
僅在此子類型中提供的成員。
?
十四、串聯操作
串聯是指將一個序列追加到另一個序列的運算。
下圖演示對兩個字符序列執行的串聯運算。
方法名 | 說明 | C# 查詢表達式語法 |
Concat | 串聯兩個序列以組成一個序列。 | X |
?
十五、聚合操作
聚合運算從值集合計算單個值。從一個月的日溫度值計算日平均溫度就是聚合運算的一個示例。??
下圖顯示了對一個數字序列執行兩個不同聚合運算的結果。第一個運算對這些數字執行求和。第二個運算返回該序列中的最大值。 ?
?
方法名 | 說明 | C# 查詢表達式語法 |
Aggregate | 對集合值執行自定義聚合運算。 | X |
Average | 計算值集合的平均值。 | X |
| Count | 對集合中的元素進行計數,還可以僅對滿足某一謂詞函數的元素進行計數。 | X |
LongCount | 對大型集合中的元素進行計數,還可以僅對滿足某一謂詞函數的元素進行計數。 | X |
| Max | 確定集合中的最大值。 | X |
| Min | 確定集合中的最小值。 | X |
Sum | 計算集合中值的總和。 | X |
原文地址:http://www.cnblogs.com/liqingwen/p/5801249.html
.NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注
總結
以上是生活随笔為你收集整理的LINQ:进阶 - LINQ 标准查询操作概述的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET 程序集单元测试工具 Smoke
- 下一篇: 升讯威微信营销系统开发实践:(3)中控服