简体   繁体   中英

Why does the using statement work on IEnumerator and what does it do?

Back when I was learning about foreach , I read somewhere that this:

foreach (var element in enumerable)
{
    // do something with element
}

is basically equivalent to this:

using (var enumerator = enumerable.GetEnumerator())
{
    while (enumerator.MoveNext())
    {
        var element = enumerator.Current;

        // do something with element
    }
}

Why does this code even compile if neither IEnumerator nor IEnumerator<T> implement IDisposable ? C# language specification only seems to mention the using statement in the context of IDisposable .

What does such an using statement do?

Please, check the followinglink about foreach statement. It uses try/finally block with Dispose call if it's possible. That's the code which is behind using statement.

IEnumerator may not implement IDisposable but GetEnumerator() returns a IEnumerator<T> which does. From the docs on IEnumerator<T> :

In addition, IEnumerator implements IDisposable, which requires you to implement the Dispose method. This enables you to close database connections or release file handles or similar operations when using other resources. If there are no additional resources to dispose of, provide an empty Dispose implementation.

This is of course assuming that your enumeration is an IEnumerable<T> and not just a IEnumerable . If your original enumeration was just an IEnumerable then it wouldn't compile.

It appears no one answered the main question yet:

What does such a using statement do?

I'll go with an example from one of my favorite books - C# in Depth by Jon Skeet . You may have a result of IEnumerator<T> produced by a function like this:

static IEnumerable<string> Iterator()
{
    try
    {
        Console.WriteLine("Before first yield");
        yield return "first";
        Console.WriteLine("Between yields");
        yield return "second";
        Console.WriteLine("After second yield");
    }
    finally
    {
        Console.WriteLine("In finally block");
    }
}

And then you would use it like:

foreach (string value in Iterator())
{
    Console.WriteLine("Received value: {0}", value);
    if (value != null)
    {
        break;
    }
}

The iterator returns only the first "first" value from the sequence as we break on the very first iteration. Now how your iterator might 'know' that you aren't going to proceed till the end of the loop so it would fire it's finally block? Here comes the hidden using statement. That's how the code from the example above would look like if you couldn't use a foreach loop:

IEnumerable<string> enumerable = Iterator();
using (IEnumerator<string> enumerator = enumerable.GetEnumerator())
{
    while (enumerator.MoveNext())
    {
        string value = enumerator.Current;
        Console.WriteLine("Received value: {0}", value);
        if (value != null)
        {
            break;
        }
    }
}

When we leave the scope of the hidden using statement, the Dispose() method fires:

finally
{
    Console.WriteLine("In finally block");
}

*All of the code lines and some of the text above are provided by Jon Skeet. I could have rewritten it but decided that I should rather leave it intact. If the author doesn't want me to share it - I'll delete the answer or rewrite it ASAP.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM