迭代器之迷

迭代器之謎

一.序言

yiled 關鍵字曾今一直困擾着我,彷彿是夏日清晨的迷霧,原本以爲散去之後從此就不會再有,可是沒想到第二天依舊籠罩在心頭,那麼今天,對,就是此時當下,我要揭開她那神祕的面紗,撥雲見日。

迭代器,這名字很唬人,坦白說初次聽到這個名字我做不到顧名思義,我不知道這是用來幹嘛的,也猜不出一二…

後來,在那個月黑風高的夜晚,只記得,那夜狂風大作,樹葉吱吱作響,只見一個人影,欻欻欻,以迅雷不及掩耳之勢從我眼前掠過,那身姿,輕盈柔美,她是誰,她來自哪,她又將去哪,說時遲那時快,只見我縱身一跳,奪窗而出,可我忘記了,我住的在八樓… 那可是八樓呀…八樓

有時候就是這麼皮,扯遠了,哈哈哈哈…

二.畫龍

先拋開迭代器這個名詞,我先拋出一個問題,假設你需要遍歷一個集合你會怎麼做呢?大多數情況下我們無非就是使用for循環或者foreach。可是又有多少初學的朋友知道之所以能用foreach對一個集合進行遍歷操作,是因爲該集合實現了IEnumerable接口,比如我們熟知的數組Array,列表List,字典Dictionary,這些都實現了IEnumerable接口的,我們可以從這些集合的實現中看到這點,直接上圖看得更明白
在這裏插迭代器_圖01_集合實現IEnumerable展示入圖片描述
那這言外之意說明什麼呢,是不是自己寫一個實現了IEnumerable接口的類,然後就可以用foreach關鍵字來對這個類進行遍歷了。如果你這麼想過,那麼恭喜你,你天生就是爲計算機而生的,骨骼精奇,是編程的一把好手,比我聰明數百倍,我當初就沒這麼想過,哎,我要躲在小角落默默的哭泣了…

好啦,玩笑嬉戲一下,下邊我們來代碼實現一下,然後,你會發現新大陸

代碼的功能是遍歷狼羣中狼的數量

首先,定義狼,內部就一個名字

public class Wolf
{
    //名稱
    private string name;

    public Wolf(string name)
    {
        this.name = name;
    }

    public void Log()
    {
        Debug.Log(name);
    }
}

其次,定義狼羣,實現IEnumerable接口,內部定義了一個狼的集合,充當foreach遍歷訪問的數據

public class Wolfs : IEnumerable
{
    //狼羣
    private Wolf[] wolfs;
    public Wolfs()
    {
        wolfs = new Wolf[2] {
            new Wolf("zhangsan"),
            new Wolf("lisi")
        };
    }
    //數量
    public int count
    {
        get
        {
            return wolfs.Length;
        }
    }
    //得到wolfs的元素
    //這是索引器的寫法:以數組的形式訪問wolfs
    //調用方使用(對象名[0])就可以訪問到wolfs[0]元素
    //等同於下邊的Get方法:使用(對象名.Get(0))訪問到wolfs[0]元素
    public Wolf this[int i]
    {
        get
        {
            return wolfs[i];
        }
    }
/*  public Wolf Get(int i)
    {
        return wolfs[i];
    }*/

    //這個方法是IEnumerable中的接口,必須得實現
    //返回值是IEnumerator類型的對象
    public IEnumerator GetEnumerator()
    {
        return new WolfsEnumerator(this);
    }
}

最後,實現WolfsEnumerator類,不實現可不可以呢?咦,這話問的,GetEnumerator方法返回的就是IEnumerator類型的對象,爲了滿足這一條件就必須實現這麼一個類,最終的目的是使用foreach關鍵字去遍歷狼羣,這是必經之路,這是規定

先看看 IEnumerator接口的原型:可以看到兩個方法和一個屬性,這些都必須在子類中去實現它

    public interface IEnumerator
    {
        object Current { get; }

        bool MoveNext();
        void Reset();
    }

再來看看實現IEnumerator的子類,目的是爲了遍歷狼羣,這其中的MoveNext()方法是重中之重,因爲foreach一次就是執行一次MoveNext()

public class WolfsEnumerator : IEnumerator
{
    //構造中傳入狼羣
    public WolfsEnumerator(Wolfs wolfs)
    {
        this.wolfs = wolfs;

        index = 0;
    }

    //要被遍歷的狼羣
    private Wolfs wolfs;
    //當前狼羣的索引
    private int index;
    //遍歷到當前的狼
    private Wolf wolf;
    //對外的訪問入口
    public object Current
    {
        get
        {
            return wolf;
        }
    }

    //實現的接口
    //
    public bool MoveNext()
    {
        bool result = false;

        if (index >= wolfs.count )
        {
            result = false;
        }
        else
        {
            //注意,這裏就是Wolfs裏邊的索引器
            //得到Wolfs內部的wolfs集合的對象
            //實際上就是給Current對象賦值
            wolf = wolfs[index];
            index++;
            result = true;

        }
        return result;
    }
    //實現的接口
    public void Reset()
    {
        index = 0;
    }

寫到這裏,在看這篇文章的你肯定還是很迷糊,不要着急,看看是如何被調用的,你必然會有種醍醐灌頂的感覺,來,我們代碼走起

Wolfs wolfs = new Wolfs();
//看到沒,看到沒,有沒有很神奇
foreach (Wolf w in wolfs)
{
    w.Log();
}

有沒有什麼神奇,我們自己定義的對象,居然也可以使用foreach進行遍歷,遍歷的是什麼,遍歷的就是Wolfs類中的wolfs集合,所以,我們首尾呼應一下:只要完整實現了IEnumerable接口的對象就可以用foreach關鍵字進行遍歷

我們上邊寫了那麼多,那我們可不可以直接調用實現過的那些方法來實現這一結果呢,我們試試看:

Wolfs wolfs = new Wolfs();
//調用我們實現過的GetEnumerator()方法:返回的是一個IEnumerator對象
IEnumerator tor = wolfs.GetEnumerator();
//通過循環調用我們實現過的MoveNext()方法
while (tor.MoveNext())
{
    Wolf w = (Wolf)tor.Current;//通過Current對象進行訪問
    w.Log();
}

可以看到:

foreach (Wolf w in wolfs)
{
    w.Log();
}
/*------------------二者等價-----------------------*/
/*------------------神不神奇-----------------------*/
IEnumerator tor = wolfs.GetEnumerator();
while (tor.MoveNext())
{
    Wolf w = (Wolf)tor.Current;
    w.Log();
}

所以,我前邊才說了每執行一次foreach實際上就是執行一次MoveNext()方法。

三.點睛

那,說到這,我還沒有提到到底什麼是迭代器,也沒有提到yield,我是不是跑偏題了,是麼?其實並沒有,我們接着往下看

說曹操曹操就到,yield關鍵字來了,yield關鍵字實際上是爲了簡化代碼而生的,就那上邊的代碼來說,我可以不實現WolfsEnumerator這類,此話一出,我尼瑪是不是直接從你口中脫口而出,你的心裏是不是在想你上邊明明說了這是規定,必須得實現GetEnumerator()方法,而這個方法恰恰返回得就是個IEnumerator類型的對象,你怎麼能說不實現呢?

很好,非常好,你能如此的思考,說明這文章你看的很細緻,就只有那麼一丁點的距離,你就要看到新大陸了,請先抑制住內心的喜悅和驚奇…

這其中的神奇之處就在於yield關鍵字,同樣的,我們上代碼

//首先 定義狼
public class Wolf
{
    //名稱
    private string name;

    public Wolf(string name)
    {
        this.name = name;
    }

    public void Log()
    {
        Debug.Log(name);
    }
}
//其次 定義狼羣
//爲了方便閱讀:刪除了不必要的代碼
public class Wolfs : IEnumerable
{
    private Wolf[] wolfs;
    public Wolfs()
    {
        wolfs = new Wolf[2] {
            new Wolf("zhangsan"),
            new Wolf("lisi")
        };
    }
    //這裏是關鍵:省去了WolfsEnumerator類的實現
    //WolfsEnumerator類主要實現了:
    //狼羣集合數據的傳入,MoveNext()方法,和Current對象的賦值
    public IEnumerator GetEnumerator()
    {
        yield return wolfs[0]; 
        yield return wolfs[1]; 
        /*
        for(int i=0;i<wolfs.Length;i++)
        {
            yield return wolfs[i];
        }
        */
    }

我只想說神奇不神奇,GetEnumerator()方法中的實現居然用yield return的表達式給替代了,所以說(敲黑板了,敲黑板了),yield return 關鍵字代表着啥呢?其實不代表啥,就是一種語法糖,用於簡化迭代器代碼,迭代器字眼終於出現了,而這裏的WolfsEnumerator就是我們所說的迭代器,它用於使Wolfs對象能夠用foreach關鍵字遍歷其內部的wolfs集合

所以,看到yield return xxx 我們就應該想到IEnumerator接口,想到MoveNext()方法,想到Current屬性,這是其本質,主要是用於輔助一個對象遍歷其中的集合元素

最後,我們可以在來看幾行代碼,來感受下yield return xxx表達式的魅力

    IEnumerator Test()
    {
        //yield return new Wolf("zhangjian");
        //yield return new Wolf("lisi");
        yield return 1;
        yield return 2;
    }

   void Start()
   {
        IEnumerator tor = Test();
        while(tor.MoveNext())
        {
            Debug.Log((int)tor.Current);
        }
   }

可以看到Test()方法背後實際上維護着一個int類型的集合,這個集合當中有兩個元素,分別是1和2,我隱約都看到了隱藏在這個Test()方法背後的TestEnumerator類的實現,當然了,我看到了,不知道你是否也看到了。

到此,迭代器之謎也就落下了帷幕,非常歡迎拍磚,狠狠拍的那種…

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