

Java技术
2005: 03 04 05 06 07 08
09 10 11 12
2006: 01 02
Asp.net
2005: 07 08 09 10 11 12
2006: 01 02

一个迭代器块(iterator block)是一个能够产生有序的值序列的块。迭代器块和普通语句块的区别就是其中出现的一个或多个yield语句。
只要相应的函数成员的返回值类型是一个枚举器接口(见4.1.1)或是一个可枚举接口(见4.1.2),一个迭代器块就可以用作方法体、运算符体或访问器体。
迭代器块并不是C#语法中的独立元素。它们受多种因素的制约,并且对函数成员声明的语义有很大影响,但在语法上它们只是块(block)。
当一个函数成员用一个迭代器块来实现时,如果函数成员的形式参数列表指定了ref或out参数,则会引起编译错误。
如果在迭代器块中出现了return语句,则会因此编译错误(但yield return语句是允许的)。
如果迭代器块包含不安全上下文,则会引起编译错误。一个迭代器块必须定义在一个安全的上下文中,即使它的声明嵌套在一个不安全上下文中。
枚举器(enumerator)接口包括非泛型接口System.Collections.IEnumerator和泛型接口System.Collections.Generic.IEnumerator<T>的所有实例化。在这一章中,这些接口将分别称作IEnumerator和IEnumerator<T>。
可枚举(enumerable)接口包括非泛型接口System.Collections.IEnumerable和泛型接口System.Collections.Generic.IEnumerable<T>。在这一章中,这些接口分别称作IEnumerable和IEnumerable<T>。
一个迭代器块能够产生一个有序的值序列,其中所有的制具有相同的类型。这个类型称为迭代器块的生成类型(yield type)。
在一个类的一个实例成员中的迭代器块里,表达式this是一个值。这个值的类型就是出现这种用法的类,并且它是对被调用的方法所在的对象的一个引用。
在一个结构的一个实例成员中的迭代器块里,表达式this是一个变量。这个变量的类型就是出现这种用法的结构。这个变量存贮了对被调用成员所在结构的一个拷贝。结构的实例成员中的迭代器块里的this变量和以该结构为类型的值变量完全一样。
如果一个函数成员使用了迭代器块来返回一个枚举器接口类型,对该函数成员的调用不会立即执行迭代器块中的代码,而是建立并返回一个枚举器对象。这个对象封装了迭代器块中指定的代码,而对迭代器中指定的代码的执行发生在调用该枚举器对象的MoveNext()方法时。一个枚举器对象具有如下特征:
典型的枚举器对象是由编译器自动生成的封装了迭代器块中的代码并实现了枚举器接口的枚举器类的实例,但其他的实现也是允许的。如果一个枚举器类是由编译器自动生成的,则该类是直接或间接地嵌套在函数成员中的,具有私有的可访问性,并且具有一个由编译器保留使用的名字。
一个枚举器对象可以实现上面所述之外的其它接口。
后面的几节详细地描述了由一个枚举器对象所实现的IEnumerable和IEnumerable<T>接口中的MoveNext()、Current和Dispose()成员的确切行为。
注意,枚举器对象不支持IEnumerator.Reset()方法。调用该方法会抛出System.NotSupporteException异常。
枚举器对象的MoveNext()方法封装了迭代器块的代码。对MoveNext()方法的调用执行了迭代器块中的代码,并为枚举器对象的Current属性设置一个适当的值。MoveNext()方法完成得确切动作取决于调用MoveNext()方法是枚举器对象的状态:
当MoveNext()方法执行迭代器块时,执行过程会通过四种途径中断:yield return语句、yield break语句、遇到迭代器块的结尾以及迭代器块中抛出了异常并被传播到块外。
一个枚举器对象的Current属性受迭代器块中的yield return语句的影响。
当一个枚举器对象处于suspended状态时,Current属性的值由最后一次对MoveNext()方法的调用设置。当一个枚举器对象处于before、running或after状态时,访问Current属性的结果是未定义的。
如果一个迭代器块的生成类型不是object,通过枚举器对象实现的IEnumerable以及相应的IEnumerator<T>对Current的访问会将结果转换为object。
Dispose()方法通过将枚举器对象的状态设置为after来清除迭代器。
当一个返回一个可枚举接口类型的函数成员使用了迭代器块时,对该函数成员的调用不会立即执行迭代器块中的代码,而是建立并返回一个可枚举对象。该可枚举对象有一个GetEnumerator()方法,能够返回一个枚举器对象。该枚举器对象封装了迭代器块中指定的代码,当调用这个枚举器对象的MoveNext()方法时,会执行迭代器块中的代码。一个可枚举对象具有如下特征:
典型的可枚举对象是由编译器自动生成的封装了迭代器块中的代码并实现了可枚举接口的可枚举类的实例,但其他的实现也是允许的。如果一个可枚举类是由编译器自动生成的,则该类是直接或间接地嵌套在函数成员中的,具有私有的可访问性,并且具有一个由编译器保留使用的名字。
一个可枚举对象可以实现上述之外的其它接口。例如,一个可枚举对象还可以实现IEnumerator和IEnumerator<T>,使得它既是可枚举的又是一个枚举器。这种情况下,当可枚举对象的GetEnumerator()方法第一次被调用时,将返回可枚举对象本身。以后对可枚举对象的GetEnumerator()方法的调用(如果有的话),将返回可枚举对象的一个拷贝。因此,每个被返回的枚举器具有其自己的状态,并且一个枚举器和其它枚举器互不影响。
一个可枚举对象提供了对IEnumerator和IEnumberator<T>接口的GetEnumerator()方法的实现。两个GetEnumerator()方法共享一个实现,能够获取并返回一个有效的枚举器对象。该枚举器对象使用可枚举对象被初始化时所保存的参数值和实例值进行初始化,该枚举器对象的功能如4.2节所描述。
迭代器块中的yield语句用于生成一个值,或发出一个迭代完成的信号。
embedded-statement:
...
yield-statement
yield-statement:
yield return expression ;
yield break ;
内嵌语句:
...
yield语句
yield语句:
yield return 表达式 ;
yield break ;
为了保证和现有程序的兼容性,yield并不是一个保留字,只有当一个return语句紧随其后时,yield语句才有这特殊的意义。其它情况下,yield语句可以用作标识符。
yield语句的出现首很多限制,如下所描述:
下面的例子展示了一些yield语句的有效的和无效的用法。
delegate IEnumerable<int> D();
IEnumerator<int> GetEnumerator() {
try {
yield return 1; // 正确
yield break; // 正确
}
finally {
yield return 2; // 错误,yield出现在finally块中E
yield break; // 错误,yield出现在finally块中
}
try {
yield return 3; // 错误,yield return语句出现在try...catch语句中
yield break; // 正确
}
catch {
yield return 4; // 错误,yield return语句出现在try...catch语句中
yield break; // 正确
}
D d = delegate {
yield return 5; // 错误,yield语句出现在匿名方法中
};
}
int MyMethod() {
yield return 1; // 错误,迭代器块具有错误的返回值类型
}
从yield return语句中的表达式的类型到迭代器块的生成类型(见4.1.3)必存在一个隐式转换。
yield return语句依照下面的步骤执行:
对枚举器对象的MoveNext()方法的下一次调用将从上一次挂起的地方恢复对迭代器块的执行。
yield break语句依照下面的步骤执行:
由于一个yield break语句无条件地将控制转移到其它地方,因此一个yield break的终点将永远不可达。
对于下面形式的yield return语句:
yield return expr ;
这一节将描述标准C#结构中的迭代器可能的实现。这里描述的实现是基于和Microsoft C#编译器相同的原则的,但决不是唯一可能的实现。
下面的Stack<T>类使用一个迭代器实现了它的GetEnumerator()方法。该迭代器按照从顶至底的顺序枚举了堆栈中的所有元素。
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];
}
}
GetEnumerator()方法可以转换为编译器自动生成的枚举器类的实例,它封装了迭代器块中指定的代码,如下所示:
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();
}
}
}
上面的转换中,迭代器块中的代码被转换为状态机并放在枚举器类的MoveNext()方法中。此外,局部变量i被转换为枚举器对象的域,因此在对MoveNext()方法的调用过程中它将一直存在。
下面的例子打印了整数1至10的一个简单的乘法表。例子中的FromTo()方法返回了一个用迭代器实现的可枚举对象。
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();
}
}
}
FromTo()方法可以被转换为由编译器自动生成的可枚举类的实例,它封装了迭代器块中的代码,如下所示:
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.CoompareExchange()方法可以用于确保线程安全。
from和to参数被转换为可枚举类的域。因为迭代器块改变了from,因此引入了一个附加的__from域来保存每个枚举器中的from的初始值。
当__state是0时,MoveNext()方法将跑出一个InvalidOperationException异常。这将保证不会发生没有首先调用GetEnumerator()方法而直接将可枚举对象用作枚举器。