IEnumerable和IEnumerator 詳解
IEnumerable接口是非常的簡單,只包含一個抽象的方法GetEnumerator(),它返回一個可用於循環訪問集合的IEnumerator對象。IEnumerator對象有什麽呢?它是一個真正的集合訪問器,沒有它,就不能使用foreach語句遍歷集合或數組,因為只有IEnumerator對象才能訪問集合中的項,假如連集合中的項都訪問不了,那麽進行集合的循環遍歷是不可能的事情了。那麽讓我們看看IEnumerator接口有定義了什麽東西。看下圖我們知道IEnumerator接口定義了一個Current屬性,MoveNext和Reset兩個方法,這是多麽的簡約。既然IEnumerator對象時一個訪問器,那至少應該有一個Current屬性,來獲取當前集合中的項吧。
MoveNext方法只是將遊標的內部位置向前移動(就是移到一下個元素而已),要想進行循環遍歷,不向前移動一下怎麽行呢?
詳細講解:
說到IEnumerable總是會和IEnumerator、foreach聯系在一起。
C# 支持關鍵字foreach,允許我們遍歷任何數組類型的內容:
//遍歷數組的項 int[] myArrayOfInts = {10,20,30,40}; foreach(int i in my myArrayOfInts) { Console.WirteLine(i); }
雖然看上去只有數組才可以使用這個結構,其實任何支持GetEnumerator()方法的類型都可以通過foreach結構進行運算。
public class Garage { Car[] carArray = new Car[4]; //在Garage中定義一個Car類型的數組carArray,其實carArray在這裏的本質是一個數組字段 //啟動時填充一些Car對象 public Garage() { //為數組字段賦值 carArray[0] = new Car("Rusty", 30); carArray[1] = new Car("Clunker", 50); carArray[2] = newCar("Zippy", 30); carArray[3] = new Car("Fred", 45); } }
理想情況下,與數據值數組一樣,使用foreach構造叠代Garage對象中的每一個子項比較方便:
//這看起來好像是可行的 lass Program { static void Main(string[] args) { Console.WriteLine("*********Fun with IEnumberable/IEnumerator************\n"); Garage carLot = new Garage(); //交出集合中的每一Car對象嗎 foreach (Car c in carLot) { Console.WriteLine("{0} is going {1} MPH", c.CarName, c.CurrentSpeed); } Console.ReadLine(); } }
讓人沮喪的是,編譯器通知我們Garage類沒有實現名為GetEnumerator()的方法(顯然用foreach遍歷Garage對象是不可能的事情,因為Garage類沒有實現GetEnumerator()方法,Garage對象就不可能返回一個IEnumerator對象,沒有IEnumerator對象,就不可能調用方法MoveNext(),調用不了MoveNext,就不可能循環的了)。這個方法是有隱藏在System.collections命名空間中的IEnumerable接口定義的。(特別註意,其實我們循環遍歷的都是對象而不是類,只是這個對象是一個集合對象)
支持這種行為的類或結構實際上是宣告它們向調用者公開所包含的子項:
//這個接口告知調方對象的子項可以枚舉 public interface IEnumerable { IEnumerator GetEnumerator(); }
可以看到,GetEnumerator方法返回對另一個接口System.Collections.IEnumerator的引用。這個接口提供了基礎設施,調用方可以用來移動IEnumerable兼容容器包含的內部對象。
//這個接口允許調用方獲取一個容器的子項 public interface IEnumerator { bool MoveNext(); //將遊標的內部位置向前移動 object Current{get;} //獲取當前的項(只讀屬性) void Reset(); //將遊標重置到第一個成員前面 }
所以,要想Garage類也可以使用foreach遍歷其中的項,那我們就要修改Garage類型使之支持這些接口,可以手工實現每一個方法,不過這得花費不少功夫。雖然自己開發GetEnumerator()、MoveNext()、Current和Reset()也沒有問題,但有一個更簡單的辦法。因為System.Array類型和其他許多類型(如List)已經實現了IEnumerable和IEnumerator接口,你可以簡單委托請求到System.Array,如下所示:
namespace MyCarIEnumerator { public class Garage:IEnumerable { Car[] carArray = new Car[4]; //啟動時填充一些Car對象 public Garage() { carArray[0] = new Car("Rusty", 30); carArray[1] = new Car("Clunker", 50); carArray[2] = new Car("Zippy", 30); carArray[3] = new Car("Fred", 45); } public IEnumerator GetEnumerator() { return this.carArray.GetEnumerator(); } } } //修改Garage類型之後,就可以在C#foreach結構中安全使用該類型了。
//除此之外,GetEnumerator()被定義為公開的,對象用戶可以與IEnumerator類型交互: namespace MyCarIEnumerator { class Program { static void Main(string[] args) { Console.WriteLine("*********Fun with IEnumberable/IEnumerator************\n"); Garage carLot = new Garage(); //交出集合中的每一Car對象嗎 foreach (Car c in carLot) //之所以遍歷carLot,是因為carLot.GetEnumerator()返回的項時Car類型,這個十分重要 { Console.WriteLine("{0} is going {1} MPH", c.CarName, c.CurrentSpeed); } Console.WriteLine("GetEnumerator被定義為公開的,對象用戶可以與IEnumerator類型交互,下面的結果與上面是一致的"); //手動與IEnumerator協作 IEnumerator i = carLot.GetEnumerator(); while (i.MoveNext()) { Car myCar = (Car)i.Current; Console.WriteLine("{0} is going {1} MPH", myCar.CarName, myCar.CurrentSpeed); } Console.ReadLine(); } } }
下面我們來看看手工實現IEnumberable接口和IEnumerator接口中的方法:
namespace ForeachTestCase { //繼承IEnumerable接口,其實也可以不繼承這個接口,只要類裏面含有返回IEnumberator引用的GetEnumerator()方法即可 class ForeachTest:IEnumerable { private string[] elements; //裝載字符串的數組 private int ctr = 0; //數組的下標計數器 /// <summary> /// 初始化的字符串 /// </summary> /// <param name="initialStrings"></param> ForeachTest(params string[] initialStrings) { //為字符串分配內存空間 elements = new String[8]; //復制傳遞給構造方法的字符串 foreach (string s in initialStrings) { elements[ctr++] = s; } } /// <summary> /// 構造函數 /// </summary> /// <param name="source">初始化的字符串</param> /// <param name="delimiters">分隔符,可以是一個或多個字符分隔</param> ForeachTest(string initialStrings, char[] delimiters) { elements = initialStrings.Split(delimiters); } //實現接口中得方法 public IEnumerator GetEnumerator() { return new ForeachTestEnumerator(this); } private class ForeachTestEnumerator : IEnumerator { private int position = -1; private ForeachTest t; public ForeachTestEnumerator(ForeachTest t) { this.t = t; } #region 實現接口 public object Current { get { return t.elements[position]; } } public bool MoveNext() { if (position < t.elements.Length - 1) { position++; return true; } else { return false; } } public void Reset() { position = -1; } #endregion } static void Main(string[] args) { // ForeachTest f = new ForeachTest("This is a sample sentence.", new char[] { ‘ ‘, ‘-‘ }); ForeachTest f = new ForeachTest("This", "is", "a", "sample", "sentence."); foreach (string item in f) { System.Console.WriteLine(item); } Console.ReadKey(); } } }
IEnumerable<T>接口
實現了IEnmerable<T>接口的集合,是強類型的。它為子對象的叠代提供類型更加安全的方式。
public class ListBoxTest:IEnumerable<String> { private string[] strings; private int ctr = 0; #region IEnumerable<string> 成員 //可枚舉的類可以返回枚舉 public IEnumerator<string> GetEnumerator() { foreach (string s in strings) { yield return s; } } #endregion #region IEnumerable 成員 //顯式實現接口 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion //用字符串初始化列表框 public ListBoxTest(params string[] initialStrings) { //為字符串分配內存空間 strings = new String[8]; //復制傳遞給構造方法的字符串 foreach (string s in initialStrings) { strings[ctr++] = s; } } //在列表框最後添加一個字符串 public void Add(string theString) { strings[ctr] = theString; ctr++; } //允許數組式的訪問 public string this[int index] { get { if (index < 0 || index >= strings.Length) { //處理不良索引 } return strings[index]; } set { strings[index] = value; } } //發布擁有的字符串數 public int GetNumEntries() { return ctr; } }
class Program { static void Main(string[] args) { //創建一個新的列表框並初始化 ListBoxTest lbt = new ListBoxTest("Hello", "World"); //添加新的字符串 lbt.Add("Who"); lbt.Add("Is"); lbt.Add("Douglas"); lbt.Add("Adams"); //測試訪問 string subst = "Universe"; lbt[1] = subst; //訪問所有的字符串 foreach (string s in lbt) { Console.WriteLine("Value:{0}", s); } Console.ReadKey(); } }
綜上所述,一個類型是否支持foreach遍歷,必須滿足下面條件:
方案1:讓這個類實現IEnumerable接口
方案2:這個類有一個public的GetEnumerator的實例方法,並且返回類型中有public 的bool MoveNext()實例方法和public的Current實例屬性。
IEnumerable和IEnumerator 詳解