Yield Return inside a using

Asked

Viewed 281 times

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);
            }
        }
    }

Is it certain that my Dispose() will be called either the yield return will not let the using make a point finally that he implicitly creates?

  • 1

    It will pass yes! , so put a breakpoint on public void Dispose() and normally execute your code

2 answers

6

I don’t know if there’s any other implicit issue, but if it’s just this, there’s no problem with the yield return. He is still one return. Generally speaking, there’s no way the routine can go past finally implicit. If you want to know about the rare really catastrophic cases where the finally will not be executed, see in that reply. But nothing related to yield return.

At least I’ve never seen anything say that’s possible. On the contrary, despite not having read anything in this sense, everything I have seen clearly indicates that under normal conditions there is no way to circumvent the finally.

  • I was worried about a possibility that Dispose() would not be called for a few hundred files on disk due to an operation that requires a Yield Return, thus creating a big problem in my small application. I found this answer on a subject almost the same as mine, and answered directly by one of C#’s developers. Your answer was helpful as well. Thanks.

5


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

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

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

The guy IEnumerator<T> defines the method Dispose. When the foreach ends, this method is called. The Dispose of method Enumerators with yield is used to execute block code finally, that otherwise would not run for interrupted cycles.

Of course, if the enumerator is used manually, the Dispose and not iterate to the end, the code us finally/Dispose is not executed.

It is also important to note that methods with yield can return the type IEnumerable not generic despite this interface nay define the method Dispose.
In that case, the foreach tries to convert the enumerable to IDisposable to call the method Dispose in the same.

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 from the 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();
}
  • I marked your answer as Answer by example and explanation. Thanks.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.