第12章中,咱們看到能夠用foreach語句遍歷數組。在本章,咱們會進一步探討數組,來看看爲何它們能夠被foreach語句處理。咱們還會研究如何使用迭代器爲用戶自定義類增長該功能。數組
數組foreach語句爲咱們依次取出數組中的每一個元素。安全
int[] arr1={10,11,12,13}; foreach(int item in arr1) { Console.WriteLine("Item value: {0}",item); }
爲何數組能夠這麼作?由於數組能夠按需提供一個叫作枚舉器(enumerator)的對象。枚舉器能夠依次返回請求的數組中的元素。枚舉器「知道」項的次序而且跟蹤它在序列中的位置,而後返回請求的當前項。
對於由枚舉器的類型,必須有一個方法來獲取它。獲取對象枚舉器的方法是調用對象的GetEnumerator方法。實現GetEnumerator方法的類型叫作可枚舉類型(enumerable type或enumerable)。數組是可枚舉類型。
函數
foreach結構設計用來和可枚舉類型一塊兒使用。只要給它的遍歷對象是可枚舉類型,它就會執行以下行爲:編碼
實現了IEnumerator接口的枚舉器包含3個函數成員:Current、MoveNext、Reset。spa
枚舉器與序列中的當前項保持聯繫的方式徹底取決於實現。能夠經過對象引用、索引值或其餘方式來實現。對於內置的一維數組來講,就是用項的索引。
設計
有了集合的枚舉器,咱們就可使用MoveNext和Current成員來模仿foreach循環遍歷集合中的項。
例:手動作foreach語句自動作的事情。3d
class Program { static void Main() { int[] MyArray={10,11,12,13}; IEnumerator ie=MyArray.GetEnumerator(); while(ie.MoveNext()) { int i=(int)ie.Current; Console.WriteLine("{0}",i); } } }
IEnumerator接口
可枚舉類是指實現了IEnumerator接口的類。IEnumerator接口只有一個成員–GetEnumerator方法,它返回對象的枚舉器。code
下面是個可枚舉類的完整示例,類名Spectrum,枚舉器類爲ColorEnumerator。對象
using System; using System.Collections; class ColorEnumerator:IEnumerator { string[] _colors; int _position=-1; public ColorEnumerator(string[] theColors) { _colors=new string[theColors.Length]; for(int i=0;i<theColors.Length;i++) { _colors[i]=theColors[i]; } } public object Current { get { if(_position==-1||_position>=_colors.Length) { throw new InvalidOperationException(); } return _colors[_position]; } } public bool MoveNext() { if(_position<_colors.Length-1) { _position++; return true; } else { return false; } } public void Reset() { _position=-1; } } class Spectrum:IEnumerable { string[] Colors={"violet","blue","cyan","green","yellow","orange","red"}; public IEnumerator GetEnumerator() { return new ColorEnumerator(Colors); } } class Program { static void Main() { var spectrum=new Spectrum(); foreach(string color in spectrum) { Console.WriteLine(color); } } }
目前咱們描述的枚舉接口都是非泛型版本。實際上,在大多數狀況下你應該使用泛型版本IEnumerable<T>
和IEnumerator<T>
。它們叫作泛型是由於使用了C#泛型(參見第17章),其使用方法和非泛型形式差很少。
二者間的本質差異以下:blog
IEnumerable<T>
接口的GetEnumerator方法返回實現IEnumator<T>
的枚舉器類的實例IEnumerator<T>
的類實現了Current屬性,它返回實際類型的對象,而不是object基類的引用須要重點注意的是,咱們目前所看到的非泛型接口的實現不是類型安全的。它們返回object類型的引用,而後必須轉化爲實際類型。
而泛型接口的枚舉器是類型安全的,它返回實際類型的引用。若是要建立本身的可枚舉類,應該實現這些泛型接口。非泛型版本可用於C#2.0之前沒有泛型的遺留代碼。
儘管泛型版本和非泛型版本同樣簡單易用,但其結構略顯複雜。
可枚舉類和枚舉器在.NET集合類中被普遍使用,因此熟悉它們如何工做很重要。不過,雖然咱們已經知道如何建立本身的可枚舉類和枚舉器了,但咱們仍是很高興聽到,C#從2.0版本開始提供了更簡單的建立枚舉器和可枚舉類型的方式。
實際上,編譯器將爲咱們建立它們。這種結構叫作迭代器(iterator)。咱們能夠把手動編碼的可枚舉類型和枚舉器替換爲由迭代器生成的可枚舉類型和枚舉器。
在解釋細節前,咱們先看兩個示例。下面的方法實現了一餓產生和返回枚舉器的迭代器。
public IEnumerator<string>BlackAndWhite() { yield return "black"; yield return "gray"; yield return "white"; }
下面方法聲明瞭另外一個版本,並輸出相同結果:
public IEnumerator<string>BlackAndWhite() { string[] theColors={"black","gray","white"}; for(int i=0;i<theColors.Length;i++) { yield return theColors[i]; } }
迭代器塊是有一個或多個yield語句的代碼塊。下面3種類型的代碼塊中的任意一種均可以是迭代器塊:
迭代器塊與其餘代碼塊不一樣。其餘塊包含的語句被當作命令式。即先執行代碼塊中的第一個語句,而後執行後面的語句,最後控制離開塊。
另外一方面,迭代器塊不是須要在同一時間執行的一串命令式命令,而是描述了但願編譯器爲咱們建立的枚舉器類的行爲。迭代器塊中的代碼描述瞭如何枚舉元素。
迭代器塊由兩個特殊語句:
編譯器獲得有關枚舉項的描述後,使用它來構建包含全部須要的方法和屬性實現的枚舉器類。結果類被嵌套包含在迭代器聲明的類中。
以下圖所示,根據迭代器塊的返回類型,你可讓迭代器產生枚舉器或可枚舉類型。
class MyClass { public IEnumerator<string> GetEnumerator() { return BlackAndWhite(); //返回枚舉器 } public IEnumerator<string> BlackAndWhite()//迭代器 { yield return "black"; yield return "gray"; yield return "white"; } } class Program { static void Main() { var mc=new MyClass(); foreach(string shade in mc) { Console.WriteLine(shade); } } }
下圖演示了MyClass的代碼及產生的對象。注意編譯器爲咱們自動作了多少工做。
IEnumerator<string>
IEnumerator<string>
以前示例建立的類包含兩部分:產生返回枚舉器方法的迭代器以及返回枚舉器的GetEnumerator方法。
本節例子中,咱們用迭代器來建立可枚舉類型,而不是枚舉器。與以前的示例相比,本例有如下不一樣:
IEnumerable<string>
而不是IEnumerator<string>
。所以MyClass首先調用BlackAndWhite方法獲取它的可枚舉類型對象,而後調用對象的GetEnumerator方法來獲取結果,從而實現GetEnumerator方法class MyClass { public IEnumerator<string> GetEnumerator() { IEnumerable<string> myEnumerable=BlackAndWhite(); return myEnumerable.GetEnumerator(); } public IEnumerable<string> BlackAndWhite()//迭代器 { yield return "black"; yield return "gray"; yield return "white"; } } class Program { static void Main() { var mc=new MyClass(); foreach(string shade in mc) { Console.Write(shade); } foreach(string shade in mc.BlackAndWhite) { Console.Write(shade); } } }
下圖演示了在代碼的可枚舉迭代器產生泛型可枚舉類型。
IEnumerable<string>
IEnumerator<string>
和IEnumerable<string>
前面兩節展現了,咱們能夠建立迭代器來返回可枚舉類型或枚舉器。下圖總結了如何使用普通迭代器模式。
下例中,Spectrum類有兩個可枚舉類型的迭代器。注意儘管它有兩個方法返回可枚舉類型,但類自己不是可枚舉類型,由於它沒有實現GetEnumerator
using System; using System.Collections.Generic; class Spectrum { string[] colors={"violet","blue","cyan","green","yellow","orange","red"}; public IEnumerable<string> UVtoIR() { for(int i=0;i<colors.Length;i++) { yield return colors[i]; } } public IEnumerable<string> IRtoUV() { for(int i=colors.Length-1;i>=0;i--) { yield return colors[i]; } } } class Program { static void Main() { var spectrum=new Spectrum(); foreach(string color in spectrum.UVtoIR()) { Console.Write(color); } Console.WriteLine(); foreach(string color in spectrum.IRtoUV()) { Console.Write(color); } Console.WriteLine(); } }
本例演示兩個內容:第一,使用迭代器來產生具備兩個枚舉器的類;第二,演示迭代器如何實現屬性。
using System; using System.Collections.Generic; class Spectrum { bool _listFromUVtoIR; string[] colors={"violet","blue","cyan","green","yellow","orange","red"}; public Spectrum(bool listFromUVtoIR) { _listFromUVtoIR=listFromUVtoIR; } public IEnumerator<string> GetEnumerator() { return _listFromUVtoIR?UVtoIR:IRtoUV; } public IEnumera<string> UVtoIR { get { for(int i=0;i<colors.Length;i++) { yield return colors[i]; } } } public IEnumerable<string> IRtoUV { get { for(int i=colors.Length-1;i>=0;i--) { yield return colors[i]; } } } } class Program { static void Main() { var startUV=new Spectrum(true); var startIR=new Spectrum(false); foreach(string color in startUV) { Console.Write(color); } Console.WriteLine(); foreach(string color in startIR) { Console.Write(color); } Console.WriteLine(); } }
以下是須要了解的有關迭代器的其餘重要事項。
在後臺,由編譯器生成的枚舉器類是包含4個狀態的狀態機。
若是狀態機在Before或Suspended狀態時調用MoveNext方法,就轉到了Running狀態。在Running狀態中,它檢測集合的下一項並設置爲知。
若是有更多項,狀態機會轉入Suspended狀態,若是沒有更多項,它轉入並保持在After狀態。