本篇將圍繞 《試試 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 進行了重寫,讀者可以直接在本博客的頁面上運行這些示例。
如果無法正常的展示示例,讀者也可以通過本倉庫下載示例相關的代碼。