When called a method with yield, What it does is create an object of the enumerable type.
The foreach creates a IEnumerator<T> through the method GetEnumerator.
Usually, when the method has just enumerated all the elements, the code in the blocks finally is executed. A using is equivalent to having a try... finally with the Dispose in the finally.
But cycles do not always run to the end. An example of this is when they have break or the method Take linq.
The guy IEnumerator<T> defines the method Dispose. When the foreach ends, this method is called.
The Dispose of method Enumerators with yield is used to execute block code finally, that otherwise would not run for interrupted cycles.
Of course, if the enumerator is used manually, the Dispose and not iterate to the end, the code us finally/Dispose is not executed.
It is also important to note that methods with yield can return the type IEnumerable not generic despite this interface nay define the method Dispose.
In that case, the foreach tries to convert the enumerable to IDisposable to call the method Dispose in the same.
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 from the 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();
}
It will pass yes! , so put a breakpoint on
public void Dispose()and normally execute your code– user6026