總結一下 IEnumerable 的例子

  • 2020 年 3 月 16 日
  • 筆記

本篇將圍繞 《試試IEnumerable的10個小例子》和《試試IEnumerable的另外6個小例子》給出的例子,總結一下對於IEnumerable介面的一些使用方法,希望讀者能夠從中獲得一些啟發。

框架類型的迭代

對於一個實現了IEnumerable介面的類型來說,開發中最常用的,就是把這個類型的對象放入到foreach等循環關鍵詞中進行迭代,遍歷其中的元素進行處理。

這種遍歷通常分為兩種目的:遍歷和查找。

IEnumerable及其泛型版本IEnumerable<T>定義了一個類型的「可迭代性」。這點很容易理解,系統中的很多集合類型都實現了該介面。

因此這些集合類型均可以採用foreach進行迭代遍歷。但是每個集合類型的迭代方式和結果是不完全相同的,這取決於集合本身的特性。例如:

  • List<>Stack<>Queue<>的迭代的順序不相同,因為數據結構本身要求是不同的
  • ConcurrentDictionary<,>Dictionary<,>在迭代時的執行緒安全性是不同的,因為針對執行緒安全的設計是不同的
  • BlockingCollection.GetConsumingEnumerable方法返回一個會產生阻塞的消費者對象,

所以,即使都是丟進foreach,但是效果也是不完全一樣的。使用這些,需要讀者對這些類型本身需要增進了解。

建議讀者在使用框架中實現了IEnumerable的類型時,一定要注意迭代的細節,可以通過MSDN上的文檔了解其特殊性。

Linq

Linq是一個說小不小的話題,這裡只是說其中的 Linq To Object 部分內容。

通過Linq中提供的一些擴展方法,可以方便的控制對於一個IEnumerable對象的迭代方式。通過這些方法的應用,可以在很多時候避免複雜的條件和循環嵌套。

同時,Linq中抽象的Func和Action,也要求開發人員在平時的編寫過程中注意對於迭代本身的歸類和整理。Where(IsLeapYear)會比Where(x=>(x % 4 == 0 && x % 100 != 0) || x % 400 == 0)來的更加容易閱讀。

設計複雜的數據結構及其迭代演算法

除了基礎的數據結構,開發過程中有時需要自定義一些集合類型。這些集合類型需要自己實現一個迭代過程。例如:二叉樹及其遍歷,對列表進行分頁等等。

這些數據結構的迭代通常需要特定演算法的支援。

在《試試IEnumerable的另外6個小例子》中關於樹的幾個例子便數據此類中。

本地函數

在C#7.0引入了本地函數之後, IEnumerable結合本地函數,快速實現自定義迭代過程的奇怪操作也就跟著出現。

通過這種操作可以在一個函數內採用一些以前不容易實現的方式實現一些操作:

  • 將多重循環拉平
  • 將多級條件判斷變為循環判斷
  • 無需創建新的類就能快速生成一個上下文需要的特殊迭代演算法

這相關的例子在《試試IEnumerable的10個小例子》中較多。

按照月老闆的名言:「業務複雜度是不會因為系統設計變化而減少的,它只是從一個地方轉移到了另外的地方。」,我們可以知道,這種寫法其實沒有使得原來就有的判斷和循環變少。只是改變了語法結構。

讀者可以將這種操作作為一種「語法糖」進行使用。如果是在團隊項目中,則需要尊重團隊成員的共同意見,因為這種操作並非所有人都願意接受。

當然,這種做法在一些地方會產生好處。例如在將本地函數、IEnumerable和Task相結合的 T10測試網路連接 中。這種寫法就減少了傳統寫法中需要創建一個List或者Array的開銷。

總之,這種寫法,提供了一種新的思路。是否一定要使用,將取決於讀者團隊的接受程度。

非同步迭代器

在 C# 8 和 .netcore 3.0 到來的版本中,我們迎接到了IAsyncEnumerable介面來實現非同步迭代器的功能。

IEnumerable是同步方法的迭代器,IAsyncEnumerable可以看做是其非同步版本。有了這個介面,那麼在迭代的過程中也可以充分利用async/await帶來的編程快感。

本系列中沒有添加這部分的示例,但是主體思路是一致的。

她的出現,只會使得開發者更容易應用以上總結的幾種主要場景。

詳細的例子,可以參見相關文章進行了解

總結

本系列到此便結束了,希望讀者多在實踐中體會以上總結的幾種使用場景。

本系列中的例子已經全部使用dotnetfiddle.net進行了重寫,讀者可以直接在本部落格的頁面上運行這些示例。

如果無法正常的展示示例,讀者也可以通過本倉庫下載示例相關的程式碼