在使用嵌套方法、yield return 和 using 结合时出现了奇怪的执行顺序

10

我不明白为什么Program.Fetch1Program.Fetch2的执行顺序不完全相同。唯一的区别是Program.Fetch1通过调用Program.Fetch来执行实际的获取操作。

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

输出:

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

我唯一的猜测是,在Program.Fetch1中,Context.Dispose首先被调用,因为已经离开了using声明的范围。但是对于Program.Fetch1也是如此。那么为什么这些方法的行为不同呢?

更新:我的问题是yield return statement inside a using() { } block Disposes before executing的重复。


你检查过为这段代码生成的IL吗? - Alex Voskresenskiy
@Alex Voskresenskiy,没有,我没有。但是我以前从来没有进行过IL检查 - 所以我首先需要找出如何进行检查。 - Martin
@Martin IL对于yield/return来说很糟糕,我不会费心。你可以尝试使用ILSpy反编译以查看C#中的状态机。 - Rawling
2个回答

6
这是因为这些选项实际上是不同的:
- 使用yieldFetchFetch2创建了一个状态机,以便能够返回未实例化的IEnumerable。 - 在Fetch1中,您只是调用了Fetch并返回生成的状态机,并在没有等待该IEnumerable实际被实例化的情况下丢弃了上下文。 基本上,区别在于Fetch2中具有延迟执行层(使用yield),而在Fetch1中没有,这意味着在返回时使用范围立即结束。

@I3arnonv 我认为你搞混了一些东西:Program.Fetch2并没有添加另一个层级 - Program.Fetch2只是调用了Context.Fetch - Martin
@Martin 是的,我混淆了 Context.FetchProgram.Fetch。我已经更新了答案。 - i3arnon

2
你的Fetch方法返回一个IEnumerable。当它被返回时,你的using块会释放上下文。然而,IEnumerable只是执行的承诺。当你枚举它时,它将执行。这就是所谓的延迟执行
因此,当你使用foreach实际枚举它时,枚举将发生并且Fetch的代码将实际执行。在一个真正的程序中,你会因为上下文已经被释放而得到错误。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接