Das besondere Codebeispiel sind Sie bei ihnen eine Reihe von Transformationen beinhaltet. Bitte beachten Sie, dass dies eine ungefähre Beschreibung des Algorithmus ist. Die tatsächlichen Namen, die vom Compiler verwendet werden, und der genaue Code, der generiert wird, können unterschiedlich sein. Dann ist die Idee jedoch dieselbe.
Die erste Transformation ist die "foreach" Transformation, die diesen Code transformiert:
foreach (var x in y)
{
//body
}
in diesen Code:
var enumerator = y.GetEnumerator();
while (enumerator.MoveNext())
{
var x = enumerator.Current;
//body
}
if (y != null)
{
enumerator.Dispose();
}
Die zweite Transformation findet alle yield return-Anweisungen in dem Funktionskörper , weist jedem eine Zahl zu (ein Zustandswert) und erstellt direkt nach der Ausbeute eine "Sprungmarke".
Die dritte Transformation hebt alle lokalen Variablen und Funktionsargumente im Methodenrumpf in ein Objekt namens Closure auf.
der Code in Ihrem Beispiel gegeben, damit so aussehen würde:
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;
}
}
}
Der Körper Methode wird aus dem ursprünglichen Verfahren dann bewegt, sich auf ein Verfahren innerhalb „Closure“ genannt Movenext, das eine Bool zurückgibt und implementiert IEnumerable.MoveNext. Jeder Zugriff auf alle lokalen Benutzer wird über "this" weitergeleitet, und jeder Zugriff auf alle Klassenmitglieder wird durch diese.originalThis weitergeleitet.
Jede ist "yield return expr" Übersetzung:
currentValue = expr;
state = //the state number of the yield statement;
return true;
Jede Aussage yield break in übersetzt:
state = -1;
return false;
Es gibt eine "implizite" yield break-Anweisung am Ende der Funktion. Am Anfang der Prozedur wird dann eine switch-Anweisung eingefügt, die die Zustandsnummer betrachtet und zum zugehörigen Label springt.
ist die ursprüngliche Methode dann in etwa wie folgt übersetzt:
IEnumerator<string> strings(IEnumerable<string> args)
{
return new ClosureEnumerable(this,args);
}
Die Tatsache, dass der Zustand des Verfahrens alle in ein Objekt gedrückt wird, und dass die Movenext-Methode verwendet eine switch-Anweisung/Zustandsvariablen, was Erlaubt es dem Iterator, sich so zu verhalten, als würde die Kontrolle bei dem nächsten Aufruf von "MoveNext" unmittelbar nach der letzten Anweisung "yield return" an den Punkt zurückgegeben.
Es ist jedoch wichtig, darauf hinzuweisen, dass die vom C# -Compiler verwendete Umwandlung nicht der beste Weg ist, dies zu tun. Es leidet an einer schlechten Leistung, wenn versucht wird, "Ausbeute" mit rekursiven Algorithmen zu verwenden. Es ist ein gutes Papier, das einen besseren Weg skizziert dies hier zu tun:
http://research.microsoft.com/en-us/projects/specsharp/iterators.pdf
Es ist lesenswert, wenn Sie es noch nicht gelesen haben.
Wow. Ausgezeichnete und umfassende Antwort. Ich wünschte, ich könnte dies mehr als einmal abstimmen. –