[在完成返回收益的IEnumerable枚举之前退出调用代码会发生什么?
一个简化的示例:
public void HandleData()
{
int count = 0;
foreach (var datum in GetFileData())
{
//handle datum
if (++count > 10)
{
break;//early exit
}
}
}
public static IEnumerable<string> GetFileData()
{
using (StreamReader sr = _file.BuildStreamer())
{
string line = String.Empty;
while ((line = sr.ReadLine()) != null)
{
yield return line;
}
}
}
在这种情况下,及时关闭StreamReader显得非常重要。是否需要一种模式来处理这种情况?
这是一个很好的问题。
您看到,在使用foreach()迭代生成的IEnumerable时,您是安全的。下面的枚举器本身实现IDisposable,在foreach的情况下会调用该IDisposable(即使循环因中断而退出),并会清理GetFileData中的状态。
但是如果您直接使用Enumerator.MoveNext,则会遇到麻烦,如果更早退出,则永远不会调用Dispose(当然,如果您要完成手动迭代,则将是)。对于基于Enumerator的手动迭代,您也可以考虑将枚举器放在using语句中(如下面的代码所述)。
希望本示例涵盖不同的用例,将为您的问题提供一些反馈。
static void Main(string[] args)
{
// Dispose will be called
foreach(var value in GetEnumerable())
{
Console.WriteLine(value);
break;
}
try
{
// Dispose will be called even here
foreach (var value in GetEnumerable())
{
Console.WriteLine(value);
throw new Exception();
}
}
catch // Lame
{
}
// Dispose will not be called
var enumerator = GetEnumerable().GetEnumerator();
// But if enumerator and this logic is placed inside the "using" block,
// like this: using(var enumerator = GetEnumerable().GetEnumerable(){}), it will be.
while(enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
break;
}
Console.WriteLine("{0}Here we'll see dispose on completion of manual enumeration.{0}", Environment.NewLine);
// Dispose will be called: ended enumeration
var enumerator2 = GetEnumerable().GetEnumerator();
while (enumerator2.MoveNext())
{
Console.WriteLine(enumerator2.Current);
}
}
static IEnumerable<string> GetEnumerable()
{
using (new MyDisposer())
{
yield return "First";
yield return "Second";
}
Console.WriteLine("Done with execution");
}
public class MyDisposer : IDisposable
{
public void Dispose()
{
Console.WriteLine("Disposed");
}
}
最初观察者:https://blogs.msdn.microsoft.com/dancre/2008/03/15/yield-and-usings-your-dispose-may-not-be-called/作者将此称为(手动MoveNext()和抢先休息不会触发Dipose()的事实)是“一个错误”,但这是预期的实现。