10

I'm unable to understand why Program.Fetch1 and Program.Fetch2 do not result in the exact same execution order. The only difference is that Program.Fetch1 is calling Program.Fetch to do the actual fetch operation.

class Program
{
    static IEnumerable<int> Fetch1()
    {
        using (Context c = new Context())
        {
            return Fetch(c);
        }
    }

    static IEnumerable<int> Fetch(Context c)
    {
        foreach (int i in c.Fetch())
        {
            yield return i;
        }
    }

    static IEnumerable<int> Fetch2()
    {
        using (Context c = new Context())
        {
            foreach (int i in c.Fetch())
            {
                yield return i;
            }
        }
    }

    static void Main(string[] args)
    {
        Console.WriteLine("Fetch1:");
        foreach (int i in Fetch1())
        {
            Console.WriteLine(i);
        }
        Console.WriteLine("Fetch2:");
        foreach (int i in Fetch2())
        {
            Console.WriteLine(i);
        }
    }
}


class Context : IDisposable
{

    public void Dispose()
    {
        Console.WriteLine("Context.Dispose");
    }

    public IEnumerable<int> Fetch()
    {
        return new int[] { 1, 2 };
    }
}

Output:

Fetch1:
Context.Dispose
1
2
Fetch2:
1
2
Context.Dispose

My only guess is that Context.Dispose is called first in Program.Fetch1 because the scope of the using declaration was already left. But this is true for Program.Fetch1 as well. So why do those method behave differently?

Update: My question is a duplicate of yield return statement inside a using() { } block Disposes before executing

Martin
  • 10,738
  • 14
  • 59
  • 67

2 Answers2

6

That's because those options are actually different:

  • Fetch and Fetch2, using yield, create a state machine to be able to return an unmaterialized IEnumerable.
  • In Fetch1 you are simply calling Fetch and returning the generated state machine and disposing of the context without waiting for that IEnumerable to actually be materialized.

Basically, the difference is that in Fetch2 you have a layer of deferred execution (using yield) while in Fetch1 you don't, which means the using scope ends immediately when you return.

i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • @I3arnonv I think you mixed something up: `Program.Fetch2` does not add another layer - `Program.Fetch2` is only calling `Context.Fetch` – Martin Aug 04 '14 at 16:13
  • @Martin true, I mixed `Context.Fetch` and `Program.Fetch`. I updated the answer. – i3arnon Aug 04 '14 at 16:24
2

Your Fetch method returns an IEnumerable. When this is returned, your using block disposes the context. However, an IEnumerable is only the promise of execution. It will execute. When you enumerate it. This is known as deferred execution.

So when you actually enumerate it with your foreach, the enumeration will take place and the code of Fetch will actually execute. In a real program you would get errors because your context was already disposed.

nvoigt
  • 75,013
  • 26
  • 93
  • 142