22.4 yield statement
The yield statement is used in an iterator block to produce an enumerator object value, or to indicate the end of iteration.
embedded-statement:(embedded statement)
...
yield-statement(yield statement)
yield-statement:(yield statement)
yield return expression ;
yield break ;
In order to ensure compatibility with existing programs, yield is not a reserved word, and yield has special meaning only immediately before the return or break keyword. In other contexts, it can be used as an identifier.
There are several restrictions on where the yield statement can appear, as described below.
l When the yield statement appears outside the method body, operator body and accessor body, it will cause a compile-time error.
l When the yield statement appears within an anonymous method, it will cause a compile-time error.
l When the yield statement appears in the finally statement of the try statement, it will cause a compile-time error.
l The yield return statement will cause a compile-time error when it appears anywhere in any try statement that contains a catch substatement.
The following examples show some valid and invalid uses of the yield statement.
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; // 错误, 迭代器块的错误返回类型 }
There must be an implicit conversion (§6.1) from the expression type in the yield return statement to the iterator's yield type (§22.1.3).
The yield return statement is executed as follows.
l The expression given in the statement will be evaluated (evaluated), implicitly converted to the generated type, and assigned to the Current property of the enumerator object.
l The execution of the iterator block will be suspended. If the yield return statement is in one or more try blocks, the associated finally block will not be executed at this time.
l The MoveNext method of the enumerator object returns true to the caller, indicating that the enumerator object successfully advances to the next item.
The next call to the MoveNext method of the enumerator object restarts execution from where the iterator block was suspended.
The yeld break statement is executed as follows.
l If the yield break statement is contained within one or more try blocks with finally blocks, initial control will be transferred to the finally block of the innermost try statement. When control reaches the end of the finally block, control is transferred to the next most recent try statement's finally block. This process will be repeated until all inner finally blocks of try statements have been executed.
l Control is returned to the caller of the iterator block. This may be due to the MoveNext method or Dispose method of the enumerator object.
Since the yield break statement unconditionally transfers control elsewhere, the end point of the yield break statement will never be reached.
22.4.1 Explicit assignment
For the yield return statement stmt in the form of yield return expr
l Like the beginning of stmt, the variable v at the beginning of expr has Explicit assignment status.
l If v is explicitly assigned at the end point of expr, it will also be explicitly assigned at the end point of stmt; otherwise, it will not be explicitly assigned at the end point of stmt
22.5 Implementation example
This section describes possible implementations of iterators in the form of standard C# constructs. The implementation described here is based on the same principles as the Microsoft C# compiler, but it is by no means the mandatory or only possible implementation.
The following 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]; } }
The GetEnumerator method can be converted to an instance of the compiler-generated enumerator class that encapsulates the code in the iterator block, as shown below.
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 the previous conversion, the code inside the iterator block was converted to a state machine and placed in the MoveNext method of the enumerator class. In addition, the local variable i is converted into a field of the enumerator object, so it can persist during the call to MoveNext.
The following example prints a simple multiplication table from integers 1 to 10. In this example, the FromTo method returns an enumerable object and is implemented using an iterator.
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(); } } }
The FromTo method can be converted into an instance of a compiler-generated enumerable class that encapsulates the code in an iterator block, as shown below.
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中文网(www.php.cn)!