为什么在C#中要使用finally?

212
无论什么在最终块内部是执行的(几乎)总是,那么将代码封装其中或者让其保持未封闭的状态之间有什么区别呢?

3
“你说让它不要关闭,具体是什么意思?” - Ramesh
5
你所说的“(almost)”是什么意思? - Beska
1
这完全不是真的。整个重点在于,如果发生错误,控制流将跳转到catch块。它始终运行所有代码的唯一方法是如果从未发生异常,那么你为什么要使用try / catch呢? - Ed S.
52
如果在机器执行try代码块时拔掉电源线,那么finally代码块将不会被调用。 - Dour High Arch
3
@Ed:使用事务。你的try代码块必须进行某种临时或内存中的更改,以便在finally代码块中实现单个原子性的持久化更改。这通常不容易,并且可能需要特殊的硬件支持。 - Dour High Arch
显示剩余2条评论
13个回答

452

无论是否发生异常,finally块中的代码都将被执行。当需要执行某些必须运行的清理函数(例如关闭连接)时,这非常方便。

现在,我猜测您的问题是为什么应该这样做:

try
{
    doSomething();
}
catch
{
    catchSomething();
}
finally
{
    alwaysDoThis();
}

当你可以做到这一点时:

try
{
    doSomething();
}
catch
{
    catchSomething();
}

alwaysDoThis();
答案是,很多时候 catch 语句中的代码要么重新抛出异常,要么跳出当前函数。如果 catch 语句中的代码执行了返回或抛出新异常操作,则 "alwaysDoThis();" 调用不会被执行。

52
这同样适用于try{}块内的“return”。 - Lucas
6
实际上,即使没有 catch{} 块(只使用 try/finally,让异常溢出),它也适用。 - Lucas
非常好。您还可以使用using块来创建和释放资源,而不是使用finally,并在语句内部使用try catch块(using不能替代try catch)。这样做的唯一优点是,当您离开块时,立即释放资源(您可能会忘记使用finally),但我相信有很多情况下您可能更愿意使用finally。请参见https://dev59.com/43VC5IYBdhLWcg3wliGe#248984。 - Aaron Newton
如果在catch块中出现异常,finally块仍然不会被执行。 - Suyash Gupta

70

虽然已经提出了使用try-finally的大多数优点,但是我想再加上一个:

try
{
    // Code here that might throw an exception...

    if (arbitraryCondition)
    {
        return true;
    }

    // Code here that might throw an exception...
}
finally
{
    // Code here gets executed regardless of whether "return true;" was called within the try block (i.e. regardless of the value of arbitraryCondition).
}

这种行为使其在各种情况下非常有用,特别是当您需要执行清理(释放资源)时,尽管在这种情况下使用using块通常更好。


12

即使你在 catch 块中没有处理异常,finally 块也将被执行。


12

每当您使用不受管控的代码请求,如流读取器、数据库请求等,并且想要捕获异常时,请使用try-catch-finally,并在finally中关闭流、数据读取器等。如果您没有这样做,当出现错误时连接将无法关闭,这在数据库请求中非常糟糕。

 SqlConnection myConn = new SqlConnection("Connectionstring");
        try
        {
            myConn.Open();
            //make na DB Request                
        }
        catch (Exception DBException)
        {
            //do somehting with exception
        }
        finally
        {
           myConn.Close();
           myConn.Dispose();
        }

如果您不想捕获错误,请使用

 using (SqlConnection myConn = new SqlConnection("Connectionstring"))
        {
            myConn.Open();
            //make na DB Request
            myConn.Close();
        }

如果出现错误,连接对象将自动释放,但如果您未捕获错误,则不会发生此操作。


2
Dispose() 方法也会关闭连接,不需要同时调用 Close() 方法。Close() 方法并不会 Dipose(),因此可以重新打开连接。 - Lucas

10

最终语句可以甚至在返回后执行。

private int myfun()
{
    int a = 100; //any number
    int b = 0;
    try
    {
        a = (5 / b);
        return a;
    }
    catch (Exception ex)
    {
        Response.Write(ex.Message);
        return a;
    }

 //   Response.Write("Statement after return before finally");  -->this will give error "Syntax error, 'try' expected"
    finally
    {
      Response.Write("Statement after return in finally"); // --> This will execute , even after having return code above
    } 

    Response.Write("Statement after return after finally");  // -->Unreachable code
}

7

finally,如下所示:

try {
  // do something risky
} catch (Exception ex) {
  // handle an exception
} finally {
  // do any required cleanup
}

无论您的try...catch块是否抛出异常,都可以保证执行代码的机会。

这使得它非常适合释放资源、数据库连接、文件句柄等操作。


3
所有这些例子通常最好使用 using 块来处理,但这并不会削弱你的答案。 - Joel Coehoorn

6

我将解释如何使用finally处理文件读取异常的示例。

  • 不使用finally
try{

  StreamReader strReader = new StreamReader(@"C:\Ariven\Project\Data.txt");
  Console.WriteLine(strReader.ReadeToEnd());
  StreamReader.Close();
}
catch (Exception ex)
{
  Console.WriteLine(ex.Message);
}

在上面的例子中,如果名为Data.txt文件丢失,则会抛出异常并得到处理,但称为StreamReader.Close();语句将永远不会被执行。
因此,与读取器相关联的资源从未被释放。

  • 为解决上述问题,我们使用finally
StreamReader strReader = null;
try{
    strReader = new StreamReader(@"C:\Ariven\Project\Data.txt");
    Console.WriteLine(strReader.ReadeToEnd());
}
catch (Exception ex){
    Console.WriteLine(ex.Message);
}
finally{
    if (strReader != null){
        StreamReader.Close();
    }
}

愉快的编码 :)

注意: "@"用于创建原样字符串,以避免出现"无法识别的转义序列"错误。 @符号意味着直接读取该字符串,不要解释控制字符。


那最后一行应该是 strReader.Close() 吗? - userSteve

2

假设你需要将光标设置回默认指针,而不是等待(沙漏)指针。如果在设置光标之前抛出异常,并且不直接崩溃应用程序,则可能会留下令人困惑的光标。


2

finally块对于清理在try块中分配的任何资源以及运行必须执行的任何代码非常有价值,即使出现异常,控制也总是传递到finally块。


2
有时候,您可能不想处理异常(没有catch块),但您希望执行一些清理代码。
例如:
try
{
    // exception (or not)
}
finally
{
    // clean up always
}

如果异常未被捕获,最终块的执行取决于操作系统是否选择触发异常解除操作。 - Vikas Verma

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