初次使用C#中的yield

  這幾天在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裏面都有詳細的講解,感受沒有什麼好多說的。

相關文章
相關標籤/搜索