提到迭代器我們不能不想到迭代器模式,那我就以迭代器模式作為開(kāi)場(chǎng)白.
在我們的應(yīng)用程序中常常有這樣一些數(shù)據(jù)結(jié)構(gòu):
它們是一個(gè)數(shù)據(jù)的集合,如果你知道它們內(nèi)部的實(shí)現(xiàn)結(jié)構(gòu)就可以去訪問(wèn)它們,它們各自的內(nèi)部存儲(chǔ)結(jié)構(gòu)互不相同,各種集合有各自的應(yīng)用場(chǎng)合.說(shuō)到這里大家可能想出一大堆這樣的集合了:List,Hashtable,ArrayList等等。這些集合各自都有各自的個(gè)性,這就是它們存在的理由。但如果你想遍歷它你必須知道它內(nèi)部的存儲(chǔ)細(xì)節(jié),作為一個(gè)集合元素,把內(nèi)部細(xì)節(jié)暴露出來(lái)肯定就不好了,這樣客戶程序就不夠穩(wěn)定了,在你更換集合對(duì)象的時(shí)候,比如List不能滿足需求的時(shí)候,你換成Hashtable,因?yàn)橐郧暗目蛻舫绦蜻^(guò)多的關(guān)注了List內(nèi)部實(shí)現(xiàn)的細(xì)節(jié),所以不能很好的移植。而迭代器模式就是為解決這個(gè)問(wèn)題而生的:
提供一種一致的方式訪問(wèn)集合對(duì)象中的元素,而無(wú)需暴露集合對(duì)象的內(nèi)部表示。
比如現(xiàn)在有這樣一個(gè)需求,遍歷集合內(nèi)的元素,然后輸出,但是并不限定集合是什么類型的集合,也就是未來(lái)集合可能發(fā)生改變。
思考:
集合會(huì)發(fā)生改變,這是變化點(diǎn),集合改變了,遍歷方法也改變,我們要保證遍歷的方法穩(wěn)定,那么就要屏蔽掉細(xì)節(jié)。找到了變化點(diǎn)那我們就將其隔離起來(lái)(一般使用interface作為隔離手段):假設(shè)所有的集合都繼承自ICollection接口,這個(gè)接口用來(lái)隔離具體集合的,將集合屏蔽在接口后面,作為遍歷我們肯定需要這樣一些方法:MoveNext,Current,既然ICollection負(fù)責(zé)數(shù)據(jù)存儲(chǔ),職責(zé)又要單一,那么就新建立一個(gè)接口叫做Iterator吧,每種具體的集合都有自己相對(duì)應(yīng)的Iterator實(shí)現(xiàn):
下面是一個(gè)簡(jiǎn)易的實(shí)現(xiàn)代碼:
- /// <summary>
- /// 集合的接口
- /// </summary>
- public interface ICollection
- {
- int Count { get; }
- /// <summary>
- /// 獲取迭代器
- /// </summary>
- /// <returns>迭代器</returns>
- Iterator GetIterator();
- }
- /// <summary>
- /// 迭代器接口
- /// </summary>
- public interface Iterator
- {
- bool MoveNext();
- object Current { get; }
- }
- public class List : ICollection
- {
- private const int MAX = 10;
- private object[] items;
- public List()
- {
- items = new object[MAX];
- }
- public object this[int i]
- {
- get { return items[i]; }
- set { this.items[i] = value; }
- }
- #region ICollection Members
- public int Count
- {
- get { return items.Length; }
- }
- public Iterator GetIterator()
- {
- return new ListIterator(this);
- }
- #endregion
- }
- public class ListIterator : Iterator
- {
- private int index = 0;
- private ICollection list;
- public ListIterator(ICollection list)
- {
- this.list = list;
- index = 0;
- }
- #region Iterator Members
- public bool MoveNext()
- {
- if (index + 1 > list.Count)
- return false;
- else
- {
- index++;
- return true;
- }
- }
- public object Current
- {
- get { return list[index]; }
- }
- #endregion
- }
- /// <summary>
- /// 測(cè)試
- /// </summary>
- public class Program
- {
- static void Main()
- {
- ICollection list = new List();
- Iterator iterator = list.GetIterator();
- while (iterator.MoveNext())
- {
- object current = iterator.Current;
- }
- }
- }
看看最后的測(cè)試,是不是不管具體的集合如何改變,遍歷代碼都非常穩(wěn)定?而且擴(kuò)展新的集合類也非常方便,只是添加代碼不會(huì)修改原來(lái)的代碼,符合開(kāi)閉原則。當(dāng)然,這么好的解決方案微軟當(dāng)然不會(huì)放過(guò),現(xiàn)在C# 2.0里已經(jīng)內(nèi)置了對(duì)迭代器的支持,看看System.Collections, System.Collections.Generic命名空間,所有的集合都實(shí)現(xiàn)了這個(gè)接口:IEnumerable,這個(gè)接口還有泛型的版本。注意到這個(gè)接口只有一個(gè)方法:IEnumerator GetEnumerator();,IEnumerator就是迭代器的接口,相當(dāng)于我的實(shí)例里面的Iterator,它也有泛型的版本。
那么現(xiàn)在在.net里所有的集合類都可以這樣訪問(wèn)了:
但是這樣訪問(wèn)也太麻煩了,所以C#里出現(xiàn)了foreach關(guān)鍵字,我們來(lái)看看foreach背后發(fā)生了什么?假如有如下的代碼:
- public static void Main()
- {
- ArrayList list = new ArrayList();
- list.Add(1);
- list.Add(2);
- list.Add(3);
- foreach (object item in list)
- {
- Console.WriteLine(item.ToString());
- }
- }
下面是它對(duì)應(yīng)的IL代碼:
- .method private hidebysig static void Main() cil managed
- {
- .entrypoint
- .maxstack 2
- .locals init (
- [0] class [mscorlib]System.Collections.ArrayList list,
- [1] object item,
- [2] class [mscorlib]System.Collections.IEnumerator CS$5$0000,
- [3] class [mscorlib]System.IDisposable CS$0$0001)
- L_0000: newobj instance void [mscorlib]System.Collections.ArrayList::.ctor()
- L_0005: stloc.0
- L_0006: ldloc.0
- L_0007: ldc.i4.1
- L_0008: box int32
- L_000d: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)
- L_0012: pop
- L_0013: ldloc.0
- L_0014: ldc.i4.2
- L_0015: box int32
- L_001a: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)
- L_001f: pop
- L_0020: ldloc.0
- L_0021: ldc.i4.3
- L_0022: box int32
- L_0027: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)
- L_002c: pop
- L_002d: ldloc.0
- L_002e: callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Collections.ArrayList::GetEnumerator()
- L_0033: stloc.2
- L_0034: br.s L_0048
- L_0036: ldloc.2
- L_0037: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
- L_003c: stloc.1
- L_003d: ldloc.1
- L_003e: callvirt instance string [mscorlib]System.Object::ToString()
- L_0043: call void [mscorlib]System.Console::WriteLine(string)
- L_0048: ldloc.2
- L_0049: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
- L_004e: brtrue.s L_0036
- L_0050: leave.s L_0063
- L_0052: ldloc.2
- L_0053: isinst [mscorlib]System.IDisposable
- L_0058: stloc.3
- L_0059: ldloc.3
- L_005a: brfalse.s L_0062
- L_005c: ldloc.3
- L_005d: callvirt instance void [mscorlib]System.IDisposable::Dispose()
- L_0062: endfinally
- L_0063: call string [mscorlib]System.Console::ReadLine()
- L_0068: pop
- L_0069: ret
- .try L_0034 to L_0052 finally handler L_0052 to L_0063
- }
從.locals init 那里可以看出編譯器為我們添加了兩個(gè)局部變量,一個(gè)就是迭代器。
這三行代碼告訴我們,調(diào)用list的GetEnumerator()方法,獲取迭代器實(shí)例將其賦值給編譯器為我們添加的那個(gè)迭代器局部變量,接著是L_0034: br.s L_0048,
br.s這個(gè)指令是強(qiáng)制跳轉(zhuǎn),我們接著看
調(diào)用迭代器的MoveNext()方法,L_004e: brtrue.s L_0036 如果是true的話跳轉(zhuǎn),
獲取當(dāng)前值,然后輸出
看到?jīng)]有,實(shí)際foreach后面干的事就是獲取迭代器,然后一個(gè)while循環(huán),不過(guò)這樣一些確實(shí)簡(jiǎn)潔多了。
說(shuō)到這里是不是
和
這兩樣代碼是一樣的呢?如果不一樣那推薦使用哪一個(gè)呢?當(dāng)然是使用第二種,簡(jiǎn)潔嘛,除了簡(jiǎn)潔之外就沒(méi)有其它的了?細(xì)心讀者會(huì)發(fā)現(xiàn)上面的IL代碼,
在結(jié)束循環(huán)后還有一大塊,可我們的C#代碼中并沒(méi)有啊,接著分析:
.try L_0034 to L_0052 finally handler L_0052 to L_0063
這里說(shuō)明從L_0034到L_0052是被放在try里面的,恰好這段代碼是循環(huán)體里的東西,L_0052到L_0063里是放在finally里的,看來(lái)foreach還給我們加了一
個(gè)try{}finally{}結(jié)構(gòu)啊。那看看L_0052到L_0063里是什么東西吧:
判斷迭代器對(duì)象是否是一個(gè)IDisposable實(shí)例,如果是,那就要調(diào)用它的Dispose()方法了(為啥它要實(shí)現(xiàn)IDisposable接口?那肯定這個(gè)迭代器里使用了一些非托管資源)。
看到了吧,foreach也真夠智能的,看來(lái)使用foreach的方式是比自己用while方式安全穩(wěn)定多了。
(PS:好像是扯遠(yuǎn)了點(diǎn),不過(guò)大家一起了解一下,呵呵,其實(shí)我當(dāng)初也沒(méi)想說(shuō)這個(gè),不過(guò)后來(lái)看IL代碼有點(diǎn)不對(duì)勁,就當(dāng)作個(gè)副產(chǎn)品吧)
C# 2.0里還出現(xiàn)個(gè)關(guān)鍵字yield,我看了半天MSDN也沒(méi)明白它的意思:
在迭代器塊中用于向枚舉數(shù)對(duì)象提供值或發(fā)出迭代結(jié)束信號(hào)。到現(xiàn)在還是沒(méi)明白,不過(guò)yield這個(gè)東西后面卻包含了很多東西,有一些非常“奇怪”的特性,
我稱之為奇怪的意思是與我們以前的思維有的不符,Linq的一些特質(zhì)也是建立在這個(gè)特性之上的。關(guān)于yield的更多討論我想放在另外一篇文章中,因?yàn)槲矣X(jué)得有必要。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注