一直对迭代器处于能用的层次,这次准备深入探索下迭代器的实现原理。
在.net中,一个类实现了IEnumerable
接口,那它就可以被迭代。在IEnumerable
中只包含了一个接口方法需要被实现:IEnumerator GetEnumerator();
,而返回的就是实现了IEnumerator
接口的对象。
IEnumerable
与IEnumerator
的关系,犹如:一本书与一个书签。IEnumerator
记录当前的位置,其每次调用MoveNext(),书签向前移动一个位置。当然一个IEnumerable
中可以有多个IEnumerator
。
从C#1时代foreach
就已经实现了对迭代器的支持。
C#1手写一个迭代器
在C# 1时代没有 yield 关键字,手写一个迭代器编码量还是多了挺多的。
1 | // 自定义迭代器 |
C#2使用yield简化上面迭代器
使用yield
关键字告诉编译器:这是一个需要执行的迭代块。
1 | static IEnumerable<int> GetEnumerable() |
用IL看看编译后生成的代码啥样
1 | [ ] |
迭代器的调用步骤
网上有个挺好的例子,借鉴下。可以得出以下结论:
- 调用CreateEnumerable()并不会执行内部方法,而是在首次 MoveNext() 时才进行执行。
- 代码执行到 yield return 后就会停止向下执行,而是等到再次调用MoveNext()才会继续执行。
- 在一个方法中可以多处进行 yield return 语句。
- 代码不会在 yield return 时结束,而是在 MoveNext() 返回false时结束。
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70class Program
{
private static readonly string Padding = new string(' ', 30);
static void Main(string[] args)
{
IEnumerable<int> iterable = CreateEnumerable();
IEnumerator<int> iterator = iterable.GetEnumerator();
Console.WriteLine("start to iterate");
while (true)
{
Console.WriteLine("calling MoveNext()....");
bool moveNext = iterator.MoveNext();
Console.WriteLine($"....MoveNext result={moveNext}");
if (!moveNext)
{
break;
}
Console.WriteLine("fetching current value....");
Console.WriteLine($"current value={iterator.Current} ");
}
Console.ReadKey();
}
static IEnumerable<int> CreateEnumerable()
{
Console.WriteLine($"{Padding} start of CreateEnumerable()");
for (int i = 0; i < 3; i++)
{
Console.WriteLine($"{Padding} about to yield {i}");
yield return i;
Console.WriteLine($"{Padding} after yield");
}
Console.WriteLine($"{Padding} yielding final value");
yield return -1;
Console.WriteLine($"{Padding} End of CreateEnumerable()");
}
}
/**
执行结果如下:
start to iterate
calling MoveNext()....
start of CreateEnumerable()
about to yield 0
....MoveNext result=True
fetching current value....
current value=0
calling MoveNext()....
after yield
about to yield 1
....MoveNext result=True
fetching current value....
current value=1
calling MoveNext()....
after yield
about to yield 2
....MoveNext result=True
fetching current value....
current value=2
calling MoveNext()....
after yield
yielding final value
....MoveNext result=True
fetching current value....
current value=-1
calling MoveNext()....
End of CreateEnumerable()
....MoveNext result=False
*/