40

Imagine you have a method that internally uses an IDisposable object (for example a streamreader), and yield returns items as they are read from the file. Like this:

public IEnumerable<YourObject> Read(string filename)
{
    using(var filestream = new FileStream(filename, FileMode.Open))
    {
        using(var reader = new StreamReader(filestream))
        {
            string line;

            while((line = reader.ReadLine()) != null)
            {
                yield return new YourObject(line);
            }
        }
    }
}

Will the reader and the filestream be disposed when I use LINQ-methods that doesn't iterate the complete collection?

YourOjbect firstLine = Read("myfile.txt").First();
Thomas
  • 7,933
  • 4
  • 37
  • 45
  • 3
    Have you tried checking that using debugger? – dzendras Jun 01 '12 at 11:48
  • 3
    +1 Excellent question :) I suspect we just have to trust the GC here. – leppie Jun 01 '12 at 11:49
  • 1
    It _looks_ like the filestream indeed is disposed. If I assign the filestream to a property and consult the filestream's properties after the method is called, then most of them throw `ObjectDisposedExceptions` – Thomas Jun 01 '12 at 11:52

3 Answers3

28

When you use yield keyword compiler generates nested class, which implements IEnumerable, IEnumerator and IDisposable and stores all context data:

[CompilerGenerated]
private sealed class <Read>d__0 : IEnumerable<YourObject>, IEnumerable, IEnumerator<YourObject>, IEnumerator, IDisposable
{
    // Fields
    private int <>1__state;
    private YourObject <>2__current;
    public string <>3__filename;
    public Foo <>4__this;
    private int <>l__initialThreadId;
    public FileStream <filestream>5__1;
    public string <line>5__3;
    public StreamReader <reader>5__2;
    public string filename;

    // Methods
    [DebuggerHidden]
    public <Read>d__0(int <>1__state);
    private void <>m__Finally4();
    private void <>m__Finally5();
    private bool MoveNext();
    [DebuggerHidden]
    IEnumerator<YourObject> IEnumerable<YourObject>.GetEnumerator();
    [DebuggerHidden]
    IEnumerator IEnumerable.GetEnumerator();
    [DebuggerHidden]
    void IEnumerator.Reset();
    void IDisposable.Dispose();

    // Properties
    YourObject IEnumerator<YourObject>.Current { [DebuggerHidden] get; }
    object IEnumerator.Current { [DebuggerHidden] get; }
}

As you can see, all local variables from context of the yielding method are moved to fields of this generated class. Interesting methods are those which have m_Finally in their names:

private void <>m__Finally4()
{
    this.<>1__state = -1;
    if (this.<filestream>5__1 != null)
    {
        this.<filestream>5__1.Dispose();
    }
}

As you can see, these methods dispose your disposable objects (FileStream and StreamReader). When the called? At the end of enumerating, or when Dispose is called:

private bool MoveNext()
{
    bool CS$1$0000;
    try
    {
        int CS$4$0001 = this.<>1__state;
        if (CS$4$0001 != 0)
        {
            if (CS$4$0001 != 3)
            {
                goto Label_00AB;
            }
            goto Label_0074;
        }
        this.<>1__state = -1;
        this.<filestream>5__1 = new FileStream(this.filename, FileMode.Open);
        this.<>1__state = 1;
        this.<reader>5__2 = new StreamReader(this.<filestream>5__1);
        this.<>1__state = 2;
        while ((this.<line>5__3 = this.<reader>5__2.ReadLine()) != null)
        {
            this.<>2__current = new YourObject(this.<line>5__3);
            this.<>1__state = 3;
            return true;
        Label_0074:
            this.<>1__state = 2;
        }
        this.<>m__Finally5();
        this.<>m__Finally4();
    Label_00AB:
        CS$1$0000 = false;
    }
    fault
    {
        this.System.IDisposable.Dispose();
    }
    return CS$1$0000;
}

void IDisposable.Dispose()
{
    switch (this.<>1__state)
    {
        case 1:
        case 2:
        case 3:
            try
            {
                switch (this.<>1__state)
                {
                    case 2:
                    case 3:
                        break;

                    default:
                        break;
                }
                try
                {
                }
                finally
                {
                    this.<>m__Finally5();
                }
            }
            finally
            {
                this.<>m__Finally4();
            }
            break;
    }
}

If you look to First() implementation of Enumerable, then you'll see - it calls Dispose after returning first item:

using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
   if (enumerator.MoveNext())
   {
       return enumerator.Current;
   }
}

Thus Dispose of auto-generated class will be called, and all local variables, which require disposing will be disposed by calls to m_Finally methods.

BTW (not about usage with LINQ) if you look at foreach statement implementation you'll see that enumerator is disposed after enumerating. Thus Dispose on generated class will be called even in case of break or exception.

sean
  • 367
  • 2
  • 12
Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
17

Yes, they're disposed.

[Edit] As long as you're using LINQ-methods or foreach loops, the disposal is taken care of automatically. However, if you decide to manually call .Enumerator().MoveNext() on the method, then you need to take care of the disposal yourself. [/Edit]

This code:

class something : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Disposing");
        Console.WriteLine(Environment.StackTrace);
    }
}
static IEnumerable<string> ie()
{
    using (new something())
    {
        Console.WriteLine("first");
        yield return "first";
        Console.WriteLine("second");
        yield return "second";
    }
}
static void Main(string[] args)
{
    Console.WriteLine("before");
    ie().First();
    Console.WriteLine("after");
}

Prints:

before
first
Disposing
   at System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
   at System.Environment.get_StackTrace()
   at TestApp.Program.something.Dispose() in C:\Users\Tim\Documents\Visual Studi
o 2010\Projects\TestApp\TestApp\Program.cs:line 198
   at TestApp.Program.<ie>d__0.<>m__Finally2() in C:\Users\Tim\Documents\Visual
Studio 2010\Projects\TestApp\TestApp\Program.cs:line 0
   at TestApp.Program.<ie>d__0.System.IDisposable.Dispose() in C:\Users\Tim\Docu
ments\Visual Studio 2010\Projects\TestApp\TestApp\Program.cs:line 0
   at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
   at TestApp.Program.Main(String[] args) in C:\Users\Tim\Documents\Visual Studi
o 2010\Projects\TestApp\TestApp\Program.cs:line 214
   at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args
)
   at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySec
urity, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, C
ontextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, C
ontextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()
after
Thomas
  • 7,933
  • 4
  • 37
  • 45
Tim S.
  • 55,448
  • 7
  • 96
  • 122
  • It seems that the readers will be created and disposed each time the collection is partially or fully enumerated. – TheEvilPenguin Jun 01 '12 at 11:55
  • Well shouldn't that be the case since you say `First()` it no longer needs to read again rite ? – V4Vendetta Jun 01 '12 at 11:57
  • @usr The answer is right, because the question is not about the caller getting an enumerator, but the enumerator bieing created iwthin a LINQ execution, and then it IS disposed by the LINQ runtime when n longer needed. – TomTom Jun 01 '12 at 12:03
1

This is a general LINQ problem/question, and yes - LINQ will, when executed, dispose all disposable elements it gets.

Bridge
  • 29,818
  • 9
  • 60
  • 82
TomTom
  • 61,059
  • 10
  • 88
  • 148