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