如果我有一个如下的协程,那么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!");
}
}
答案 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块中。
答案 1 :(得分:13)
到目前为止,所有答案都省略了一个至关重要的细节:如果在执行的迭代器/枚举器上调用finally
时,yield return
块中包含IDisposable.Dispose
的代码将执行yield return
。如果外部代码在迭代器上调用GetEnumerator()
然后调用MoveNext()
,直到迭代器在yield return
块内执行finally
,然后外部代码放弃枚举器而不调用Dispose
,finally
块中的代码将无法运行。根据迭代器正在做什么,它可能被垃圾收集器消灭(虽然没有机会清理任何外部资源),或者它可能永久或半永久地根据内存泄漏(如果,例如,它将一个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()
等,请从此处获取代码,运行它并查看会发生什么:
回应@Henk Holterman的评论
以下四种中的任何一种都有效:
* IEnumerable
* IEnumerable<T>
* IEnumerator
* IEnumerator<T>