迭代器

引言:數據庫

   在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 中如何提供對迭代器的支持.函數

1、迭代器的介紹工具

   迭代器你們能夠想象成數據庫的遊標,即一個集合中的某個位置,C# 1.0中使用foreach語句實現了訪問迭代器的內置支持,使用foreach使咱們遍歷集合更加容易(比使用for語句更加方便,而且也更加容易理解),foreach被編譯後會調用GetEnumerator來返回一個迭代器,也就是一個集合中的初始位置(foreach其實也至關因而一個語法糖,把複雜的生成代碼工做交給編譯器去執行)。測試

2、C#1.0如何實現迭代器this

   在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 }
複製代碼

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

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

   在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工具獲得一張截圖:blog

  從上面截圖能夠看出,yield return 語句實際上是C#中提供的另外一個語法糖,簡化咱們實現迭代器的源代碼,把具體實現複雜迭代器的過程交給編譯器幫咱們去完成,看來C#編譯器真是作得很是人性化,把複雜的工做留給本身作,讓咱們作一個簡單的工做就行了。

4、迭代器的執行過程

 爲了讓你們更好的理解迭代器,下面列出迭代器的執行流程:

5、迭代器的延遲計算

  從第四部分中迭代器的執行過程當中能夠知道迭代器是延遲計算的, 由於迭代的主體在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方法返回的集合進行迭代的輸出語句,在代碼中都有明確指出,相信你們通過這樣的解釋後就不難理解測試三的運行結果了。

6、小結

  本專題主要介紹了C# 2.0中經過yield return語句對迭代器實現的簡化,然而對於編譯器而言,卻沒有簡化,它一樣生成了一個類去實現IEnumerator接口,只是咱們開發人員去實現一個迭代器獲得了簡化而已。但願經過本專題,你們能夠對迭代器有一個進一步的認識,而且迭代器的延遲計算也是Linq的基礎,本專題以後將會和你們介紹C# 3.0中提出的新特性,然而C# 3.0中提出來的Lambda,Linq能夠說是完全改變咱們編碼的風格,後面的專題中將會和你們一一分享我所理解C# 3.0 中的特性。

相關文章
相關標籤/搜索