Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I cancel an IEnumerable?

In a method returning IEnumerable<>, I'm opening and looping over a resource (e.g. a database row reader). Once the loop finished, the resource is closed again.

However, it may happen that the caller decides not to finish the enumeration. This leaves the resource open.

Example:

IEnumerable<Foo> Bar ()
{
    using (var r = OpenResource()) {
        while (r.Read ()) {
            yield return r;
        }
    }
}

// OK - this closes the resource again
foreach (var foo in Bar()) {
    Console.WriteLine (foo);
}

// Not OK - resource stays open!
Console.WriteLine (Bar().First());

How would I solve this? Can I easily cancel an enumeration, i.e. tell it to skip over the rest of the loop, or dispose it (putting the cleanup code in Dispose)?

I considered returning a Func<Result, bool> so the user can have it return false if he's done with iterating. Similarly, some kind of cancel token could be used, too. But both approaches seem cumbersome to me.

like image 448
mafu Avatar asked Mar 04 '16 13:03

mafu


People also ask

How do I get a count of IEnumerable?

IEnumerable has not Count function or property. To get this, you can store count variable (with foreach, for example) or solve using Linq to get count.

Is IEnumerable an iterator?

IEnumerable is the return type from an iterator. An iterator is a method that uses the yield return keywords.


1 Answers

Normally it is the IEnumerator<> that implements the IDisposable, and if you look at the definition of IEnumerator<> you'll see that:

public interface IEnumerator<out T> : IDisposable, IEnumerator

The foreach statement correctly Dispose() the IEnumerator<> that receives from the IEnumerable<>, so that:

IEnumerable<SomeClass> res = SomeQuery();

foreach (SomeClass sc in res)
{
    if (something)
        break;
}

upon exiting the foreach in any way (the break, an exception, naturally finishing res), the Dispose() of the IEnumerator<> should be called. See https://msdn.microsoft.com/en-us/library/aa664754(v=vs.71).aspx for an example of how the foreach is implemented (a try... finally... with a Dispose() inside the finally)

Note that the C# will produce "correct" code for using used inside a yield function. See for example here: http://goo.gl/Igzmiz

public IEnumerable<Foo> Bar()
{
    using (var r = OpenResource()) 
    {
        while (r.Read ()) 
        {
            yield return new Foo();
        }
    }
}

is converted to something that

void IDisposable.Dispose()
{
    int num = this.<>1__state;
    if (num == -3 || num == 1)
    {
        try
        {
        }
        finally
        {
            this.<>m__Finally1();
        }
    }
}

The Dispose() method of IEnumerator<> will call a m__Finally1 method that will (IDisposable)this.<r>5__1.Dispose(); (where 5__1 is the r returned from OpenResource()). The m__Finally is even called if the code simply "exits" the while (r.Read ()):

if (!this.<r>5__1.Read())
{
    this.<>m__Finally1();

and/or if there is an exception.

 catch
 {
     this.System.IDisposable.Dispose();
like image 114
xanatos Avatar answered Sep 27 '22 22:09

xanatos