为什么在Try ... Catch中使用Finally?

71

我发现在 Try .. Catch 中的 Finally 总是会在 try catch 块的执行任何部分之后执行。

跳过 Finally 部分并在 try catch 块外面运行它是否有任何不同呢?

例子1,Try ... Catch ... Finally ... End Try

    Try
        'Do something
    Catch ex As Exception
        'Handle exception
    Finally
        'Do cleanup
    End Try

示例2:尝试...捕获...结束尝试...在最后执行finally操作

    Try
        'Do something
    Catch ex As Exception
        'Handle exception
    End Try
    'Do cleanup
14个回答

86

是的,它是不同的。最终将始终运行(除非程序崩溃)。如果在try catch块内退出函数,或者在try或catch中抛出另一个错误,finally仍将执行。如果不使用finally语句,您将无法获得该功能。


3
不幸的是,如果程序以各种方式崩溃,finally代码块会默认运行。但如果你想要覆盖这个默认行为,可以处理 AppDomain.UnhandledException 事件并调用 Environment.FailFast - Daniel Earwicker
虽然如果你是Java程序员,那就不太幸运:未处理的异常会执行“finally”块,你真的无法对其进行任何处理。 - Daniel Earwicker
13
你可以采取行动,设计/编写你的代码,使其不使用异常来控制流程,并避免使用 finally 块进行类似操作。finally 应该用于清理工作,这应该始终发生。 - Henry B
2
不是所有情况下都会执行 finally 语句块,比如从操作系统中终止程序或者计算机崩溃的情况。 - kemiller2002
2
它不会执行的两种情况是执行try/catch的线程停止执行或JVM被终止。因此,线程可能会停止而程序的其余部分继续运行,你将永远不会知道它是否运行(除非有良好的日志/编码)。 - amischiefr

16

带有四个单选按钮的代码:

  • 在TRY中返回
  • 在CATCH中返回
  • 在CATCH中抛出异常
  • 完成CATCH

private void checkFinally()
{
    try
    {
        doFinally();
    }
    catch
    {
        Console.WriteLine(" Breaking news: a crash occured. ");
    }
}

private void doFinally()
{
    Console.WriteLine(" ");
    Console.Write("Here goes: " 
        + (radioReturnInTry.Checked ? "2. Return in try: " 
                : (radioReturnInCatch.Checked? "3. Retrun in catch: "
                    : (radioThrowInCatch.Checked? "4. Throw in catch: "
                        : "1. Continue in catch: "))) );
    try
    {
        if (radioReturnInTry.Checked)
        {
            Console.Write(" Returning in try. ");
            return;
        }
        Console.Write(" Throwing up in try.  ");
        throw new Exception("check your checkbox.");
    }
    catch (Exception ex)
    {
        Console.Write(" ...caughtcha! ");
        if (radioReturnInCatch.Checked)
        {
            Console.Write("Returning in catch. ");
            return;
        }
        if (radioThrowInCatch.Checked)
        {
            Console.Write(" Throwing up in catch. ");
            throw new Exception("after caught");
        }
    }
    finally { Console.Write(" Finally!!"); }
    Console.WriteLine(" Done!!!"); // before adding checkboxThrowInCatch, 
    // this would never happen (and was marked grey by ReSharper)

}

输出:

  • 方法1:在catch中继续执行:尝试抛出异常。...捕捉到它!最后!!完成!!!
  • 方法2:在try中返回:尝试返回。最后!!
  • 方法3:在catch中返回:尝试抛出异常。...捕捉到它!在catch中返回。最后!!
  • 方法4:在catch中抛出异常:尝试抛出异常。...捕捉到它!在catch中抛出异常。最后!!突发新闻:发生了崩溃。

总结一下: Finally 处理两件事情:

  1. 如果代码在 try 或 catch 中返回,则 Finally 处理该问题。
  2. 如果您在 try 中遇到异常,并在 catch 中抛出异常,或者
  3. 如果您在 try 中遇到异常但没有捕获该异常,或者

总结 "FINALLY": Finally 如果您在尝试中没有返回并在试验期间捕获任何异常, 而且

  1. 在 catch 中也没有返回,
  2. 也没有抛出或有抛出异常的代码。

最后但同样重要 (Finally):如果您的代码存在未捕获异常,则在不触发 Finally 的情况下飞出。

希望这清楚了。(现在对我来说是这样...)

Moshe


2
太懒得读和比较条件了。如果有人能把它转换成表格形式就好了! - paradocslover

10
区别在于当try块中的代码抛出一个未被catch块捕获的异常时。通常,catch块会捕获特定类型的异常,并让其他异常通过。在这种情况下,finally块仍将运行。如果try块中的代码返回,finally块也将运行。

1
无论异常是否被捕获,finally块始终会执行(除非出现像“蛮力”应用程序退出这样的异常情况)。 - Rune FS
1
@Rune FS:是的,但如果异常被捕获,awe的两个示例之间没有区别(除非引发进一步的异常、返回语句等)。如果未被捕获,则存在差异。这就是awe所问的。 - RichieHindle

7

最后包含需要在所有情况下进行评估的代码[无论是否发生异常]。

没有办法退出try块而不执行其finally块。如果finally块存在,则始终执行。(这个说法基本上是正确的。有一种方法可以退出try块而不执行finally块。如果代码从try块中执行System.exit(0);,则应用程序将在不执行finally的情况下终止。另一方面,如果您在try块期间拔掉计算机,则finally也不会执行。)

主要用于处理对象。当您想关闭用户定义资源(如文件,打开的资源(db stmts))时,它将非常有用。

编辑

此外,在stackoverflow异常之后,finally不会被执行。


6
如果发生 StackOverflowException,那么 finally 块会被执行吗? - Colin Mackay
这假设进程不会突然终止。 - rahul

3
无论函数因何种异常而退出,最终块都将执行。(对于此规则,有一些例外情况,请参见此Stackoverflow问题以获取更多信息。)
例如:
Try
    'Do something
Catch ex As Exception
    if 'Some Condition
       throw ex
    else
       'Handle exception
Finally
    'Do cleanup
End Try

在这种情况下,即使您在函数中抛出异常,Finally块仍将被执行。
这是一个好习惯,因为它确保您的清理代码始终执行。当然,使用资源获取即初始化习惯是一种更清洁的方法来确保资源被清理,但我对VB.net不够熟悉,不知道是否可能实现。

“always”?finally块不会运行的原因有很多。 - Colin Mackay
@Nick:请注意,finally块不能保证始终运行;有一些例外!其中之一是StackOverflowException。 - Fredrik Mörk
在 try 块中拔掉计算机电源也会导致 finally 不执行 :) - Giovanni Galbo
如果执行到外部作用域,则保证所有内部的finally块都已经“运行”[尽管有些可能由于异常而提前退出]。请注意,在vb.net或CIL中,外部try块可能包含将在嵌套finally块的上下文中运行的代码,但是C#不公开此类功能。至于RAII,vb.net和C#可以通过using结构将该概念应用于局部变量,但不能将其有用地应用于字段。 - supercat
他们真的应该添加一个PlugPulled异常。还有一个NotPluggedIn异常,以处理所有可能性。当然,StackOverflow异常应该重定向到这个网站。 - user4624979

3
这是在处理数据库连接或任何需要清除对象的情况下很好的想法。以防在运行查询时发生了错误,您仍然可以安全地关闭连接。它还有助于清理try/catch/finally块之外的代码无法访问的内容。

2

最后应该用于保持系统一致所需完成的所有事情。这通常意味着释放资源

无论抛出什么异常,都会执行finally块。应该在以下情况下使用它来释放资源:

  • 完成连接
  • 关闭文件处理程序
  • 释放内存
  • 关闭数据库连接

让我给出一个完整的示例。假设您正在通过网络发送消息。伪代码如下:

// With finally                  |  //Without finally
try{                             |  try{  
  send_message()                 |    send_message() 
} catch(NetworkError){           |  } catch(NetworkError){ 
  deal_with_exception()          |    deal_with_exception()
} finally {                      |  }
  finalizes_connection()         |  finalizes_connection() 
}                                |

两段代码的唯一区别在于try块中引发异常的类型不同,例如MethodNotFound。在第一种情况下,将调用方法finalizes_connection(),而在第二种情况下则不会。
连接通常是通过多个程序完成的。那么如果出现MethodNotFound异常,其他程序会发生什么情况呢?在第一种情况下,您的程序将完成连接,并且其他程序也会如愿以偿。在第二种情况下,其他程序可能会永远等待您的响应。如果其他程序每次只能接收一个连接怎么办?这样会影响其他程序的正常运行。
例如,在Windows中,如果您打开了一个文件,其他程序将无法以读取方式打开该文件。对于内存来说,它永远不会被释放,这会导致内存泄漏。请注意保留HTML标记。

1
如果你只捕获特定的异常类型,那么这是显而易见的,但在我的例子中,我捕获了类型为 Exception 的异常,它实际上是任何类型的异常。然后对于 finally 的需求并不明显,但其他人已经很好地回答了这个问题。 - awe

1

就我所知,在我的.NET代码中从未使用过try/catch/finally块。

通常情况下,中间层捕获异常是很少需要的。异常通常会传播到演示层的顶级处理程序(可能在层边界处被捕获和重新抛出,以便记录日志)。

因此,在中间层中,您更经常看到try/finally(或“using”语句),以便清理资源。而在演示层的顶级处理程序中使用try/catch。

在极少数需要同时捕获异常和进行一些清理的情况下,我更喜欢重构如下:

try
{
    ... do something
}
catch
{
   ... handle exception
}
finally
{
   ... cleanup
}

变成:

try
{
    DoSomethingAndCleanup();
}
catch
{
   ... handle exception
}

...
private void DoSomethingAndCleanup()
{
    try
    {
        ... do something
    }
    finally
    {
        ... cleanup
    }
}

在我看来,这样更加简洁。


如果原始异常在内部代码中有意义但在表示层中没有意义,那么在中间层捕获异常是有意义的。然后,抛出一个异常,提供有关实际出错情况的上下文更详细的信息是有意义的。 - awe
很遗憾,没有一种惯用的方式可以在没有解决异常的前提下执行某些操作。方法通常会将对象暂时置于损坏状态,但在返回之前会修复它。这样的代码应该由 try 块保护,如果发生意外异常,它将明确地使对象无效;捕获一个没有预期解决方案的异常似乎很棘手,但我不确定在 C# 中是否存在其他替代方案。 - supercat

1

在编程中,你可以使用finally来进行清理代码,例如需要关闭的数据库连接或打开的文件。无论是否发生异常,几乎任何需要执行的清理代码都可以使用finally块。

此外,你的异常处理可能需要重新抛出异常或其他异常,在这种情况下,块后面的代码将不会被执行。


1

在 finally 块中进行清理操作是为了确保它被执行。如果 catch 块没有处理异常(例如仅记录日志),甚至导致另一个异常,finally 块中的代码仍将运行。


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