Das folgende Beispiel definiert eine Klasse, die in einer foreach-Anweisung aufgezählt werden kann:
public class ExampleIterator : IEnumerable {public IEnumerator GetEnumerator() {yield return 1;}}
Wie Abpictureung 1 zeigt, besitzt die Klasse WhatDoesTheYieldReallyGenerate.ExampleIterator einen eingebetteten Klassenaufruf GetEnumerator>d__0. Etwas eigentümlich ist die Benennung der eingebetteten Klasse. Es scheint, als ob der eigentliche Klassenname d__0 lautet und <GetEnumerator> auf einen generischen .NET-Typ verweist. Tatsächlich aber ist der Bezeichner <GetEnumerator> Teil des Klassenbezeichners. Mit einem derartigen Bezeichner hätten Sie in C# oder VB.NET eine Fehlermeldung des Compilers geerntet. Die Namenskonvention ist absichtlich so festgelegt, damit niemals die Situation eintreten kann, dass ein Programmierer eine Klasse definiert, die mit der generierten Klasse kollidiert. Die Klasse <GetEnumerator>d__0 besitzt darüber hinaus das Attribut CompilerGenerated und sie ist versiegelt (sealed), weshalb man von diesem Typ im Code keine Klasse ableiten kann.
Das Schlüsselwort yield generiert eine private Klasse, die die Schnittstelle IEnumerator implementiert. Diesen Code müssten Sie vor .NET 2.0 selbst schreiben, jetzt aber wird er automatisch generiert. Weiterhin ist interessant, wie eine Instanz der privaten Klasse an den Client zurückgegeben wird. Damit Sie die Abläufe besser verstehen können, wurde das yield-Beispiel umgeschrieben und veranschaulicht eine Aufrufsequenz, die sich im "reverse-engineered" Code genau festmachen lässt. Das modifizierte yield-Beispiel sieht wie in Listing 1 aus.
Listing 1
public class ExampleIterator : IEnumerable {int _param;private int Method1( int param) {return param + param;}private int Method2( int param) {return param * param;}public IEnumerator GetEnumerator() {Console.WriteLine( "before");_param = 10;yield return Method1( _param);yield return Method2( _param);Console.WriteLine( "after");}}
Listing 2
public IEnumerator GetEnumerator() {ExampleIterator.<GetEnumerator>d__0 d__1 =new ExampleIterator.<GetEnumerator>d__0(0);d__1.<>4__this = this;return d__1;}
Die Details in der IL-Implementierung von GetEnumerator sind für das, was in C# kodiert ist, überhaupt nicht relevant. Ein genauer Blick auf den Code zeigt, dass die private Klasse instanziiert und der Variablen d__1 zugewiesen wird. Der Konstruktorwert 0 ist für private Zwecke vorgesehen und wird vom Code nicht beeinflusst. Auffällig ist die Zuweisung des Datenelements d__1.<>4__this an die this-Instanz. Vom strukturellen Gesichtspunkt her bedeutet das, dass der privaten Klasse eine Referenz auf die übergeordnete Klasse zugewiesen wird. Schließlich gibt der letzte Schritt die erzeugte IEnumerator-Instanz zurück. Eines der Rätsel ist nun gelöst - wie der Wert 1 des ursprünglichen yield-Beispiels in eine IEnumerator-Instanz konvertiert wird. Der Compiler entfernt den C#-Code, der die Methode GetEnumerator implementiert, und ersetzt ihn durch Code, der die private Klasse instanziiert und diese Instanz zurückgibt.
So weit so gut, doch stellt sich die Frage, was mit dem ursprünglichen GetEnumerator-Code passiert ist. Und wie ist es möglich, dass der yield-Code scheinbar aus der GetEnumerator-Methode heraus und dann zurück in die Methode GetEnumerator springt? Beide Fragen lassen sich klären, wenn man sich die in IL generierte IEnumerator.MoveNext-Methode ansieht, die der Codeausschnitt in Listing 3 zeigt.
Listing 3
private bool MoveNext(){switch (this.<>1__state){case 0:{this.<>1__state = -1;Console.WriteLine("before");this.<>4__this._param = 10;this.<>2__current =this.<>4__this.Method1(this.<>4__this._param);this.<>1__state = 1;return true;}case 1:{this.<>1__state = -1;this.<>2__current =this.<>4__this.Method2(this.<>4__this._param);this.<>1__state = 2;return true;}case 2:{this.<>1__state = -1;Console.WriteLine("after");break;}}return false;}
Das Schlüsselwort yield ist ein Element der .NET-Umgebung, das die Sprache erweitert, um die Implementierung bestimmter Codeabschnitte einfacher zu gestalten. Das Schlüsselwort yield müssen Sie nicht verwenden, wo Sie bislang eine aufzählbare Auflistung implementiert haben. Selbst wenn es scheint, dass das Schlüsselwort yield funktioniert, hat das generierte Ergebnis große Ähnlichkeit mit Spagetti-Code. Das macht neugierig, ob der erzeugte Code immer noch funktioniert, wenn man ihn mit mehreren komplizierteren und esoterischen Sprachkonstrukten kombiniert. Bisher ist nichts Gegenteiliges bekannt, doch man muss sich fragen, ob nicht irgendwo versteckte Bugs lauern und nur darauf warten, im denkbar ungünstigsten Moment zuzuschlagen.



