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