C# yield關鍵字解析

相信好多程序員都是因爲unity的協程(Coroutine)認識yield這個關鍵字的,知道在unity的開發中諸如yield return null、yield return new WaitForSeconds(1.0f)的用法,其實yield是C#的關鍵字,unity的協程只是在c#的基礎上做了一層封裝,我們現在來看看yield這個關鍵字。說到yield就不得不說迭代器,迭代器模式是設計模式的一種,因爲其運用的普遍性,很多語言都有內嵌的原生支持。在.NET中,迭代器模式是通過IEnumerator、IEnumerable兩個接口和兩個同名的泛型接口來封裝的:public interface IEnumerator { object Current { get; } bool MoveNext(); void Reset(); }IEnumerator只定義了一個屬性、兩個函數,Current爲迭代器的當前值,通過調用MoveNext函數讓迭代器的前進一步,返回值表示該迭代器是否結束,Reset函數用於重置數據。 public interface IEnumerable { IEnumerator GetEnumerator(); }IEnumerable更簡單,返回迭代器。一般這兩個接口的實現位於不同的類中。
foreach關鍵字之所以能方便對數組、List、Dictionary進行循環,其實也是在背後調用IEnumarator的MoveNext函數從頭遍歷到尾,取出每次的Current值,說白了它是個語法糖,在編譯後會對我們的代碼自動替換。我們來看下List的迭代器實現: public struct Enumerator : IEnumerator, System.Collections.IEnumerator { private List list; private int index; private int version; private T current; internal Enumerator(List list) { this.list = list; index = 0; version = list._version; current = default(T); } public void Dispose() { } public bool MoveNext() { List localList = list; if (version == localList._version && ((uint)index < (uint)localList._size)) { current = localList._items[index]; index++; return true; } return MoveNextRare(); } private bool MoveNextRare() { if (version != list._version) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); } index = list._size + 1; current = default(T); return false; } public T Current { get { return current; } } Object System.Collections.IEnumerator.Current { get { if( index == 0 || index == list._size + 1) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen); } return Current; } } void System.Collections.IEnumerator.Reset() { if (version != list._version) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); } index = 0; current = default(T); } }public class List : IEnumerable, ICollection, IList, ICollection, IEnumerable, IList{ IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(this); }可以看到其實現是規規矩矩的繼承了IEnumerator、IEnumerable及其兩個泛型接口,一切都很完美,只有一個問題,是什麼問題呢?答:寫的太累了(手動滑稽)。終於引出了yield,沒錯,yield可以大大的簡化迭代器代碼,讓Coder寫起來更加輕鬆自在,我們的迭代代碼可以這樣寫: public class Iteration: IEnumerable { public List lstInfo = new List() { 1, 3, 5, 7, 9, 11 }; public IEnumerator GetEnumerator() { for (int i = 0; i < lstInfo.Count; ++i) { yield return lstInfo[i]; } } }對於使用者來說方式還是一樣: static void IterationTest() { Iteration obj = new Iteration(); foreach (var item in obj) { Console.WriteLine(item); } }當然啦,List的迭代器代碼還是好多是關於版本號判斷的,我們的示例並沒有相關的邏輯,不過就算是加上,代碼依然可以精簡很多,這就是yield的魅力所在。有些人看到這可能還是迷惑,因爲大部分的程序員的思路都是線性的,上面的Iteration類的GetEnumerator函數的for循環不是一下都遍歷完了嗎,怎麼還能給foreach用,好蒙啊。。。yield很神奇吧?是這樣的:Jon Skeet說:“迭代器模式的一個重要方面就是:不用一次返回所有數據,調用代碼一次只需獲取一個元素。”你可以理解爲每次執行yield return都能夠返回一個數據並暫停當前的狀態,那暫停的狀態什麼時候會繼續呢?在下一次調用到MoveNext的時候。什麼時候會調用MoveNext?foreach執行完一次,進入下一次的時候。
如果還不是很明白,我們再來看看《c# in Depth》的經典例子:class IteratorWorkflow { static readonly string Padding = new string(' ', 30); static IEnumerable GetEnumerable() { Console.WriteLine("{0}Start of GetEnumerator()", Padding); for (int i = 0; i < 3; i++) { Console.WriteLine("{0}About to yield {1}", Padding, i); yield return i; Console.WriteLine("{0}After yield", Padding); } Console.WriteLine("{0}Yielding final value", Padding); yield return -1; Console.WriteLine("{0}End of GetEnumerator()", Padding); } public static void Main() { IEnumerable iterable = GetEnumerable(); IEnumerator iterator = iterable.GetEnumerator(); Console.WriteLine("Starting to iterate"); while (true) { Console.WriteLine("Calling MoveNext()..."); bool result = iterator.MoveNext(); Console.WriteLine("... MoveNext result={0}", result); if (!result) { break; } Console.WriteLine("Fetching Current..."); Console.WriteLine("... Current result={0}", iterator.Current); } } }輸出的結果爲: 我相信,如果你有對照着這個例子認真分析一遍的話,應該就能掌握yield這個知識點了,如果還不清楚,代碼Copy下來,自己跑一遍~~
最後有幾個知識點總結歸納一下:
1·在遇到yield break或者返回IEnumerator的函數體結束前,不管yield return 的值爲多少,MoveNext都是會返回True。
2·在第一次調用MoveNext之前,返回IEnumerable的代碼都不會執行,即使你有主動去調用它。
3·執行到yield return的地方,代碼就暫停了,並返回相應的值,在下一次調用MoveNext時,從上次暫停的地方繼續執行。
4·yield return 代碼不能放入try...catch塊中,但是能放入try...finally塊中。
更多unity2018的功能介紹請到paws3d爪爪學院查找。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章