views:

397

answers:

3

Say I have a class that implements IEnumerable<T>. It currently uses the yield keyword in the GetEnumerator() method. But now I need to do a bit more, for example I would like to clean up after myself. To do this, unless I have overlooked anything, I need to implement the IEnumerator<T> interface. But where would you say I should do that?

Should the class itself implement it and GetEnumerator() return this? Or would it be better to hide it in a private class? Or should it maybe just be an entirely different class? What are some common practices to this?

+2  A: 

I would go with a class for the IEnumerator<T>. This will need to store state (current position) that is not part of the collection, but rather part of the process of enumerating.

csharptest.net
You may want to read up on `yield return` - it looks like you write a method, but you're actually generating a class with state.
Daniel Earwicker
@Earwicker, yeah that I know. So that was kind of the reason for my private class suggestion. Since I would guess that would kind of end up being similar. Although I don't really have a clue :p
Svish
+6  A: 

If all you need to do is clean up some resources when the enumerator is disposed of, like at the end of a foreach loop, then you can do that with what you have, just add a try/finally block to your iterator method.

Like this:

public IEnumerator<T> GetEnumerator()
{
    try
    {
        // your iterator code here
    }
    finally
    {
        // cleanup code here
    }
}

That's all there is to it.

Lasse V. Karlsen
I thought you couldn't yield inside a try block?
csharptest.net
@csharptest.net - you can if it only has a finally block after it. It's catch that is not allowed.
Daniel Earwicker
Brilliant. Had no idea this was even possible. Tested it out, and the `yield return` can exist in a try block. *However* it cannot exist in the `finally` block. And, like @Earwicker noted, it cannot exist in a `try` block that has a `catch` block.
Svish
@Earwicker Doh! -1 for me, I didn't know that (clearly) thanks for the info :)
csharptest.net
I recently wrote a series of articles explaining why we have these restrictions on where the yield return can go. See http://blogs.msdn.com/ericlippert/archive/tags/Iterators/default.aspx if you're interested.
Eric Lippert
A: 

You may not need to write class for this at all. An iterator method can return IEnumerable<T>, so if your class only needs to implement IEnumerable<T> and nothing else, just write an iterator method for the whole thing.

If not, and you have to stick with an outer class that implements IEnumerable<T> by having an iterator method to do GetEnumerator, then you don't have to do anything very difficult to clean up after iteration.

IEnumerator<T> derives from IDisposable. An iterator method implements Dispose by executing finally blocks or any code after the last yield return.

Daniel Earwicker
code after the last yield return? how would that work? especially if the `yield return`s were in an infinite loop...
Svish
I've worded that really badly! The code after the last `yield return` is executed if it is reachable, but on the last call to `IEnumerator.MoveNext`, not `Dispose`. If the yield returns are in an infinite loop, code after that loop will be unreachable and the compiler will tell you. If not, then that code is executed after the caller enumerates all the items in the collection. Hence use a finally block to execute regardless of whether the enumeration is completed or abandoned, and put code at the end of the method if you want it to execute only if the enumeration is not abandoned.
Daniel Earwicker