c#yield和try-finally

时间:2011-05-25 18:39:34

标签: c# yield coroutine try-finally

如果我有一个如下的协程,那么finally块中的代码会被调用吗?

public IEnumerator MyCoroutine(int input)
{
  try
  {
    if(input > 10)
    {
      Console.WriteLine("Can't count that high.");
      yield break;
    }
    Console.WriteLine("Counting:");
    for(int i = 0; i < input; i++)
    {
      Console.WriteLine(i.ToString());
      yield return null;
    }
  }
  finally
  {
    Console.WriteLine("Finally!");
  }
}

4 个答案:

答案 0 :(得分:18)

只要迭代器/枚举器处理得正确(IDisposable.Dispose()被调用),那么是:

  

无论try块如何退出,控制总是传递给finally块。

http://msdn.microsoft.com/en-us/library/zwc8s4fz.aspx

但是,请注意确实调用了Dispose(),否则您最多可能会产生意想不到的结果。有关此现象的更多信息以及需要注意的一些问题,请查看此博客文章:

Yield and usings - your Dispose may not be called!

(感谢Scott B提供的链接,因为每个人似乎都错过了它所以提供答案)

此外:

  

yield return语句不能位于try-catch块内的任何位置。如果try块后面跟一个finally块,它可以位于try块中。

http://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx

答案 1 :(得分:13)

到目前为止,所有答案都省略了一个至关重要的细节:如果在执行的迭代器/枚举器上调用finally时,yield return块中包含IDisposable.Dispose的代码将执行yield return。如果外部代码在迭代器上调用GetEnumerator()然后调用MoveNext(),直到迭代器在yield return块内执行finally,然后外部代码放弃枚举器而不调用Disposefinally块中的代码将无法运行。根据迭代器正在做什么,它可能被垃圾收集器消灭(虽然没有机会清理任何外部资源),或者它可能永久或半永久地根据内存泄漏(如果,例如,它将一个lambda表达式附加到一个长期存在的对象的事件处理程序中。

请注意,虽然vb和c#都非常适合确保foreach循环在枚举器上调用Dispose,但是可以通过显式调用GetEnumerator()来使用迭代器,并且可能有些代码可能会在不调用Dispose()的情况下执行此操作。迭代器无法做到这一点,但任何编写迭代器的人都需要注意这种可能性。

答案 2 :(得分:1)

根据documentation,是的,始终会调用finally中的代码。

由于您正在使用yield,因此在您访问方法返回的IEnumerator之前,不会执行finally块。例如:

void Main()
{
    var x = MyCoroutine(12);

    //Console.WriteLines will happen when the following
    //statement is executed
    var y = x.MoveNext();
}

答案 3 :(得分:1)

如果您只是懒得添加Main()等,请从此处获取代码,运行它并查看会发生什么:

YieldReturnAndFinally

回应@Henk Holterman的评论

以下四种中的任何一种都有效:

* IEnumerable
* IEnumerable<T>
* IEnumerator
* IEnumerator<T>
相关问题