浅谈yield
c#1.0使用foreach 語句可以輕松地迭代集合。在c#1.0中,創(chuàng)建枚舉器仍需要做大量的工作。c#2.0添加了yield語句,以便于創(chuàng)建枚舉器。下面我們淺談下yield的使用:
1、包含yield語句的方法或?qū)傩苑Q為迭代塊。迭代塊必須聲明為返回IEnumerator或IEnumerable接口。這個(gè)塊可以包含多個(gè)yield return語句或yield break語句,但不能包含return語句。
??????yield return語句返回集合的一個(gè)元素,并移動到下一個(gè)元素上。
??????yield break語句:停止迭代
yield 語句只能出現(xiàn)在 iterator 塊中,該塊可用作方法、運(yùn)算符或訪問器的體。這類方法、運(yùn)算符或訪問器的體受以下約束的控制:
不允許不安全塊。
方法、運(yùn)算符或訪問器的參數(shù)不能是 ref 或 out。
yield 語句不能出現(xiàn)在匿名方法中。有關(guān)更多信息,請參見匿名方法(C# 編程指南)。
當(dāng)和 expression 一起使用時(shí),yield return 語句不能出現(xiàn)在 catch 塊中或含有一個(gè)或多個(gè) catch 子句的 try 塊中。有關(guān)更多信息,請參見異常處理語句(C# 參考)。
?2、yield語句從本質(zhì)上講是運(yùn)用了延遲計(jì)算(Lazy evaluation或delayed evaluation)的思想。在Wiki上可以找到延遲計(jì)算的解釋:將計(jì)算延遲,直到需要這個(gè)計(jì)算的結(jié)果的時(shí)候才計(jì)算,這樣就可以因?yàn)楸苊庖恍┎槐匾挠?jì)算而改進(jìn)性能,在合成一些表達(dá)式時(shí)候還可以避免一些不必要的條件,因?yàn)檫@個(gè)時(shí)候其他計(jì)算都已經(jīng)完成了,所有的條件都已經(jīng)明確了,有的根本不可達(dá)的條件可以不用管了。
??????延遲計(jì)算來源自函數(shù)式編程,在函數(shù)式編程里,將函數(shù)作為參數(shù)來傳遞,你想呀,如果這個(gè)函數(shù)一傳遞就被計(jì)算了,那還搞什么搞,如果你使用了延遲計(jì)算,表達(dá)式在沒有使用的時(shí)候是不會被計(jì)算的,比如有這樣一個(gè)應(yīng)用:x=expression,將這個(gè)表達(dá)式賦給x變量,但是如果x沒有在別的地方使用的話這個(gè)表達(dá)式是不會被計(jì)算的,在這之前x里裝的是這個(gè)表達(dá)式。舉個(gè)例子,linq就是運(yùn)用了延遲計(jì)算的思想。看下面的代碼:
var?result?=?from?book?in?books
????where?book.Title.StartWiths(“t”)
????select?book
if(state?>?0)
{
????foreach(var?item?in?result)
????{
????????//….
??? }
}
result是一個(gè)實(shí)現(xiàn)了IEnumerable接口的類(在Linq里,所有實(shí)現(xiàn)了IEnumerable接口的類都被稱作sequence),對它的foreach或者while的訪問必須通過它對應(yīng)的IEnumerator的MoveNext()方法,如果我們把一些耗時(shí)的或者需要延遲的操作放在MoveNext()里面,那么只有等到MoveNext()被訪問,也就是result被使用的時(shí)候那些操作才會執(zhí)行,而給result賦值啊,傳遞啊,什么的,那些耗時(shí)的操作都沒有被執(zhí)行。
如果上面這段代碼,最后由于state小于0,而對result沒有任何需求了,在Linq里返回的結(jié)果都是IEnumerable的,如果這里沒有使用延遲計(jì)算,那那個(gè)Linq表達(dá)式不就白運(yùn)算了么?如果是Linq to Objects還稍微好點(diǎn),如果是Linq to SQL,而且那個(gè)數(shù)據(jù)庫表又很大,真是得不償失啊,所以微軟想到了這點(diǎn),這里使用了延遲計(jì)算,只有等到程序別的地方使用了result才會計(jì)算這里的Linq表達(dá)式的值的,這樣Linq的性能也比以前提高了不少,而且Linq to SQL最后還是要生成SQL語句的,對于SQL語句的生成來說,如果將生成延遲,那么一些條件就先確定好了,生成SQL語句的時(shí)候就可以更精練了。還有,由于MoveNext()是一步步執(zhí)行的,循環(huán)一次執(zhí)行一次,所以如果有這種情況:我們遍歷一次判斷一下,不滿足我們的條件了我們就退出,如果有一萬個(gè)元素需要遍歷,當(dāng)遍歷到第二個(gè)的時(shí)候就不滿足條件了,這個(gè)時(shí)候我們就可就此退出,后面那么多元素實(shí)際上都沒處理呢,那些元素也沒有被加載到內(nèi)存中來。
延遲計(jì)算還有很多惟妙惟肖的特質(zhì),也許以后你也可以按照這種方式來編程了呢。寫到這里我突然想到了Command模式,Command模式將方法封裝成類,Command對象在傳遞等時(shí)候是不會執(zhí)行任何東西的,只有調(diào)用它內(nèi)部那個(gè)方法他才會執(zhí)行,這樣我們就可以把命令到處發(fā),還可以壓棧啊等等而不擔(dān)心在傳遞過程中Command被處理了,也許這也算是一種延遲計(jì)算吧。
?
講了yield的一些基礎(chǔ),覺得有必要講下IEnumerator與IEnumerable接口區(qū)別:
?
public interface IEnumerable
{
??? IEnumerator GetEnumerator();
}
?
public interface IEnumerator
{
??? bool MoveNext();
??? void Reset();
?
??? Object Current { get; }
}
?1、一個(gè)Collection要支持foreach方式的遍歷,必須實(shí)現(xiàn)IEnumerable接口(亦即,必須以某種方式返回IEnumerator object)。
?
2、IEnumerator object具體實(shí)現(xiàn)了iterator(通過MoveNext(),Reset(),Current)。
?
3、從這兩個(gè)接口的用詞選擇上,也可以看出其不同:IEnumerable是一個(gè)聲明式的接口,聲明實(shí)現(xiàn)該接口的class是“可枚舉(enumerable)”的,但并沒有說明如何實(shí)現(xiàn)枚舉器(iterator);IEnumerator是一個(gè)實(shí)現(xiàn)式的接口,IEnumerator object就是一個(gè)iterator。
?
4、IEnumerable和IEnumerator通過IEnumerable的GetEnumerator()方法建立了連接,client可以通過IEnumerable的GetEnumerator()得到IEnumerator object,在這個(gè)意義上,將GetEnumerator()看作IEnumerator object的factory method也未嘗不可。
IEnumerator?是所有枚舉數(shù)的基接口。??
???
? 枚舉數(shù)只允許讀取集合中的數(shù)據(jù)。枚舉數(shù)無法用于修改基礎(chǔ)集合。?這也是為什么說“不要在foreach循環(huán)中修改元素的原因 “.
???
? 最初,枚舉數(shù)被定位于集合中第一個(gè)元素的前面。Reset?也將枚舉數(shù)返回到此位置。在此位置,調(diào)用?? Current?會引發(fā)異常。因此,在讀取?Current?的值之前,必須調(diào)用?MoveNext?將枚舉數(shù)提前到集合的第一個(gè)元素。??
???
? 在調(diào)用?? MoveNext?? 或?? Reset?? 之前,Current?? 返回同一對象。MoveNext?? 將?? Current?? 設(shè)置為下一個(gè)元素。??
???
? 在傳遞到集合的末尾之后,枚舉數(shù)放在集合中最后一個(gè)元素后面,且調(diào)用?? MoveNext?? 會返回?? false。如果最后一次調(diào)用?? MoveNext?? 返回?? false,則調(diào)用?? Current?? 會引發(fā)異常。若要再次將?? Current?? 設(shè)置為集合的第一個(gè)元素,可以調(diào)用?? Reset,然后再調(diào)用?? MoveNext。??
???
? 只要集合保持不變,枚舉數(shù)就將保持有效。如果對集合進(jìn)行了更改(例如添加、修改或刪除元素),則該枚舉數(shù)將失效且不可恢復(fù),并且下一次對?? MoveNext?? 或?? Reset?? 的調(diào)用將引發(fā)?? InvalidOperationException。如果在?? MoveNext?? 和?? Current?? 之間修改集合,那么即使枚舉數(shù)已經(jīng)無效,Current?? 也將返回它所設(shè)置成的元素。??
???
? 枚舉數(shù)沒有對集合的獨(dú)占訪問權(quán);因此,枚舉一個(gè)集合在本質(zhì)上不是一個(gè)線程安全的過程。甚至在對集合進(jìn)行同步處理時(shí),其他線程仍可以修改該集合,這會導(dǎo)致枚舉數(shù)引發(fā)異常。若要在枚舉過程中保證線程安全,可以在整個(gè)枚舉過程中鎖定集合,或者捕捉由于其他線程進(jìn)行的更改而引發(fā)的異常。??
?
?
代碼下載:/Files/qlb5626267/YieldDemo.rar
參考文獻(xiàn):你必須知道的.net
轉(zhuǎn)載于:https://www.cnblogs.com/qlb5626267/archive/2009/05/08/1452517.html
總結(jié)
- 上一篇: C# 中数据缓存总结
- 下一篇: 已解决:Multisim仿真出现错误:“