引言:數據庫
在C# 1.0中咱們常用foreach來遍歷一個集合中的元素,然而一個類型要可以使用foreach關鍵字來對其進行遍歷必須實現IEnumerable或IEnumerable<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; } } }
當運行測試一的代碼時,控制檯中什麼都不輸出,緣由是生成的迭代器延遲了i 值的輸出,你們能夠用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 中的特性。