[C#基礎知識系列]專題十二:迭代器


引言:

   在C# 1.0中我們經常使用foreach來遍歷一個集合中的元素,然而一個類型要能夠使用foreach關鍵字來對其進行遍歷必須實現IEnumerableIEnumerable<T>接口,(之所以來必須要實現IEnumerable這個接口,是因為foreach是迭代語句,要使用foreach必須要有一個迭代器才行的,然而IEnumerable接口中就有IEnumerator GetEnumerator()方法是返回迭代器的,所以實現了IEnumerable接口,就必須實現GetEnumerator()這個方法來返回迭代器,有了迭代器就自然就可以使用foreach語句了),然而在C# 1.0中要獲得迭代器就必須實現IEnumerable接口中的GetEnumerator()方法,然而要實現一個迭代器就必須實現IEnumerator接口中的bool MoveNext()和void Reset()方法,然而 C# 2.0中提供 yield關鍵字來簡化迭代器的實現,這樣在C# 2.0中如果我們要自定義一個迭代器就容易多了。下面就具體介紹了C# 2.0 中如何提供對迭代器的支持.

一、迭代器的介紹

   迭代器大家可以想象成數據庫的游標,即一個集合中的某個位置,C# 1.0中使用foreach語句實現了訪問迭代器的內置支持,使用foreach使我們遍歷集合更加容易(比使用for語句更加方便,並且也更加容易理解),foreach被編譯后會調用GetEnumerator來返回一個迭代器,也就是一個集合中的初始位置(foreach其實也相當於是一個語法糖,把復雜的生成代碼工作交給編譯器去執行)。

二、C#1.0如何實現迭代器

   在C# 1.0 中實現一個迭代器必須實現IEnumerator接口,下面代碼演示了傳統方式來實現一個自定義的迭代器:

  1 using System;
  2 using System.Collections;
  3 
  4 namespace 迭代器Demo
  5 {
  6     class Program
  7     {
  8         static void Main(string[] args)
  9         {
 10             Friends friendcollection = new Friends();
 11             foreach (Friend f in friendcollection)
 12             {
 13                 Console.WriteLine(f.Name);
 14             }
 15 
 16             Console.Read();
 17         }
 18     }
 19 
 20     /// <summary>
 21     ///  朋友類
 22     /// </summary>
 23     public class Friend
 24     {
 25         private string name;
 26         public string Name
 27         {
 28             get { return name; }
 29             set { name = value; }
 30         }
 31         public Friend(string name)
 32         {
 33             this.name = name;
 34         }
 35     }
 36 
 37     /// <summary>
 38     ///   朋友集合
 39     /// </summary>
 40     public class Friends : IEnumerable
 41     {
 42         private Friend[] friendarray;
 43 
 44         public Friends()
 45         {
 46             friendarray = new Friend[]
 47             {
 48                 new Friend("張三"),
 49                 new Friend("李四"),
 50                 new Friend("王五")
 51             };
 52         }
 53 
 54         // 索引器
 55         public Friend this[int index]
 56         {
 57             get { return friendarray[index]; }
 58         }
 59 
 60         public int Count
 61         {
 62             get { return friendarray.Length; }
 63         }
 64 
 65         // 實現IEnumerable<T>接口方法
 66        public  IEnumerator GetEnumerator()
 67         {
 68             return new FriendIterator(this);
 69         }
 70     }
 71 
 72     /// <summary>
 73     ///  自定義迭代器,必須實現 IEnumerator接口
 74     /// </summary>
 75     public class FriendIterator : IEnumerator
 76     {
 77         private readonly Friends friends;
 78         private int index;
 79         private Friend current;
 80         internal FriendIterator(Friends friendcollection)
 81         {
 82             this.friends = friendcollection;
 83             index = 0;
 84         }
 85 
 86         #region 實現IEnumerator接口中的方法
 87         public object Current
 88         {
 89             get
 90             {
 91                 return this.current;
 92             }
 93         }
 94 
 95         public bool MoveNext()
 96         {
 97             if (index + 1 > friends.Count)
 98             {
 99                 return false;
100             }
101             else
102             {
103                 this.current = friends[index];
104                 index++;
105                 return true;
106             }
107         }
108 
109         public void Reset()
110         {
111             index = 0;
112         }
113 
114         #endregion 
115     }
116 }

運行結果(上面代碼中都有詳細的注釋,這里就不說明了,直接上結果截圖):

三、使用C#2.0的新特性簡化迭代器的實現

   在C# 1.0 中要實現一個迭代器必須實現IEnumerator接口,這樣就必須實現IEnumerator接口中的MoveNext、Reset方法和Current屬性,從上面代碼中看出,為了實現FriendIterator迭代器需要寫40行代碼,然而在C# 2.0 中通過yield return語句簡化了迭代器的實現,下面看看C# 2.0中簡化迭代器的代碼:

 1 namespace 簡化迭代器的實現
 2 {
 3     class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             Friends friendcollection = new Friends();
 8             foreach (Friend f in friendcollection)
 9             {
10                 Console.WriteLine(f.Name);
11             }
12 
13             Console.Read();
14         }
15     }
16 
17     /// <summary>
18     ///  朋友類
19     /// </summary>
20     public class Friend
21     {
22         private string name;
23         public string Name
24         {
25             get { return name; }
26             set { name = value; }
27         }
28         public Friend(string name)
29         {
30             this.name = name;
31         }
32     }
33 
34     /// <summary>
35     ///   朋友集合
36     /// </summary>
37     public class Friends : IEnumerable
38     {
39         private Friend[] friendarray;
40 
41         public Friends()
42         {
43             friendarray = new Friend[]
44             {
45                 new Friend("張三"),
46                 new Friend("李四"),
47                 new Friend("王五")
48             };
49         }
50 
51         // 索引器
52         public Friend this[int index]
53         {
54             get { return friendarray[index]; }
55         }
56 
57         public int Count
58         {
59             get { return friendarray.Length; }
60         }
61 
62         // C# 2.0中簡化迭代器的實現
63         public IEnumerator GetEnumerator()
64         {
65             for (int index = 0; index < friendarray.Length; index++)
66             {
67                 // 這樣就不需要額外定義一個FriendIterator迭代器來實現IEnumerator
68                 // 在C# 2.0中只需要使用下面語句就可以實現一個迭代器
69                 yield return friendarray[index];
70             }
71         }
72     }
73 }

  在上面代碼中有一個yield return 語句,這個語句的作用就是告訴編譯器GetEnumerator方法不是一個普通的方法,而是實現一個迭代器的方法,當編譯器看到yield return語句時,編譯器知道需要實現一個迭代器,所以編譯器生成中間代碼時為我們生成了一個IEnumerator接口的對象,大家可以通過Reflector工具進行查看,下面是通過Reflector工具得到一張截圖:

  從上面截圖可以看出,yield return 語句其實是C#中提供的另一個語法糖,簡化我們實現迭代器的源代碼,把具體實現復雜迭代器的過程交給編譯器幫我們去完成,看來C#編譯器真是做得非常人性化,把復雜的工作留給自己做,讓我們做一個簡單的工作就好了。

四、迭代器的執行過程

 為了讓大家更好的理解迭代器,下面列出迭代器的執行流程:

五、迭代器的延遲計算

  從第四部分中迭代器的執行過程中可以知道迭代器是延遲計算的, 因為迭代的主體在MoveNext()中實現(因為在MoveNext()方法中訪問了集合中的當前位置的元素),Foreach中每次遍歷執行到in的時候才會調用MoveNext()方法,所以迭代器可以延遲計算,下面通過一個示例來演示迭代器的延遲計算:

namespace 迭代器延遲計算Demo
{
    class Program
    {
        /// <summary>
        ///  演示迭代器延遲計算
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            // 測試一
            //WithIterator();
            //Console.Read();

            // 測試二
            //WithNoIterator();
            //Console.Read();

            // 測試三
            foreach (int j in WithIterator())
            {
                Console.WriteLine("在main輸出語句中,當前i的值為:{0}", j);
            }

            Console.Read();
        }

        public static IEnumerable<int> WithIterator()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("在WithIterator方法中的, 當前i的值為:{0}", i);
                if (i > 1)
                {
                    yield return i;
                }
            }
        }

        public static IEnumerable<int> WithNoIterator()
       {
           List<int> list = new List<int>();
           for (int i = 0; i < 5; i++)
           {
               Console.WriteLine("當前i的值為:{0}", i);
               if (i > 1)
               {
                   list.Add(i);
               }
           }

           return list;
       }
    }
}

當運行測試一的代碼時,控制台中什么都不輸出,原因是生成的迭代器延遲了值的輸出,大家可以用Reflector工具反編譯出編譯器生成的中間語言代碼就可以發現原因了,下面是一張截圖:

從圖中可以看出,WithIterator()被編譯成下面的代碼了(此時編譯器把我們自己方法體寫的代碼給改了):

public static IEnumerable<int> WithIterator()
{
       return new <WithIterator>d_0(-2);  
}

  從而當我們測試一的代碼中調用WithIterator()時,對於編譯器而言,就是實例化了一個<WithIterator>d_0的對象(<WithIterator>d_0類是編譯看到WithIterator方法中包含Yield return 語句生成的一個迭代器類),所以運行測試一的代碼時,控制台中什么都不輸出。

當運行測試二的代碼時,運行結果就如我們期望的那樣輸出(這里的運行結果就不解釋了,列出來是為了更好說明迭代器的延遲計算):

當我們運行測試三的代碼時,運行結果就有點讓我們感到疑惑了, 下面先給出運行結果截圖,然后在分析原因。

可能剛開始看到上面的結果很多人會有疑問,為什么2,3,4會運行兩次的呢?下面具體為大家分析下為什么會有這樣的結果。

測試代碼三中通過foreach語句來遍歷集合時,當運行in的時候就會運行IEnumerator.MoveNext()方法,下面是上面代碼的MoveNext()方法的代碼截圖:

  從截圖中可以看到有Console.WriteLine()語句,所以用foreach遍歷的時候才會有結果輸出(主要是因為foreach中in 語句調用了MoveNext()方法),至於為什么2,3,4會運行兩行,主要是因為這里有兩個輸出語句,一個是WithIterator方法體內for語句中的輸出語句,令一個是Main函數中對WithIterator方法返回的集合進行迭代的輸出語句,在代碼中都有明確指出,相信大家經過這樣的解釋后就不難理解測試三的運行結果了。

六、小結

  本專題主要介紹了C# 2.0中通過yield return語句對迭代器實現的簡化,然而對於編譯器而言,卻沒有簡化,它同樣生成了一個類去實現IEnumerator接口,只是我們開發人員去實現一個迭代器得到了簡化而已。希望通過本專題,大家可以對迭代器有一個進一步的認識,並且迭代器的延遲計算也是Linq的基礎,本專題之后將會和大家介紹C# 3.0中提出的新特性,然而C# 3.0中提出來的Lambda,Linq可以說是徹底改變我們編碼的風格,后面的專題中將會和大家一一分享我所理解C# 3.0 中的特性。

 

附件:源程序代碼http://files.cnblogs.com/zhili/%E8%BF%AD%E4%BB%A3%E5%99%A8Demo.zip

 破解版的Reflector工具:http://files.cnblogs.com/zhili/Reflector.zip

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM