国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > C# > 正文

C#特性 迭代器(下) yield以及流的延遲計算

2019-10-29 21:47:48
字體:
來源:轉載
供稿:網友
這篇文章主要介紹了C#特性 迭代器(下) yield以及流的延遲計算,需要的朋友可以參考下
 

從0遍歷到20(不包括20),輸出遍歷到的每個元素,并將大于2的所有數字放到一個IEnumerable<int>中返回

解答1:(我以前經常這樣做)
 

  1. static IEnumerable<int> WithNoYield() 
  2.     { 
  3.       IList<int> list = new List<int>(); 
  4.       for (int i = 0; i < 20; i++) 
  5.       { 
  6.         Console.WriteLine(i.ToString()); 
  7.         if(i > 2) 
  8.           list.Add(i); 
  9.       } 
  10.       return list; 
  11.     } 
?

解答2:(自從有了C# 2.0我們還可以這樣做)

 

  1. static IEnumerable<int> WithYield() 
  2.     { 
  3.       for (int i = 0; i < 20; i++) 
  4.       { 
  5.         Console.WriteLine(i.ToString()); 
  6.         if(i > 2) 
  7.           yield return i; 
  8.       } 
  9.     } 
?

如果我用下面這樣的代碼測試,會得到怎樣的輸出? 
測試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代碼:
 

  1. .method private hidebysig static void Main() cil managed 
  2.   .entrypoint 
  3.   .maxstack 1 
  4.   .locals init ( 
  5.     [0] int32 i, 
  6.     [1] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> CS$5$0000) 
  7.   L_0000: call class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> TestLambda.Program::WithYield() 
  8.   L_0005: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator() 
  9.   L_000a: stloc.1  
  10.   L_000b: br.s L_0020 
  11.   L_000d: ldloc.1  
  12.   L_000e: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current() 
  13.   L_0013: stloc.0  
  14.   L_0014: ldloca.s i 
  15.   L_0016: call instance string [mscorlib]System.Int32::ToString() 
  16.   L_001b: call void [mscorlib]System.Console::WriteLine(string
  17.   L_0020: ldloc.1  
  18.   L_0021: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext() 
  19.   L_0026: brtrue.s L_000d 
  20.   L_0028: leave.s L_0034 
  21.   L_002a: ldloc.1  
  22.   L_002b: brfalse.s L_0033 
  23.   L_002d: ldloc.1  
  24.   L_002e: callvirt instance void [mscorlib]System.IDisposable::Dispose() 
  25.   L_0033: endfinally  
  26.   L_0034: call string [mscorlib]System.Console::ReadLine() 
  27.   L_0039: pop  
  28.   L_003a: ret  
  29.   .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()方法的代碼:
 

  1. private bool MoveNext() 
  2.   { 
  3.     switch (this.<>1__state) 
  4.     { 
  5.       case 0: 
  6.         this.<>1__state = -1; 
  7.         this.<i>5__1 = 0; 
  8.         goto Label_006A; 
  9.  
  10.       case 1: 
  11.         this.<>1__state = -1; 
  12.         goto Label_005C; 
  13.  
  14.       default
  15.         goto Label_0074; 
  16.     } 
  17.   Label_005C: 
  18.     this.<i>5__1++; 
  19.   Label_006A: 
  20.     if (this.<i>5__1 < 20) 
  21.     { 
  22.       Console.WriteLine(this.<i>5__1.ToString()); 
  23.       if (this.<i>5__1 > 2) 
  24.       { 
  25.         this.<>2__current = this.<i>5__1; 
  26.         this.<>1__state = 1; 
  27.         return true
  28.       } 
  29.       goto Label_005C; 
  30.     } 
  31.   Label_0074: 
  32.     return false
  33.   } 
?

原來我們for循環里面的Console.WriteLine跑到這里來了,所以沒等到MoveNext()調用,for里面的輸出也是不會被執行的,因為每次遍歷都要訪問MoveNext()方法,所以沒有等到返回結果里面的元素遍歷完WithYield()也是不會退出的。現在我們的測試程序所表現出來的怪異行為是可以找到依據了,那就是:編譯器在后臺搞了鬼。

實際上這種實現在理論上是有支撐的:延遲計算(Lazy evaluation或delayed evaluation)在Wiki上可以找到它的解釋:將計算延遲,直到需要這個計算的結果的時候才計算,這樣就可以因為避免一些不必要的計算而改進性能,在合成一些表達式時候還可以避免一些不必要的條件,因為這個時候其他計算都已經完成了,所有的條件都已經明確了,有的根本不可達的條件可以不用管了。反正就是好處很多了。

延遲計算來源自函數式編程,在函數式編程里,將函數作為參數來傳遞,你想呀,如果這個函數一傳遞就被計算了,那還搞什么搞,如果你使用了延遲計算,表達式在沒有使用的時候是不會被計算的,比如有這樣一個應用:x=expression,將這個表達式賦給x變量,但是如果x沒有在別的地方使用的話這個表達式是不會被計算的,在這之前x里裝的是這個表達式。

 

看來這個延遲計算真是個好東西,別擔心,整個Linq就是建立在這之上的,這個延遲計算可是幫了Linq的大忙啊(難道在2.0的時候,微軟就為它的Linq開始蓄謀了?),看下面的代碼:
 

  1. var result = from book in books 
  2.   where book.Title.StartWiths(“t”) 
  3.   select book 
  4. if(state > 0) 
  5.   foreach(var item in result) 
  6.   { 
  7.     //…. 
?

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,這個我們平常經常使用的東西居然背后還隱藏著這么多奇妙的地方,我也是今天才知道,看來未來的路還很遠很遠啊。

路漫漫其修遠兮,吾將上下而求索。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 广平县| 大足县| 博罗县| 昌吉市| 图木舒克市| 上杭县| 塘沽区| 麻栗坡县| 余庆县| 喀什市| 沧源| 海宁市| 遂昌县| 宜昌市| 宁津县| 康马县| 汽车| 杭锦旗| 台南市| 鄂托克旗| 大化| 阿鲁科尔沁旗| 山西省| 弥勒县| 西乌珠穆沁旗| 巴楚县| 高平市| 嘉兴市| 西峡县| 葵青区| 理塘县| 堆龙德庆县| 商都县| 甘洛县| 吉水县| 阿荣旗| 玛沁县| 龙陵县| 元氏县| 明溪县| 揭东县|