yield的使用及原理

不久前在點部落格看到有人在說明yield這個關鍵字的用法。

那位作者大致上只是說明yield該如何使用而已,而沒有提到它有什麼好處,以及它的運作原理。

今天突然想要研究yield的用法,就去google了一下。沒想到找到幾篇不錯的文章。趁機記錄下來以便查詢。

在這篇文章中,作者說明了使用yield的呼叫流程。基本上,一開始大家的作法都會是先將要傳回的資料整理成一份List,再傳回給外面呼叫的函式來使用。這樣的方式有些缺點:

  1. 要耗用一個List的變數及記憶體空間。

  2. 要花時間將資料放入到List中,傳回。


而在改用yield之後,可以避免掉以上的兩個缺點。改為傳回一個IEnumerator<>的變數,讓呼叫的函式來使用。因為是IEnumerator,所以事先不用複製一份,像是放在List中這樣傳回。

當然,您也是可以自己提供一個自己實作IEnumerator<>的物件傳回給呼叫端使用。不過,這樣等於您自己還在要寫一段程式碼。

使用yield之後,實作IEnumerator<> 的工作等於是編譯器會幫您在背後進行。至於編譯器所產生的程式碼可看這篇文章中,作者用反組譯程式所看到的程式碼那樣。

說到這裏,可能要來寫個範例,大家比較能知道其中的差異及效能上的差別。

程式碼如下:
        static void TestYield()
{
Random r = new Random();

List listData = new List();

for (int index = 0; index < 10000; index++)
{
listData.Add(r.Next());
}

Stopwatch watch = new Stopwatch();

watch.Reset();
watch.Start();  

// Get data by normal list

for (int i = 0; i < 1000; i++)
{
List result = GetDataByList(listData);
Console.WriteLine("Test(ByList) #" + i.ToString("00000") + ",Count = " + result.Count.ToString());
}

watch.Stop();
Console.WriteLine("Total time " + watch.ElapsedMilliseconds.ToString());

// Get data by yield
watch.Reset();
watch.Start();
for (int i = 0; i < 1000; i++)
{
IEnumerator result = GetDataByYield(listData);
Console.WriteLine("Test(ByYield) #" + i.ToString("00000"));
}

watch.Stop();
Console.WriteLine("Total time " + watch.ElapsedMilliseconds.ToString());
}

上面這段程式碼主要是為了測試使用一般作法,跟使用yield的作法的差別。其中會呼叫到的兩個函式分別如下:

GetDataByList()這個函式,主要是將傳入的List資料中可被2整除的值傳回。它會先全部計算確認過,放到一個List中傳回給外面的函式來使用。
        static List GetDataByList(List listData)
{
List result = new List();
foreach (int data in listData)
{
if (data % 2 == 0)
result.Add(data);
}
return result;
}

而GetDataByYield()這個函式,也是有一樣的功能,不過,它不是一次算完才傳回,而是每得到一筆資料就傳回一筆。

        static IEnumerator GetDataByYield(List listData)
{
foreach (int data in listData)
{
if (data % 2 == 0)
yield return data;
}
}

執行測試的程式碼後,會發現兩者的效能的確有差。第一個結果是用ByList的方式取值的,總共花了487ms。而第二個結果是用yield的方式取值的,總共花了135ms。差了快3.5倍的效能了。所以可以的話,還是改用yield來增進效能囉。



留言

這個網誌中的熱門文章

DOS Batch指令檔中如何記錄log資訊

用捷徑方式執行需帶入命令列參數的Windows Form程式

使用regular expression來match中括號(square bracket)