緣起
接續 前一篇 對 IEnumerator 及 IEnumerable 的探討, 及進一步 Study, 可以發現, .NET Framework 的集合物件都有實作 IEnumerable (非泛型) 或 IEnumerable<T> (泛型),
故若要產生一個物件集合, 供外部逐一取出元素, 直接利用現成的泛型類別 (ex: List<T>, Dictionary<T>) 產生之後, 回傳給呼叫端; 再由呼叫端用 foreach 逐一讀取, 這是很直覺的寫法.
但有沒有更方便的方式呢? 在某些情境下, 或許可以用 yield 這個 Syntactic Sugar.
完整程式範例, 筆者放在 GitHub, 請由此下載.
故若要產生一個物件集合, 供外部逐一取出元素, 直接利用現成的泛型類別 (ex: List<T>, Dictionary<T>) 產生之後, 回傳給呼叫端; 再由呼叫端用 foreach 逐一讀取, 這是很直覺的寫法.
但有沒有更方便的方式呢? 在某些情境下, 或許可以用 yield 這個 Syntactic Sugar.
完整程式範例, 筆者放在 GitHub, 請由此下載.
程式範例
茲就以下的範例作說明:
1. GetEmployees1(): 採用直覺的寫法, 需要一個 List<Employee> 型別的物件作暫存, 產生集合後, 整個回傳. 由外部逐一取出元素,
2. GetEmployees2(): 採用 yield 的寫法, 不需要一個 List<Employee> 型別的物件作暫存.
重要的是, 當您逐步偵錯時, 可以發現執行至 IEnumerable<Employee> emps2 = GetEmployees2(); 時, 程式並未進去 GetEmployees2() 方法, 而是在 Main() 裡 foreach (var item in emps2) 的迴圈, 每一次才取回一筆.
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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | class Employee { public string Id { get ; set ; } public string Name { get ; set ; } } class Program { /// <summary> /// 回傳 User000 - User004 (by 一般的方式) /// </summary> /// <returns></returns> private static IEnumerable<Employee> GetEmployees1() { List<Employee> emps = new List<Employee>(); for ( int i = 0; i < 5; i++) { emps.Add( new Employee() { Id = String.Format( "{0:D3}" , i), Name = "User" + String.Format( "{0:D3}" , i) }); } return emps; } /// <summary> /// 回傳 User000 - User004 (by yield) /// </summary> /// <returns></returns> private static IEnumerable<Employee> GetEmployees2() { for ( int i = 0; i < 5; i++) { yield return ( new Employee() { Id = String.Format( "{0:D3}" , i), Name = "User" + String.Format( "{0:D3}" , i) }); } } static void Main( string [] args) { Console.WriteLine( "===== Without yield ====" ); IEnumerable<Employee> emps1 = GetEmployees1(); foreach (var item in emps1) { Console.WriteLine( "Id={0}, Name={1}" , item.Id, item.Name); } Console.WriteLine( "===== With yield ====" ); IEnumerable<Employee> emps2 = GetEmployees2(); foreach (var item in emps2) { Console.WriteLine( "Id={0}, Name={1}" , item.Id, item.Name); } Console.ReadLine(); ////Output: //===== Without yield ==== //Id=000, Name=User000 //Id=001, Name=User001 //Id=002, Name=User002 //Id=003, Name=User003 //Id=004, Name=User004 //===== With yield ==== //Id=000, Name=User000 //Id=001, Name=User001 //Id=002, Name=User002 //Id=003, Name=User003 //Id=004, Name=User004 } } |
依 ASP.NET MVC 5 網站開發美學 Ch03 的說明, yield 也是一個編譯器魔法 (compiler magic, 雷同於 Syntactic Sugar), 編譯器會在 yield 指令, 產生一個迭代運算的有限狀態機 (state machine), 這個狀態機主要控制巡覽時的存取動作, 每次巡覽觸發時, 才真正進入集合撈資料; 亦即對集合物件的存取, 會推遲到真正查詢時才觸發, 這個機制稱為延遲查詢 (Deferred Query) 或 延遲執行 (Deferred Execution)
關於 yield 的編譯細節, 可以參考以下 2 篇文章.
總結
本篇介紹了 yield 的使用方式, 就應用程式開發而言, 用到的機會應該不多, 但若是發展 Framework, 則用到的機會應該比較大, 參考看看囉.
參考文件
- {Book} ASP.NET MVC 5 網站開發美學 Ch03
- 針對 LINQ 的一些必備知識作說明
- {MSDN} yield
- 官方的說明
- {C# in Depth} Iterator block implementation details: auto-generated state machines
- 利用反組譯工具, 呈現 yield 區塊, 經由編譯器第1段編譯後的程式段落
- {Shawn Hargreaves Blog} Iterator state machines
- 如何利用 yield 建立 state machine 的程式
- {限量ㄟ蓋步} C# - yeild return 使用方法
- 內含 yield 的使用範例
- {Youtube} C# yield's Syntactic Sugar:
- 這個影片有用到 .NET Reflector 工具, 展示如何取得 yield 的實際對應程式內容.
- [DotNetPerls] C# Yield
- 相對於 MSDN 提供的非泛型範例, 該文章提供了一個泛型的範例, 並作了效能的比較, 有泛型的, 效能比較好.
沒有留言:
張貼留言