在.NET中,迭代器模式是通過IEnumerator和IEnumerable接口以及它們的泛型版本來實現的。如果某個類實現了IEnumerable接口,就說明它可以被迭代訪問,調用GetEnumerator()方法將返回IEnumerator的實現,這個就是迭代器本身。
在C# 1.0中,利用foreach語句實現了訪問迭代器的內置支持,讓集合的遍歷變得簡單、明了。其實,foreach的實現就是調用GetEnumerator和MoveNext方法以及Current屬性。所以說,在C# 1.0中要獲得迭代器就必須實現IEnumerable接口中的GetEnumerator方法,要實現一個迭代器就要實現IEnumerator接口中的MoveNext和Reset方法
在C# 2.0中提供的語法糖來簡化迭代器的實現,可以通過yield關鍵字來簡化迭代器的實現。
C#多線程編程實例 線程與窗體交互【附源碼】 http://www.linuxidc.com/Linux/2014-07/104294.htm
C#數學運算表達式解釋器 http://www.linuxidc.com/Linux/2014-07/104289.htm
在C語言中解析JSON配置文件 http://www.linuxidc.com/Linux/2014-05/101822.htm
C++ Primer Plus 第6版 中文版 清晰有書簽PDF+源代碼 http://www.linuxidc.com/Linux/2014-05/101227.htm
假設我們要實現一個字符列表類型,並且可以通過foreach來遍歷這個類型。那麼,在C# 1.0中,就要實現IEnumerable和IEnumerator接口。
namespace IteratorTest { class Program { static void Main(string[] args) { CharList charList = new CharList("Hello World"); foreach (var c in charList) { Console.WriteLine(c); } Console.Read(); } } class CharList : IEnumerable { public string TargetStr { get; set; } public CharList(string str) { this.TargetStr = str; } public IEnumerator GetEnumerator() { return new CharIterator(this.TargetStr); } } class CharIterator : IEnumerator { //引用要遍歷的字符串 public string TargetStr { get; set; } //指出當前遍歷的位置 public int position { get; set; } public CharIterator(string targetStr) { this.TargetStr = targetStr; this.position = this.TargetStr.Length; } public object Current { get { if (this.position == -1 || this.position == this.TargetStr.Length) { throw new InvalidOperationException(); } return this.TargetStr[this.position]; } } public bool MoveNext() { //如果滿足繼續遍歷的條件,設置position的值 if (this.position != -1) { this.position--; } return this.position > -1; } public void Reset() { this.position = this.TargetStr.Length; } } }
在上面的例子中,CharIterator就是迭代器的實現,position字段存儲當前的迭代位置,通過Current屬性可以得到當前迭代位置的元素,MoveNext方法用於更新迭代位置,並且查看下一個迭代位置是不是有效的。
當我們通過VS單步調試下面語句的時候
foreach (var c in charList)
代碼首先執行到foreach語句的charList處獲得迭代器CharIterator的實例,然後代碼執行到in會調用迭代器的MoveNext方法,最後變量c會得到迭代器Current屬性的值;前面的步驟結束後,會開始一輪新的循環,調用MoveNext方法,獲取Current屬性的值。
通過C# 1.0中迭代器的代碼看到,要實現一個迭代器就要實現IEnumerator接口,然後實現IEnumerator接口中的MoveNext、Reset方法和Current屬性。
在C# 2.0中可以直接使用yield語句來簡化迭代器的實現。
class CharList : IEnumerable { public string TargetStr { get; set; } public CharList(string str) { this.TargetStr = str; } public IEnumerator GetEnumerator() { for (int index = this.TargetStr.Length; index > 0; index--) { yield return this.TargetStr[index-1]; } } }
通過上面的代碼可以看到,通過使用yield return語句,我們可以替換掉整個CharIterator類。
yield return語句就是告訴編譯器,要實現一個迭代器塊。如果GetEnumerator方法的返回類型是非泛型接口,那麼迭代器塊的生成類型(yield type)是object,否則就是泛型接口的類型參數。
通過IL代碼可以看到,對於yield return語句語句,編譯器為我們生成了一個嵌套的類型(nested type) <GetEnumerator>d__0,並且這個類實現了IEnumerator接口。
當編譯器遇到迭代塊時,它創建了一個實現了狀態機的內部類。這個類記住了我們迭代器的准確當前位置以及本地變量,包括參數。這個類有點類似與C# 1.0中手寫的那段代碼,它將所有需要記錄的狀態保存為實例變量。為了實現一個迭代器,這個狀態機需要按順序執行的操作:
注意,當我們想要避免迭代器中的裝箱和拆箱是,就要實現迭代器的泛型版本,由於泛型IEnumerable <T>接口繼承了泛型型IEnumerable接口,我們需要在泛型迭代器代碼中加入
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
這樣,非泛型方法轉而調用泛型方法,從而不需要再去實現非泛型的IEnumerable接口了。
前面簡單提到了迭代器的工作流程,下面我們通過一個例子進一步看看迭代器工作流程。
class Program { static readonly String Padding = new String(' ', 30); static IEnumerable<Int32> CreateEnumerable() { Console.WriteLine("{0}Start of CreateEnumerable", 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 CreateEnumerable()", Padding); } static void Main(string[] args) { IEnumerable<Int32> iterable = CreateEnumerable(); IEnumerator<Int32> iterator = iterable.GetEnumerator(); Console.WriteLine("Starting to iterate"); while (true) { Console.WriteLine("Calling MoveNext()..."); Boolean result = iterator.MoveNext(); Console.WriteLine("...MoveNext result={0}", result); if (!result) { break; } Console.WriteLine("Fetching Current..."); Console.WriteLine("...Current result={0}", iterator.Current); } Console.Read(); } }
一般迭代器都會結合foreach語句,然後foreach會在最後調用Dispose方法。這裡為了演示,代碼中使用while語句實現循環。
稍微打斷一下,插入一個內容的介紹,通常為了實現IEnumerable,我們只會返回IEnumerator;如果僅僅是在方法中生成一個序列,可以返回IEnumerable。所以將代碼改為下面的方式也可以工作:
static IEnumerator<Int32> CreateEnumerable() { …… } …… //IEnumerable<Int32> iterable = CreateEnumerable(); IEnumerator<Int32> iterator = CreateEnumerable();
兩種方式的IL代碼是不同的,這裡只列出了編譯器內嵌類型實現了那些接口,更詳細的內容可以通過ILSpy查看:
返回IEnumerable
.class nested private auto ansi sealed beforefieldinit '<CreateEnumerable>d__0' extends [mscorlib]System.Object implements class [mscorlib]System.Collections.Generic.IEnumerable`1<int32>, [mscorlib]System.Collections.IEnumerable, class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>, [mscorlib]System.Collections.IEnumerator, [mscorlib]System.IDisposable {……}
返回IEnumerator
.class nested private auto ansi sealed beforefieldinit '<CreateEnumerable>d__0' extends [mscorlib]System.Object implements class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>, [mscorlib]System.Collections.IEnumerator, [mscorlib]System.IDisposable {……}
回到這個例子,程序的輸出結果為:
在這段代碼中有幾個注意點:
第一點尤為重要:這意味著如果在方法調用時需要立即執行,就不能使用迭代器塊。例如如果將參數驗證放在迭代塊中,那麼他將不能夠很好的起作用,這是經常會導致的錯誤的地方,而且這種錯誤不容易發現。
更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2015-02/114151p2.htm