22.4 yield-Anweisung
Die yield-Anweisung wird in einem Iteratorblock verwendet, um einen Enumeratorobjektwert zu erzeugen oder das Ende der Iteration anzuzeigen.
embedded-statement: (eingebettete Anweisung)
...
Yield-Statement (Yield-Anweisung)
Yield-Statement: (Yield-Anweisung)
Yield Return Expression ;
Yield Break ;
Um die Kompatibilität mit vorhandenen Programmen sicherzustellen, ist yield kein reserviertes Wort und yield hat nur unmittelbar vor den Schlüsselwörtern return oder break eine besondere Bedeutung. In anderen Kontexten kann es als Identifikator verwendet werden. Es gibt verschiedene Einschränkungen, wo die
Renditeabrechnung erscheinen kann, wie unten beschrieben.
l Wenn die Yield-Anweisung außerhalb des Methodenkörpers, des Operatorkörpers und des Zugriffskörpers erscheint, führt dies zu einem Fehler bei der Kompilierung.
l Wenn die yield-Anweisung in einer anonymen Methode erscheint, führt dies zu einem Fehler bei der Kompilierung.
l Wenn die yield-Anweisung in der final-Anweisung der try-Anweisung erscheint, führt dies zu einem Fehler bei der Kompilierung.
l Die yield return-Anweisung verursacht einen Fehler bei der Kompilierung, wenn sie irgendwo in einer try-Anweisung erscheint, die eine Catch-Unteranweisung enthält.
Die folgenden Beispiele zeigen einige gültige und ungültige Verwendungen der Yield-Anweisung.
delegate IEnumerable<int> D(); IEnumerator<int> GetEnumerator() { try { yield return 1; // Ok yield break; // Ok } finally { yield return 2; // 错误, yield 在finally中 yield break; // 错误, yield 在 finally中 } try { yield return 3; // 错误, yield return 在try...catch中 yield break; // Ok } catch { yield return 4; // 错误, yield return 在 try...catch中 yield break; // Ok } D d = delegate { yield return 5; // 错误, yield 在匿名方法中 }; } int MyMethod() { yield return 1; // 错误, 迭代器块的错误返回类型 }
Es muss eine implizite Konvertierung (§6.1) vom Ausdruckstyp in der Yield-Return-Anweisung zum Yield-Typ des Iterators (§22.1.3) erfolgen.
Die Yield-Return-Anweisung wird wie folgt ausgeführt.
l Der in der Anweisung angegebene Ausdruck wird ausgewertet (ausgewertet), implizit in den generierten Typ konvertiert und der Current-Eigenschaft des Enumeratorobjekts zugewiesen.
l Die Ausführung des Iteratorblocks wird ausgesetzt. Wenn sich die Yield-Return-Anweisung in einem oder mehreren Try-Blöcken befindet, wird der zugehörige Final-Block zu diesem Zeitpunkt nicht ausgeführt.
l Die MoveNext-Methode des Enumeratorobjekts gibt „true“ an den Aufrufer zurück, was angibt, dass das Enumeratorobjekt erfolgreich zum nächsten Element übergeht.
Der nächste Aufruf der MoveNext-Methode des Enumeratorobjekts setzt die Ausführung an der Stelle fort, an der der Iteratorblock angehalten wurde.
Die Yield Break-Anweisung wird wie folgt ausgeführt.
l Wenn die Yield Break-Anweisung in einem oder mehreren Try-Blöcken mit Final-Blöcken enthalten ist, wird die anfängliche Kontrolle an den Final-Block der innersten Try-Anweisung übertragen. Wenn die Steuerung den Endpunkt des „final“-Blocks erreicht, wird die Steuerung an den „final“-Block der nächstgelegenen try-Anweisung übertragen. Dieser Vorgang wird wiederholt, bis alle inneren „finally“-Blöcke der try-Anweisungen ausgeführt wurden.
l Die Kontrolle wird an den Aufrufer des Iteratorblocks zurückgegeben. Dies kann an der MoveNext-Methode oder der Dispose-Methode des Enumeratorobjekts liegen.
Da die Yield-Break-Anweisung die Kontrolle bedingungslos an eine andere Stelle überträgt, wird der Endpunkt der Yield-Break-Anweisung niemals erreicht.
22.4.1 Explizite Zuweisung
Für die Yield Return Statement stmt in Form von Yield Return expr
l Wie der Anfang von stmt, am Anfang von expr Die Variable v hat den Status „Explizite Zuweisung“.
l Wenn v explizit am Endpunkt von expr zugewiesen wird, wird es auch explizit am Endpunkt von stmt zugewiesen; andernfalls wird es nicht explizit am Endpunkt von stmt zugewiesen
22.5 Implementierungsbeispiel
In diesem Abschnitt werden mögliche Implementierungen von Iteratoren in Form von Standard-C#-Konstrukten beschrieben. Die hier beschriebene Implementierung basiert auf den gleichen Prinzipien wie der Microsoft C#-Compiler, ist jedoch keineswegs die zwingende oder einzig mögliche Implementierung.
Die folgende Stack
using System; using System.Collections; using System.Collections.Generic; class Stack<T>: IEnumerable<T> { T[] items; int count; public void Push(T item) { if (items == null) { items = new T[4]; } else if (items.Length == count) { T[] newItems = new T[count * 2]; Array.Copy(items, 0, newItems, 0, count); items = newItems; } items[count++] = item; } public T Pop() { T result = items[--count]; items[count] = T.default; return result; } public IEnumerator<T> GetEnumerator() { for (int i = count - 1; i >= 0; --i) yield items[i]; } }
Die GetEnumerator-Methode kann in eine Instanz der vom Compiler generierten Enumeratorklasse konvertiert werden, die den Code im Iteratorblock kapselt, wie unten gezeigt.
class Stack<T>: IEnumerable<T> { ... public IEnumerator<T> GetEnumerator() { return new __Enumerator1(this); } class __Enumerator1: IEnumerator<T>, IEnumerator { int __state; T __current; Stack<T> __this; int i; public __Enumerator1(Stack<T> __this) { this.__this = __this; } public T Current { get { return __current; } } object IEnumerator.Current { get { return __current; } } public bool MoveNext() { switch (__state) { case 1: goto __state1; case 2: goto __state2; } i = __this.count - 1; __loop: if (i < 0) goto __state2; __current = __this.items[i]; __state = 1; return true; __state1: --i; goto __loop; __state2: __state = 2; return false; } public void Dispose() { __state = 2; } void IEnumerator.Reset() { throw new NotSupportedException(); } }
In der vorherigen Transformation wurde der Code im Iteratorblock in eine Zustandsmaschine konvertiert und in der MoveNext-Methode der Enumeratorklasse platziert. Darüber hinaus wird die lokale Variable i in ein Feld des Enumeratorobjekts konvertiert, sodass sie während des Aufrufs von MoveNext bestehen bleiben kann.
Das folgende Beispiel druckt eine einfache Multiplikationstabelle mit den ganzen Zahlen 1 bis 10. In diesem Beispiel gibt die FromTo-Methode ein aufzählbares Objekt zurück und wird mithilfe eines Iterators implementiert.
using System; using System.Collections.Generic; class Test { static IEnumerable<int> FromTo(int from, int to) { while (from <= to) yield return from++; } static void Main() { IEnumerable<int> e = FromTo(1, 10); foreach (int x in e) { foreach (int y in e) { Console.Write("{0,3} ", x * y); } Console.WriteLine(); } } }
Die FromTo-Methode kann in eine Instanz einer vom Compiler generierten aufzählbaren Klasse konvertiert werden, die den Code in einem Iteratorblock kapselt, wie unten gezeigt.
using System; using System.Threading; using System.Collections; using System.Collections.Generic; class Test { ... static IEnumerable<int> FromTo(int from, int to) { return new __Enumerable1(from, to); } class __Enumerable1: IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator { int __state; int __current; int __from; int from; int to; int i; public __Enumerable1(int __from, int to) { this.__from = __from; this.to = to; } public IEnumerator<int> GetEnumerator() { __Enumerable1 result = this; if (Interlocked.CompareExchange(ref __state, 1, 0) != 0) { result = new __Enumerable1(__from, to); result.__state = 1; } result.from = result.__from; return result; } IEnumerator IEnumerable.GetEnumerator() { return (IEnumerator)GetEnumerator(); } public int Current { get { return __current; } } object IEnumerator.Current { get { return __current; } } public bool MoveNext() { switch (__state) { case 1: if (from > to) goto case 2; __current = from++; __state = 1; return true; case 2: __state = 2; return false; default: throw new InvalidOperationException(); } } public void Dispose() { __state = 2; } void IEnumerator.Reset() { throw new NotSupportedException(); } } }
这个可枚举类实现了可枚举接口和枚举器接口,这使得它成为可枚举的或枚举器。当GetEnumerator方法被首次调用时,将返回可枚举对象自身。后续可枚举对象的GetEnumerator调用,如果有的话,都返回可枚举对象的拷贝。因此,每次返回的枚举器都有其自身的状态,改变一个枚举器将不会影响另一个。Interlocked.CompareExchange方法用于确保线程安全操作。
from和to参数被转换为可枚举类的字段。由于from在迭代器块内被修改,所以引入另一个__from字段来保存在每个枚举其中from的初始值。
如果当__state是0时MoveNext被调用,该方法将抛出InvalidOperationException异常。这将防止没有首次调用GetEnumerator,而将可枚举对象作为枚举器而使用的现象发生。
(C# 2.0 Specification 全文完)
以上就是C# 2.0 Specification(迭代器)(二)的内容,更多相关内容请关注PHP中文网(m.sbmmt.com)!