Reputation: 21098
Consider the following code, the first demonstrates that the "cleanup" executes when we're finished iterating over the IEnumerable of strings. The second pass is what is causing me grief. I need to be able abandon the IEnumerable before reaching the end, and then have the clean up code execute. But if you run this you'll see that in the second pass the clean up never fires.
What is the preferred way of abandoning an IEnumerable like this?
static void Main(string[] args)
{
// first pass
foreach (String color in readColors())
Console.WriteLine(color);
// second pass
IEnumerator<string> reader = readColors().GetEnumerator();
if (reader.MoveNext())
{
Console.WriteLine(reader.Current);
reader.Dispose();
}
}
static IEnumerable<string> readColors()
{
string[] colors = { "red", "green", "blue" };
for (int i = 0; i < colors.Length; i++)
yield return colors[i];
Console.WriteLine("Cleanup goes here");
}
Upvotes: 7
Views: 894
Reputation: 41328
You need to put the main part of your iterator method into a try..finally, with the cleanup code in the finally:
public IEnumerable<string> readColors()
{
try
{
string[] colors = { "red", "green", "blue" };
for (int i = 0; i < colors.Length; i++)
yield return colors[i];
}
finally
{
Console.WriteLine("Cleanup goes here");
}
}
Remember that under the hood an iterator method causes a seperate class to be created, that implements IEnumerable
and IEnumerator
. By putting your cleanup in the finally block, it ends up in the generated class' Dispose
method.
[Edit: (as pointed out in other answers) prefer a using
statement over your approach of calling Dispose
manually. I was assuming you'd done it like this just to highlight the issue under discussion, but it is worth pointing out anyway]
Upvotes: 7
Reputation: 241711
That's one way to abandon it. The reason that you are not seeing
Cleanup goes here
printed on the console is because the loop for (int i = 0; i < colors.Length; i++)
never runs to completion. See below for how to force the cleanup code to execute.
Here's another way. This is the preferred pattern for using IDisposable
objects in C#. This is preferred because this will cause IEnumerator.Dispose
to be called even if an exception occurs.
using (IEnumerator<string> reader = readColors().GetEnumerator()) {
reader.MoveNext();
Console.WriteLine(reader.Current);
}
As for forcing the cleanup code you have to execute you can do the following:
static IEnumerable<string> readColors() {
string[] colors = { "red", "green", "blue" };
try {
for (int i = 0; i < colors.Length; i++) {
yield return colors[i];
}
}
finally {
Console.WriteLine("Cleanup goes here");
}
}
Upvotes: 4
Reputation: 4854
try {
string[] colors = { "red", "green", "blue" };
for (int i = 0; i < colors.Length; i++) {
if(condition == true)
break;
yield return colors[i];
}
}
finally {
Console.WriteLine("Cleanup goes here");
}
Upvotes: 0
Reputation: 48265
I think the preferred way of doing cleanup is by using IDisposable
. In this case, your better of implementing your own IEnumerable<string>
with a specific IEnumerator<string>
and using the normal Dispose
method. You get dispose for free when using foreach
.
class MyEnumerator : IEnumerator<string>
{
// ...
#region IDisposable Members
public void Dispose()
{
// do your cleanup here
throw new NotImplementedException();
}
#endregion
// ...
}
Upvotes: 1