When using yield within a "using" statement, when does Dispose occur?
Asked Answered
S

1

23

I have a question regarding deferred execution and the disposing of data.

Consider the following example:

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++;
        }
    }
}

Will the break statement immediately cause the reader in ParseFile to dispose or is it still considered in context and will lock the file open until the program itself is closed?

Stedmann answered 7/1, 2013 at 21:0 Comment(4)
Write a quick console app and find out :)Squirm
Oh a side note, rather than using break when the counter hits two, just add a Take(2) to the end of ParseFile.Quan
Pick up Jon Skeet's book "C# in Depth" if you want a really good explanation of what happens in iterator blocks, or just look over the language spec, as he often suggests.Vickers
@Quan Your answer below was exactly what I was looking for. I know that there are many better methods of pulling only 2 lines from a file. That was just a quick example to show what I was trying to get at. Thank you again for your help!Stedmann
Q
20

So we have several separate issues going on here.

First off, dealing with the using in the iterator block. IEnumerator extends IDisposable. The code that generates iterator blocks is actually robust enough that any try/finally blocks (a using results in a try/finally block being created) results in the contents of the finally block being called in the Dispose method of the enumerator, if it wasn't already called. So as long as the enumerator is disposed, it won't leak the StreamReader.

So now we ask ourselves if the enumerator is disposed. All foreach statements will call Dispose on the enumerator (should it implement IDisposable). They do so even if you exit using a break or return statement, as well as when it finishes normally.

So you can be sure that under all circumstances the resource won't be leaked, barring the cases where nothing can be prevented from leaking (i.e. someone unpugging the machine).

Quan answered 7/1, 2013 at 21:7 Comment(4)
It's a little more complicated than that. In the OP's case, the iterator will absolutely dispose becuase of the break statement. But the call to ParseFile produces a reference to StreamReader inside a 'local-using' that has now gone out of scope. I think this queues it up on the finalizer queue and disposed when collected?Fleet
@Fleet No, that's not true. When the IEnumerator is disposed by the foreach loop it will call dispose on the StreamReader (assuming it was inside of the using at that point). It's the way iterator blocks were implemented; to make doing this possible.Quan
I guess we should take PsychoDad's advice and try it out becuase that would be very cool if that's how it works!Fleet
Trying to find the specific quote, but you can start reading in case you get there quicker. blogs.msdn.com/b/ericlippert/archive/2009/07/16/… Pretty sure it'll be the most relevant, or the one after that, of the seven part series.Quan

© 2022 - 2024 — McMap. All rights reserved.