IEnumerator<T>
实现了 IDisposable
接口,而 foreach
循环会在循环结束时释放它们正在枚举的内容(包括使用 foreach
循环的 linq 方法,例如 .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";
}
我不会粘贴整个生成的状态机,因为它非常大。您可以在
这里查看。
状态机的核心是以下 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;
}
在进入状态2时,我们创建了MemoryStream
对象,并且在退出状态3时通过调用<>m__Finally1()
进行了处理。
下面是Dispose
方法的代码:
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
块内部的状态。
(状态-3似乎是一种保护措施,以防我们编写了yield return Foo()
并且Foo()
抛出异常:在这种情况下,我们将保持在状态-3中,无法再进行迭代。但是在这种情况下,我们仍然可以处置MemoryStream
)。
为了完整起见,<>m__Finally1
的定义如下:
private void <>m__Finally1()
{
<>1__state = -1;
if (<ms>5__1 != null)
{
((IDisposable)<ms>5__1).Dispose();
}
}
您可以在
C# 语言规范的第10.14.4.3节中找到此规范:
- 如果枚举器对象的状态已挂起,调用Dispose:
- 将状态更改为运行中。
- 执行任何finally块,就好像上一次执行的yield return语句是yield break语句。如果这导致异常被抛出并从迭代器体传播出去,则枚举器对象的状态被设置为after,并将异常传播给Dispose方法的调用方。
- 将状态更改为after。