在“using”语句中使用yield时,Dispose是在何时发生的?

23

关于延迟执行和数据释放,我有一个问题。

考虑以下例子:

private IEnumerable<string> ParseFile(string fileName)
{
    using(StreamReader sr = new StreamReader(fileName))
    {
        string line;
        while((line = sr.ReadLine()) != null)
        {
            yield return line;
        }
    }
}

private void LineReader(string fileName)
{
    int counter = 0;

    foreach(string line in ParseFile(fileName))
    {
        if(counter == 2)
        {
            break; // will this cause a dispose on the StreamReader?
        } else
        {
            Console.WriteLine(line);
            counter++;
        }
    }
}

break语句会立即导致ParseFile中的读取器被释放吗,还是它仍然在上下文中考虑,并且会锁定该文件直到程序本身关闭?


2
写一个快速的控制台应用程序,然后找出答案 :) - jjxtra
顺便说一下,与其在计数器达到二时使用 break,不如在 ParseFile 结尾处添加一个 Take(2) - Servy
1
如果你想要一个非常好的迭代器块解释,可以阅读Jon Skeet的书《C#深入理解》,或者只需查看语言规范,因为他经常建议这样做。 - Eric Andres
1
@Servy,你下面的回答正是我想要的。我知道有许多更好的方法可以从文件中提取仅两行内容。那只是一个快速的示例,展示了我的意图。再次感谢你的帮助! - Middas
1个回答

20

这里涉及到几个不同的问题。

首先,我们需要处理迭代器块中的usingIEnumerator 扩展了 IDisposable 接口。生成迭代器块的代码实际上足够健壮,以至于任何 try/finally 块(使用 using 会创建一个 try/finally 块)都会导致 finally 块中的内容在枚举器的 Dispose 方法中被调用,如果没有被调用的话。 只要枚举器被处理,它就不会泄漏 StreamReader

现在我们要问自己枚举器是否已被处理。所有的 foreach 语句都会调用枚举器的 Dispose 方法(如果它实现了IDisposable)。即使您使用 breakreturn 语句退出,或者按照正常的方式完成循环,它们仍然会这样做。

因此,在所有情况下,资源都不会泄漏,除非有一些无法防止泄漏的情况发生(例如有人拔掉电源线等)。


这比那要复杂一些。在 OP 的情况下,迭代器由于 break 语句而被绝对释放。但是 ParseFile 的调用会产生对 StreamReader 的引用,该引用位于一个现在已经超出范围的“本地使用”内。我认为这将把它排队到终结器队列中,并在收集时进行处理? - n8wrl
3
不,那不是真的。当IEnumeratorforeach循环处理并释放时,它会调用StreamReader上的dispose方法(假设此时它在using语句块内)。这是迭代器块实现的方式,以便实现该功能。 - Servy
我猜我们应该采纳PsychoDad的建议,尝试一下,因为如果它能够起作用的话,那将非常酷! - n8wrl
3
尝试查找特定引语,但如果您可以更快地找到它,可以开始阅读。http://blogs.msdn.com/b/ericlippert/archive/2009/07/16/iterator-blocks-part-three-why-no-yield-in-finally.aspx 。我相信这篇文章会是最相关的,或者是那七篇系列文章中的下一篇。 - Servy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接