在C# 1.1中,可以使用foreach循環來遍歷諸如數組、集合這樣的數據結構:
string[] cities = {"New York","Paris","London"}; foreach(string city in cities) { Console.WriteLine(city); }
實際上,可以在foreach循環中使用任何自定義數據集合,只要該集合類型實現了返回IEnumerator接口的GetEnumerator方法即可。通常,需要通過實現IEnumerable接口來完成這些工作:
public interface IEnumerable { IEnumerator GetEnumerator(); } public interface IEnumerator { object Current{get;} bool MoveNext(); void Reset(); }
在通常情況下,實現IEnumerable接口的類是作為要遍歷的集合類型的嵌套類來提供的。這樣,此種迭代器設計模式維持了迭代的狀態。將嵌套類作為枚舉器的好處是因為它可以訪問其包含類的所有私有成員,而且,對迭代客戶端隱藏了底層數據結構的實際實現細節,使得能夠在多種數據結構上使用相同的客戶端迭代邏輯,如圖1所示。
圖1 迭代器設計模式
此外,由於每個迭代器都保持單獨的迭代狀態,所以多個客戶端可以執行單獨的並發迭代。通過實現IEnumerable接口,諸如數組和隊列這樣的數據結構可以支持這種非常規的迭代。在foreach循環中生成的代碼調用類的GetEnumerator方法可以簡單地獲得一個IEnumerator對象,然後將其用於while循環,接著,通過連續調用它的MoveNext方法來遍歷集合。如果您需要顯式地遍歷集合,您可以直接使用IEnumerator(無須使用foreach語句)。
但是使用這種方法有一些問題。首先,如果集合包含值類型,則需要對它們進行裝箱和拆箱才能獲得項,因為IEnumerator.Current返回一個Object類的對象。這將導致潛在的性能降低 和托管堆上的壓力增大。即使集合包含引用類型,仍然會產生從對象向下強制類型轉換的不利結果。雖然大多數開發人員不熟悉這一特性,事實上在C# 1.0中,不必實現IEnumerator或IEnumerable接口就可以為每個循環實現迭代器模式。編譯器將選擇調用強類型化版本,以避免強制類型轉換和裝箱。結果是,即使在1.0版本中,也可能沒有導致性能損失。
為了更好地闡明這個解決方案並使其易於實現,Microsoft .NET框架2.0在System.Collections.Generics命名空間中定義了類型安全的泛型IEnumerable<ItemType>和IEnumerator<ItemType>:
public interface IEnumerable { IEnumerator GetEnumerator(); } public interface IEnumerator : IDisposable { ItemType Current{get;} bool MoveNext(); }
除了利用泛型之外,新的接口與其前身還略有差別。與IEnumerable不同,IEnumerator是從IDisposable派生而來的,並且沒有Reset方法。圖2中的代碼顯示了實現IEnumerable<string>的簡單city集合,而圖3顯示了編譯器展開foreach循環的代碼中如何使用該接口。圖2中的實現使用了名為MyEnumerator的嵌套類,它將一個引用作為構造參數返回給要枚舉的集合。MyEnumerator清楚地知道city集合(本例中的一個數組)的實現細節。此外,MyEnumerator類使用m_Current成員變量維持當前的迭代狀態,此成員變量用作數組的索引。
public class CityCollection : IEnumerable<string> { string[] m_Cities = {"New York","Paris","London"}; public IEnumerator<string> GetEnumerator() { return new MyEnumerator(this); } //Nested class definition class MyEnumerator : IEnumerator<string> { CityCollection m_Collection; int m_Current; public MyEnumerator(CityCollection collection) { m_Collection = collection; m_Current = -1; } public bool MoveNext() { m_Current++; if(m_Current < m_Collection.m_Cities.Length) return true; else return false; } public string Current { get { if(m_Current == -1) throw new InvalidOperationException(); return m_Collection.m_Cities[m_Current]; } } public void Dispose(){} } }