程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C#特征 迭代器(下) yield和流的延遲盤算

C#特征 迭代器(下) yield和流的延遲盤算

編輯:C#入門知識

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,這個我們平凡常常應用的器械竟然面前還隱蔽著這麼多奧妙的處所,我也是明天才曉得,看來將來的路還很遠很遠啊。

路漫漫其修遠兮,吾將高低而求索。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved