一、示例:
在C#中,凡是實現了IEnumerator接口的數據類型都可以用foreach語句進行迭代訪問,可是,對於自定義類型如何實現這個接口以支持foreach的迭代呢?
* 要實現這個功能,先來看看IEnumerable和IEnumerator接口的定義:
public interface IEnumerable { //IEnumerable只有一個方法,返回可循環訪問集合的枚舉數。 IEnumerator GetEnumerator() ; } public interface IEnumerator { // 方法 //移到集合的下一個元素。如果成功則返回爲 true;如果超過集合結尾,則返回false。 bool MoveNext(); // 將集合設置爲初始位置,該位置位於集合中第一個元素之前 void Reset(); // 屬性:獲取集合中的當前元素 object Current { get; } } namespace IEnumberableTest { class Program { static void Main(string[] args) { myEnum enum1 = new myEnum(20); foreach (point p in enum1) { Console.WriteLine("(" + p.x.ToString() + "," + p.y.ToString() + "," + p.z.ToString() + ")"); } Console.Read(); } } //一個結構體,用於類myEnum struct point { public int x; public int y; public int z; } //一個派生於IEnumerable和IEnumerator接口的自定義類 class myEnum : IEnumerable, IEnumerator { //定義索引 private int index; //定義一個point結構的數組 private point[] points; //類的構造函數,用於初始化point結構數組 public myEnum(int numofpoint) { this.index = -1; points = new point[numofpoint]; for (int i = 0; i < points.Length; i++) { points[i].x = i; points[i].y = i * i; points[i].z = i * i * i; } } //實現IEnumerable接口的GetEnumerator方法,返回一個IEnumerator,這裏返回我們的自定義類,因爲要對這個類的對象進行迭代 public IEnumerator GetEnumerator() { return (IEnumerator)this; } //實現IEnumerator接口的Reset方法,將集合索引置於第一個元素之前 public void Reset() { index = -1; } //實現IEnumerator接口的Current屬性,返回一個自定義的point結構,即point數組的第index元素 public object Current { get { return points[index]; } } //實現IEnumerator接口的MoveNext方法,用於向前訪問集合元素,如果超出集合範圍,返回false public bool MoveNext() { index++; return index >= points.Length ? false : true; } } }
|
二、區別:
IEnumerable和IEnumerator有什麼區別?這是一個很讓人困惑的問題(在很多forum裏都看到有人在問這個問題)。研究了半天,得到以下幾點認識:
1、一個Collection要支持foreach方式的遍歷,必須實現IEnumerable接口(亦即,必須以某種方式返回IEnumerator object)。
2、IEnumerator object具體實現了iterator(通過MoveNext(),Reset(),Current)。
3、從這兩個接口的用詞選擇上,也可以看出其不同:IEnumerable是一個聲明式的接口,聲明實現該接口的class是“可枚舉(enumerable)”的,但並沒有說明如何實現枚舉器(iterator);IEnumerator是一個實現式的接口,IEnumerator object就是一個iterator。
4、IEnumerable和IEnumerator通過IEnumerable的GetEnumerator()方法建立了連接,client可以通過IEnumerable的GetEnumerator()得到IEnumerator object,在這個意義上,將GetEnumerator()看作IEnumerator object的factory method也未嘗不可。
IEnumerator 是所有枚舉數的基接口。
枚舉數只允許讀取集合中的數據。枚舉數無法用於修改基礎集合。
最初,枚舉數被定位於集合中第一個元素的前面。Reset 也將枚舉數返回到此位置。在此位置,調用 Current 會引發異常。因此,在讀取 Current 的值之前,必須調用 MoveNext 將枚舉數提前到集合的第一個元素。
在調用 MoveNext 或 Reset 之前,Current 返回同一對象。MoveNext 將 Current 設置爲下一個元素。
在傳遞到集合的末尾之後,枚舉數放在集合中最後一個元素後面,且調用 MoveNext 會返回 false。如果最後一次調用 MoveNext 返回 false,則調用 Current 會引發異常。若要再次將 Current 設置爲集合的第一個元素,可以調用 Reset,然後再調用 MoveNext。
只要集合保持不變,枚舉數就將保持有效。如果對集合進行了更改(例如添加、修改或刪除元素),則該枚舉數將失效且不可恢復,並且下一次對 MoveNext 或 Reset 的調用將引發 InvalidOperationException。如果在 MoveNext 和 Current 之間修改集合,那麼即使枚舉數已經無效,Current 也將返回它所設置成的元素。
枚舉數沒有對集合的獨佔訪問權;因此,枚舉一個集合在本質上不是一個線程安全的過程。甚至在對集合進行同步處理時,其他線程仍可以修改該集合,這會導致枚舉數引發異常。若要在枚舉過程中保證線程安全,可以在整個枚舉過程中鎖定集合,或者捕捉由於其他線程進行的更改而引發的異常。