本文只是 C# 5.0 规范中的内容,稍作调整,主要是下载 Demo 看看,这玩意用法挺多。
使用迭代器块实现的函数成员称为迭代器(iterator)。(所谓函数成员,包括方法、属性、事件、索引器、用户定义运算符、实例构造函数、静态构造函数和析构函数。)
只要相应函数成员的返回类型是枚举器接口 enumerator 或可枚举接口 enumerable 之一,迭代器块就可用作该函数成员的函数体。如下“音乐标题”类所示,有三个函数,有返回 IEnumerator 的,还有返回 IEnumerable 的,其中,返回 Reverse 函数可以反序迭代一个集合,而 Subset 函数可以迭代一个集合的子集:
public class MusicTitles
{
string[] names = { "Tubular Bells", "Hergest Ridge", "Ommadawn", "Platinum" };
public IEnumerator GetEnumerator()
{
for (int i = 0; i < 4; i++)
{
yield return names[i];
}
}
public IEnumerable Reverse()
{
for (int i = 3; i >= 0; i--)
{
yield return names[i];
}
}
public IEnumerable Subset(int index, int length)
{
for (int i = index; i < index + length; i++)
{
yield return names[i];
}
}
}
可以用如下方式迭代:
foreach (string title in titles)
{
Console.WriteLine(title);
}
foreach (string title in titles.Reverse())
{
Console.WriteLine(title);
}
foreach (string title in titles.Subset(2, 2))
{
Console.WriteLine(title);
}
迭代器块可以是 method-body、Operator-body 或 accessor-body,而不能将事件、实例构造函数、静态构造函数和析构函数作为迭代器来实现。
当使用迭代器块实现函数成员时,为该函数成员的形参列表指定任何 ref 或 out 形参将产生编译时错误。
枚举器接口 (enumerator interface) 为非泛型接口 System.Collections.IEnumerator 和泛型接口 System.Collections.Generic.IEnumerator<T> 的所有实例化。
简洁起见,将这些接口分别表示为 IEnumerator 和 IEnumerator<T>。
可枚举接口 (enumerable interface) 为非泛型接口 System.Collections.IEnumerable 和泛型接口 System.Collections.Generic.IEnumerable<T> 的所有实例化。
简洁起见,将这些接口分别表示为 IEnumerable 和 IEnumerable<T>。
迭代器产生一系列值,所有值的类型均相同。此类型称为迭代器的产生类型 (yield type)。
C# 1.0 使用 foreach 语句可以迭代集合,但创建枚举器需要大量代码。C# 2.0 添加了 yield 语句,便于创建枚举器。
如果返回枚举器接口类型的函数成员是使用迭代器块实现的,调用该函数成员不会立即执行迭代器块中的代码。而是先创建并返回一个枚举器对象 (enumerator object)。此对象封装了在迭代器块中指定的代码,并且在调用该枚举器对象的 MoveNext 方法时执行该迭代器块中的代码。枚举器对象具有下列特点:
枚举器对象通常是编译器生成的枚举器类的一个实例,它封装了迭代器块中的代码,并实现了枚举器接口,但也可能实现其他方法。如果枚举器类由编译器生成,则该类将直接或间接嵌套在包含该函数成员的类中,它将具有私有可访问性,并且它将具有一个供编译器使用的保留名称。
枚举器对象可实现除上面指定的那些接口以外的其他接口。
下面的各节将描述由枚举器对象所提供的 IEnumerable 和 IEnumerable<T> 接口实现的 MoveNext、Current 和 Dispose 成员的确切行为。
请注意,枚举器对象不支持 IEnumerator.Reset 方法。调用此方法将导致引发 System.NotSupportedException。
枚举器对象的 MoveNext 方法封装了迭代器块的代码。调用 MoveNext 方法将执行迭代器块中的代码,并相应设置枚举器对象的 Current 属性。MoveNext 执行的具体操作取决于调用 MoveNext 时的枚举器对象的状态:
当 MoveNext 执行迭代器块时,可以采用四种方式来中断执行:通过 yield return 语句、通过 yield break 语句、到达迭代器块的末尾以及引发异常并将异常传播到迭代器块之外。
枚举器对象的 Current 属性将受迭代器块中的 yield return 语句影响。
当枚举器对象处于挂起 (suspended) 状态时,Current 的值为上一次调用 MoveNext 时设置的值。当枚举器对象处于运行前 (before)、运行中 (running) 或运行后 (after) 状态时,访问 Current 的结果不确定。
对于产生类型不是 object 的迭代器,通过枚举器对象的 IEnumerable 实现来访问 Current 的结果对应于通过枚举器对象的 IEnumerator<T> 实现来访问 Current 并将该结果强制转换为 object。
Dispose 方法用于通过使枚举器对象变为运行后 (after) 状态来清除迭代。
如果返回可枚举接口类型的函数成员是使用迭代器块实现的,调用该函数成员不会立即执行迭代器块中的代码。而是先创建并返回一个可枚举对象 (enumerable object)。可枚举对象的 GetEnumerator 方法返回一个封装有迭代器块中指定的代码的枚举器对象,当调用该枚举器对象的 MoveNext 方法时,将执行迭代器块中的代码。可枚举对象具有下列特点:
可枚举对象通常是编译器生成的可枚举类的实例,它封装了迭代器块中的代码,并实现了可枚举接口,但也可能实现其他方法。如果可枚举类由编译器生成,则该类将直接或间接嵌套在包含该函数成员的类中,它将具有私有可访问性,并且它将具有供编译器使用的保留名称。
可枚举对象可实现除上面指定的那些接口以外的其他接口。具体而言,可枚举对象还可实现 IEnumerator 和 IEnumerator<T>,从而使其既可作为可枚举对象,也可作为枚举器对象。在该类型的实现中,首次调用可枚举对象的 GetEnumerator 方法时,将返回可枚举对象本身。对可枚举对象的 GetEnumerator 的后续调用(如果存在),将返回可枚举对象的副本。因此,每个返回的枚举器都有自己的状态,一个枚举器中的更改不会影响其他枚举器。
可枚举对象实现了 IEnumerable 和 IEnumerable<T> 接口的 GetEnumerator 方法。这两种 GetEnumerator 方法的实现是相同的,都是获取并返回一个可用的枚举器对象。枚举器对象是以初始化该可枚举对象时保存的实例值和实参值进行初始化的,此外,枚举器对象函数如前所述。
下面的 Stack<T> 类使用一个迭代器实现其 GetEnumerator 方法。
规范中的该示例有编译错误,所以采用 MSDN 中的实例:
public class Stack<T> : IEnumerable<T>
{
PRivate T[] items;
private 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] = default(T);
return result;
}
public IEnumerator<T> GetEnumerator()
{
for (int i = count - 1; i >= 0; --i)
yield return items[i];
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerable<T> TopToBottom
{
get { return this; }
}
public IEnumerable<T> BottomToTop
{
get
{
for (int index = 0; index <= count - 1; index++)
{
yield return items[index];
}
}
}
public IEnumerable<T> TopN(int itemsFromTop)
{
// Return less than itemsFromTop if necessary.
int startIndex = itemsFromTop >= count ? 0 : count - itemsFromTop;
for (int index = count - 1; index >= startIndex; index--)
{
yield return items[index];
}
}
}
若用如下代码:
Stack<int> stack = new Stack<int>();
stack.Push(1);
stack.Push(2);
stack.Push(3);
stack.Push(4);
stack.Push(5);
foreach (int s in stack)
{
Console.Write(s + " ");
}
stack.Pop();
Console.Write("/n");
foreach (int s in stack)
{
Console.Write(s + " ");
}
Console.Write("/n");
foreach (int s in stack.TopToBottom)
{
Console.Write(s + " ");
}
Console.Write("/n");
foreach (int s in stack.BottomToTop)
{
Console.Write(s + " ");
}
Console.Write("/n");
foreach (int s in stack.TopN(2))
{
Console.Write(s + " ");
}
Console.ReadKey();
运行结果:
5 4 3 2 1
4 3 2 1
4 3 2 1
1 2 3 4
4 3
下载 Demo
新闻热点
疑难解答