2013-05-29 46 views
2

我想了解在C#中yield關鍵字的使用,因爲我正在使用的隊列建模包大量使用它。收益聲明對程序流的影響

爲了說明如何使用屈服的,我玩弄下面的代碼:

using System; 
using System.Collections.Generic; 
public class YieldTest 
{ 
    static void Main() 
    { 
     foreach (int value in ComputePower(2, 5)) 
     { 
      Console.Write(value); 
      Console.Write(" "); 
     } 
     Console.WriteLine(); 
    } 
    /** 
    * Returns an IEnumerable iterator of ints 
    * suitable for use in a foreach statement 
    */ 
    public static IEnumerable<int> ComputePower(int number, int exponent) 
    { 
     Console.Write ("Arguments to ComputePower are number: " + number + " exponent: " + exponent + "\n"); 
     int exponentNum = 0; 
     int numberResult = 1; 
     while (exponentNum < exponent) 
     { 
      numberResult *= number; 
      exponentNum++; 
      // yield: 
      // a) returns back to the calling function (foreach), 
      // b) updates iterator value (2,4,8,16,32 etc.) 
      yield return numberResult; 
     } 
    } 
} 

這是非常明顯的代碼做什麼,它只是提高了2到使用ComputePower它返回一個IEnumerable動力。在調試代碼時,我看到yield語句將控制返回到foreach循環,並且value變量用最新的功率結果更新。 2,4,8,16,32

不充分理解使用yield,我期望ComputePower通過ComputePower和便見"Arguments to ComputePower are "等控制檯寫發生5被調用的次數作爲值迭代倍。實際上發生的是似乎ComputePower方法只被調用一次。每次運行我只看到一次"Arguments to ComputePower.."字符串。

有人可以解釋爲什麼會出現這種情況嗎?它與yield關鍵字有關嗎?

+3

你讀過[yield](http://msdn.microsoft.com/library/9k7k7cf0.aspx)嗎? – Corak

+0

是的,還是Jon Skeet的_C#深入解釋了這個非常好的 – djf

+0

[yield statement implementation]的可能重複(http://stackoverflow.com/questions/742497/yield-statement-implementation) –

回答

6

foreach將迭代從ComputePower返回的IEnumerable。 「收益回報」會自動創建IEnumerable的實現,因此您不必手動滾動它。如果你把一個斷點裏面你的「而」 -loop你會看到它獲取調用每次迭代

從MSDN:

通過使用foreach語句或LINQ查詢消耗的iterator方法。 foreach循環的每次迭代調用迭代器方法。在迭代器方法中達到yield return語句時,將返回expression,並保留代碼中的當前位置。下一次調用迭代器函數時,將從該位置重新啓動執行。

+4

關鍵部分是「下一次調用迭代器函數時從該位置重新啓動執行」。 – JohnDRoach

+1

我認爲JohnDRoadch是正確的。我從python對產量的幫助中學到了這一點,並認爲它們有相似的概念。 – David

+0

謝謝Marius,很好的解釋,JohnDRoach我認爲你的重點回答了我所問的問題 - '執行重新開始......'對於理解yield的使用是絕對關鍵的。再次感謝你們。 – Pete855217

0

yield運算符將強制編譯器創建一個將實現您的邏輯的自定義類。 理解它的更好的方法是反編譯結果EXE並觀察它。

1

在高層次上,您可以將yield視爲「返回值並凍結該方法的當前狀態」。當發電機下一次被調用時,該方法將解凍並從收益率後的線開始恢復。因此,只有在方法開始時的任何行纔會被調用一次,而不會在實際存在於存在yield的循環中,它不會再次啓動整個方法。

在低級別,yield由編譯器將您的方法轉換爲狀態機,在該方法的開始處添加一個跳轉表,並在該方法的開始處添加一個跳轉表(我們開始執行的代碼行當你調用該方法時)由發生器所在的'狀態'決定。類似的編碼技術用於等待/異步狀態機,並且允許程序員在更容易理解的模型下隱藏很多複雜性。

+0

感謝Patashu,產量的實現看起來非常複雜,正如你所說的隱藏了很多複雜性。 – Pete855217

1

yield return會導致編譯器構建一個狀態機,該狀態機使用您的方法的主體來實現IEnumerable<T>。它從你的方法中返回一個對象,而不會在你編寫它的時候真正調用你的方法的主體 - 編譯器用更復雜的東西代替它。

當你調用由狀態機(如foreach循環期間)產生的IEnumerator<T>MoveNext(),直到它到達第一yield return聲明狀態機執行的方法的代碼。然後它將Current的值設置爲您返回的任何值,然後將控制返回給調用者。

在實踐中,看起來您的方法體每次迭代都會執行一次,並且每次到達yield return語句時循環都會「中斷」。

如果你在你的方法的while循環中放置了一個斷點,你會看到堆棧包含一個調用MoveNext()對編譯器生成類型的調用,你的方法的主體已經成爲它的一部分。

+0

謝謝Tragedian。這就是問題的關鍵所在:方法體的外觀每次迭代只執行一次。很好的解釋。 – Pete855217