本筆記摘抄自:https://www.cnblogs.com/liqingwen/p/5801249.html,記錄一下學習過程以備後續查用。 “標準查詢運算符”是組成語言集成查詢 (LINQ) 模式的方法,大多數這些方法都在序列上運行,其中的序列是一個對象,其類型實現了IEnumerable<T> ...
本筆記摘抄自:https://www.cnblogs.com/liqingwen/p/5801249.html,記錄一下學習過程以備後續查用。
“標準查詢運算符”是組成語言集成查詢 (LINQ) 模式的方法,大多數這些方法都在序列上運行,其中的序列是一個對象,其類型實現了IEnumerable<T>介面
或 IQueryable<T> 介面。標準查詢運算符提供了包括篩選、投影、聚合、排序等功能在內的查詢功能,各個標準查詢運算符在執行時間上有所不同,具體情況
取決於它們是返回單一值還是值序列:
返回單一值的方法(例如Average和Sum)會立即執行,返回序列的方法會延遲查詢執行,並返回一個可枚舉的對象。
對於在記憶體中集合上運行的方法(即擴展IEnumerable<T>的方法),返回的可枚舉對象將捕獲傳遞到方法的參數。在枚舉該對象時,將使用查詢運算符的
邏輯,並返回查詢結果。與之相反,擴展IQueryable<T>的方法不會實現任何查詢行為,但會生成一個表示要執行的查詢的表達式樹,查詢處理由源
IQueryable<T>對象處理。
一、按標準執行方式分類
標準查詢運算符方法的LINQ to Objects實現採用兩種主要方式之一來執行:立即執行和延遲執行,採用延遲執行的查詢運算符可以進一步分為兩類:流式和
非流式。
1、執行方式
立即執行:立即執行意味著在代碼中聲明查詢的位置讀取數據源並執行運算,返回單個不可枚舉的結果的所有標準查詢運算符都立即執行。
延遲執行:延遲執行意味著不在代碼中聲明查詢的位置執行運算,僅當對查詢變數進行枚舉操作時才執行運算,例如通過使用foreach語句,這意味著查詢的
執行結果取決於執行查詢而非定義查詢時的數據源內容。如果多次枚舉查詢變數,則每次結果可能都不同。幾乎所有返回類型為IEnumerable<T>或
IOrderedEnumerable<TElement>的標準查詢運算符都以延遲方式執行。
2、延遲執行
流式:運算符不需要在生成元素前讀取所有源數據。在執行時,流式運算符一邊讀取每個源元素,一邊對該源元素執行運算,併在可行時生成元素,流式運
算符將持續讀取源元素直到可以生成結果元素,這意味著可能要讀取多個源元素才能生成一個結果元素。
非流式:運算符必須讀取所有源數據才能生成結果元素,諸如排序和分組等運算屬於此類別。在執行時,非流式查詢運算符讀取所有源數據,將其放入數據
結構中,執行運算然後生成結果元素。
二、排列數據
排序操作按一個或多個特性對序列的元素進行排序。第一個排序條件對元素執行主要排序,通過指定第二個排序條件,可以對各個主要排序組中的元素進行
排序。
下圖演示對一個字元序列按字母排序操作的執行結果 :
方法名 | 說明 | C# 查詢表達式語法 |
OrderBy | 按升序對值進行排序。 | orderby |
OrderByDescending | 按降序對值進行排序。 | orderby … descending |
ThenBy | 按升序執行次要排序。 | orderby …, … |
ThenByDescending | 按降序執行次要排序。 | orderby …, … descending |
Reverse | 顛倒集合中的元素的順序。 | X |
下麵代碼演示orderby升序排序:
class Program { static void Main(string[] args) { #region LINQ 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); } Console.Read(); #endregion } }View Code
運行結果如下:
下麵代碼演示orderby降序排序:
class Program { static void Main(string[] args) { #region LINQ orderby降序排序 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); } Console.Read(); #endregion } }View Code
運行結果如下:
下麵代碼演示orderby主要和次要排序(示例一):
class Program { static void Main(string[] args) { #region LINQ 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); } Console.Read(); #endregion } }View Code
運行結果如下:
下麵代碼演示orderby主要和次要排序(示例二):
class Program { static void Main(string[] args) { #region LINQ orderby主要和次要排序示例二 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); } Console.Read(); #endregion } }View Code
運行結果如下:
三、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 |
下麵代碼演示過濾數據:
class Program { static void Main(string[] args) { #region LINQ 過濾數據 string[] words = { "the", "quick", "brown", "fox", "jumps" }; var query = from word in words where word.Length == 3 select word; foreach (var word in query) { Console.WriteLine(word); } Console.Read(); #endregion } }View Code
運行結果如下:
五、量詞操作
限定符運算返回一個Boolean值,該值指示序列中是否有一些元素滿足條件或是否所有元素都滿足條件。
下圖描述了兩個不同源序列上的兩個不同限定符運算。第一個運算詢問是否有一個或多個元素為字元“A”,結果為true,第二個運算詢問是否所有元素都為字符“A”,結果為true。
方法名 |
說明 |
C# 查詢表達式語法 |
All |
確定是否序列中的所有元素都滿足條件。 |
X |
Any |
確定序列中是否有元素滿足條件。 |
X |
Contains |
確定序列是否包含指定的元素。 |
X |
六、投影操作
投影是指將對象轉換為一種新形式的操作,該形式通常只包含那些將隨後使用的屬性。通過使用投影,您可以構建依據每個對象生成的新類型,還可以映射
屬性,並對該屬性執行數學函數,另外還可以在不更改原始對象的情況下映射該對象。
方法名 | 說明 | C# 查詢表達式語法 |
Select | 映射基於轉換函數的值。 | select |
SelectMany | 映射基於轉換函數的值序列,然後將它們展平為一個序列。 | 使用多個 from 子句 |
下麵代碼演示Select:
class Program { static void Main(string[] args) { #region LINQ 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); } Console.Read(); #endregion } }View Code
運行結果如下:
下麵代碼演示SelectMany:
class Program { static void Main(string[] args) { #region LINQ SelectMany 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); } Console.Read(); #endregion } }View Code
運行結果如下:
Select()和SelectMany()的工作都是依據源值生成一個或多個結果值。Select()為每個源值生成一個結果值。因此,總體結果是一個與源集合具有相同元素數
目的集合。與之相反,SelectMany()將生成單一總體結果,其中包含來自每個源值的串聯子集合。作為參數傳遞到SelectMany()的轉換函數必須為每個源值返
回一個可枚舉值序列,然後,SelectMany() 將串聯這些可枚舉序列以創建一個大的序列。
下麵兩個插圖演示了這兩個方法的操作之間的概念性區別。在每種情況下,假定選擇器(轉換)函數從每個源值中選擇一個由花卉數據組成的數組。
下圖描述 Select() 如何返回一個與源集合具有相同元素數目的集合。
下圖描述SelectMany()如何將中間數組序列串聯為一個最終結果值,其中包含每個中間數組中的每個值。
下麵的示例比較Select()和SelectMany()的行為。代碼將通過從源集合的每個花卉名稱列表中提取前兩項來創建一個“花束”。在此示例中,轉換函數Select使用
的“單一值”本身就是一個值集合。這需要額外的foreach迴圈,以便枚舉每個子序列中的每個字元串。
/// <summary> /// 花束類 /// </summary> class Bouquet { public List<string> Flowers { get; set; } } class Program { static void Main(string[] args) { #region LINQ Select和SelectMany比較 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(); #endregion } }View Code
運行結果如下:
七、劃分數據
LINQ 中的分區指的是在不重新排列元素的情況下,將輸入序列劃分為兩部分,然後返回其中一個部分的操作。
下圖顯示對一個字元序列執行三個不同的分區操作的結果。第一個操作返回序列中的前三個元素;第二個操作跳過前三個元素,返回剩餘的元素;第三個操作
跳過序列中的前兩個元素,返回接下來的三個元素。
運算符名稱 |
說明 |
C# 查詢表達式語法 |
Skip |
跳過序列中的指定位置之前的元素。 |
X |
SkipWhile |
基於謂詞函數跳過元素,直到某元素不再滿足條件。 |
X |
Take |
提取序列中的指定位置之前的元素。 |
X |
TakeWhile |
基於謂詞函數提取元素,直到某元素不再滿足條件。 | X |
八、聯接操作
將兩個數據源“聯接”就是將一個數據源中的對象與另一個數據源中共用某個通用特性的對象關聯起來。
當查詢所面向的數據源相互之間具有無法直接領會的關係時,聯接就成為一項重要的運算。在面向對象的編程中,這可能意味著在未建模對象之間進行關聯,
例如對單向關係進行反向推理。
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 |
下麵代碼演示分組數據:
class Program { static void Main(string[] args) { #region LINQ 分組數據 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 item in group) { Console.WriteLine(item); } } Console.Read(); #endregion } }View Code
運行結果如下:
十、生成操作
生成是指創建新的值序列。
方法名 |
說明 |
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查詢。