IEnumerator<T>
实施IDisposable
, and foreach
循环将在完成时处理它们正在枚举的东西(这包括使用foreach
循环,例如.ToArray()
).
事实证明,编译器生成的生成器方法的状态机实现了Dispose
以一种聪明的方式:如果状态机处于“内部”状态using
阻止,然后调用Dispose()
在状态机上将处置受保护的事物using
陈述。
让我们举个例子:
public IEnumerable<string> M() {
yield return "1";
using (var ms = new MemoryStream())
{
yield return "2";
yield return "3";
}
yield return "4";
}
我不会粘贴整个生成的状态机,因为它非常大。您可以在 SharpLab 上看到它 https://sharplab.io/#v2:EYLgZgpghgLgrgJwgZwLQBMD2MB2EYBimCAtrADTogDUAPgAIAMABPQIwAsA3ALABQTVmwB0ASQDyvAQGZWAJmYBhZgG9+zDa1ntpAHnaMAfMwCyACgCUq9Ztvs2rAOzMARGxdTbdlmYBuUBGYSZGYAXmY8AHdTCBJiAE8AZRgkKBJLCxsvaz5s7PsnVzkPDSy8jQL6ZxdpDzLbAF96zUrqjjrczSa+bqA==.
状态机的核心是以下 switch 语句,它跟踪我们通过每个状态的进度yield return
声明:
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<>2__current = "1";
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
<ms>5__1 = new MemoryStream();
<>1__state = -3;
<>2__current = "2";
<>1__state = 2;
return true;
case 2:
<>1__state = -3;
<>2__current = "3";
<>1__state = 3;
return true;
case 3:
<>1__state = -3;
<>m__Finally1();
<ms>5__1 = null;
<>2__current = "4";
<>1__state = 4;
return true;
case 4:
<>1__state = -1;
return false;
}
你可以看到我们创建了MemoryStream
当我们进入状态 2 并处理它时(通过调用<>m__Finally1()
)当我们退出状态 3 时。
这是Dispose
method:
void IDisposable.Dispose()
{
int num = <>1__state;
if (num == -3 || (uint)(num - 2) <= 1u)
{
try
{
}
finally
{
<>m__Finally1();
}
}
}
如果我们处于状态 -3、2 或 3,那么我们将调用<>m__Finally1();
。状态 2 和 3 是那些在using
block.
(状态 -3 似乎是一个守卫,以防我们写yield return Foo()
and Foo()
抛出异常:在这种情况下,我们将停留在状态 -3 并且无法进一步迭代。然而,我们仍然可以处置MemoryStream
在这种情况下)。
只是为了完整性,<>m__Finally1
定义为:
private void <>m__Finally1()
{
<>1__state = -1;
if (<ms>5__1 != null)
{
((IDisposable)<ms>5__1).Dispose();
}
}
您可以在以下位置找到相关规范:C# 语言规范 https://www.microsoft.com/en-us/download/details.aspx?id=7029,第 10.14.4.3 节:
- If the state of the enumerator object is suspended, invoking Dispose:
- 将状态更改为正在运行。
- 执行任何finally 块,就好像最后执行的yield return 语句是yield break 语句一样。如果这导致抛出异常并将其传播到迭代器主体之外,则枚举器对象的状态将设置为 after,并将异常传播到 Dispose 方法的调用方。
- 将状态更改为之后。