如果我在C#中从try/finally块中返回,finally中的代码是否总是会运行?

53

根据一些初步测试,看起来它可以做到,但我想知道的是它是否保证能够返回结果,或者在某些情况下是否无法返回?这对我的应用程序至关重要,但我还没有找到一个不会返回结果的使用情况。
我想了解这个问题的专业知识。


2
有些异常无法像栈溢出那样被捕获。 - Michael Gattuso
请查看此处:https://dev59.com/PnVD5IYBdhLWcg3wNY1Z#50627 - Vinko Vrsalovic
@DavidZ.:实际上,答案是肯定的。http://ideone.com/RFZ2M - Ry-
2
可能是一个重复的问题,What really happens in a try { return x; } finally { x = null; } statement? 这个链接回答了这个问题,也许包含了其他相关问题的答案。 - Erik Philips
我改正了 :) 很好知道,每天学到更多的东西 :) - David Z.
显示剩余3条评论
4个回答

94

其他答案中存在不准确之处。

当控制流程通过return, goto, break, continue, 或者简单地运行到尽头而“正常” 离开try块时,控制传递到finally块。如果通过被封闭的catch块捕获了异常而离开try块,则控制将传递到finally块。

在所有其他情况下,不能保证一定会调用finally块中的代码。特别是:

  • 如果try块中的代码进入无限循环或线程被冻结且永远不会解冻,则永远不会调用finally块中的代码。

  • 如果进程在调试器中暂停,然后被强制终止,则不会调用finally块。如果进程执行fail-fast操作,则不会调用finally块。

  • 如果电源线从插座中拔出,则不会调用finally块。

  • 如果抛出异常但没有相应的catch块,则是否运行finally块是运行时实现的“实现细节”。运行时可以在未捕获异常出现时选择任何行为。 "不运行finally块"和"运行finally块"都是"任何行为"的示例,因此可以选择任何一个。通常,运行时会要求用户在finally块运行之前附加调试器;如果用户拒绝,则运行finally块。但是:运行时不需要这样做。它可能会直接执行fail-fast操作。

您不能依赖于always被调用的finally块。如果您需要关于代码执行的强有力保证,则不应编写try-finally,而应编写受限执行区域。正确编写CER是C#编程中最困难的任务之一,因此在尝试编写代码之前,请仔细研究文档。

顺便说一句,“finally-blocked gotos”的“有趣事实”是:

try { goto X; } finally { throw y; } 
X : Console.WriteLine("X");

X是一个无法到达的标签,被一个可到达的goto所指定!所以下次你在派对上可以说:“嘿,大家有谁能写一个C#程序,其中有一个无法到达的标签被一个可到达的goto所指定?”然后你就可以看看哪些人读过可达性规范,哪些人没有啦!


42
故事的寓意是:永远不要邀请Eric Lippert参加你的聚会 ;) - Tergiver
34
鲜为人知的事实:编译器开发者举办的派对是最棒的。 - Eric Lippert
3
但这里有一个真正不好的情况。假设你有以下代码:void X() { try { ObtainAdminPowers(); DoSomethingDangerous(); } finally { ReleaseAdminPowers(); }},并且 DoSomethingDangerous 抛出了一个异常。现在假设我们有 try { X(); } catch (Exception) when (M()) { }。方法 M() 在管理员权限被释放之前运行!X 的作者认为除了 DoSomethingDangerous 之外,没有其他代码会使用管理员权限,但是作者是错误的! - Eric Lippert
1
@qqqqqqq:在这种情况下,正确的缓解措施是荒谬的 void X() { try { Obtain(); DoIt(); } catch { Release(); throw; } Release(); },任何明智的人都不会写出这样的代码。这是.NET异常中不幸存在的安全设计缺陷之一。 - Eric Lippert
1
@EricLippert 可以说根本原因是 try { M } finally { N } 的意义与 { try { M } catch (Exception e) { N; throw; } N } 不同。我理解其动机,但不禁想知道是否可以在不引入完全不同的异常相关基元的情况下实现它。 - Joker_vD
显示剩余17条评论

56

通常情况下,finally块中的代码将被执行,无论try或catch块中发生了什么事情。如果你从方法中返回或不返回都没有关系。

但是,有些情况下这并不是真的。例如,如果finally块中的代码抛出异常,它将像任何其他代码块一样停止执行。

Eric Lippert撰写了一个更全面的答案,概述了其他情况:https://dev59.com/RGkv5IYBdhLWcg3w6lHj#10260233

对于goto,答案仍然是肯定的。考虑以下代码:

try
{
    Console.WriteLine("Inside the Try");
    goto MyLabel;
}
finally
{
    Console.WriteLine("Inside the Finally");
}

MyLabel:
    Console.WriteLine("After the Label");

执行后的输出结果如下:

在 Try 语句块内

在 Finally 语句块内

标签后面


7
这里有一些例子:

Environment.FailFast()

环境.失败快速()
        try
        {
            Console.WriteLine("Try");
            Environment.FailFast("Test Fail");

        }
        catch (Exception)
        {
            Console.WriteLine("catch");
        }
        finally
        {
            Console.WriteLine("finally");
        }

输出只有 "Try"。 Stackoverflow
        try
        {
            Console.WriteLine("Try");
            Rec();
        }
        catch (Exception)
        {
            Console.WriteLine("catch");
        }
        finally
        {
            Console.WriteLine("finally");
        }

Rec是什么:

    private static void Rec()
    {
        Rec();
    }

输出仅为“Try”,进程因 StackOverflow 终止。
未处理异常。
        try
        {
            Console.WriteLine("Try");
            throw new Exception();
        }
        finally
        {
            Console.WriteLine("finally");
        }

3
最后一个例子(未处理的异常)实际上执行了 finally 块。 - N. M.

2

如果出现终止应用程序的致命异常,Finally块将不会被调用。这包括堆栈溢出、在调用方法时JIT期间发生的异常以及CLR运行时内部的致命异常。

正如@mintech所指出的,如果应用程序在块内挂起,它将根本无法到达finally块。这包括等待同步对象、死锁、无限循环甚至没有关闭方式的UI。


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