在try catch / finally语句中finally的意义是什么


74

多年来,我已经在多种语言中使用了try-catch / except-finally变体,今天有人问我,最后的意义是什么,我无法回答。

基本上,您为什么要在最后放入一条语句,而不是只在整个try-catch块之后放一条语句?换句话说,以下代码块之间是有区别的:

try{ //a}
catch {//b}
finally {//c}


try{//a}
catch{//b}
//c

编辑:人们
,我知道最终会做些什么,我已经使用了很长时间了,但是我的问题是在上面的示例中,插入//c最后似乎是多余的,不是吗?

Answers:


97

finally块的目的是确保代码可以在三种情况下运行,而这三种情况单独使用“ catch”块是很难做到的:

  1. 如果try块中的代码通过退出return
  2. 如果catch块中的代码要么抛出被捕获的异常,要么偶然或有意地抛出新的异常。
  3. 如果try块中的代码遇到一个例外,该例外try没有catch

可以finally在每次编写return或抛出之前复制代码,然后将catch块包装在自己的try / catch中以允许发生意外异常的可能性,但是放弃所有这些而仅使用finally块要容易得多。

顺便说一句,我希望语言设计师可以包括的一件事是exceptionfinally块的一个参数,以处理需要在异常后进行清理但仍希望其渗透到调用堆栈中的情况(例如,可以包装代码以用于构造函数中的构造函数,以及Dispose构造函数要异常退出的对象)。


19
我们尝试了,您抓住了,终于有了这个问题的答案!
DanielMöller'16

2
我认为这里的关键是即使return语句中有一条语句,它也要执行try/catch。如果没有这种理解,一个finally子句就显得微不足道,因为它知道acatch all本质上将允许try/catch块之后的代码执行,而不管异常情况如何。非常感谢@supercat!
旋转

1
@Swivel:在catch情况下,无论有finally没有异常,返回和异常(通常是重新抛出)都更容易处理。
超级猫

抱歉,第三个错误。您可以尝试在Visual Studio 2019控制台应用程序中尝试简单地并最终阻止。在try块中引发异常,不要捕获该异常,并且永远不会调用finally块...
S Itzik

@SItzik:现在好些了吗?
supercat

1

即使try块中抛出异常,也会执行finally块。因此,例如,如果您以前打开过一个流,则可能要关闭该流,要么引发异常,要么不引发异常。最后,block对于此类问题很有用。


1
但是,您也可以在try-catch之后关闭流,而不必使用finally块。
Gereon99,19年

2
这不能回答OP的问题。
Dan Dascalescu

0

finally是一种语法糖,允许使用DRY原理try-catch。如果库代码没有足够的信息来处理某些状态并希望客户端代码解决该问题,通常会引发异常。如果您没有库与客户端的代码分离,则可以使用if代替来处理所有内容try

让我们看看没有finally以下情况的标准情况:

void myFunction() {
  var r = allocateResources();
  r.doSomething();
  if(somethingBadHappens) {
    freeResources(r);
    throw new Exception(CODE42);
  }
  r.doSomethingMore();
  freeResources(r);
}

在上面的代码段中,您需要重复freeResources():这可以是您需要重复的多个语句。这种气味和finally阻塞是干净代码的解决方案:

void myFunction() {
  var r = allocateResources();
  try {
    r.doSomething();
    if(somethingBadHappens) throw new Exception(CODE42);
    r.doSomethingMore();
  }
  finally {
    freeResources(r);
  }
  happyFunction();
}

让我们实现三个抽象级别:

  • A1是库代码提供allocateResources()功能
  • A2是我们提供的代码myFunction,消耗A1
  • A3myFunction在try-catch块中消耗的一些客户端代码:
function A3code() {
  try {
    myFunction();
    doSomething();
  }
  catch(Exception e) {
    // no hanging resources here
    Console.WriteLine(e);
  }
}

现在让我们看看会发生什么:

  • 如果allocateResources()抛出A1,我们不知道如何在A2中处理它(A2代码可以在不带控制台的环境中运行),因此我们将情况推迟到A3,而无需添加任何其他代码。如果在这里抛出Exception,则不会执行finally块,因为finally绑定到的try没有输入。
  • 如果somethingBadHappens在try块中,堆栈会退回到A3,在这种情况下,finally执行块之前先要处理该情况,所以如果没有异常发生,我们就不需要重复。
  • finally我们添加catch块并尝试解决A1中可能出现在调用r.doSomething方法中的某些异常之前,通常,我们希望尽快处理异常,以使客户端代码(A3)对于客户端编码人员而言更为舒适。
  • happyFunction()仅在没有任何东西抛出myFunction()try块的内部或外部)时执行。

正如@supercat指出的那样,finally如果try块通过return退出,则该块也将执行。我建议您避免这种不良习惯,并且在每个函数中只有一个返回值(也许在函数的开始部分早就存在)。单次返回功能的原因是:

  1. 该代码更具可读性:您在最后看了一下该函数返回的内容。在多次返回中,您必须找到所有返回事件,检查所有if,考虑何时满足if,然后您才知道函数返回什么。
  2. 编译器可以优化代码,请参见copy elision

多次返回的原因是避免了许多嵌套的ifs,但是还有其他技术可以解决它。Exception在此规则中是例外。


-1

Finally 即使遇到异常,也请确保代码已执行。

finally块对于清理try块中分配的所有资源以及运行即使有异常也必须执行的任何代码很有用

http://msdn.microsoft.com/zh-CN/library/zwc8s4fz(v=vs.80).aspx


4
但是我提到的两个代码块有什么区别?在两种情况下//c都执行,对不对?
阿里

请参阅已编辑的问题。
阿里

@Ali-finally块允许您清理分配的所有资源,并且如果您不在本地处理除外,则允许排他性地备份备份堆栈。
Dampsquid 2012年

6
在第二个版本中// c,如果// a中称为“ return”的代码退出该函数,则不会执行。最后,即使该函数提前退出,也要确保// c被调用。... schwehr.org/blog/archives/2012-03.html#e2012-03-01T13_11_15.txt
barryhunter 2012年

5
不回答实际问题。
Adaddinsane
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.