让我们稍微更改一下原始代码块,将其归结为要点,同时仍然保持其足够有趣以进行分析。这并不完全等同于您发布的内容,但我们仍在使用迭代器的值。
class Disposable : IDisposable {
public void Dispose() {
Console.WriteLine("Disposed!");
}
}
IEnumerable<int> CreateEnumerable() {
int i = 0;
using (var d = new Disposable()) {
while (true) yield return ++i;
}
}
void UseEnumerable() {
foreach (int i in CreateEnumerable()) {
Console.WriteLine(i);
if (i == 10) break;
}
}
这将在打印之前打印从 1 到 10 的数字Disposed!
幕后到底发生了什么?还有很多。我们先处理外层,UseEnumerable
. The foreach
是以下内容的语法糖:
var e = CreateEnumerable().GetEnumerator();
try {
while (e.MoveNext()) {
int i = e.Current;
Console.WriteLine(i);
if (i == 10) break;
}
} finally {
e.Dispose();
}
对于确切的细节(因为即使这也被简化了一点)我建议你C# 语言规范 https://www.microsoft.com/download/details.aspx?id=7029,第 8.8.4 节。这里重要的一点是foreach
需要隐式调用Dispose
的枚举器。
接下来,using
中的声明CreateEnumerable
也是语法糖。事实上,让我们用原始语句写出整个内容,以便稍后我们可以更理解翻译:
IEnumerable<int> CreateEnumerable() {
int i = 0;
Disposable d = new Disposable();
try {
repeat:
i = i + 1;
yield return i;
goto repeat;
} finally {
d.Dispose();
}
}
语言规范第 10.14 节详细介绍了迭代器块实现的确切规则。它们以抽象操作而非代码的形式给出。关于 C# 编译器生成什么类型的代码以及每个部分的作用的很好的讨论在C# 深入探讨 http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx,但我将提供一个仍然符合规范的简单翻译。重申一下,这不是编译器会做的actually产生,但它是一个足够好的近似值来说明正在发生的事情,并且忽略了处理线程和优化的更多毛茸茸的部分。
class CreateEnumerable_Enumerator : IEnumerator<int> {
// local variables are promoted to instance fields
private int i;
private Disposable d;
// implementation of Current
private int current;
public int Current => current;
object IEnumerator.Current => current;
// State machine
enum State { Before, Running, Suspended, After };
private State state = State.Before;
// Section 10.14.4.1
public bool MoveNext() {
switch (state) {
case State.Before: {
state = State.Running;
// begin iterator block
i = 0;
d = new Disposable();
i = i + 1;
// yield return occurs here
current = i;
state = State.Suspended;
return true;
}
case State.Running: return false; // can't happen
case State.Suspended: {
state = State.Running;
// goto repeat
i = i + 1;
// yield return occurs here
current = i;
state = State.Suspended;
return true;
}
case State.After: return false;
default: return false; // can't happen
}
}
// Section 10.14.4.3
public void Dispose() {
switch (state) {
case State.Before: state = State.After; break;
case State.Running: break; // unspecified
case State.Suspended: {
state = State.Running;
// finally occurs here
d.Dispose();
state = State.After;
}
break;
case State.After: return;
default: return; // can't happen
}
}
public void Reset() { throw new NotImplementedException(); }
}
class CreateEnumerable_Enumerable : IEnumerable<int> {
public IEnumerator<int> GetEnumerator() {
return new CreateEnumerable_Enumerator();
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
}
IEnumerable<int> CreateEnumerable() {
return new CreateEnumerable_Enumerable();
}
这里的要点是代码块在出现时被分割yield return
or yield break
语句,迭代器负责记住中断时“我们在哪里”。任何finally
体内的块被推迟到Dispose
。代码中的无限循环实际上不再是无限循环,因为它被周期性中断yield return
声明。注意,because the finally
块实际上不是finally
不再阻塞,当你处理迭代器时,它的执行就不太确定了。这就是为什么使用foreach
(或任何其他确保Dispose
迭代器的方法在 a 中被调用finally
块)是必不可少的。
这是一个简化的例子;当你使循环变得更加复杂、引入异常等等时,事情会变得更加有趣。 “只是让这个工作”的负担落在编译器身上。