詳解C#迭代器

  1、迭代器(Iterator)經過持有迭代狀態能夠獲取當前迭代元素而且識別下一個須要迭代的元素,從而能夠遍歷集合中每個元素而不用瞭解集合的具體實現方式;函數

  實現迭代器功能的方法被稱爲迭代器方法,迭代器方法的返回值類型能夠是如下4種接口類型中任意一種:位於命名空間System.Collections中的IEnumerable、IEnumerator和位於命名空間System.Collections.Generic中的IEnumerable<T>、IEnumerator<T>,其中接口IEnumerable和IEnumerator代碼:this

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}
public interface IEnumerator
{
    object Current { get; }

    bool MoveNext();
    void Reset();
}

  ※對應泛型接口IEnumerable<T>和IEnumerator<T>代碼:spa

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}
public interface IEnumerator<out T> : IDisposable, IEnumerator
{
    T Current { get; }
}

  ※接口IEnumerable中聲明瞭獲取IEnumerator類型對象的方法GetEnumerator(),接口IEnumerator中經過屬性Current和方法MoveNext()實現迭代器功能;自定義類型能夠經過繼承接口IEnumerable擁有迭代器功能,而接口IEnumerator爲迭代器功能提供具體實現,設計兩者符合單一職責原則,也所以能夠對同一個對象同時進行屢次迭代;設計

  ※get訪問器也能夠聲明爲迭代器方法,但事件、構造函數和析構函數不能夠聲明爲迭代器方法;迭代器方法不能包含引用參數;code

  2、迭代器方法的內部使用狀態機實現,但可使用yield關鍵字快速實現迭代器方法,使用yield return語句添加須要迭代的元素,在首次迭代時,會一直執行到第一個yield return語句並保存當前迭代狀態,在接下來的每次迭代過程當中都會從暫停的位置繼續執行到下一個yield return語句並保存迭代狀態(MoveNext()方法返回true並將當前迭代的值賦值給Current),直到到達迭代器的結尾(MoveNext()方法返回false)完成本次迭代;也能夠在迭代過程當中使用yield break語句馬上結束本次迭代:對象

IEnumerable<int> MyIterator()
{
    yield return 10;
    yield return 20;
}

  ※其中yield return語句的返回值須要能夠隱式轉換爲迭代器方法返回值IEnumerable<T>中類型參數的類型;blog

  ※這是編譯器爲咱們準備的一種語法糖,編譯器會將其轉換爲使用狀態機實現的實現了接口IEnumerable<T>的嵌套類,查看迭代器方法的IL代碼,能夠看到編譯器生成的狀態機嵌套類:繼承

  3、對於實現了接口IEnumerable或IEnumerable<T>的類型的對象,可使用foreach對其進行遍歷:接口

foreach (int item in MyIterator())
{
    //do…其中item爲int類型,值分別爲10和20
}

  ※其中item其實是類型中GetEnumerator()方法返回值類型中的Current屬性,所以其類型即該屬性的類型,一般可使用var表示,由編譯器進行推斷,例如對哈希表進行遍歷時:事件

foreach (var item in myHashTable)
{
    //do…其中item爲DictionaryEntry類型,item.Key and item.Value
}

  ※因爲屬性Current只有get訪問器,所以不能在foreach循環中對item進行賦值(=運算符操做)操做,但能夠對其對象中的成員進行修改和調用,詳見;

  ※在遍歷內置集合的對象時,若是集合長度發生改變,則極有可能紊亂迭代過程,所以在迭代這些集合對象時不能進行任何修改集合長度的操做,不然會拋出異常InvalidOperationException;

  4、除使用foreach外,還能夠手動進行迭代,對於實現了泛型接口的類型的對象在手動迭代時還須要使用using語句以在其迭代完成後調用Dispose()方法:

using (IEnumerator<int> enumerator = MyIterator().GetEnumerator())

{
    //此時myEnmerator.Current爲其類型的默認值
    while (enumerator.MoveNext())
    {
        //do…其中能夠經過enumerator.Current訪問迭代器的值
    }
}    

  1.對於只實現了接口IEnumerator或IEnumerator<T>的類型的對象,只能手動進行迭代:

IEnumerator<int> MyIterator()
{
    yield return 1;
    yield return 2;
}
using (IEnumerator<int> enumerator = MyIterator())
{
    while (enumerator.MoveNext())
    {
        //do…
    }
}

  2.能夠經過實現接口IEnumerable或IEnumerable<T>來自定義迭代器類型,此時沒必要手動實現IEnumerator或IEnumerator<T>接口,編譯器會自動實現該接口中的Current屬性、MoveNext()、Reset()和Dispose()方法:

public class MyIterator : IEnumerable<int>
{
    public IEnumerator<int> GetEnumerator()
    {
        yield return 10;
        yield return 20;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        Console.WriteLine("Non-Generic GetEnumerator");
        return GetEnumerator(); //注意這裏不是yield return
        //或者手動進行迭代:
        //using (IEnumerator<int> iterator = this.GetEnumerator())
        //{
        //while (iterator.MoveNext())
        //{
        //yield return iterator.Current;
        //}
        //}
    }
}    

  ※此時,在使用foreach遍歷時,若是使用var,會由編譯器自動推斷item爲int類型,並調用泛型接口的實現,若是將item的類型指定爲object類型,則依然會調用泛型接口的實現,但會產生裝箱操做:

foreach (var item in new MyIterator()) //或顯式指定爲int:int item
{
    Console.WriteLine(item); //10 20
}

  ※此時,在使用foreach遍歷時,若是將其轉換爲IEnumerable類型的對象,則會調用非泛型接口的實現,編譯器將自動推斷item爲object類型,併產生裝箱操做:

foreach (var item in (IEnumerable)new MyIterator()) //或顯式指定爲object:object item
{
    Console.WriteLine(item); //Non-Generic GetEnumerator 10 20
}

  3.直接調用迭代器方法或迭代器類型中自動生成的Reset()方法時會拋出異常NotSupportedException,若要從頭開始從新迭代,必須獲取新的迭代器,或在迭代器類型中手動實現Reset()方法;

 


若是您以爲閱讀本文對您有幫助,請點一下「推薦」按鈕,您的承認是我寫做的最大動力!

做者:Minotauros
出處:https://www.cnblogs.com/minotauros/

本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。

相關文章
相關標籤/搜索