C#特性-迭代器(上)及一些研究過程中的副產(chǎn)品
提到迭代器我們不能不想到迭代器模式,那我就以迭代器模式作為開場白.
在我們的應(yīng)用程序中常常有這樣一些數(shù)據(jù)結(jié)構(gòu):
它們是一個數(shù)據(jù)的集合,如果你知道它們內(nèi)部的實現(xiàn)結(jié)構(gòu)就可以去訪問它們,它們各自的內(nèi)部存儲結(jié)構(gòu)互不相同,各種集合有各自的應(yīng)用場合.說到這里大家可能想出一大堆這樣的集合了:List,Hashtable,ArrayList等等。這些集合各自都有各自的個性,這就是它們存在的理由。但如果你想遍歷它你必須知道它內(nèi)部的存儲細節(jié),作為一個集合元素,把內(nèi)部細節(jié)暴露出來肯定就不好了,這樣客戶程序就不夠穩(wěn)定了,在你更換集合對象的時候,比如List不能滿足需求的時候,你換成Hashtable,因為以前的客戶程序過多的關(guān)注了List內(nèi)部實現(xiàn)的細節(jié),所以不能很好的移植。而迭代器模式就是為解決這個問題而生的:
提供一種一致的方式訪問集合對象中的元素,而無需暴露集合對象的內(nèi)部表示。
比如現(xiàn)在有這樣一個需求,遍歷集合內(nèi)的元素,然后輸出,但是并不限定集合是什么類型的集合,也就是未來集合可能發(fā)生改變。
思考:
集合會發(fā)生改變,這是變化點,集合改變了,遍歷方法也改變,我們要保證遍歷的方法穩(wěn)定,那么就要屏蔽掉細節(jié)。找到了變化點那我們就將其隔離起來(一般使用interface作為隔離手段):假設(shè)所有的集合都繼承自ICollection接口,這個接口用來隔離具體集合的,將集合屏蔽在接口后面,作為遍歷我們肯定需要這樣一些方法:MoveNext,Current,既然ICollection負責數(shù)據(jù)存儲,職責又要單一,那么就新建立一個接口叫做Iterator吧,每種具體的集合都有自己相對應(yīng)的Iterator實現(xiàn):
下面是一個簡易的實現(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> /// 測試 /// </summary> public class Program { static void Main() { ICollection list = new List(); Iterator iterator = list.GetIterator(); while (iterator.MoveNext()) { object current = iterator.Current; } } }
看看最后的測試,是不是不管具體的集合如何改變,遍歷代碼都非常穩(wěn)定?而且擴展新的集合類也非常方便,只是添加代碼不會修改原來的代碼,符合開閉原則。當然,這么好的解決方案微軟當然不會放過,現(xiàn)在C# 2.0里已經(jīng)內(nèi)置了對迭代器的支持,看看System.Collections, System.Collections.Generic命名空間,所有的集合都實現(xiàn)了這個接口:IEnumerable,這個接口還有泛型的版本。注意到這個接口只有一個方法:IEnumerator GetEnumerator();,IEnumerator就是迭代器的接口,相當于我的實例里面的Iterator,它也有泛型的版本。
那么現(xiàn)在在.net里所有的集合類都可以這樣訪問了:
IEnumerator ienumerator = list.GetEnumerator();
while(ienumerator.MoveNext())
{
object current = ienumerator.Current;
}
但是這樣訪問也太麻煩了,所以C#里出現(xiàn)了foreach關(guān)鍵字,我們來看看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()); } }
下面是它對應(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 那里可以看出編譯器為我們添加了兩個局部變量,一個就是迭代器。
L_002d: ldloc.0
L_002e: callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Collections.ArrayList::GetEnumerator()
L_0033: stloc.2
這三行代碼告訴我們,調(diào)用list的GetEnumerator()方法,獲取迭代器實例將其賦值給編譯器為我們添加的那個迭代器局部變量,接著是L_0034: br.s L_0048,
br.s這個指令是強制跳轉(zhuǎn),我們接著看
L_0048: ldloc.2
L_0049: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
調(diào)用迭代器的MoveNext()方法,L_004e: brtrue.s L_0036 如果是true的話跳轉(zhuǎn),
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)
獲取當前值,然后輸出
看到?jīng)]有,實際foreach后面干的事就是獲取迭代器,然后一個while循環(huán),不過這樣一些確實簡潔多了。
說到這里是不是
IEnumerator ienumerator = list.GetEnumerator();
while (ienumerator.MoveNext())
{
object item = ienumerator.Current;
Console.WriteLine(item.ToString());
}
和
foreach (object item in list)
{
Console.WriteLine(item.ToString());
}
這兩樣代碼是一樣的呢?如果不一樣那推薦使用哪一個呢?當然是使用第二種,簡潔嘛,除了簡潔之外就沒有其它的了?細心讀者會發(fā)現(xiàn)上面的IL代碼,
在結(jié)束循環(huán)后還有一大塊,可我們的C#代碼中并沒有啊,接著分析:
.try L_0034 to L_0052 finally handler L_0052 to L_0063
這里說明從L_0034到L_0052是被放在try里面的,恰好這段代碼是循環(huán)體里的東西,L_0052到L_0063里是放在finally里的,看來foreach還給我們加了一
個try{}finally{}結(jié)構(gòu)啊。那看看L_0052到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()
判斷迭代器對象是否是一個IDisposable實例,如果是,那就要調(diào)用它的Dispose()方法了(為啥它要實現(xiàn)IDisposable接口?那肯定這個迭代器里使用了一些非托管資源)。
看到了吧,foreach也真夠智能的,看來使用foreach的方式是比自己用while方式安全穩(wěn)定多了。
(PS:好像是扯遠了點,不過大家一起了解一下,呵呵,其實我當初也沒想說這個,不過后來看IL代碼有點不對勁,就當作個副產(chǎn)品吧)
C# 2.0里還出現(xiàn)個關(guān)鍵字yield,我看了半天MSDN也沒明白它的意思:
在迭代器塊中用于向枚舉數(shù)對象提供值或發(fā)出迭代結(jié)束信號。到現(xiàn)在還是沒明白,不過yield這個東西后面卻包含了很多東西,有一些非常“奇怪”的特性,
我稱之為奇怪的意思是與我們以前的思維有的不符,Linq的一些特質(zhì)也是建立在這個特性之上的。關(guān)于yield的更多討論我想放在另外一篇文章中,因為我覺得有必要。
相關(guān)文章
adonet基礎(chǔ)示例分享(adonet連接數(shù)據(jù)庫)
這篇文章主要介紹了adonet基礎(chǔ)示例分享(adonet連接數(shù)據(jù)庫),需要的朋友可以參考下2014-04-04C#利用FluentFTP實現(xiàn)FTP上傳下載功能詳解
FTP作為日常工作學習中,非常重要的一個文件傳輸存儲空間,想必大家都非常的熟悉了,那么如何快速的實現(xiàn)文件的上傳下載功能呢,本文以一個簡單的小例子,簡述如何通過FluentFTP實現(xiàn)文件的上傳和下載功能2023-02-02詳解C#的設(shè)計模式編程之抽象工廠模式的應(yīng)用
這篇文章主要介紹了C#的設(shè)計模式編程之抽象工廠模式的應(yīng)用,注意區(qū)分一下簡單工廠模式、工廠方法模式和抽象工廠模式概念之間的區(qū)別,需要的朋友可以參考下2016-02-02