Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I abandon an IEnumerator without iterating to the end?

Tags:

c#

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");
}
like image 753
Ralph Shillington Avatar asked Dec 21 '09 15:12

Ralph Shillington


2 Answers

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]

like image 182
Rob Levine Avatar answered Oct 24 '22 18:10

Rob Levine


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");
    }
}
like image 38
jason Avatar answered Oct 24 '22 18:10

jason