Yield return within a using

5

Consider the following situation:

public class MeuRecursoBemCaro : IDisposable
    {
        public T FazerOperacaoBemCara(T item)
        {
            //Do operação
        }
        public void Dispose()
        {
            //Dispose logic
        }
    }

public IEnumerable<T> OperarSobreMeusItens(IEnumerable<T> MeusItens)
    {
        using (MeuRecursoBemCaro recurso = new MeuRecursoBemCaro())
        {
            foreach(T item in MeusItens)
            {
                yield return recurso.FazerOperacaoBemCara(item);
            }
        }
    }

Are you sure my Dispose() will be called or yield return will not let using fall into the finally clause that it implicitly creates?

    
asked by anonymous 22.05.2014 / 20:43

2 answers

5

When calling a method with yield , what it does is create an object of enumerable type. The foreach creates a IEnumerator<T> through the GetEnumerator method.

Typically, when the method has just enumerated all elements, the code in the finally blocks is executed. A using is equivalent to having a try... finally with Dispose in finally .

But cycles do not always run through to the end. An example of this is when they have break or the Take method of LINQ.

Type IEnumerator<T> sets the Dispose method. When foreach runs out, this method is called. The Dispose of method enumerators with yield is used to execute finally block code, which otherwise would not be executed for interrupted cycles.

Of course, if the enumerator is manually used, Dispose is missing and it does not iterate through to the end, the code in finally / Dispose does not execute.

It is also important to note that methods with yield can return non-generic IEnumerable type even though this interface does not set the Dispose method. In this case, foreach tries to convert the enumerable to IDisposable to call the Dispose method on it.

That is, consider the following class:

    using System;
    using System.Collections;
    using System.Collections.Generic;

class Program
{
    class MyType : IDisposable
    {
        public string Id;

        public void Dispose()
        {
            Console.WriteLine("Disposing MyType " + Id);
        }
    }

    public static IEnumerable<int> X1(string id)
    {
        using (new MyType { Id = id })
        {
            yield return 1;
            yield return 2;
        }
        Console.WriteLine("End {0}", id);
    }

    public static IEnumerable X2(string id)
    {
        using (new MyType { Id = id })
        {
            yield return 1;
            yield return 2;
        }
        Console.WriteLine("End {0}", id);
    }

    static void Main(string[] args)
    {
        foreach (var i in X1("Generic loop")){ }
        foreach (var i in X1("Interrupted generic loop"))
        {
            break;
        }
        X1("Manual generic dispose").GetEnumerator().Dispose();
        X1("No Enumerator generic dispose").GetEnumerator();
        foreach (var i in X2("Old-style loop")) { }
        foreach (var i in X2("Interrupted old-style loop"))
        {
            break;
        }
        X2("No Enumerator old-style dispose").GetEnumerator();
        Console.ReadLine();
    }
}

The output of this program is:

Disposing MyType Generic loop
End Generic loop
Disposing MyType Interrupted generic loop
Disposing MyType Old-style loop
End Old-style loop
Disposing MyType Interrupted old-style loop

The compiled code of Main is equivalent to (code obtained with ILSpy ):

// Program
private static void Main(string[] args)
{
    foreach (int i in Program.X1("Generic loop"))
    {
    }
    using (IEnumerator<int> enumerator2 = Program.X1("Interrupted generic loop").GetEnumerator())
    {
        if (enumerator2.MoveNext())
        {
            int j = enumerator2.Current;
        }
    }
    Program.X1("Manual generic dispose").GetEnumerator().Dispose();
    Program.X1("No Enumerator generic dispose").GetEnumerator();
    foreach (object k in Program.X2("Old-style loop"))
    {
    }
    IEnumerator enumerator4 = Program.X2("Interrupted old-style loop").GetEnumerator();
    try
    {
        if (enumerator4.MoveNext())
        {
            object l = enumerator4.Current;
        }
    }
    finally
    {
        IDisposable disposable = enumerator4 as IDisposable;
        if (disposable != null)
        {
            disposable.Dispose();
        }
    }
    Program.X2("No Enumerator old-style dispose").GetEnumerator();
    Console.ReadLine();
}
    
22.05.2014 / 22:43
6

I do not know if there is any other implicit question, but if that's all there is no problem with yield return . It does not stop being return . Generally speaking, there is no way for the routine to fail to pass through finally implied. If you want to know about the rare, really catastrophic cases where finally will not run, see in this answer . But nothing related to yield return .

At least I've never seen anything saying that this was possible. On the contrary, although I have not read anything in this sense, everything I have seen clearly indicates that under normal conditions there is no way to circumvent finally .

    
22.05.2014 / 21:48