2008-09-25 52 views
29

我很想弄清楚自己,但我想知道粗略地說,將函數與yield語句轉換爲枚舉器的狀態機的算法是什麼?例如如何做C#關閉此:實現C#產出語句的算法

IEnumerator<string> strings(IEnumerable<string> args) 
{ IEnumerator<string> enumerator2 = getAnotherEnumerator();  
    foreach(var arg in arg) 
    { enumerator2.MoveNext(); 
     yield return arg+enumerator.Current; 
    } 
} 

成這樣:

bool MoveNext() 
{ switch (this.state) 
    { 
     case 0: 
      this.state = -1; 
      this.enumerator2 = getAnotherEnumerator(); 
      this.argsEnumerator = this.args.GetEnumerator(); 
      this.state = 1; 
      while (this.argsEnumerator.MoveNext()) 
      { 
       this.arg = this.argsEnumerator.Current; 
       this.enumerator2.MoveNext(); 
       this.current = this.arg + this.enumerator2.Current; 
       this.state = 2; 
       return true; 

       state1: 
       this.state = 1; 
      } 
      this.state = -1; 
      if (this.argsEnumerator != null) this.argsEnumerator.Dispose(); 
      break; 

     case 2: 
      goto state1; 
    } 
    return false; 
} 

結果當然可以根據原始代碼是完全不同的。

回答

58

你正在尋找的特定代碼示例涉及一系列轉換。 請注意,這是對算法的近似描述。編譯器使用的實際名稱以及它生成的確切代碼可能不同。然後,想法是一樣的。

第一變換是「的foreach」變換,其將這樣的代碼:

foreach (var x in y) 
{ 
    //body 
} 

到這個代碼:

var enumerator = y.GetEnumerator(); 
while (enumerator.MoveNext()) 
{ 
    var x = enumerator.Current; 
    //body 
} 

if (y != null) 
{ 
    enumerator.Dispose(); 
} 

第二變換查找所有的功能體的產率return語句,爲每個(狀態值)分配一個數字,並在產出後立即創建一個「goto標籤」。

第三個轉換將方法體中的所有局部變量和函數參數提升爲一個稱爲閉包的對象。

鑑於您的示例代碼,這將類似於此:

class ClosureEnumerable : IEnumerable<string> 
{ 
    private IEnumerable<string> args; 
    private ClassType originalThis; 
    public ClosureEnumerator(ClassType origThis, IEnumerable<string> args) 
    { 
     this.args = args; 
     this.origianlThis = origThis; 
    } 
    public IEnumerator<string> GetEnumerator() 
    { 
     return new Closure(origThis, args); 
    } 
} 

class Closure : IEnumerator<string> 
{ 
    public Closure(ClassType originalThis, IEnumerable<string> args) 
    { 
     state = 0; 
     this.args = args; 
     this.originalThis = originalThis; 
    } 

    private IEnumerable<string> args; 
    private IEnumerator<string> enumerator2; 
    private IEnumerator<string> argEnumerator; 

    //- Here ClassType is the type of the object that contained the method 
    // This may be optimized away if the method does not access any 
    // class members 
    private ClassType originalThis; 

    //This holds the state value. 
    private int state; 
    //The current value to return 
    private string currentValue; 

    public string Current 
    { 
     get 
     { 
      return currentValue; 
     } 
    } 
} 

的方法體,然後從原來的方法轉移,到內「封閉」的方法叫的MoveNext,它返回一個布爾,並實現IEnumerable.MoveNext。 對任何本地人的任何訪問都通過「this」進行路由,並且對任何類成員的任何訪問均通過this.original路由。

任何 「收益率回報EXPR」 被翻譯成:

currentValue = expr; 
state = //the state number of the yield statement; 
return true; 

任何產量break語句被翻譯成:

state = -1; 
return false; 

有一個在的末端的 「隱含的」 yield break語句功能。 然後在查看狀態號的過程開始處引入一個switch語句並跳轉到關聯的標號。

的原始方法,然後翻譯成這樣的:

IEnumerator<string> strings(IEnumerable<string> args) 
{ 
    return new ClosureEnumerable(this,args); 
} 

,該方法的狀態都推入對象和MoveNext方法使用switch語句的事實/狀態變量是什麼允許迭代器的行爲就像在下次調用「MoveNext」時最後一個「yield return」語句之後立即傳回控制點一樣。

有必要指出的是很重要的,但是,由C#編譯器使用的轉型是不是要做到這一點的最好辦法。在嘗試使用遞歸算法使用「yield」時,它的性能很差。有一個很好的文件,概述了一種更好的方式,在這裏做這樣的:

http://research.microsoft.com/en-us/projects/specsharp/iterators.pdf

如果你還沒有讀過這是值得一讀。它最近我wrote an article -

7

Raymond chen回答了這個問題; http://blogs.msdn.com/b/oldnewthing/archive/2008/08/12/8849519.aspx

(編輯以指向系列,而不是部分4的部分1)

+1

Raymond Chen只是說「這很神奇」 – 2008-09-25 07:34:00

+0

我認爲marxidad想弄清楚編譯器用來解釋和轉換的算法是什麼迭代器的C#代碼阻塞到對應於狀態機的IL中。 – 2008-09-25 07:54:57

7

就發現了這個問題。我將不得不將這裏提到的其他鏈接添加到文章中...