這幾天在Python程序員的微信訂閱號中老是見到yield的關鍵字,纔想起來在C#中也是有yield,可是隻是知道有,歷來沒有了解過他的用法,今天有時間就來看看是怎麼使用的。剛開始確定就是搜索一下用法了,找到兩篇說明示例,一是 C# 中的"yield"使用,第二個是MSDN的官方api yield(C# 參考)html
說實話第一個示例看完仍是很模糊的概念,例子也沒有看懂是在幹嗎,一直到MSDN中給出結果集我才明白了到底的用法是怎麼樣的。程序員
先來舉例一個需求: 一個方法返回一個IEnumerable 類型結果集(例如返回一個list<int>的結果),一般的代碼是這樣的express
1 /// <summary> 2 /// 3 /// </summary> 4 /// <returns></returns> 5 public IEnumerable<int> Method() 6 { 7 List<int> results = new List<int>(); 8 int counter = 0; 9 int result = 1; 10 11 while (counter++ < 10) 12 { 13 result = result * 2; 14 results.Add(result); 15 } 16 return results; 17 }
這樣就完成了需求。api
可是 有了yield以後就能夠這樣寫了安全
1 /// <summary> 2 /// 3 /// </summary> 4 /// <returns></returns> 5 public IEnumerable<int> YieldDemo() 6 { 7 int counter = 0; 8 int result = 1; 9 while (counter++ < 10) 10 { 11 result = result * 2; 12 yield return result; 13 } 14 }
兩種效果是同樣的,可是從我我的而言我喜歡第二個,感受更簡潔一些。微信
題外話:在寫這兩個例子中我又增長了一個知識點函數
返回值IEnumerable其實和IEnumerable<object>是等價的,只是IEnumerable的結果須要動態的解析; 測試
可是還搞不清楚這二者實現有什麼區別,因此我想看看這兩個在作同一件事的時候效率如何,下面來嘗試使用while循環10000000的取數據的耗時比較spa
使用 BenchmarkDotNet 測試結果以下.net
使用Stopwatch測試結果也是同樣的
從這個測試裏面能夠看出YieldDemo方法幾乎沒有耗時,可是實際狀況是不可能的吧,因此我又嘗試作了遍歷的測試
1 Stopwatch stop = new Stopwatch(); 2 stop.Start(); 3 var res = new YieldTest().YieldDemo(); 4 foreach (var item in res) 5 { 6 7 } 8 var a = stop.ElapsedMilliseconds; 9 stop.Restart(); 10 11 12 var rrrrr = new YieldTest().Method(); 13 foreach (var item in rrrrr) 14 { 15 16 } 17 var b = stop.ElapsedMilliseconds; 18 stop.Restart();
這個測試的結果是a=168,b=142.對比上一個測試結果讓我更加疑惑,我就開始打斷點,看看執行的順序是怎樣的。
結果以下:
在 第三行 斷點壓根就沒有進YieldDemo這個方法,而是當進行foreach 遍歷結果的時候,纔開始進入了YieldDemo這個方法,更奇怪的是每次的foreach 都會進入YieldDemo的while一次去取數據
這個結果讓我有點懵了,只能再仔細看看文檔解析,
迭代器方法運行到 yield return 語句時,會返回一個 expression,並保留當前在代碼中的位置。 下次調用迭代器函數時,將從該位置從新開始執行。 能夠使用 yield break 語句來終止迭代。
貌似這裏面是涉及到了迭代器的東西。立刻找迭代器的知識點,在 詳解C# 迭代器 中看到這樣一句解釋
須要強調的一點是,對於迭代塊,雖然咱們寫的方法看起來像是在順序執行,實際上咱們是讓編譯器來爲咱們建立了一個狀態機。這就是在C#1中咱們書寫的那部分代碼---調用者每次調用只須要返回一個值,所以咱們須要記住最後一次返回值時,在集合中位置。
當編譯器遇到迭代塊時,它建立了一個實現了狀態機的內部類。這個類記住了咱們迭代器的準確當前位置以及本地變量,包括參數。
這句話貌似解析了上面的疑問,可是看的有點雲裏霧裏,還要花時間消化一下里面的具體原理 。
官方提示使用yield有一些限制,須要注意
1 不能將 yield return 語句置於 try-catch 塊中。 可將 yield return 語句置於 try-finally 語句的 try 塊中。 2 可將 yield break 語句置於 try 塊或 catch 塊中,但不能將其置於 finally 塊中。 3 若是 foreach 主體(在迭代器方法以外)引起異常,則將執行迭代器方法中的 finally 塊。 4 匿名方法。 有關詳細信息,請參閱匿名方法。 5 包含不安全的塊的方法。 有關詳細信息,請參閱unsafe。
針對第一點讓我感受使用好有限制,爲啥不能在try-catch 中使用呢?
關於其餘的一些使用方法在MSDN裏面都有詳細的講解,感受沒有什麼好多說的。