迭代器相关接口
IEnumerator 枚举数
- IEnumerator 中包含了遍历集合必需的方法,允许使用迭代器模式而不是索引模式遍历元素集合。
- 枚举数相当于一个“游标”,记录了当前的遍历状态,每个游标独立于其它的游标。
- 通过枚举数确定第一个和下一个元素,就不需要事先知道元素总数,也不需要按照索引获取元素。
MoveNext
- 从集合的一个元素移动到下一个元素,同时返回是否已经遍历完集合中的每个元素。
Current
- 只读属性,返回当前的元素。
IEnumerable 可枚举
- 所有的集合至少要实现
IEnumerable<T>
(可枚举的) 接口。 - IEnumerable 唯一的作用就是通过 GetEnumerator 返回一个 IEnumerator 枚举数实例。IEnumerator 通常由一个嵌套类实现,以便访问到集合的内部变量;并维护每个独立游标的状态,以便支持同时维护多个游标的情况。
foreach
- “运行时”并不认识 foreach,C#编译器会对代码进行必要的转换。
- 对于数组:依靠
Length
属性和数组索引操作符[]
,把foreach转化为等价的 for 循环。 - 对于集合:通过迭代器遍历整个集合。(不需要长度已知,也不需要可以按索引检索)
- 对于数组:依靠
- foreach 循环变量是只读变量,循环期间禁止修改。
- 如果 IEnumerator 实现了 IDisposable 接口,退出 foreach 循环后会调用 Dispose() 方法清理状态。
标准查询
[[LINQ]]
foreach 循环中不能修改集合
- foreach变量并不是原本的集合元素,对 foreach 变量赋值并不能改变集合元素本身,会引起混淆。
- foreach循环期间也不能改变集合中元素的数量。如果修改元素个数,iterator中应该忽略还是集成新的修改?
- 综上,C#编译器禁用了foreach中修改集合。
Duck Typing
- C#编译器不要求一定要实现
IEnumerable/IEnumerable<T>
才能对一个数据类型进行遍历。只需要能够查找到能返回“包含Current属性和MoveNext()方法的一个类型”的GetEnumerator()方法。Duck typing按名称查找方法,而不依赖接口或显式方法调用。当 [[DuckTyping]] 找不到可枚举模式的恰当实现时,编译器才会检查集合是否实现了接口。
枚举数模式
- 为自定义类型实现迭代的功能, 实现
IEnumerable
接口。 - 如果某个类型实现了IEnumerable接口,就意味着它可以被迭代访问。
- 把被迭代对象和迭代器分开,使得多个迭代器可以同时独立地操作同一个序列。
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
// IEnumerable相当于数据库中的表
public class CountingEnumerable : IEnumerable<int>
{
// 因为C#不能以返回类型区分两个重载方法
// 隐式实现 IEnumerator
public IEnumerator<int> GetEnumerator()
{
return new CountingEnumerator();
}
// 使用显示接口实现 IEnumerable
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
// IEnumerator 嵌套类相当于数据库中的游标
private class CountingEnumerator : IEnumerator<int>
{
private int current = -1;
public bool MoveNext()
{
current++;
return current < 10;
}
public void Reset()
{
current = -1;
}
public int Current => current;
object IEnumerator.Current => Current;
public void Dispose()
{
}
}
}
迭代器块
- 迭代器模式简化了枚举数模式的语法,C#编辑器会自动把其扩展成枚举数模式的代码。
- 使用
yield return
语句实现迭代器块方法。 - 当编译器看到迭代器块时,会为类创建一个嵌套类型作为它的游标,来正确记录块中的位置和局部变量的值。
- 迭代器块实际上可以让你在这个代码块所涉及的特定位置“暂停”当前执行流,并随后携带着同样的状态返回同一位置。
1
2
3
4
5
6
public IEnumerator GetEnumerator()
{
for (int i = 0; i < values.Length; ++i) {
yield return value[i];
}
}