C#枚舉器原理解析

來博客園已經快八個月了,這是第一次發表博客,以前之因此沒有動筆寫,徹底是由於本身功力還太淺薄,喜歡博客園的緣由,就是由於這裏乾淨,純粹,寫下垃圾的博客,供別人閱讀是一件可恥的事。能與博客園結緣是必然的事,我喜歡.net技術,而博客園是國內公認的,關於.net技術網站中最好的一個,在這裏能夠見到衆多MS的MVP,就是最好的證實。「原創纔是博客」的理念在這裏,被你們踐行的很好,這樣的氛圍很適合對技術感興趣的人。八個月前在網上搜索資料時,無心看到了站長嘟嘟大神的佳做,文筆清晰,觀點獨特,細緻入微,讀後讓我欽佩不已;便註冊了一個博客園的帳號。廢話不說了,上碼,若是你能夠一眼看穿下面這段代碼的執行流程,請您就飄過吧(這段代碼摘抄自《C#高級編程》,第七版,讓你們帶着問題來學習我以爲是很好的方式)編程

 static void Main()        安全

{            函數

      var game = new GameMoves();學習

               var a = game.Cross();            網站

      while (a.MoveNext())ui

      {.net

        a = a.Current as IEnumerator;設計

     }            繼承

     Console.Read();        遞歸

}

 public class GameMoves    

{        

    IEnumerator cross;        

    IEnumerator circle;        

    public GameMoves()        

    {            

        cross = Cross();            

        circle = Circle();        

    }        

     int move = 0;        

    public const int MaxMoves = 10;        

    public IEnumerator Cross()        

    {            

        while (true)            

        {                

             Console.WriteLine("Cross,move{0}", move);                

            if (++move >= MaxMoves) yield break;                

            yield return circle;                    

        }        

     }        

    public IEnumerator Circle()        

    {            

        while (true)            

        {                

            Console.WriteLine("Circle,move{0}", move);                

             if (++move >= MaxMoves) yield break;                

            yield return cross;            

        }        

    }    

}    

上面的代碼我不會作太多的解釋,若是你理解了本文內容,你應該能夠看懂(注:實在看不懂,就用單步執行吧)。下面再來看看另外這段代碼,當我第一次看到這段代碼的執行結果時,被驚呆了,徹底顛覆了我兩年編程學習對函數執行的認識。

class Program    

{        

     static void Main(string[] args)        

    {               

        var ie = IE();            

         Console.WriteLine("IE()已經調用了,你看到輸出了嗎?");                     ie.MoveNext();            

        Console.Read();         

     }        

    public static IEnumerator IE()        

    {             

        while (true)             

        {                 

            Console.WriteLine("你在調用IE()時看不到個人執行,我不是並行方法啊哦\n哈哈,神奇吧,除非你用IE()的返回值調用了MoveNext()\n而且每次調用均可以看到我哦");                  

             yield return "任意類型均可以直接返回哦";             

        }         

     }          

}

這段代碼稍後我會詳細的解釋,爲了解釋清楚,請耐心聽我慢慢道來,再看一段代碼(哈哈,學編程的,要習慣哦)    

static void Main(string[] args)        

{            

    String[] myArr = new String[] { "The",  "quick", "brown"};                           

            Console.WriteLine("用標準的C#語法使用枚舉器");            

     foreach (var str in myArr)

            {  

                   Console.WriteLine(str);            

    }            

     Console.WriteLine("使用枚舉接口使用枚舉器");

            var myEnumerator = myArr.GetEnumerator();

            while ((myEnumerator.MoveNext()) && (myEnumerator.Current != null))

            {                

        Console.WriteLine("{0}", myEnumerator.Current);

            }

            Console.Read();

}

能夠看到,執行結果徹底同樣,編譯器在處理foreach語句塊時,就是編譯爲與以上相似的等價代碼的(對GetEnumerator()的調用);foreach只是語法糖而已;這個方法(GetEnumerator())的返回值是一個接口(IEnumerator),也就是說,在這個方法體內須要出現一個實現了這個接口(System.Collections.IEnumerator)的類。那就先來看看這個接口的定義;

public interface IEnumerator

{  

    object Current{get;}  //注意這個屬性是隻讀的,這也爲何咱們不能在foreach塊中改變集合元素的緣由(集合元素內部的屬性能夠更改哦);

     bool MoveNext();      // 將枚舉數推動到集合的下一個元素,成功返回true,失敗返回false   

    void Reset();             //將枚舉數設置爲其初始位置,該位置位於集合中第一個元素以前

}

這個接口還有一個泛型版本

public interface  IEnumerator<T>:IDisposable,IEnumerator

{

     T Current{get;};  

    bool MoveNext();     

     void Reset();   

    void Dispose();

}

值得注意的是,泛型版本繼承了IDispose接口,(關於泛型,理解的重點是,要區分開泛型的外殼類和外殼類包含的元素類,這個元素的類型是可變的,而本質上,真正可變的類型確是外殼類自己,這裏的名詞是個人杜撰,還算形象吧)其餘的註釋已經很清楚,再也不過多解釋;(這裏涉及的接口比較多,名稱還很相似,注意分辨哦) 而這個方法(System.Collections.IEnumerator GetEnumerator())自己倒是在IEnumerable接口中定義,能夠看到不少的集合類都繼承實現了這個接口(IEnumerable),而事實上,要使用枚舉器,這個接口並非必須的,只要集合類有一個這個簽名的的方法(System.Collections.IEnumerator GetEnumerator())就足夠了,編譯器根本不會檢查集合類是否實現了接口IEnumberable,這看起來有點神奇,彷佛對代碼安全有點影響,事實上根本沒有,仔細想一下就會明白,編譯器默認會嘗試將foreach轉換爲對 GetEnumerator()的調用,但它發現,類中根本沒有這個方法,天然報錯,注意,報錯不是由於集合沒有實現接口IEnumberable,而是由於集合沒有提供GetEnumerator(),這個我講的已經太羅嗦了,爲了證實這一點,運行下面代碼就能夠證實;注意program類沒有繼承IEnumberable哦,  

class Program

{

            static void Main(string[] args)

            {

                   Program program=new Program();

                   foreach (string s in program)

                  {

                          Console.WriteLine(s);

                    }

                    Console.Read();

            }

            public  IEnumerator GetEnumerator()

            {

                        yield return"一";

                      yield return "二";

                        yield return "三";

             }

}

 

下面仔細解釋一下剛纔遺留的代碼(再次貼來,請你把它貼到VS裏執行一下)  

class Program    

{

        static void Main(string[] args)

        {            

      var ie = IE();

               Console.WriteLine("IE()已經調用了,你看到輸出了嗎?");

                 ie.MoveNext();

                 Console.Read();

        }

        public static IEnumerator IE()

       {

                while (true)

                {

                       Console.WriteLine("你在調用IE()時看不到個人執行,我不是並行方法啊哦\n哈哈,神奇吧,除非你用IE()的返回值調用了MoveNext()\n而且每次調用均可以看到我哦");               

                       yield return "任意類型均可以直接返回哦";

                }

        }

}

至於MS爲何要這樣設計,若是你已經對LINQ有必定的理解,相信你應該能夠理解這一點,LINQ的延遲查詢正是利用這一點。關於LINQ的問題不是這篇博客的重點,反正記住這是MS特殊處理的就行啦。若是要本身實現枚舉器,這個特性也是頗有的。下面咱們再看看這段代碼的另外一個值得關注的地方;yield return語句,能夠看到IE()的返回值是 IEnumerator接口類型,而咱們根本沒有定義實現這個接口的類,那這個方法還怎麼工做啊,應該直接編譯錯誤纔是啊,哈哈,要注意,返回值是經過yield return傳遞的哦,但是yield return裏的類型和返回值類型不同啊,相信不少新手都有這樣的迷惑,其實這是又是語法糖而已,(MS對開發人員真是太好了,爲了咱們少擊打鍵盤,費勁心思啊,但同時對於新手也真是不小的挑戰,容易讓新手浮於技術的表面,而看不到本質的原理)編譯器會把yield return語句轉化爲一個實現了IEnumerator的類,這個類對於編程人員是透明的;每次yield return時都由這個類來收集結果,至於返回值,在調用這個這個方法時,已經當即返回了。能夠把這個這個返回值看作一個特殊的句柄,只有用它調用MoveNext()時(MoveNext()會由foreach語句自動調用哦),該方法才真正執行,固然,yield return部分這時已經再也不方法內了,不然就陷入死遞歸了,感興趣的同窗能夠試試吧yield return返回語句分散開來,中間再夾雜一些其餘語句,而後手工調用Movenext()觀察一下奇怪的執行路線,好了,要介紹的已經差很少了,不明白孩子再好好理解理解吧,否則LINQ就無法學了,本文介紹的內容雖然很基礎,但我認爲對很多人仍是有很多意義的,因此就大膽發到首頁了哈,請大鳥們不要~~

相關文章
相關標籤/搜索