Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using IDisposable object in method that returns IEnumerable<T>

Tags:

c#

linq

resources

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(); 
like image 527
Thomas Avatar asked Jun 01 '12 11:06

Thomas


People also ask

What is IEnumerable T?

IEnumerable<T> is the base interface for collections in the System. Collections. Generic namespace such as List<T>, Dictionary<TKey,TValue>, and Stack<T> and other generic collections such as ObservableCollection<T> and ConcurrentStack<T>.

What is IEnumerable<> in c#?

IEnumerable is an interface defining a single method GetEnumerator() that returns an IEnumerator interface. It is the base interface for all non-generic collections that can be enumerated. This works for read-only access to a collection that implements that IEnumerable can be used with a foreach statement.

Why use IEnumerable in c#?

IEnumerable in C# is an interface that defines one method, GetEnumerator which returns an IEnumerator interface. This allows readonly access to a collection then a collection that implements IEnumerable can be used with a for-each statement.

Is IDisposable called automatically?

Dispose() will not be called automatically. If there is a finalizer it will be called automatically. Implementing IDisposable provides a way for users of your class to release resources early, instead of waiting for the garbage collector.


1 Answers

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.

like image 197
Sergey Berezovskiy Avatar answered Sep 19 '22 18:09

Sergey Berezovskiy