︿
Top

2015年1月17日 星期六

C# yield

緣起

接續 前一篇 對 IEnumerator 及 IEnumerable 的探討, 及進一步 Study, 可以發現, .NET Framework 的集合物件都有實作 IEnumerable (非泛型) 或 IEnumerable<T> (泛型),
故若要產生一個物件集合, 供外部逐一取出元素, 直接利用現成的泛型類別 (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, 則用到的機會應該比較大, 參考看看囉.

參考文件



沒有留言:

張貼留言