欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時(shí)間:2014年12月25日 15:20:43   投稿:mdxy-dxy  
這篇文章主要介紹了C#特性 迭代器(下) yield以及流的延遲計(jì)算,需要的朋友可以參考下

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

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

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;
      }
    }

如果我用下面這樣的代碼測(cè)試,會(huì)得到怎樣的輸出?
測(cè)試1:

測(cè)試WithNoYield()

復(fù)制代碼 代碼如下:

static void Main()
        {
            WithNoYield();
            Console.ReadLine();
        }

測(cè)試WithYield()

復(fù)制代碼 代碼如下:

static void Main()
        {
            WithYield();
            Console.ReadLine();
        }

測(cè)試2:
測(cè)試WithNoYield()

復(fù)制代碼 代碼如下:

static void Main()
        {
            foreach (int i in WithNoYield())
            {
                Console.WriteLine(i.ToString());
            }
            Console.ReadLine();
        }

測(cè)試WithYield()

復(fù)制代碼 代碼如下:

static void Main()
        {
            foreach (int i in WithYield())
            {
                Console.WriteLine(i.ToString());
            }
            Console.ReadLine();
        }

給你5分鐘時(shí)間給出答案,不要上機(jī)運(yùn)行

*********************************5分鐘后***************************************

測(cè)試1的運(yùn)算結(jié)果
測(cè)試WithNoYield():輸出從0-19的數(shù)字
測(cè)試WithYield():什么都不輸出
測(cè)試2的運(yùn)算結(jié)果
測(cè)試WithNoYield():輸出1-19接著輸出3-19
測(cè)試WithYield():輸出12334455…….
(為節(jié)省空間上面的答案沒有原樣粘貼,可以自己運(yùn)行測(cè)試)

 

是不是感到很奇怪,為什么使用了yield的程序表現(xiàn)的如此怪異呢?

測(cè)試1中對(duì)WithYield()的測(cè)試,明明方法調(diào)用了,居然一行輸出都沒有,難道for循環(huán)根本沒有執(zhí)行?通過斷點(diǎn)調(diào)試果然如此,for循環(huán)根本沒有進(jìn)去,這是咋回事?測(cè)試2中對(duì)WithYield()的測(cè)試輸出是有了,不過輸出怎么這么有趣?穿插著輸出,在foreach遍歷WithYield()的結(jié)果的時(shí)候,好像不等到最后一條遍歷完,WithYield()不退出,這又是怎么回事?

還是打開IL代碼瞧一瞧到底發(fā)生了什么吧

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
}

這里沒什么稀奇的,在上一篇我已經(jīng)分析過了,foreach內(nèi)部就是轉(zhuǎn)換成調(diào)用迭代器的MoveNext()方法進(jìn)行while循環(huán)。我瀏覽到WithYield()方法:

復(fù)制代碼 代碼如下:

private static IEnumerable<int> WithYield()
{
    return new <WithYield>d__0(-2);
}

暈,怎么搞的,這是我寫的代碼么?我的for循環(huán)呢?經(jīng)過我再三確認(rèn),確實(shí)是我寫的代碼生成的。我心里暗暗叫罵,編譯器,你怎么能這樣“無恥”,在背后修改我的代碼,你這不侵權(quán)么。還給我新生成了一個(gè)類<WithYield>d__0,這個(gè)類實(shí)現(xiàn)了這么幾個(gè)接口:IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable(好啊,這個(gè)類將枚舉接口和迭代器接口都實(shí)現(xiàn)了)
現(xiàn)在能解答測(cè)試1為什么沒有輸出了,調(diào)用WithYield()里面就是調(diào)用了一下<WithYield>d__0的構(gòu)造方法,<WithYield>d__0的構(gòu)造方法的代碼:

復(fù)制代碼 代碼如下:

public <WithYield>d__0(int <>1__state)
    {
        this.<>1__state = <>1__state;
        this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId;
    }

這里沒有任何輸出。
在測(cè)試2中,首先我們會(huì)調(diào)用<WithYield>d__0的GetEnumerator()方法,這個(gè)方法里將一個(gè)整型局部變量<>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循環(huán)里面的Console.WriteLine跑到這里來了,所以沒等到MoveNext()調(diào)用,for里面的輸出也是不會(huì)被執(zhí)行的,因?yàn)槊看伪闅v都要訪問MoveNext()方法,所以沒有等到返回結(jié)果里面的元素遍歷完WithYield()也是不會(huì)退出的?,F(xiàn)在我們的測(cè)試程序所表現(xiàn)出來的怪異行為是可以找到依據(jù)了,那就是:編譯器在后臺(tái)搞了鬼。

實(shí)際上這種實(shí)現(xiàn)在理論上是有支撐的:延遲計(jì)算(Lazy evaluation或delayed evaluation)在Wiki上可以找到它的解釋:將計(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í)候是不會(huì)被計(jì)算的,比如有這樣一個(gè)應(yīng)用:x=expression,將這個(gè)表達(dá)式賦給x變量,但是如果x沒有在別的地方使用的話這個(gè)表達(dá)式是不會(huì)被計(jì)算的,在這之前x里裝的是這個(gè)表達(dá)式。

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

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<T>接口的類(在Linq里,所有實(shí)現(xiàn)了IEnumerable<T>接口的類都被稱作sequence),對(duì)它的foreach或者while的訪問必須通過它對(duì)應(yīng)的IEnumerator<T>的MoveNext()方法,如果我們把一些耗時(shí)的或者需要延遲的操作放在MoveNext()里面,那么只有等到MoveNext()被訪問,也就是result被使用的時(shí)候那些操作才會(huì)執(zhí)行,而給result賦值啊,傳遞啊,什么的,那些耗時(shí)的操作都沒有被執(zhí)行。

如果上面這段代碼,最后由于state小于0,而對(duì)result沒有任何需求了,在Linq里返回的結(jié)果都是IEnumerable<T>的,如果這里沒有使用延遲計(jì)算,那那個(gè)Linq表達(dá)式不就白運(yùn)算了么?如果是Linq to Objects還稍微好點(diǎn),如果是Linq to SQL,而且那個(gè)數(shù)據(jù)庫表又很大,真是得不償失啊,所以微軟想到了這點(diǎn),這里使用了延遲計(jì)算,只有等到程序別的地方使用了result才會(huì)計(jì)算這里的Linq表達(dá)式的值的,這樣Linq的性能也比以前提高了不少,而且Linq to SQL最后還是要生成SQL語句的,對(duì)于SQL語句的生成來說,如果將生成延遲,那么一些條件就先確定好了,生成SQL語句的時(shí)候就可以更精練了。還有,由于MoveNext()是一步步執(zhí)行的,循環(huán)一次執(zhí)行一次,所以如果有這種情況:我們遍歷一次判斷一下,不滿足我們的條件了我們就退出,如果有一萬個(gè)元素需要遍歷,當(dāng)遍歷到第二個(gè)的時(shí)候就不滿足條件了,這個(gè)時(shí)候我們就可就此退出,后面那么多元素實(shí)際上都沒處理呢,那些元素也沒有被加載到內(nèi)存中來。

延遲計(jì)算還有很多惟妙惟肖的特質(zhì),也許以后你也可以按照這種方式來編程了呢。寫到這里我突然想到了Command模式,Command模式將方法封裝成類,Command對(duì)象在傳遞等時(shí)候是不會(huì)執(zhí)行任何東西的,只有調(diào)用它內(nèi)部那個(gè)方法他才會(huì)執(zhí)行,這樣我們就可以把命令到處發(fā),還可以壓棧啊等等而不擔(dān)心在傳遞過程中Command被處理了,也許這也算是一種延遲計(jì)算吧。

本文也只是很淺的談了一下延遲計(jì)算的東西,從這里還可以牽扯到并發(fā)編程模型和協(xié)同程序等更多內(nèi)容,由于本人才疏學(xué)淺,所以只能介紹到這個(gè)地步了,上面一些說法也是我個(gè)人理解,肯定有很多不妥地方,歡迎大家拍磚。

foreach,yield,這個(gè)我們平常經(jīng)常使用的東西居然背后還隱藏著這么多奇妙的地方,我也是今天才知道,看來未來的路還很遠(yuǎn)很遠(yuǎn)啊。

路漫漫其修遠(yuǎn)兮,吾將上下而求索。

相關(guān)文章

  • C# TextBox數(shù)據(jù)綁定的方法

    C# TextBox數(shù)據(jù)綁定的方法

    這篇文章主要為大家詳細(xì)介紹了C# TextBox數(shù)據(jù)綁定的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-09-09
  • C#過濾DataTable中空數(shù)據(jù)和重復(fù)數(shù)據(jù)的示例代碼

    C#過濾DataTable中空數(shù)據(jù)和重復(fù)數(shù)據(jù)的示例代碼

    這篇文章主要給大家介紹了關(guān)于C#過濾DataTable中空數(shù)據(jù)和重復(fù)數(shù)據(jù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • C#中+=是什么意思及+=的用法

    C#中+=是什么意思及+=的用法

    這篇文章主要介紹了C#中+=是什么意思及+=的用法說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • 使用checked語句防止數(shù)據(jù)溢出的解決方法

    使用checked語句防止數(shù)據(jù)溢出的解決方法

    本篇文章是對(duì)用checked語句防止數(shù)據(jù)溢出的解決方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-05-05
  • C#各類集合匯總

    C#各類集合匯總

    這篇文章主要介紹了C#各類集合的相關(guān)資料,文中示例代碼非常詳細(xì),幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-07-07
  • C#數(shù)組排序的兩種常用方法

    C#數(shù)組排序的兩種常用方法

    這篇文章主要介紹了C#數(shù)組排序的兩種常用方法,實(shí)例分析了C#操作數(shù)組的相關(guān)技巧,需要的朋友可以參考下
    2015-05-05
  • C#實(shí)現(xiàn)的文件壓縮和解壓縮類

    C#實(shí)現(xiàn)的文件壓縮和解壓縮類

    這篇文章主要介紹了C#實(shí)現(xiàn)的文件壓縮和解壓縮類,實(shí)例分析了C#針對(duì)文件壓縮與解壓縮的常用技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2015-03-03
  • c#數(shù)據(jù)綁定之linq使用示例

    c#數(shù)據(jù)綁定之linq使用示例

    本實(shí)例以MS AdventureWorks2008Entities數(shù)據(jù)庫為基礎(chǔ),演示了LINQ TO ENTITY、LINQ TO ENTITYSQL和LINQ TO ENTITYCLIENT。
    2014-04-04
  • C#在foreach遍歷刪除集合中元素的三種實(shí)現(xiàn)方法

    C#在foreach遍歷刪除集合中元素的三種實(shí)現(xiàn)方法

    這篇文章主要給大家總結(jié)介紹了關(guān)于C#在foreach遍歷刪除集合中元素的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用C#具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12
  • 詳解C#之事件

    詳解C#之事件

    這篇文章主要介紹了C#之事件的知識(shí)點(diǎn),文中代碼非常詳細(xì),幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以參考下
    2020-06-06

最新評(píng)論