Ralph Shillington
Ralph Shillington

Reputation: 21098

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

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

Answers (4)

Rob Levine
Rob Levine

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

jason
jason

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

Theofanis Pantelides
Theofanis Pantelides

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

bruno conde
bruno conde

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

Related Questions