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();
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>.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With