總結:
1、枚舉器就像是序列中的“游標”或“書簽”。可以有多個“書簽”,移動其中任何一個都可以枚舉集合,與其他枚舉器互不影響。用來遍歷數據結構(單項表鏈、數組、集合類成員等)。
2、可以使用foreach 遍歷枚舉器。foreach 用來遍歷鴨子類型.點擊查看foreach詳細用法
什么是枚舉器
實現IEnumerator接口的類就是枚舉器。
枚舉器作用
1、枚舉器就像是序列中的“游標”或“書簽”。可以有多個“書簽”,移動其中任何一個都可以枚舉集合,與其他枚舉器互不影響。用來遍歷數據結構(表鏈、數組、集合類成員等)。
2、以下案例數組
作為內部數據結構,后期也可以換成數組,鏈表,樹,圖等等,而使用者卻不用關心這些內部數據表示,這就是迭代器的妙處所在。
存在的問題
1、對於需要遞歸遍歷的數據結構(如二叉樹),指示狀態可能就會變得相當復雜。為了減少實現此模式所帶來的挑戰,C# 2.0引入了迭代器, 新增了 yield 上下文關鍵字。
2、只能順序遍歷
枚舉器的原理
指針初始位置不在數據結構內(數組、表鏈、樹等結構)、第一次movenext()后, 數據結構項(表鏈 、數組等)不為空則返回true,否則為false
IEnumerator<string> enumerator = mc.BlackAndWhite().GetEnumerator(); try { //數據結構項(表鏈 、數組等)不為空則返回true,否則為false while (enumerator.MoveNext()) { //讀取當前指針位置處的值 string shade = enumerator.Current; Console.Write(shade); } } finally { if (enumerator != null) { enumerator.Dispose(); } }
IEnumerator接口
實現了IEnumerator接口的枚舉器包含3個public類型的成員:Current、MoveNext()以及Reset()。
在 IEnumerator 嵌套類中實現,以便可以創建多個枚舉器。
枚舉器內部可以用數組、表鏈、等其他數據結構。以下案例用數組
Current:返回當前處理的元素。
- 它是只讀屬性。
- 它返回object類型的引用,所以可以返回任何類型。
MoveNext():把枚舉器位置向前到集合中的下一項的方法。
- 它也返回布爾值,指示新的位置是有效位置還是已經超過了序列的尾部。
- 如果新的位置是有效的。
- 如果新的位置是無效的(比如當前位置到達了尾部),方法返回false。
- 枚舉器的原始位置在序列中的第一項之前,因此MoveNext必須在第一次使用Current之前調用。
int[] i = {1,1,1,2 }; var ie= i.GetEnumerator(); //錯誤的寫法,原因是枚舉器位於集合中第一個元素之前,緊跟在創建枚舉器之后。 MoveNext 在讀取的值之前,必須調用以將枚舉數前移到集合的第一個元素 Current 。 Console.Write(ie.Current); ie.MoveNext();
Reset():把位置重置為原始狀態的方法。(Reset 方法通常會拋出 NotImplementedException,因此不得進行調用。如果需要重新開始枚舉,只要新建一個枚舉器即可。)
枚舉器的實現
這種方式不好,不能創建多個枚舉實例。
using System; using System.Collections; namespace ConsoleEnum { public class cars : IEnumerator,IEnumerable { private car[] carlist; int position = -1; //Create internal array in constructor. public cars() { carlist= new car[6] { new car("Ford",1992), new car("Fiat",1988), new car("Buick",1932), new car("Ford",1932), new car("Dodge",1999), new car("Honda",1977) }; } //IEnumerator and IEnumerable require these methods. public IEnumerator GetEnumerator() { return (IEnumerator)this; } //IEnumerator public bool MoveNext() { position++; return (position < carlist.Length); } //IEnumerable public void Reset() { position = -1; } //IEnumerable public object Current { get { return carlist[position];} } } }
本文中的示例盡量簡單(所以采用數組而不是其他數據解構(單項表鏈)),以更好地解釋這些接口的使用。不過該案例也反應出一個問題。
如果多線程訪問方法就會造成這個實例,由於MoveNext()是共享的。就會導致亂序。
若要使代碼更可靠並確保代碼使用當前最佳做法准則,請修改代碼,如下所示:
最佳做法
- 將 IEnumerable和IEnumerator兩個接口的功能分開。集合類本身實現IEnumerable,集合類內部嵌套枚舉器 (繼承
IEnumerator接口
的類),以便可以創建多個枚舉器。 - 枚舉器就像是序列中的“游標”或“書簽”。可以有多個“書簽”,移動其中任何一個都可以枚舉集合,與其他枚舉器互不影響。
- 為 方法提供
Current
異常處理IEnumerator
。 如果集合的內容更改,將reset
調用 方法。 因此,當前枚舉器失效,您將收到IndexOutOfRangeException
異常。 其他情況也可能導致此異常。 因此,實現Try...Catch
塊以捕獲此異常並引發InvalidOperationException
異常。
using System; using System.Collections; namespace ConsoleEnum { public class cars : IEnumerable { private car[] carlist; //Create internal array in constructor. public cars() { carlist= new car[6] { new car("Ford",1992), new car("Fiat",1988), new car("Buick",1932), new car("Ford",1932), new car("Dodge",1999), new car("Honda",1977) }; } //private enumerator class private class MyEnumerator:IEnumerator { public car[] carlist; int position = -1; //constructor public MyEnumerator(car[] list) { carlist=list; } private IEnumerator getEnumerator() { return (IEnumerator)this; } //IEnumerator public bool MoveNext() { position++; return (position < carlist.Length); } //IEnumerator public void Reset() { position = -1; } //IEnumerator public object Current { get { try { return carlist[position]; } catch (IndexOutOfRangeException) { throw new InvalidOperationException(); } } } } //end nested class public IEnumerator GetEnumerator() { return new MyEnumerator(carlist); } } }
枚舉器在集合中應用
首先、集合必須繼承IEnumerable 接口,該接口就是告訴別人他是可以枚舉的,他的內部已經實現了枚舉器。別人可以通過IEnumerable 接口 提供的GetEnumerator()方法獲得枚舉器。然后通過枚舉器的movenext訪問集合成員。
第二、然后需要在集合類的內部放一個枚舉器(嵌套一個現實 IEnumerator接口 的類)。然把集合自身的單向鏈表傳入枚舉器,枚舉器就像放在鏈表上的游標。
第三、別人就可以通過獲取調用集合類的GetEnumerator()方法,獲取到集合類的枚舉器。通過枚舉器順序的訪問集合。
實現IEnumerable接口的類有哪些:
數組、集合類 等等
泛型枚舉接口
目前我們描述的枚舉接口都是非泛型版本。實際上,在大多數情況下你應該使用泛型版本IEnumerable<T>
和IEnumerator<T>
。它們叫做泛型是因為使用了C#泛型(參見第17章),其使用方法和非泛型形式差不多。
兩者間的本質差別如下:
- 對於非泛型接口形式:
- IEnumerable接口的GetEnumerator方法返回實現IEnumerator枚舉器類的實例
- 實現IEnumerator的類實現了Current屬性,它返回object的引用,然后我們必須把它轉化為實際類型的對象
- 對於泛型接口形式:
IEnumerable<T>
接口的GetEnumerator方法返回實現IEnumator<T>
的枚舉器類的實例- 實現
IEnumerator<T>
的類實現了Current屬性,它返回實際類型的對象,而不是object基類的引用
需要重點注意的是,我們目前所看到的非泛型接口的實現不是類型安全的。它們返回object類型的引用,然后必須轉化為實際類型。
而泛型接口的枚舉器是類型安全的,它返回實際類型的引用。如果要創建自己的可枚舉類,應該實現這些泛型接口。非泛型版本可用於C#2.0以前沒有泛型的遺留代碼。
盡管泛型版本和非泛型版本一樣簡單易用,但其結構略顯復雜。