1. 程式人生 > >IEnumerator和IEnumerable詳解

IEnumerator和IEnumerable詳解

etc 屬性 com eth 返回 int lis win length

IEnumerator和IEnumerable

從名字常來看,IEnumerator是枚舉器的意思,IEnumerable是可枚舉的意思。
了解了兩個接口代表的含義後,接著看源碼:
IEnumerator:

public interface IEnumerator
    {
        // Interfaces are not serializable
        // Advances the enumerator to the next element of the enumeration and
        // returns a boolean indicating whether an element is available. Upon
        // creation, an enumerator is conceptually positioned before the first
        // element of the enumeration, and the first call to MoveNext 
        // brings the first element of the enumeration into view.
        // 
        bool MoveNext();
    
        // Returns the current element of the enumeration. The returned value is
        // undefined before the first call to MoveNext and following a
        // call to MoveNext that returned false. Multiple calls to
        // GetCurrent with no intervening calls to MoveNext 
        // will return the same object.
        // 
        Object Current {
            get; 
        }
    
        // Resets the enumerator to the beginning of the enumeration, starting over.
        // The preferred behavior for Reset is to return the exact same enumeration.
        // This means if you modify the underlying collection then call Reset, your
        // IEnumerator will be invalid, just as it would have been if you had called
        // MoveNext or Current.
        //
        void Reset();
    }

IEnumerable:

    public interface IEnumerable
    {
        // Interfaces are not serializable
        // Returns an IEnumerator for this enumerable Object.  The enumerator provides
        // a simple way to access all the contents of a collection.
        [Pure]
        [DispId(-4)]
        IEnumerator GetEnumerator();
    }

發現IEnumerable只有一個GetEnumerator函數,返回值是IEnumerator類型,從註釋我們可以得知IEnumerable代表繼承此接口的類可以獲取一個IEnumerator來實現枚舉這個類中包含的集合中的元素的功能(比如List<T>,ArrayList,Dictionary等繼承了IEnumeratble接口的類)。

用foreach來了解IEnumerable,IEnumerator的工作原理

我們模仿ArrayList來實現一個簡單的ConstArrayList,然後用foreach遍歷。

//一個常量的數組,用於foreach遍歷
class ConstArrayList : IEnumerable
{
    public int[] constItems = new int[] { 1, 2, 3, 4, 5 };
    public IEnumerator GetEnumerator()
    {
        return new ConstArrayListEnumeratorSimple(this);
    }
}
//這個常量數組的叠代器
class ConstArrayListEnumeratorSimple : IEnumerator
{
    ConstArrayList list;
    int index;
    int currentElement;
    public ConstArrayListEnumeratorSimple(ConstArrayList _list)
    {
        list = _list;
        index = -1;
    }

    public object Current
    {
        get
        {
            return currentElement;
        }
    }

    public bool MoveNext()
    {
        if(index < list.constItems.Length - 1)
        {
            currentElement = list.constItems[++index];
            return true;
        }
        else
        {
            currentElement = -1;
            return false;
        }
    }

    public void Reset()
    {
        index = -1;
    }
}
class Program
{    
    static void Main(string[] args)
    {
        ConstArrayList constArrayList = new ConstArrayList();
        foreach(int item in constArrayList)
        {
            WriteLine(item);
        }
        ReadKey();
    }
}

輸出結果:
1
2
3
4
5

代碼達到了遍歷效果,但是在用foreach遍歷時,IEnumerator和IEnumerable究竟是如何運行的,我們可以通過增加增加日誌可以直觀的看到原因。

//一個常量的數組,用於foreach遍歷
class ConstArrayList : IEnumerable
{
    public int[] constItems = new int[] { 1, 2, 3, 4, 5 };
    public IEnumerator GetEnumerator()
    {
        WriteLine("GetIEnumerator");
        return new ConstArrayListEnumeratorSimple(this);
    }
}
//這個常量數組的叠代器
class ConstArrayListEnumeratorSimple : IEnumerator
{
    ConstArrayList list;
    int index;
    int currentElement;
    public ConstArrayListEnumeratorSimple(ConstArrayList _list)
    {
        list = _list;
        index = -1;
    }

    public object Current
    {
        get
        {
            WriteLine("Current");
            return currentElement;
        }
    }

    public bool MoveNext()
    {
        if(index < list.constItems.Length - 1)
        {
            WriteLine("MoveNext true");   
            currentElement = list.constItems[++index];
            return true;
        }
        else
        {
            WriteLine("MoveNext false");
            currentElement = -1;
            return false;
        }
    }

    public void Reset()
    {
        WriteLine("Reset");
        index = -1;
    }
}
class Program
{    
    static void Main(string[] args)
    {
        ConstArrayList constArrayList = new ConstArrayList();
        foreach(int item in constArrayList)
        {
            WriteLine(item);
        }
        ReadKey();
    }
}

輸出結果:
GetIEnumerator
MoveNext true
Current
1
MoveNext true
Current
2
MoveNext true
Current
3
MoveNext true
Current
4
MoveNext true
Current
5
MoveNext false

通過輸出結果,我們可以發現,foreach在運行時會先調用ConstArrayList的GetIEnumerator函數獲取一個ConstArrayListEnumeratorSimple,之後通過循環調用ConstArrayListEnumeratorSimple的MoveNext函數,index後移,更新Current屬性,然後返回Current屬性,直到MoveNext返回false。

總結一下:
GetIEnumerator()負責獲取枚舉器。
MoveNext()負責讓Current獲取下一個值,並判斷遍歷是否結束。
Current負責返回當前指向的值。
Rest()負責重置枚舉器的狀態(在foreach中沒有用到)
這些就是IEnumerable,IEnumerator的基本工作原理了。

其次我們發現:

ConstArrayList constArrayList = new ConstArrayList();
foreach(int item in constArrayList)
{
    writeLine(item);
}

其實就等價於:

ConstArrayList constArrayList = new ConstArrayList();
IEnumerator enumeratorSimple = constArrayList.GetEnumerator();
while (enumeratorSimple.MoveNext())
{
    int item = (int)enumeratorSimple.Current;
    WriteLine(item);
}

也就是說foreach其實是一種語法糖,用來簡化對可枚舉元素的遍歷代碼。而被遍歷的類通過實現IEnumerable接口和一個相關的IEnumerator枚舉器來實現遍歷功能。

IEnumerator和IEnumerable詳解