C#中的迭代器

迭代器

在C#中,foreach語句使得能夠進行比for循環語句更直接和簡單的對集合的迭代。.NET中迭代器是通過IEnumerableIEnumerator接口來實現的(當然,這兩個接口還有其對應的泛型版本:IEnumerable<T>IEnumerator<T>)。其源代碼如下(部分代碼省略)

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.
    IEnumerator GetEnumerator();
}

點擊查看詳細源碼

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();
}

點擊查看詳細源碼

一個基礎例子

以下兩個代碼片段(生成從 0 到 9 的整數序列)等效,我們在 C# 中使用 yield return 上下文關鍵字定義迭代器方法。

private static IEnumerable<int> GetSingleDigitNumbers()
{
    yield return 0;
    yield return 1;
    yield return 2;
    yield return 3;
    yield return 4;
    yield return 5;
    yield return 6;
    yield return 7;
    yield return 8;
    yield return 9;
}
private static IEnumerable<int> GetSingleDigitNumbers()
{
    int index = 0;
    while (index++ < 10)
        yield return index - 1;
}

讓我們自定義的數據類型實現迭代器

public class MyIEnumerable : IEnumerable
{
    private string[] _strList;
    public MyIEnumerable(string[] strList)
    {
        _strList = strList;
    }
    public IEnumerator GetEnumerator()
    {
        //return new MyIEnumerator(_strList);
        for (int i = 0; i < _strList.Length; i++)
        {
            yield return _strList[i];
        }
    }
}
public class MyIEnumerator : IEnumerator
{
    private string[] _strList;
    private int position;
    public MyIEnumerator(string[] strList)
    {
        _strList = strList;
        position = -1;
    }
    public object Current
    {
        get { return _strList[position]; }
    }

    public bool MoveNext()
    {
        position++;
        if (position < _strList.Length)
            return true;
        return false;
    }

    public void Reset()
    {
        position = -1;
    }
}

調用

public static void RunMyIEnumerable()
{
    string[] strList = new string[] { "第一個節點數據", "第二個節點數據", "第三個節點數據" };
    MyIEnumerable myIEnumerable = new MyIEnumerable(strList);
    // 1.獲取IEnumerator接口實例
    var enumerator = myIEnumerable.GetEnumerator(); 

    // 2.判斷是否可以繼續循環
    while (enumerator.MoveNext())
    {
        // 3.取值
        Console.WriteLine(enumerator.Current);
    }
    Console.WriteLine("==========");
    foreach (var item in myIEnumerable) // 效果等同於上述方式
    {
        Console.WriteLine(item);
    }
}

我們調用GetEnumerator的時候,看似裏面for循環了一次,其實這個時候沒有做任何操作。只有調用MoveNext的時候纔會對應調用for循環。
這也是爲什麼Linq to Object中要返回IEnumerable?因爲IEnumerable是延遲加載的,每次訪問的時候才取值。也就是我們在Lambda裏面寫的where、select並沒有循環遍歷(只是在組裝條件),只有在ToList或foreache的時候才真正去集合取值了。這樣大大提高了性能。
以上引用自農碼一生的博客先說IEnumerable,我們每天用的foreach你真的懂它嗎?

參考

本文引用以下文章

Iterators

先說IEnumerable,我們每天用的foreach你真的懂它嗎?

匹夫細說C#:庖丁解牛迭代器,那些藏在幕後的祕密

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章