C# 列舉器 IEnumerator 與 IEnumerable

今天要講的是列舉器,我們很常用到foreach來幫我們把陣列裡面的東西全部拿出來。那麼,為什麼陣列可以使用 foreach ,我們操作其他物件到底能不能使用 foreach ?

答案是因為,陣列有實作列舉器,所以我們可以使用 foreach 把陣列裡面的東西全部拿出來。簡而言之,若我們的自訂義的類別有實作列舉器。那就可以使用 foreach 來取出裡面的東西。

class Program
{
    static void Main(string[] args)
    {
        string[] 陣列 = { "小明", "小華", "小花" };

        foreach(var item in 陣列)
        {
            Console.WriteLine(item);
        }
        Console.ReadKey();
    }
}

輸出結果:

小明
小華
小花

列舉器 enumerator

列舉器會將陣列裡面的東西一個一個的依照順序取出來,不過 C# 裡面陣列是一個有指定大小的容器,定義陣列的大小後,裡面能放東西的數量就是固定的。所以廣泛來說,列舉器會把序列(不限定長度大小)裡面的東西一個一個取出來。
列舉器的實作方式也非常簡單,要做三個功能,1.拿出一個,2.判斷有沒有下一個,3.重頭開始(從零開始)

在C#中有定義列舉器的interface IEnumerator,所以我們說陣列裡面有實作enumerator,就是擁有這三個功能

public interface IEnumerator
{
    bool MoveNext();

    Object Current { get; }
    void Reset();
}

可列舉的enumerable

至於要怎麼知道這個物件有沒有列舉器,能不能使用foreach,就要看這個類別是不是可列舉的類別,類別有沒有繼承IEnumerable這介面。(補充:C#習慣在介面前面加上i,所以IEnumerable就是enumerable的interface,指的是可列舉的介面。)

如果有繼承IEnumerable,就代表有實作GetEnumerator()這個方法來讓我們拿到列舉器。

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

所以剛剛的 foreach 程式可以改成以下使用列舉器的版本

class Program
{
    static void Main(string[] args)
    {
        string[] 陣列 = { "小明", "小華", "小花" };

        var 列舉器 = 陣列.GetEnumerator();

        while(列舉器.MoveNext())
        {
            Console.WriteLine(列舉器.Current);
        }
        Console.ReadKey();
    }
}

執行結果當然與剛剛的一樣

小明
小華
小花

讓自定義類別可列舉

前面講到繼承IEnumerable介面的話,就要實作GetEnumerator()來回傳列舉器。所以如果你的自定義類別裡面有個集合或陣列想要列舉出來,就可以實作GetEnumerator()把陣列的列舉器return出去。

例如我定義一個班級類別,實作GetEnumerator方法,裡面是回傳陣列的GetEnumerator()

class 班級 : IEnumerable
{
    public string [] 學生們 = { "小明", "小華", "小花" };

    public IEnumerator GetEnumerator()
    {
        return 學生們.GetEnumerator();
    }
}

一般情況來說,我們會以這樣的方式來foreach取出學生

班級 A班 = new 班級();
foreach (var 學生 in A班.學生們)
{
    Console.WriteLine(學生);
}

但是我們班級這個類別實作了 GetEnumerator(),就可以直接對班級跑迴圈取出學生的資料

班級 A班 = new 班級();
foreach (var 學生 in A班)
{
    Console.WriteLine(學生);
}
Console.Read();
Facebook留言板