2011-05-25 50 views
5

如果我有一个协程如下,那么finally块中的代码会被调用吗?c#yield并且试着终于

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!"); 
    } 
} 
+8

你试过了,看看会发生什么? – CodeNaked 2011-05-25 18:41:57

+0

我想你的控制台会持有这个问题的答案。 – 2011-05-25 18:42:36

+0

这可以很容易地在你的调试器验证 – 2011-05-25 18:45:01

回答

18

只要迭代器/枚举被正确设置(IDisposable.Dispose()被称为),然后是:

控制总是传递到最后块不管如何try块退出的。

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

但是,要小心,Dispose()确实叫,否则你可能会出现意想不到的结果充其量结束。对于这种现象,有些陷阱的更多信息要留意,看看这个博客帖子:

Yield and usings - your Dispose may not be called!

(感谢斯科特B中提供的链接,在回答放置,因为大家似乎缺少它)

此外:

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

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

+1

这个答案有误导性, finally块与yield return语句(即迭代器块)有唯一的交互作用。请参阅下面的supercat的答案。另请参阅Dan Crevier撰写的这篇出色的博客文章:http://blogs.msdn.com/b/dancre/archive/2008/03/14/yield-and-usings-your-dispose-may-not-be-called。 aspx – 2012-02-14 00:36:04

+0

**是**优秀的博文!非常清楚何时以及如何运作。 – 2012-02-14 01:18:36

1

根据documentation,yes,finally中的代码总是会被调用。

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

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

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

如果你只是懒惰添加Main()等,从这里获取代码,运行它,看看会发生什么:

YieldReturnAndFinally

回应@Henk Holterman的评论

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

* IEnumerable 
* IEnumerable<T> 
* IEnumerator 
* IEnumerator<T> 
13

所有的答案至今省略了至关重要的细节:在一个finally块它包装一个yield return将执行代码,如果当IDisposable.Dispose被在迭代器/枚举其执行的yield return调用。如果外部代码在迭代器上调用GetEnumerator(),然后调用MoveNext(),直到迭代器在finally块内执行yield return,然后外部代码放弃枚举器而不调用Disposefinally块中的代码将不会运行。根据迭代器在做什么,它可能会被垃圾收集器湮没(尽管没有机会清理任何外部资源),或者它可能永久或半永久地根源于内存泄漏(这可能发生如果,例如,它将一个lambda表达式附加到一个长寿命对象的事件处理程序中)。

注意的是,虽然VB和C#是关于确保foreach循环将调用Dispose上枚举非常好,它有可能通过调用GetEnumerator()明确使用迭代器,并有可能一些代码可能会这么做,而无需调用Dispose()。迭代器可以做的事情并不多,但编写迭代器的任何人都需要意识到这种可能性。

+0

s/emit/omit/- 当第一次阅读这个优秀的答案时+1,让我困惑。 – 2013-11-30 12:53:56

+1

@ O.R.Mapper:已更正。 – supercat 2013-12-02 15:38:20