前言
我們在寫程式的時候 , 常會使用到許多資料結構 , 像是List、HashTable、Stack、Tree等等資料結構 , 儘管這些資料結構在本質以及實作上並不相同 , 但我們有時候仍然會希望它們都具有走訪(Traverse)資料的能力
所謂的走訪(Traverse)就是逐一存取此資料集合下內含的所有資料元素(Element)或節點 (Node) , 不過依據實作的演算法不同 , 走訪的方式也可能會不同. 例如樹狀資料就有前序、中序、後序三種演算法.
Enumerator & Enumerable (與 LinQ 無關)
-
實作 IEnumerator 的任何類別概念上稱之為 Enumerator (列舉器)
-
實作 IEnumerable 的任何類別概念上稱之為 Enumerable
-
Enumerator : a read-only, forward-only cursor over a sequence of values
- 實作介面
- System.Collections.IEnumerator
- System.Collections.Generic.IEnumerator<T>
- Enumerator 是實作 MoveNext()、Reset() 以及具有屬性 Current 的物件
- 實作介面
-
Enumerable : the logical representation of a sequence. It is not itself a cursor, but an object that produces cursors over itself
- 實作介面
- IEnumerable
- IEnumerable<T>
- Enumerable 必實作一個回傳 Enumerator 的 GetEnumerator()
- 實作介面
IEnumerable & IEnumerator
在 C# 中 , 若是希望資料集合類別具有走訪能力 , 需要實作 IEnumerable 以及 IEnumerator 兩個介面(或是它們的泛型版本)
-
IEnumerable
public interface IEnumerable { IEnumerator GetEnumerator(); }
由 IEnumerable 這個介面可以知道 C# 對於資料集合是否可被列舉(走訪)的定義是其是否具備取得 Enumerator(列舉器)的能力 , 換句話說實作介面 IEnumerable 代表此資料集合中的成員可以被列舉.
-
IEnumerator
public interface IEnumerator { bool MoveNext(); object Current { get; } void Reset(); }
依據介面 IEnumerator 可以知道 Enumerator 負責將其所屬的資料集合中的成員 , 逐一取出並回傳. 因此其將實作 MoveNext() & Reset()以及具有屬性 Current. 請參考下圖.
- Current 屬性 : 回傳目前走訪到的成員內容值.
- MoveNext() : 走訪到下一個成員 , 並回傳bool值來告知向下移動是否成功.
- Reset : 重置走訪的位置.
範例
走訪金木水火土五行.
public class FiveElements : IEnumerable
{
private string[] fiveElements = { "金", "木", "水", "火", "土" };
public IEnumerator GetEnumerator() => new FiveElementsEnumerator(fiveElements);
}
public class FiveElementsEnumerator : IEnumerator
{
private string[] fiveElements;
private int index = -1;
public FiveElementsEnumerator(string[] elements) => fiveElements = elements;
public bool MoveNext() => ++index < fiveElements.Length;
public object Current => fiveElements[index];
public void Reset() => index = -1;
}
測試程式
static void Main(string[] args)
{
var fiveelements = new FiveElements();
var enumerator = fiveelements.GetEnumerator();
while (enumerator.MoveNext())
{
Console.Write(enumerator.Current);
}
Console.WriteLine();
foreach (var item in fiveelements)
{
Console.Write(item);
}
Console.ReadKey();
}
輸出結果
走訪所有元素的方式有兩種
- 採用 foreach
- 採用 GetEnumerator() 的方式
不過由上面範例可知 , Foreach 語法其實就是採用 GetEnumerator 的方式實作 . 也就是說 foreach 其實是做了這四個步驟
- 呼叫 fiveelements.GetEnumerator() , 得到一個 IEnumerator.
- 呼叫 iterator.MoveNext() 以判斷走訪是否結束
- 如果 iterator.MoveNext() 回傳 false 代表走訪完畢.
- 反之如果回傳 true 則代表尚未走訪完畢 , 且會將 Current 指標移到下一個元素上.
- 回傳 Current 屬性 .
- 重複動作 2 & 3
所以下列這一行 , 可以這麼解讀
foreach (var item in fiveelements)
- foreach : 代表要走訪的進入點.
- fiveelements : 呼叫 GetEnumerator()
- in : 呼叫 MoveNext()
- item :
- 若回傳 false 則代表走訪結束 , 結束迴圈
- 若回傳 true , 走訪尚未結束並回傳 Current 屬性
.Net繼承關西
book - C# 4.0 in Nutshell
根據上圖 , 我們可以知道 C# 常用的集合介面(ICollection、IList、IDictionary)都有繼承 IEnumerable 或是 IEnumerable<T> , 也因此實作這些集合界面的類別 , 也都具備資料走訪能力.
總結
- 自定義的資料集合類別要具備可被列舉的能力 , 需實作 IEnumerable 或是 IEnumerable<T>
- 實作 IEnumerable 與 IEnumerable<T> 界面 , 需要實作 GetEnumerator() 用來回傳列舉器(Enumerator)物件 - 實作IEnumerator
- 列舉器(Enumerator)是實作走訪各個資料元素的類別的物件
-
引用91大的blog
Thank you!
You can find me on
若有謬誤 , 煩請告知 , 新手發帖請多包涵