在匹夫的上一篇文章《匹夫細說C#:不是“棧類型”的值類型,從生命周期聊存儲位置》的最后,匹夫以總結和后記的方式涉及到一部分迭代器的知識。但是覺得還是不夠過癮,很多需要說清楚的內容還是含糊不清,所以這周就專門寫一下c#中的迭代器吧。
首先思考一下,在什么情景下我們需要使用到迭代器?
假設我們有一個數據容器(可能是Array,List,Tree等等),對我們這些使用者來說,我們顯然希望這個數據容器能提供一種無需了解它的內部實現就可以獲取其元素的方法,無論它是Array還是List或者別的什么,我們希望可以通過相同的方法達到我們的目的。
此時,迭代器模式(iterator pattern)便應運而生,它通過持有迭代狀態,追蹤當前元素并且識別下一個需要被迭代的元素,從而可以讓使用者透過特定的界面巡訪容器中的每一個元素而不用了解底層的實現。
那么,在c#中,迭代器到底是以一個怎樣的面目出現的呢?
如我們所知,它們被封裝在IEnumerable和IEnumerator這兩個接口中(當然,還有它們的泛型形式,要注意的是泛型形式顯然是強類型的。且IEnumerator<T>實現了IDisposable接口)。
IEnumerable非泛型形式:
//IEnumerable非泛型形式[ComVisibleAttribute(True)][GuidAttribute("496B0ABE-CDEE-11d3-88E8-00902754C43A")]public interface IEnumerable{ IEnumerator GetEnumerator();}
IEnumerator非泛型形式:
//IEnumerator非泛型形式[ComVisibleAttribute(true)][GuidAttribute("496B0ABF-CDEE-11d3-88E8-00902754C43A")]public interface IEnumerator{ Object Current {get;} bool MoveNext(); void Reset();}
IEnumerable泛型形式:
//IEnumerable泛型形式public interface IEnumerable<out T> : IEnumerable{ IEnumerator<T> GetEnumerator(); IEnumerator GetEnumerator(); }
IEnumerator泛型形式:
//IEnumerator泛型形式public interface IEnumerator<out T> : IDisposable, IEnumerator{ void Dispose(); Object Current {get;} T Current {get;} bool MoveNext(); void Reset(); }[ComVisibleAttribute(true)]public interface IDisposable{ void Dispose();}
IEnumerable接口定義了一個可以獲取IEnumerator的方法——GetEnumerator()。
而IEnumerator則在目標序列上實現循環迭代(使用MoveNext()方法,以及Current屬性來實現),直到你不再需要任何數據或者沒有數據可以被返回。使用這個接口,可以保證我們能夠實現常見的foreach循環。
到此,各位看官是否和曾經的匹夫有相同的疑惑呢?那就是為何IEnumerable自己不直接實現MoveNext()方法、提供Current屬性呢?為何還需要額外的一個接口IEnumerator來專門做這個工作?
OK,假設有兩個不同的迭代器要對同一個序列進行迭代。當然,這種情況很常見,比如我們使用兩個嵌套的foreach語句。我們自然希望兩者相安無事,不要互相影響彼此。所以自然而然的,我們需要保證這兩個獨立的迭代狀態能夠被正確的保存、處理。這也正是IEnumerator要做的工作。而為了不違背單一職責原則,不使IEnumerable擁有過多職責從而陷入分工不明的窘境,所以IEnumerable自己并沒有實現MoveNext()方法。
為了更直觀的了解一個迭代器,匹夫這里提供一個小例子。
using System;using System.Collections.Generic;class Class1{ static void Main() { foreach (string s in GetEnumerableTest()) { Console.WriteLine(s); } } static IEnumerable<string> GetEnumerableTest() { yield return "begin"; for (int i=0; i < 10; i++) { yield return i.ToString(); } yield return "end"; }}
輸出結果如圖:
OK,那么匹夫就給各位捋一下這段代碼的執行過程。
這個例子中迭代器的執行過程,匹夫已經給各位看官簡單的描述了一下。但是還有幾點需要關注的,匹夫也想提醒各位注意一下。
好啦,簡單總結了一下C#中的迭代器的外觀。那么接下來,我們繼續向內部前進,來看看迭代器究竟是如何實現的。
上一節我們已經從外部看到了IEnumerable和IEnumerator這兩個接口的用法了,但是它們的內部到底是如何實現的呢?兩者之間又有何區別呢?
既然要深入迭代器的內部,這就是一個不得不面對的問題。
那么匹夫就寫一個小程序,之后再通過反編譯的方式,看看在我們自己手動寫的代碼背后,編譯器究竟又給我們做了哪些工作吧。
為了簡便起見,這個小程序僅僅實現一個按順序返回0-9這10個數字的功能。
首先,我們定義一個返回IEnumerator<T>的方法TestIterator()。
//IEnumerator<T>測試using System;using System.Collections;class Test{ static IEnumerator<int> TestIterator() { for (int i = 0; i < 10; i++) { yield return i; } }}
接下來,我們看看反編譯之后的代碼,探查一下編譯器到底為我們做了什么吧。
internal class Test{ // Methods 注,此時還沒有執行任何我們寫的代碼 PRivate static IEnumerator<int> TestIterator() { return new <TestIterator>d__0(0); } // Nested Types 編譯器生成的類,用來實現迭代器。 [CompilerGenerated] private sealed class <TestIterator>d__0 : IEnumerator<int>, IEnumerator, IDisposable { // Fields 字段:state和current是默認出現的 private int <>1__state; private int <>2__current; public int <i>5__1;//<i>5__1來自我們迭代器塊中的局部變量,匹夫上一篇文章中提到過 // Methods 構造函數,初始化狀態 [DebuggerHidden] public <TestIterator>d__0(int <>1__state) { this.<>1__state = <>1__state; } // 幾乎所有的邏輯在這里 private bool MoveNext() { switch (this.<>1__state) { case 0: this.<>1__state = -1; this.<i>5__1 = 0; while (this.<i>5__1 < 10) { this.<>2__current = this.<i>5__1; this.<>1__state = 1; return true; Label_0046: this.<>1__state = -1; this.<i>5__1++; } break; case 1: goto Label_0046; } return false; } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } void IDisposable.Dispose() { } // Properties int IEnumerator<int>.Current { [DebuggerHidden] get { return this.<>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return this.<>2__current; } } }}
我們先全面的看一下反編譯之后的代碼,可以發現幾乎所有的邏輯都發生在MoveNext()方法中。那么之后我們再詳細介紹下它,現在我們先從上到下把代碼捋一遍。
OK,IEnumerator接口我們看完了。下面再來看看另一個接口IEnumerable吧。
依樣畫葫蘆,這次我們仍然是寫一個實現按順序返回0-9這10個數字的功能的小程序,只不過返回類型變為IEnumerable<T>。
using System;using System.Collections.Generic;class Test{ static IEnumerable<int> TestIterator() { for (int i = 0; i < 10; i++) { yield return i; } }}
之后,我們同樣通過反編譯,看看編譯器又背著我們做了什么。
internal class Test{ private static IEnumerable<int> TestIterator() { return new <TestIterator>d__0(-2); } private sealed class <TestIterator>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable { // Fields private int <>1__state; private int <>2__current; private int <>l__initialThreadId; public int <count>5__1; public <TestIterator>d__0(int <>1__state) { this.<>1__state = <>1__state; this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId; }
新聞熱點
疑難解答