初學C#的時候,總是被IEnumerable、IEnumerator、ICollection等這樣的接口弄的糊里糊塗,我以爲有必要切底的弄清楚IEnumerable和IEnumerator的本質。數組
下面咱們先看IEnumerable和IEnumerator兩個接口的語法定義。其實IEnumerable接口是很是的簡單,只包含一個抽象的方法GetEnumerator(),它返回一個可用於循環訪問集合的IEnumerator對象。IEnumerator對象有什麼呢?它是一個真正的集合訪問器,沒有它,就不能使用foreach語句遍歷集合或數組,由於只有IEnumerator對象才能訪問集合中的項,假如連集合中的項都訪問不了,那麼進行集合的循環遍歷是不可能的事情了。那麼讓咱們看看IEnumerator接口有定義了什麼東西。看下圖咱們知道IEnumerator接口定義了一個Current屬性,MoveNext和Reset兩個方法,這是多麼的簡約。既然IEnumerator對象時一個訪問器,那至少應該有一個Current屬性,來獲取當前集合中的項吧。安全
MoveNext方法只是將遊標的內部位置向前移動(就是移到一下個元素而已),要想進行循環遍歷,不向前移動一下怎麼行呢?函數
詳細講解:測試
說到IEnumerable老是會和IEnumerator、foreach聯繫在一塊兒。this
C# 支持關鍵字foreach,容許咱們遍歷任何數組類型的內容:spa
//遍歷數組的項code
int[] myArrayOfInts = {10,20,30,40};對象
foreach(int i in my myArrayOfInts)blog
{繼承
Console.WirteLine(i);
}
雖然看上去只有數組才能夠使用這個結構,其實任何支持GetEnumerator()方法的類型均可以經過foreach結構進行運算。
1 public class Garage 2 { 3 Car[] carArray = new Car[4]; //在Garage中定義一個Car類型的數組carArray,其實carArray在這裏的本質是一個數組字段 4 5 //啓動時填充一些Car對象 6 public Garage() 7 { 8 //爲數組字段賦值 9 carArray[0] = new Car("Rusty", 30); 10 carArray[1] = new Car("Clunker", 50); 11 carArray[2] = new Car("Zippy", 30); 12 carArray[3] = new Car("Fred", 45); 13 } 14 } 15
理想狀況下,與數據值數組同樣,使用foreach構造迭代Garage對象中的每個子項比較方便:
1 //這看起來好像是可行的 2 class Program 3 { 4 static void Main(string[] args) 5 { 6 Console.WriteLine("*********Fun with IEnumberable/IEnumerator************\n"); 7 Garage carLot = new Garage(); 8 9 //交出集合中的每一Car對象嗎 10 foreach (Car c in carLot) 11 { 12 Console.WriteLine("{0} is going {1} MPH", c.CarName, c.CurrentSpeed); 13 } 14 15 Console.ReadLine(); 16 } 17 }
讓人沮喪的是,編譯器通知咱們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,以下所示:
1 namespace MyCarIEnumerator 2 { 3 public class Garage:IEnumerable 4 { 5 Car[] carArray = new Car[4]; 6 7 //啓動時填充一些Car對象 8 public Garage() 9 { 10 carArray[0] = new Car("Rusty", 30); 11 carArray[1] = new Car("Clunker", 50); 12 carArray[2] = new Car("Zippy", 30); 13 carArray[3] = new Car("Fred", 45); 14 } 15 public IEnumerator GetEnumerator() 16 { 17 return this.carArray.GetEnumerator(); 18 } 19 } 20 } 21 //修改Garage類型以後,就能夠在C#foreach結構中安全使用該類型了。
1 //除此以外,GetEnumerator()被定義爲公開的,對象用戶能夠與IEnumerator類型交互: 2 namespace MyCarIEnumerator 3 { 4 class Program 5 { 6 static void Main(string[] args) 7 { 8 Console.WriteLine("*********Fun with IEnumberable/IEnumerator************\n"); 9 Garage carLot = new Garage(); 10 11 //交出集合中的每一Car對象嗎 12 foreach (Car c in carLot) //之因此遍歷carLot,是由於carLot.GetEnumerator()返回的項時Car類型,這個十分重要 13 { 14 Console.WriteLine("{0} is going {1} MPH", c.CarName, c.CurrentSpeed); 15 } 16 17 Console.WriteLine("GetEnumerator被定義爲公開的,對象用戶能夠與IEnumerator類型交互,下面的結果與上面是一致的"); 18 //手動與IEnumerator協做 19 IEnumerator i = carLot.GetEnumerator(); 20 while (i.MoveNext()) 21 { 22 Car myCar = (Car)i.Current; 23 Console.WriteLine("{0} is going {1} MPH", myCar.CarName, myCar.CurrentSpeed); 24 } 25 Console.ReadLine(); 26 } 27 } 28 }
下面咱們來看看手工實現IEnumberable接口和IEnumerator接口中的方法:
1 namespace ForeachTestCase 2 { 3 //繼承IEnumerable接口,其實也能夠不繼承這個接口,只要類裏面含有返回IEnumberator引用的GetEnumerator()方法便可 4 class ForeachTest:IEnumerable { 5 private string[] elements; //裝載字符串的數組 6 private int ctr = 0; //數組的下標計數器 7 8 /// <summary> 9 /// 初始化的字符串 10 /// </summary> 11 /// <param name="initialStrings"></param> 12 ForeachTest(params string[] initialStrings) 13 { 14 //爲字符串分配內存空間 15 elements = new String[8]; 16 //複製傳遞給構造方法的字符串 17 foreach (string s in initialStrings) 18 { 19 elements[ctr++] = s; 20 } 21 } 22 23 /// <summary> 24 /// 構造函數 25 /// </summary> 26 /// <param name="source">初始化的字符串</param> 27 /// <param name="delimiters">分隔符,能夠是一個或多個字符分隔</param> 28 ForeachTest(string initialStrings, char[] delimiters) 29 { 30 elements = initialStrings.Split(delimiters); 31 } 32 33 //實現接口中得方法 34 public IEnumerator GetEnumerator() 35 { 36 return new ForeachTestEnumerator(this); 37 } 38 39 private class ForeachTestEnumerator : IEnumerator 40 { 41 private int position = -1; 42 private ForeachTest t; 43 public ForeachTestEnumerator(ForeachTest t) 44 { 45 this.t = t; 46 } 47 48 #region 實現接口 49 50 public object Current 51 { 52 get 53 { 54 return t.elements[position]; 55 } 56 } 57 58 public bool MoveNext() 59 { 60 if (position < t.elements.Length - 1) 61 { 62 position++; 63 return true; 64 } 65 else 66 { 67 return false; 68 } 69 } 70 71 public void Reset() 72 { 73 position = -1; 74 } 75 76 #endregion 77 } 78 static void Main(string[] args) 79 { 80 // ForeachTest f = new ForeachTest("This is a sample sentence.", new char[] { ' ', '-' }); 81 ForeachTest f = new ForeachTest("This", "is", "a", "sample", "sentence."); 82 foreach (string item in f) 83 { 84 System.Console.WriteLine(item); 85 } 86 Console.ReadKey(); 87 } 88 } 89 }
IEnumerable<T>接口
實現了IEnmerable<T>接口的集合,是強類型的。它爲子對象的迭代提供類型更加安全的方式。
1 public class ListBoxTest:IEnumerable<String> 2 { 3 private string[] strings; 4 private int ctr = 0; 5 6 #region IEnumerable<string> 成員 7 //可枚舉的類能夠返回枚舉 8 public IEnumerator<string> GetEnumerator() 9 { 10 foreach (string s in strings) 11 { 12 yield return s; 13 } 14 } 15 16 #endregion 17 18 #region IEnumerable 成員 19 //顯式實現接口 20 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 21 { 22 return GetEnumerator(); 23 } 24 25 #endregion 26 27 //用字符串初始化列表框 28 public ListBoxTest(params string[] initialStrings) 29 { 30 //爲字符串分配內存空間 31 strings = new String[8]; 32 //複製傳遞給構造方法的字符串 33 foreach (string s in initialStrings) 34 { 35 strings[ctr++] = s; 36 } 37 } 38 39 //在列表框最後添加一個字符串 40 public void Add(string theString) 41 { 42 strings[ctr] = theString; 43 ctr++; 44 } 45 46 //容許數組式的訪問 47 public string this[int index] 48 { 49 get { 50 if (index < 0 || index >= strings.Length) 51 { 52 //處理不良索引 53 } 54 return strings[index]; 55 } 56 set { 57 strings[index] = value; 58 } 59 } 60 61 //發佈擁有的字符串數 62 public int GetNumEntries() 63 { 64 return ctr; 65 } 66 }
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //建立一個新的列表框並初始化 6 ListBoxTest lbt = new ListBoxTest("Hello", "World"); 7 8 //添加新的字符串 9 lbt.Add("Who"); 10 lbt.Add("Is"); 11 lbt.Add("Douglas"); 12 lbt.Add("Adams"); 13 14 //測試訪問 15 string subst = "Universe"; 16 lbt[1] = subst; 17 18 //訪問全部的字符串 19 foreach (string s in lbt) 20 { 21 Console.WriteLine("Value:{0}", s); 22 } 23 Console.ReadKey(); 24 } 25 }
綜上所述,一個類型是否支持foreach遍歷,必須知足下面條件:
方案1:讓這個類實現IEnumerable接口
方案2:這個類有一個public的GetEnumerator的實例方法,而且返回類型中有public 的bool MoveNext()實例方法和public的Current實例屬性。