🍔 從 foreach 官方文件開始 想要使用 foreach 的集合需要實作 System.Collections.IEnumerable or System.Collections.Generic.IEnumerable interface
foreach 的真正實現,是一個迴圈控制結構,必須知道:
怎麼取得下一筆資料 (MoveNext)
怎麼拿到目前這筆資料 (Current)
怎麼從頭開始 (GetEnumerator)
這一切都來自於 Iterator 模式,也就是 .NET 中 IEnumerable / IEnumerator 的設計核心。
🍔 有一天我在摩斯排隊點餐,但前面點餐的大叔點有點久,於是我開始發白日夢… 一位客人
1 2 3 4 5 6 7 public class FatLaw { public int Number {get ;set ;} public string [] Menu {get ;set ;} }
排隊點餐的一群客人
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public class FatLawQueue : IEnumerable <FatLaw >{ private List<FatLaw> fatlaws = new List<FatLaw>(); public void Enqueue (FatLaw fatlaw ) { fatlaws.Add(fatlaw); } public IEnumerator<FatLaw> GetEnumerator () { return new FatLawEnumerator(fatlaws); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private class FatLawEnumerator : IEnumerator <FatLaw > { private List<FatLaw> _fatlaws; private int _currentIndex = -1 ; public FatLawEnumerator (List<FatLaw> fatlaws ) { _fatlaws = fatlaws; } public FatLaw Current => _fatlaws[_currentIndex]; object IEnumerator.Current => Current; public bool MoveNext () { _currentIndex ++; return _currentIndex < _fatlaws.Count; } public void Reset () { _currentIndex = -1 ; } public void Dispose () => _fatlaws = null ; } }
在沒有 foreach 之前,開發者會這樣寫
1 2 3 4 5 6 var enumerator = fatLawQueue.GetEnumerator();while (enumerator.MoveNext()){ var item = enumerator.Current; }
現在集合只要能「一個一個地走訪」,就能用 foreach。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 var fatLawQueue = new FatLawQueue();fatLawQueue.Enqueue(new FatLaw() { Number = 1 , Menu = new string [] {"雙人分享餐" ,"雪碧" }}); fatLawQueue.Enqueue(new FatLaw() { Number = 2 , Menu = new string [] {"摩斯雞塊豪雪套餐" ,"可樂" }}); fatLawQueue.Enqueue(new FatLaw() { Number = 3 , Menu = new string [] {"黃金炸蝦堡" ,"可可" }}); fatLawQueue.Enqueue(new FatLaw() { Number = 4 , Menu = new string [] {"藜麥杏鮑菇珍珠堡" ,"摩斯咖啡" }}); foreach (var fatLaw in fatLawQueue){ "♬ ♬ ♩ ♪ ♪ ♫ ♭ ♫ ♪ ♪ ♫ ♭ ♫ ♭ ♭ ♫ ♪ ♪ \n" .Dump(); $"歡迎光臨,肥佬 {fatLaw.Number} 號,請問今天吃甚麼 ? " .Dump(); $"{string .Join(" ," , fatLaw.Menu)} 是吧,客人吃有點多喔 " .Dump(); $"收您 {fatLaw.Money} ,謝謝光臨\n" .Dump(); }
🍔 改用 yield 在 MoveNext & Current 遍歷處理上,也可以用 yield return 來取代,它可以保留位置 & 狀態,下一次呼叫 MoveNext () 時,會從當前位置 & 狀態繼續執行。如此一來,遍歷的作法進一步被封裝起來,且省去自訂義 Enumerator的時間
yield return 是 C# 編譯器幫你自動產生一個類似你自己手寫的 IEnumerator 類別,它自動幫你記住「目前位置、狀態」,所以你不需要自己實作 MoveNext()、Current 等細節。因為 yield return 會把你的方法變成一個「可中斷又可繼續的流程(狀態機)」,這種設計叫做 Compiler-generated State Machine(編譯器產生的狀態機),可以把它想成:每遇到一個 yield return,編譯器都幫你標記一個中斷點(checkpoint),而在下一次呼叫 MoveNext() 時,它就從上一個中斷點「繼續往下執行」
1 2 3 4 5 6 7 8 9 public IEnumerator<FatLaw> GetEnumerator (){ foreach (var fatLaw in _fatLaws) { yield return fatLaw; } }
🍔 參考文章 IEnumerable 介面
Iteration statements - for
Iterator pattern