在通用IEnumerable上使用yield return时从调用代码中提前退出

问题描述 投票:2回答:1

[在完成返回收益的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显得非常重要。是否需要一种模式来处理这种情况?

c# ienumerable yield-return
1个回答
2
投票

这是一个很好的问题。

您看到,在使用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()的事实)是“一个错误”,但这是预期的实现。

© www.soinside.com 2019 - 2024. All rights reserved.