引言:
在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接口,下面代碼示範了傳統方式來實作一個自定義的疊代器:
using System;
using System.Collections;
namespace 疊代器Demo
{
class Program
{
static void Main(string[] args)
{
Friends friendcollection = new Friends();
foreach (Friend f in friendcollection)
{
Console.WriteLine(f.Name);
}
Console.Read();
}
}
/// <summary>
/// 朋友類
/// </summary>
public class Friend
private string name;
public string Name
get { return name; }
set { name = value; }
public Friend(string name)
this.name = name;
/// 朋友集合
public class Friends : IEnumerable
private Friend[] friendarray;
public Friends()
friendarray = new Friend[]
new Friend("張三"),
new Friend("李四"),
new Friend("王五")
};
// 索引器
public Friend this[int index]
get { return friendarray[index]; }
public int Count
get { return friendarray.Length; }
// 實作IEnumerable<T>接口方法
public IEnumerator GetEnumerator()
return new FriendIterator(this);
/// 自定義疊代器,必須實作 IEnumerator接口
public class FriendIterator : IEnumerator
private readonly Friends friends;
private int index;
private Friend current;
internal FriendIterator(Friends friendcollection)
this.friends = friendcollection;
index = 0;
#region 實作IEnumerator接口中的方法
public object Current
get
return this.current;
public bool MoveNext()
if (index + 1 > friends.Count)
return false;
else
this.current = friends[index];
index++;
return true;
public void Reset()
#endregion
}
運作結果(上面代碼中都有詳細的注釋,這裡就不說明了,直接上結果截圖):
三、使用C#2.0的新特性簡化疊代器的實作
在C# 1.0 中要實作一個疊代器必須實作IEnumerator接口,這樣就必須實作IEnumerator接口中的MoveNext、Reset方法和Current屬性,從上面代碼中看出,為了實作FriendIterator疊代器需要寫40行代碼,然而在C# 2.0 中通過yield return語句簡化了疊代器的實作,下面看看C# 2.0中簡化疊代器的代碼:
namespace 簡化疊代器的實作
// C# 2.0中簡化疊代器的實作
public IEnumerator GetEnumerator()
for (int index = 0; index < friendarray.Length; index++)
// 這樣就不需要額外定義一個FriendIterator疊代器來實作IEnumerator
// 在C# 2.0中隻需要使用下面語句就可以實作一個疊代器
yield return friendarray[index];
在上面代碼中有一個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();
// 測試三
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 中的特性。
<a href="http://down.51cto.com/data/2361876" target="_blank">附件:http://down.51cto.com/data/2361876</a>
本文轉自LearningHard 51CTO部落格,原文連結:http://blog.51cto.com/learninghard/1076730,如需轉載請自行聯系原作者