亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

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

2019-11-17 02:26:03
字體:
來源:轉載
供稿:網友

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

0x00 前言

在匹夫的上一篇文章《匹夫細說C#:不是“棧類型”的值類型,從生命周期聊存儲位置》的最后,匹夫以總結和后記的方式涉及到一部分迭代器的知識。但是覺得還是不夠過癮,很多需要說清楚的內容還是含糊不清,所以這周就專門寫一下c#中的迭代器吧。

0x01 你好,迭代器

首先思考一下,在什么情景下我們需要使用到迭代器?

假設我們有一個數據容器(可能是Array,List,Tree等等),對我們這些使用者來說,我們顯然希望這個數據容器能提供一種無需了解它的內部實現就可以獲取其元素的方法,無論它是Array還是List或者別的什么,我們希望可以通過相同的方法達到我們的目的。

此時,迭代器模式(iterator pattern)便應運而生,它通過持有迭代狀態,追蹤當前元素并且識別下一個需要被迭代的元素,從而可以讓使用者透過特定的界面巡訪容器中的每一個元素而不用了解底層的實現。

那么,在c#中,迭代器到底是以一個怎樣的面目出現的呢?

如我們所知,它們被封裝在IEnumerable和IEnumerator這兩個接口中(當然,還有它們的泛型形式,要注意的是泛型形式顯然是強類型的。且IEnumerator<T>實現了IDisposable接口)。

IEnumerable非泛型形式:

//IEnumerable非泛型形式[ComVisibleAttribute(True)][GuidAttribute("496B0ABE-CDEE-11d3-88E8-00902754C43A")]public interface IEnumerable{    IEnumerator GetEnumerator();}

IEnumerator非泛型形式:

//IEnumerator非泛型形式[ComVisibleAttribute(true)][GuidAttribute("496B0ABF-CDEE-11d3-88E8-00902754C43A")]public interface IEnumerator{    Object Current {get;}    bool MoveNext();    void Reset();}

IEnumerable泛型形式:

//IEnumerable泛型形式public interface IEnumerable<out T> : IEnumerable{    IEnumerator<T> GetEnumerator();    IEnumerator GetEnumerator(); }

IEnumerator泛型形式:

//IEnumerator泛型形式public interface IEnumerator<out T> : IDisposable, IEnumerator{    void Dispose();     Object Current {get;}     T Current {get;}    bool MoveNext();     void Reset(); }[ComVisibleAttribute(true)]public interface IDisposable{    void Dispose();}

IEnumerable接口定義了一個可以獲取IEnumerator的方法——GetEnumerator()。

而IEnumerator則在目標序列上實現循環迭代(使用MoveNext()方法,以及Current屬性來實現),直到你不再需要任何數據或者沒有數據可以被返回。使用這個接口,可以保證我們能夠實現常見的foreach循環。

為什么會有2個接口?

到此,各位看官是否和曾經的匹夫有相同的疑惑呢?那就是為何IEnumerable自己不直接實現MoveNext()方法、提供Current屬性呢?為何還需要額外的一個接口IEnumerator來專門做這個工作?

OK,假設有兩個不同的迭代器要對同一個序列進行迭代。當然,這種情況很常見,比如我們使用兩個嵌套的foreach語句。我們自然希望兩者相安無事,不要互相影響彼此。所以自然而然的,我們需要保證這兩個獨立的迭代狀態能夠被正確的保存、處理。這也正是IEnumerator要做的工作。而為了不違背單一職責原則,不使IEnumerable擁有過多職責從而陷入分工不明的窘境,所以IEnumerable自己并沒有實現MoveNext()方法。

迭代器的執行步驟

為了更直觀的了解一個迭代器,匹夫這里提供一個小例子。

using System;using System.Collections.Generic;class Class1{     static void Main()    {        foreach (string s in GetEnumerableTest())        {            Console.WriteLine(s);        }    }     static IEnumerable<string> GetEnumerableTest()    {        yield return "begin";                for (int i=0; i < 10; i++)        {            yield return i.ToString();        }                yield return "end";    }}

輸出結果如圖:

OK,那么匹夫就給各位捋一下這段代碼的執行過程。

  1. Main調用GetEnumerableTest()方法
  2. GetEnumerableTest()方法會為我們創建一個編譯器生成的新的類"Class1/'<GetEnumerableTest>c__Iterator0'"(本例中)的實例。注意,此時GetEnumerableTest()方法中,我們自己的代碼尚未執行
  3. Main調用MoveNext()方法
  4. 迭代器開始執行,直到它遇到第一個yield return語句。此時迭代器會獲取當前的值是“start”,并且返回true以告知此時還有數據
  5. Main使用Current屬性以獲取數據,并打印出來
  6. Main再次調用MoveNext()方法
  7. 迭代器繼續從上次遇到yield return的地方開始執行,并且和之前一樣,直到遇到下一個yield return
  8. 迭代器按照這種方式循環,直到MoveNext()方法返回false,以告知此時已經沒有數據了

這個例子中迭代器的執行過程,匹夫已經給各位看官簡單的描述了一下。但是還有幾點需要關注的,匹夫也想提醒各位注意一下。

  • 在第一次調用MoveNext()方法之前,我們自己在GetEnumerableTest中的代碼不會執行
  • 之后調用MoveNext()方法時,會從上次暫停(yield return)的地方開始。
  • 編譯器會保證GetEnumerableTest方法中的局部變量能夠被保留,換句話說,雖然本例中的i是值類型實例,但是它的值其實是被迭代器保存在堆上的,這樣才能保證每次調用MoveNext時,它是可用的。這也是匹夫上一篇文章中說迭代器塊中的局部變量會被分配在堆上的原因。

好啦,簡單總結了一下C#中的迭代器的外觀。那么接下來,我們繼續向內部前進,來看看迭代器究竟是如何實現的。

0x02 原來是狀態機呀

上一節我們已經從外部看到了IEnumerable和IEnumerator這兩個接口的用法了,但是它們的內部到底是如何實現的呢?兩者之間又有何區別呢?

既然要深入迭代器的內部,這就是一個不得不面對的問題。

那么匹夫就寫一個小程序,之后再通過反編譯的方式,看看在我們自己手動寫的代碼背后,編譯器究竟又給我們做了哪些工作吧。

為了簡便起見,這個小程序僅僅實現一個按順序返回0-9這10個數字的功能。

IEnumerator的內部實現

首先,我們定義一個返回IEnumerator<T>的方法TestIterator()。

//IEnumerator<T>測試using System;using System.Collections;class Test{    static IEnumerator<int> TestIterator()    {        for (int i = 0; i < 10; i++)        {            yield return i;        }    }}

接下來,我們看看反編譯之后的代碼,探查一下編譯器到底為我們做了什么吧。

internal class Test{    // Methods 注,此時還沒有執行任何我們寫的代碼    PRivate static IEnumerator<int> TestIterator()    {        return new <TestIterator>d__0(0);    }    // Nested Types 編譯器生成的類,用來實現迭代器。    [CompilerGenerated]    private sealed class <TestIterator>d__0 : IEnumerator<int>, IEnumerator, IDisposable    {        // Fields 字段:state和current是默認出現的        private int <>1__state;        private int <>2__current;        public int <i>5__1;//<i>5__1來自我們迭代器塊中的局部變量,匹夫上一篇文章中提到過        // Methods 構造函數,初始化狀態        [DebuggerHidden]        public <TestIterator>d__0(int <>1__state)        {            this.<>1__state = <>1__state;        }        // 幾乎所有的邏輯在這里        private bool MoveNext()        {            switch (this.<>1__state)            {                case 0:                    this.<>1__state = -1;                    this.<i>5__1 = 0;                    while (this.<i>5__1 < 10)                    {                        this.<>2__current = this.<i>5__1;                        this.<>1__state = 1;                        return true;                    Label_0046:                        this.<>1__state = -1;                        this.<i>5__1++;                    }                    break;                case 1:                    goto Label_0046;            }            return false;        }        [DebuggerHidden]        void IEnumerator.Reset()        {            throw new NotSupportedException();        }        void IDisposable.Dispose()        {        }        // Properties        int IEnumerator<int>.Current        {            [DebuggerHidden]            get            {                return this.<>2__current;            }        }        object IEnumerator.Current        {            [DebuggerHidden]            get            {                return this.<>2__current;            }        }    }}

我們先全面的看一下反編譯之后的代碼,可以發現幾乎所有的邏輯都發生在MoveNext()方法中。那么之后我們再詳細介紹下它,現在我們先從上到下把代碼捋一遍。

  1. 這段代碼給人的第一印象就是命名似乎很不雅觀。的確,這種在正常的C#代碼中不會出現的命名,在編譯器生成的代碼中卻是常常出現。因為這樣就可以避免和已經存在的正常名字發生沖突的可能性。
  2. 調用TestIterator()方法的結果僅僅是調用了<TestIterator>d__0(編譯器生成的用來實現迭代器的類)的構造函數。而這個構造函數會設置迭代器的初始狀態,此時的參數為0,而構造函數會將0賦值給記錄迭代器狀態的字段:this.<>1__state = <>1__state;。注意,此時我們自己的代碼并沒有執行。
  3. <TestIterator>d__0這個類實現了3個接口:IEnumerator<int>, IEnumerator, IDisposable。
  4. IDisposable的實現十分重要。因為foreach語句會在它自己的finally代碼塊中調用實現了IDisposable接口的迭代器的Dispose方法。
  5. <TestIterator>d__0類有3個字段:<>1__state,<>2__current, <i>5__1。其中,<>1__state私有字段標識迭代器的狀態,<>2__current私有字段則追蹤當前的值,而<i>5__1共有字段則是我們在迭代器塊中定義的局部變量i。
  6. MoveNext()方法的實現則依托與switch語句。根據狀態機的狀態,執行不同的代碼。
  7. 在本例中Dispose方法什么都沒有做。
  8. 在IEnumerator和IEnumerator<int>的實現中,Current都是單純的返回<>2__current的值。

OK,IEnumerator接口我們看完了。下面再來看看另一個接口IEnumerable吧。

IEnumerator VS IEnumerable

依樣畫葫蘆,這次我們仍然是寫一個實現按順序返回0-9這10個數字的功能的小程序,只不過返回類型變為IEnumerable<T>。

using System;using System.Collections.Generic;class Test{    static IEnumerable<int> TestIterator()    {        for (int i = 0; i < 10; i++)        {            yield return i;        }    }}

之后,我們同樣通過反編譯,看看編譯器又背著我們做了什么。

internal class Test{    private static IEnumerable<int> TestIterator()    {        return new <TestIterator>d__0(-2);    }    private sealed class <TestIterator>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable    {        // Fields        private int <>1__state;        private int <>2__current;        private int <>l__initialThreadId;        public int <count>5__1;        public <TestIterator>d__0(int <>1__state)        {            this.<>1__state = <>1__state;            this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId;        }
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产亚洲精品久久久久久牛牛| 久久久久久国产精品| 国产精品999| 国产一区二区三区中文| 国产精品一区二区三| 日产精品99久久久久久| 亚洲一区二区三区在线免费观看| 久久精品国产欧美亚洲人人爽| 精品呦交小u女在线| 一区二区在线视频播放| xxxx欧美18另类的高清| 92看片淫黄大片欧美看国产片| 久久中文字幕在线| 亚洲精品国产精品乱码不99按摩| 日韩欧美在线一区| 国产成人极品视频| 久久免费视频观看| 91经典在线视频| 国产精品男人爽免费视频1| 欧美日韩国产精品一区二区不卡中文| 久久99国产精品久久久久久久久| 亚洲区免费影片| 91成人在线观看国产| 色婷婷**av毛片一区| 隔壁老王国产在线精品| 亚洲激情在线观看视频免费| 亚洲娇小xxxx欧美娇小| 亚洲综合最新在线| 91亚洲一区精品| 韩剧1988免费观看全集| 久久国产精品网站| 欧美视频二区36p| 欧美成人午夜激情视频| 日本人成精品视频在线| 国产主播喷水一区二区| 成人h猎奇视频网站| 亚洲成av人片在线观看香蕉| 日韩美女中文字幕| 欧美性videos高清精品| 国产成人精品在线| www.欧美三级电影.com| 亚洲精品自拍偷拍| 亚洲高清一区二| 伊人一区二区三区久久精品| 中文字幕av一区二区| 日韩在线精品一区| 欧美成人四级hd版| 91成人福利在线| 亚洲视频自拍偷拍| 欧美激情极品视频| 日本不卡视频在线播放| 日韩电影在线观看永久视频免费网站| 日韩成人在线视频观看| 欧美成人免费全部观看天天性色| 久久久久久久国产| 奇米一区二区三区四区久久| 欧美精品免费播放| 久久国产一区二区三区| 精品高清美女精品国产区| 日韩高清av在线| 91精品国产电影| 成人字幕网zmw| 国产精品免费一区二区三区都可以| 国产精品成人国产乱一区| 久久天堂电影网| 欧美三级欧美成人高清www| 亚洲一区亚洲二区| 一区二区在线视频播放| 国产丝袜精品视频| 欧美做爰性生交视频| 国产成人极品视频| 成人免费xxxxx在线观看| 欧美成人免费全部观看天天性色| 久久久精品欧美| 国产精品激情av电影在线观看| 亚洲影视九九影院在线观看| 中文字幕欧美精品在线| 欧美剧在线观看| 久久国内精品一国内精品| 97在线观看免费高清| 欧美一级在线播放| 国产精品国产三级国产专播精品人| 日韩高清av一区二区三区| 国产精品一区二区久久久久| 成人午夜一级二级三级| 亚洲精品一区二区在线| 色综合久综合久久综合久鬼88| 中文字幕欧美精品日韩中文字幕| 动漫精品一区二区| 亚洲人成网站在线播| 国产91热爆ts人妖在线| 视频直播国产精品| 欧美区在线播放| 亚洲黄在线观看| 久久精品国产亚洲一区二区| 日韩精品视频三区| 中文字幕无线精品亚洲乱码一区| 亚洲欧美一区二区激情| 国产精品久久久久久av福利软件| 51ⅴ精品国产91久久久久久| 日韩av理论片| 欧美中文字幕第一页| 中文字幕日韩精品在线观看| 亚洲成av人乱码色午夜| 久久久久久国产精品久久| 国产精品香蕉在线观看| 国产视频精品在线| 超碰精品一区二区三区乱码| 91夜夜揉人人捏人人添红杏| 欧美另类老女人| 亚洲最大的网站| 欧美日韩一区二区免费视频| 日韩在线视频播放| 欧日韩在线观看| 欧美一区二区三区免费观看| 久久亚洲精品一区二区| 麻豆国产va免费精品高清在线| 亚洲综合一区二区不卡| 欧美日韩亚洲国产一区| 亚洲精品电影网| 在线午夜精品自拍| 久久亚洲精品小早川怜子66| 亚洲欧美一区二区三区四区| 亚洲欧洲在线看| 91精品国产自产在线观看永久| 亚洲国产高清高潮精品美女| 亚洲最新视频在线| 国内伊人久久久久久网站视频| 欧美刺激性大交免费视频| 欧美成人国产va精品日本一级| 欧美黑人xxxx| 欧美激情亚洲国产| 亚洲精品成人免费| 久久成年人免费电影| 九九视频这里只有精品| 97精品在线观看| 久久精品视频在线| 日韩av黄色在线观看| 亚洲一品av免费观看| 91视频8mav| 97在线观看免费| 国产一区欧美二区三区| 热门国产精品亚洲第一区在线| 亚洲成av人乱码色午夜| 国产成人av在线| 欧美精品九九久久| 成人免费网站在线看| 91久久综合亚洲鲁鲁五月天| 亚洲午夜未满十八勿入免费观看全集| 亚洲а∨天堂久久精品9966| 欧美最顶级丰满的aⅴ艳星| 中文字幕av日韩| 亚洲激情 国产| 欧美一级大片在线观看| 亚洲天堂久久av| 亚洲欧洲日产国产网站| 欧美精品情趣视频| 欧美日韩国产精品| 欧美最猛黑人xxxx黑人猛叫黄| 色99之美女主播在线视频| 欧美国产亚洲视频| 国产精品对白刺激| 96pao国产成视频永久免费| 国产日产欧美精品|