列舉介面 IEnumerable

逐一列舉集合元素,是集合運算是最普遍的操作,而列舉操作所需的方法,由 IEnumerator 介面所定義, System.Collections 命名空間的IEnumerable介面,其中定義了方法成員 GetEnumerator ,這個方法回傳IEnumerator 介面的實作類別物件,因此我們透過 IEnumerable介面實作類別來取得此物件,關係如下圖:



實作 IEnumerator 介面的類別通常以巢狀類別的形式存在於實作介面 IEnumerable 的類別內部,稍後的範例將有實際的說明,以下簡要列舉 IEnumerator介面定義的方法成員 - MoveNext 與 Reset 。

MoveNext

將目前的位置移動到集合中的下一個物件,定義如下:
bool MoveNext() ; 
此方法回傳一個布林資料型別的結果,當你瀏覽至集合結尾的時候,這個值將回傳false ,否則均為true 。

Reset

將位置重設至集合中的第一個物件之前,實作 IEnumerator 介面的類別經由實作其定義的函式成員提供巡覽集合元素的功能,其定義如下:
void Reset () 
這個方法會將目前集合列舉的位置重設在集合物件一開始的地方之後,你還必須引用上述的MoveNext 方法,以順利取得第一個位置的物件。

IEnumerator 介面另外還提供了一個唯讀屬性,定義如下:
objcet Current{get ; }
這個屬性取得目前列舉的元素,是一個唯讀屬性,無法透過此屬性,修改集合中的物件元素。
public class KTEnumerable : IEnumerable
{
    int[] i = new int[32];
    int currentIndex = -1;
    public KTEnumerable()
    {
        for (int k = 0; k < 32; k++)
        {
            i[k] = k * 10;
        }
    }
    public IEnumerator GetEnumerator()
    {
        ClsIEnumerator myClsIEnumerator = new ClsIEnumerator(this);
        return myClsIEnumerator;
    }
    class ClsIEnumerator : IEnumerator
    {
        KTEnumerable _ktEnumerable;
        public ClsIEnumerator(KTEnumerable ktEnumerable)
        {
            _ktEnumerable = ktEnumerable;
        }
        public object Current
        {
            get
            {
                return _ktEnumerable.i[_ktEnumerable.currentIndex];
            }
        }
        public void Reset()
        {
            _ktEnumerable.currentIndex = -1;
        }
        public bool MoveNext()
        {
            _ktEnumerable.currentIndex++;
            if (_ktEnumerable.currentIndex >= _ktEnumerable.i.Length)
                return false;
            else
                return true;
        }
    }
} 
類別 KTEnumerable 實作 IEnumerable 介面,提供列舉元素的相關方法實作。

其中宣告一個大小等於 32 的整數型別陣列 i,另外宣告一個用來代表集合中目前索引位置的 int 型別變數 currentIndex ,並且將其值初化為 –1 ,代表集合一開始第一個元素之前的位置。
建構式在每一次 KTEnumerable 物件建立的時候,利用 for 迴圈初始化陣列變數 i ,將指定的整數值依序儲存至陣列。

實作 IEnumerable 介面的 GetEnumerator 方法成員,回傳實作 IEnumerator介面的 ClsIEnumerator 類別物件。

ClsIEnumerator 類別定義為 KTEnumerable 類別內部的巢狀類別,其中的宣告一個 KTEnumerable 類別變數 _ktEnumerable,於建構式將傳入的物件參數指定給變數 _ktEnumerable。

屬性 Current回傳 _ktEnumerable 物件 i 目前索引位置的值。

Reset 方法重設目前的位置至第一個物件位置之前,指定其索引位置為 -1 。 

MoveNext 方法將 _ktEnumerable.currentIndex位置索引加 1,代表將目前的位置移到下一個物件,if判斷式檢視若其值超過陣列 i 的長度回傳 false 表示無法再往下移動,否則傳回 true ,表示移動完成。

以上完成 IEnumerable 實作類別 KTEnumerable,以及 IEnumerator 巢狀類別 ClsIEnumerator 實作,接下來於主程式中進行測試。
class Program
{
    static void Main(string[] args)
    {
        KTEnumerable ktEnumerable = new KTEnumerable();
        IEnumerator enumerator = ktEnumerable.GetEnumerator();
        while (enumerator.MoveNext())
        {
            int i = (int)enumerator.Current;
            Console.WriteLine(i);
        }
        Console.ReadLine();
    }
}
宣告一個 KTEnumerable 類別的實體物件 ktEnumerable ,引用其方法成員 GetEnumerator 取得 IEnumerator 型別物件,將其指定給物件變數 enumerator。

接下來的 while 於每次迴圈開始時引用 MoveNext 方法,當其回傳 true 表示移動至集合的下一個位置成功,接下來引用屬性值 Current 取得目前的集合元素,然後將其輸出。

執行這個範例,會輸出KTEnumerable建構式中建立的陣列 i 所有的內容元素。
0
10
20
30
40
50
60
70
80
90
100
…
實作列舉介面的集合類別,同時支援 foreach 敘述,提供更簡潔的元素列舉方式,結束這個小節的課程之前,我們來看看 foreach 與列舉的關係。修改上述主程式內容,以下列舉說明:
class Program
{
    static void Main(string[] args)
    {
        KTEnumerable ktEnumerable1 = new KTEnumerable();
        IEnumerator enumerator = ktEnumerable1.GetEnumerator();
        while (enumerator.MoveNext())
        {
            int i = (int)enumerator.Current;
            Console.WriteLine(i);
        }

        Console.WriteLine("以下的為 foreach 列舉 ... ");
        KTEnumerable ktEnumerable2 = new KTEnumerable();
        foreach (var i in ktEnumerable2)
        {
            Console.WriteLine(i);
        }
        Console.ReadKey();
    }
}
新增一段示範foreach列舉的示範程式碼,建立一個新的 KTEnumerable 物件 ktEnumerable2 ,直接利用 foreach 敘述完成輸出。

比較前後兩者的差異,foreach 並不需要再引用 GetEnumerator 取得 IEnumerator 介面的實作類別物件,程式可以更為精簡。

從上面的說明我們看到了列舉介面 IEnumerable的核心功能 - 提供列舉集合元素的實作介面規格,而 IEnumerator 亦是其唯一的成員,具備這個觀念相當重要,任何要提供列舉操作支援的類別,都必須實作IEnumerable介面。





沒有留言: