引言:
在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 中如何提供對迭代器的支持.
一、迭代器的介紹
迭代器大家可以想象成數據庫的游標,即一個集合中的某個位置,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; } } }
當運行測試一的代碼時,控制台中什么都不輸出,原因是生成的迭代器延遲了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方法返回的集合進行迭代的輸出語句,在代碼中都有明確指出,相信大家經過這樣的解釋后就不難理解測試三的運行結果了。
六、小結
本專題主要介紹了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