C#特征 迭代器(下) yield和流的延遲盤算。本站提示廣大學習愛好者:(C#特征 迭代器(下) yield和流的延遲盤算)文章只能為提供參考,不一定能成為您想要的結果。以下是C#特征 迭代器(下) yield和流的延遲盤算正文
從0遍歷到20(不包含20),輸入遍歷到的每一個元素,並將年夜於2的一切數字放到一個IEnumerable<int>中前往
解答1:(我之前常常如許做)
static IEnumerable<int> WithNoYield() { IList<int> list = new List<int>(); for (int i = 0; i < 20; i++) { Console.WriteLine(i.ToString()); if(i > 2) list.Add(i); } return list; }
解答2:(自從有了C# 2.0我們還可以如許做)
static IEnumerable<int> WithYield() { for (int i = 0; i < 20; i++) { Console.WriteLine(i.ToString()); if(i > 2) yield return i; } }
假如我用上面如許的代碼測試,會獲得如何的輸入?
測試1:
測試WithNoYield()
static void Main()
{
WithNoYield();
Console.ReadLine();
}
測試WithYield()
static void Main()
{
WithYield();
Console.ReadLine();
}
測試2:
測試WithNoYield()
static void Main()
{
foreach (int i in WithNoYield())
{
Console.WriteLine(i.ToString());
}
Console.ReadLine();
}
測試WithYield()
static void Main()
{
foreach (int i in WithYield())
{
Console.WriteLine(i.ToString());
}
Console.ReadLine();
}
給你5分鐘時光給出謎底,不要上機運轉
*********************************5分鐘後***************************************
測試1的運算成果
測試WithNoYield():輸入從0-19的數字
測試WithYield():甚麼都不輸入
測試2的運算成果
測試WithNoYield():輸入1-19接著輸入3-19
測試WithYield():輸入12334455…….
(為節儉空間下面的謎底沒有原樣粘貼,可以本身運轉測試)
是否是覺得很奇異,為何應用了yield的法式表示的如斯奇異呢?
測試1中對WithYield()的測試,明明辦法挪用了,竟然一行輸入都沒有,豈非for輪回基本沒有履行?經由過程斷點調試果真如斯,for輪回基本沒有出來,這是咋回事?測試2中對WithYield()的測試輸入是有了,不外輸入怎樣這麼風趣?交叉著輸入,在foreach遍歷WithYield()的成果的時刻,似乎不比及最初一條遍歷完,WithYield()不加入,這又是怎樣回事?
照樣翻開IL代碼瞧一瞧究竟產生了甚麼吧
Main辦法的IL代碼:
.method private hidebysig static void Main() cil managed { .entrypoint .maxstack 1 .locals init ( [0] int32 i, [1] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> CS$5$0000) L_0000: call class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> TestLambda.Program::WithYield() L_0005: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator() L_000a: stloc.1 L_000b: br.s L_0020 L_000d: ldloc.1 L_000e: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current() L_0013: stloc.0 L_0014: ldloca.s i L_0016: call instance string [mscorlib]System.Int32::ToString() L_001b: call void [mscorlib]System.Console::WriteLine(string) L_0020: ldloc.1 L_0021: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext() L_0026: brtrue.s L_000d L_0028: leave.s L_0034 L_002a: ldloc.1 L_002b: brfalse.s L_0033 L_002d: ldloc.1 L_002e: callvirt instance void [mscorlib]System.IDisposable::Dispose() L_0033: endfinally L_0034: call string [mscorlib]System.Console::ReadLine() L_0039: pop L_003a: ret .try L_000b to L_002a finally handler L_002a to L_0034 }
這裡沒甚麼稀罕的,在上一篇我曾經剖析過了,foreach外部就是轉換成挪用迭代器的MoveNext()辦法停止while輪回。我閱讀到WithYield()辦法:
private static IEnumerable<int> WithYield()
{
return new <WithYield>d__0(-2);
}
暈,怎樣弄的,這是我寫的代碼麼?我的for輪回呢?經由我再三確認,確切是我寫的代碼生成的。我心裡暗暗叫罵,編譯器,你怎樣能如許“無恥”,在面前修正我的代碼,你這不侵權麼。還給我重生成了一個類<WithYield>d__0,這個類完成了這麼幾個接口:IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable(好啊,這個類將列舉接口和迭代器接口都完成了)
如今能解答測試1為何沒有輸入了,挪用WithYield()外面就是挪用了一下<WithYield>d__0的結構辦法,<WithYield>d__0的結構辦法的代碼:
public <WithYield>d__0(int <>1__state)
{
this.<>1__state = <>1__state;
this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId;
}
這裡沒有任何輸入。
在測試2中,起首我們會挪用<WithYield>d__0的GetEnumerator()辦法,這個辦法裡將一個整型部分變量<>1__state初始化為0,再看看MoveNext()辦法的代碼:
private bool MoveNext() { switch (this.<>1__state) { case 0: this.<>1__state = -1; this.<i>5__1 = 0; goto Label_006A; case 1: this.<>1__state = -1; goto Label_005C; default: goto Label_0074; } Label_005C: this.<i>5__1++; Label_006A: if (this.<i>5__1 < 20) { Console.WriteLine(this.<i>5__1.ToString()); if (this.<i>5__1 > 2) { this.<>2__current = this.<i>5__1; this.<>1__state = 1; return true; } goto Label_005C; } Label_0074: return false; }
本來我們for輪回外面的Console.WriteLine跑到這裡來了,所以沒比及MoveNext()挪用,for外面的輸入也是不會被履行的,由於每次遍歷都要拜訪MoveNext()辦法,所以沒有比及前往成果外面的元素遍歷完WithYield()也是不會加入的。如今我們的測試法式所表示出來的奇異行動是可以找到根據了,那就是:編譯器在後台弄了鬼。
現實上這類完成在實際上是有支持的:延遲盤算(Lazy evaluation或delayed evaluation)在Wiki上可以找到它的說明:將盤算延遲,直到須要這個盤算的成果的時刻才盤算,如許便可以由於防止一些不用要的盤算而改良機能,在分解一些表達式時刻還可以免一些不用要的前提,由於這個時刻其他盤算都曾經完成了,一切的前提都曾經明白了,有的基本弗成達的前提可以不消管了。橫豎就是利益許多了。
延遲盤算起源自函數式編程,在函數式編程裡,將函數作為參數來傳遞,你想呀,假如這個函數一傳遞就被盤算了,那還弄甚麼弄,假如你應用了延遲盤算,表達式在沒有應用的時刻是不會被盤算的,好比有如許一個運用:x=expression,將這個表達式賦給x變量,然則假如x沒有在其余處所應用的話這個表達式是不會被盤算的,在這之前x裡裝的是這個表達式。
看來這個延遲盤算真是個好器械,別擔憂,全部Linq就是樹立在這之上的,這個延遲盤算可是幫了Linq的年夜忙啊(豈非在2.0的時刻,微軟就為它的Linq開端蓄謀了?),看上面的代碼:
var result = from book in books where book.Title.StartWiths(“t”) select book if(state > 0) { foreach(var item in result) { //…. } }
result是一個完成了IEnumerable<T>接口的類(在Linq裡,一切完成了IEnumerable<T>接口的類都被稱作sequence),對它的foreach或許while的拜訪必需經由過程它對應的IEnumerator<T>的MoveNext()辦法,假如我們把一些耗時的或許須要延遲的操作放在MoveNext()外面,那末只要比及MoveNext()被拜訪,也就是result被應用的時刻那些操作才會履行,而給result賦值啊,傳遞啊,甚麼的,那些耗時的操作都沒有被履行。
假如下面這段代碼,最初因為state小於0,而對result沒有任何需求了,在Linq裡前往的成果都是IEnumerable<T>的,假如這裡沒有應用延遲盤算,那誰人Linq表達式不就白運算了麼?假如是Linq to Objects還略微好點,假如是Linq to SQL,並且誰人數據庫表又很年夜,真是得失相當啊,所以微軟想到了這點,這裡應用了延遲盤算,只要比及法式其余處所應用了result才管帳算這裡的Linq表達式的值的,如許Linq的機能也比之前進步了很多,並且Linq to SQL最初照樣要生成SQL語句的,關於SQL語句的生成來講,假如將生成延遲,那末一些前提就先肯定好了,生成SQL語句的時刻便可以更精練了。還有,因為MoveNext()是一步步履行的,輪回一次履行一次,所以假如有這類情形:我們遍歷一次斷定一下,不知足我們的前提了我們就加入,假如有一萬個元素須要遍歷,當遍歷到第二個的時刻就不知足前提了,這個時刻我們便可就此加入,前面那末多元素現實上都沒處置呢,那些元素也沒有被加載到內存中來。
延遲盤算還有許多活靈活現的特質,或許今後你也能夠依照這類方法來編程了呢。寫到這裡我忽然想到了Command形式,Command形式將辦法封裝成類,Command對象在傳遞等時刻是不會履行任何器械的,只要挪用它外部誰人辦法他才會履行,如許我們便可以把敕令隨處發,還可以壓棧啊等等而不擔憂在傳遞進程中Command被處置了,或許這也算是一種延遲盤算吧。
本文也只是很淺的談了一下延遲盤算的器械,從這裡還可以牽扯到並發編程模子和協同法式等更多內容,因為自己才疏學淺,所以只能引見到這個田地了,下面一些說法也是我小我懂得,確定有許多不當處所,迎接年夜家拍磚。
foreach,yield,這個我們平凡常常應用的器械竟然面前還隱蔽著這麼多奧妙的處所,我也是明天才曉得,看來將來的路還很遠很遠啊。
路漫漫其修遠兮,吾將高低而求索。