为什么数组可以被foreach遍历呢?
原因是数组可以按需提供一个叫做枚举器的对象,枚举器可以依次返回请求数组中的元素,枚举器知道项的次序并且追踪它在序列中的位置,然后返回请求的当前项。
获取对象枚举器的方法是调用对象的GetEnumerator方法。实现GetEnumerator方法的类型叫做可枚举类型。

IEnumerator接口

位于System.Collections。
实现了IEnumerator接口的枚举器包含3个函数成员:Current、MoveNext以及Reset。
Current是返回序列中当前位置项的属性,它是只读的,它返回object类型的引用,我们必须把它转成实际类型的实例。
MoveNext是把枚举器位置前进到集合中下一项的方法。它也返回布尔值,指示新的位置是有效位置还是已经超过了序列的尾部,MoveNext必须在第一次使用Current之前调用。
Reset是把位置重置为原始状态的方法。

1
2
3
4
5
6
7
8
9
//using System.Collections;
static void Main(string[] args) {
int[] arr = { 10, 11, 12, 13 };
IEnumerator ie = arr.GetEnumerator();
while (ie.MoveNext()) {
int item = (int) ie.Current;
Console.WriteLine($"Item value:{item}");
}
}

IEnumerable接口

位于System.Collections。
可枚举类是实现了IEnumerable接口的类,IEnumerable只有一个成员——GetEnumerator方法,它返回对象的枚举器。
枚举器与迭代器完整写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using System.Collections;
namespace Test {
internal class Program {
static void Main(string[] args) {
Spectrum spectrum = new Spectrum();
foreach (var color in spectrum) {
Console.WriteLine(color);
}
}
}
class ColorEnumerator : IEnumerator {
string[] colors;
int position = -1;
public ColorEnumerator(string[] theColors) {
colors = new string[theColors.Length];
for(int i = 0;i < theColors.Length; i++) {
colors[i] = theColors[i];
}
}
public object Current {
get {
if(position == -1) {
throw new InvalidOperationException();
}
if(position>= colors.Length) {
throw new InvalidOperationException();
}
return colors[position];
}
}
public bool MoveNext() {
if(position<colors.Length-1) {
position++;
return true;
}else { return false; }
}
public void Reset() {
position = -1;
}
}
class Spectrum : IEnumerable {
string[] Colors = { "red", "blue", "green", "yello", "pink" };
public IEnumerator GetEnumerator() {
return new ColorEnumerator(Colors);
}
}
}

泛型枚举接口

大多数情况下你应该使用泛型版本的IEnumerable<T>和IEnumerator<T>。
IEnumerable<T>接口的GetEnumerator方法返回实现IEnumator<T>的枚举器类的实例,这些接口是协变接口。
泛型接口的枚举器是类型安全的,它自动将返回结果转换成实际类型的引用。

迭代器

迭代器需要System.Collections.Generic名称空间。
从C#2.0开始提供了更简单的创建枚举器和可枚举类型的方式,这种结构称为迭代器,我们可以把手动编码的可枚举类型和枚举器替换为由迭代器生成的可枚举类型和枚举器。

1
2
3
4
5
public IEnumerator<string> BlackAndWhite() {
yield return "black";
yield return "gray";
yield return "white";
}

这样的有迭代器产生的枚举器简化了很多代码。
迭代器块是有一个或多个yield语句的代码块。
迭代器块不是需要在同一时间执行的一串命令式命令,而是声明性的。
迭代器块有两个特殊的语句:
yield return 指定了序列中要返回的下一项。
yield break 指定在序列中没有其他项。
根据迭代器块的返回类型,可以让迭代器产生枚举器或可枚举类型
使用迭代器创建枚举器:

1
2
3
4
5
6
7
8
9
10
class MyClass {
public IEnumerator<string> GetEnumerator() {
return this.BlackAndWhite();
}
public IEnumerator<string> BlackAndWhite() {
yield return "black";
yield return "gray";
yield return "white";
}
}

使用迭代器创建可枚举对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
internal class Program {   
static void Main(string[] args) {
MyClass mc = new MyClass();
foreach (string color in mc) {
Console.WriteLine(color);
}
}
}
class MyClass {
public IEnumerator<string> GetEnumerator() {
return this.BlackAndWhite();
}
public IEnumerator<string> BlackAndWhite() {
yield return "black";
yield return "gray";
yield return "white";
}
}

使用迭代器创建可枚举类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
internal class Program {   
static void Main(string[] args) {
MyClass mc = new MyClass();
foreach (string color in mc) {
Console.WriteLine(color);
}
foreach (string color in mc.BlackAndWhite()) {
Console.WriteLine(color);
}
}
}
class MyClass {
public IEnumerator<string> GetEnumerator() {
return this.BlackAndWhite().GetEnumerator();
}
public IEnumerable<string> BlackAndWhite() {
yield return "black";
yield return "gray";
yield return "white";
}
}

常见迭代器模式

当我们实现返回枚举器的迭代器时,必须通过实现GetEnumerator来让类可枚举,它返回由迭代器返回的枚举器。
在类中实现返回可枚举类型的迭代器时,我们可以让类实现GetEnumerator来让类本身可枚举,或不实现让类不可枚举。
如果实现GetEnumerator,让它调用迭代器方法以获取自动生成的实现IEnumerable的类实例,然后从IEnumerable对象返回由GetEnumerator创建的枚举器。
如果不实现GetEnumerator使类本身不可枚举,仍然可以使用由迭代器返回的可枚举类,只需要直接调用迭代器方法。