When calling a method with yield
, what it does is create an object of enumerable type.
The foreach
creates a IEnumerator<T>
through the GetEnumerator
method.
Typically, when the method has just enumerated all elements, the code in the finally
blocks is executed. A using
is equivalent to having a try... finally
with Dispose
in finally
.
But cycles do not always run through to the end. An example of this is when they have break
or the Take
method of LINQ.
Type IEnumerator<T>
sets the Dispose
method. When foreach
runs out, this method is called.
The Dispose
of method enumerators with yield
is used to execute finally
block code, which otherwise would not be executed for interrupted cycles.
Of course, if the enumerator is manually used, Dispose
is missing and it does not iterate through to the end, the code in finally
/ Dispose
does not execute.
It is also important to note that methods with yield
can return non-generic IEnumerable
type even though this interface does not set the Dispose
method.
In this case, foreach
tries to convert the enumerable to IDisposable
to call the Dispose
method on it.
That is, consider the following class:
using System;
using System.Collections;
using System.Collections.Generic;
class Program
{
class MyType : IDisposable
{
public string Id;
public void Dispose()
{
Console.WriteLine("Disposing MyType " + Id);
}
}
public static IEnumerable<int> X1(string id)
{
using (new MyType { Id = id })
{
yield return 1;
yield return 2;
}
Console.WriteLine("End {0}", id);
}
public static IEnumerable X2(string id)
{
using (new MyType { Id = id })
{
yield return 1;
yield return 2;
}
Console.WriteLine("End {0}", id);
}
static void Main(string[] args)
{
foreach (var i in X1("Generic loop")){ }
foreach (var i in X1("Interrupted generic loop"))
{
break;
}
X1("Manual generic dispose").GetEnumerator().Dispose();
X1("No Enumerator generic dispose").GetEnumerator();
foreach (var i in X2("Old-style loop")) { }
foreach (var i in X2("Interrupted old-style loop"))
{
break;
}
X2("No Enumerator old-style dispose").GetEnumerator();
Console.ReadLine();
}
}
The output of this program is:
Disposing MyType Generic loop
End Generic loop
Disposing MyType Interrupted generic loop
Disposing MyType Old-style loop
End Old-style loop
Disposing MyType Interrupted old-style loop
The compiled code of Main
is equivalent to (code obtained with ILSpy ):
// Program
private static void Main(string[] args)
{
foreach (int i in Program.X1("Generic loop"))
{
}
using (IEnumerator<int> enumerator2 = Program.X1("Interrupted generic loop").GetEnumerator())
{
if (enumerator2.MoveNext())
{
int j = enumerator2.Current;
}
}
Program.X1("Manual generic dispose").GetEnumerator().Dispose();
Program.X1("No Enumerator generic dispose").GetEnumerator();
foreach (object k in Program.X2("Old-style loop"))
{
}
IEnumerator enumerator4 = Program.X2("Interrupted old-style loop").GetEnumerator();
try
{
if (enumerator4.MoveNext())
{
object l = enumerator4.Current;
}
}
finally
{
IDisposable disposable = enumerator4 as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
Program.X2("No Enumerator old-style dispose").GetEnumerator();
Console.ReadLine();
}