【发布时间】:2019-01-04 15:06:51
【问题描述】:
为了简单起见,我举了一些愚蠢的例子。
IEnumerable<T> Silly<T>(this IEnumerable<T> source)
{
foreach(var x in source) yield return x;
}
我知道这会被编译成状态机。但它也类似于
IEnumerable<T> Silly<T>(this IEnumerable<T> source)
{
using(var sillier = source.GetEnumerator())
{
while(sillier.MoveNext()) yield return sillier.Current;
}
}
现在考虑这种用法
list.Silly().Take(2).ToArray();
在这里你可以看到Silly enumerable 可能没有被完全消耗掉,但是Take(2) 它自己会被完全消耗掉。
问题:当 dispose 在 Take 枚举器上调用时,它是否也会在 Silly 枚举器上调用 dispose,更具体地说是 sillier 枚举器?
我的猜测是,由于foreach,编译器可以处理这个简单的用例,但不是那么简单的用例呢?
IEnumerable<T> Silly<T>(this IEnumerable<T> source)
{
using(var sillier = source.GetEnumerator())
{
// move next can be called on different stages.
}
}
这会成为问题吗?因为大多数枚举器不使用非托管资源,但如果使用,这可能会导致内存泄漏。
如果没有调用 dispose,我如何使一次性可枚举?
一个想法:每个yield return 后面都可以有一个if(disposed) yield break;。现在,愚蠢的枚举器的 dispose 方法只需要设置 disposed = true 并移动枚举器一次即可处理所有必需的东西。
【问题讨论】:
-
是什么让您认为
Take会处理枚举器?TakeIterator(这是Take返回的内容)与源代码的作用不大。特别是它不处理任何东西。 -
为什么不写你自己的
IEnumerator,看看当你这样使用它时是否会调用dispose? -
@HimBromBeere 是什么让您认为 take 不会 处理它创建的枚举器?如果没有,那将是其代码中的错误,您应该将其报告给 MS。当然,查看源代码,它永远不会创建未释放的枚举器。
-
@Servy 你是对的。我只是不想进入兔子洞:)
-
在这种情况下,编译您的示例然后查看编译器通过反编译器将哪些内容推送到枚举器的实现类中是很有启发性的。你可以看到所有的机器,例如处理
Dispose以确保using的逻辑finally或显式finally被调用。
标签: c# ienumerable dispose yield-return