緣起
接續 前一篇 對 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) 的迴圈, 每一次才取回一筆.
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 提供的非泛型範例, 該文章提供了一個泛型的範例, 並作了效能的比較, 有泛型的, 效能比較好.
沒有留言:
張貼留言