有try{..}catch{...} finally和没有finally的区别

9
这段代码有什么不同之处:

如下两种代码有何不同:

string path = @"c:\users\public\test.txt";
System.IO.StreamReader file = new System.IO.StreamReader(path);
char[] buffer = new char[10];
try
{
    file.ReadBlock(buffer, index, buffer.Length);
}
catch (System.IO.IOException e)
{
    Console.WriteLine("Error reading from {0}. Message = {1}", path, e.Message);
}

finally
{
    if (file != null)
    {
        file.Close();
    }
}

并且这个:

string path = @"c:\users\public\test.txt";
System.IO.StreamReader file = new System.IO.StreamReader(path);
char[] buffer = new char[10];
try
{
    file.ReadBlock(buffer, index, buffer.Length);
}
catch (System.IO.IOException e)
{
    Console.WriteLine("Error reading from {0}. Message = {1}", path, e.Message);
}
if (file != null)
{
    file.Close();
}

在这个结构中,真的需要finally块吗?为什么Microsoft提供了这样的结构?它似乎是多余的。不是吗?


3
无论 try 块中的其余部分出现了什么问题,finally 块都保证会运行。比如 Console.WriteLine 引发了异常 - Close 就不会被执行。 - Andrew Morton
1
不要试图自己管理 StreamReader 实例,为什么不将其包装在 using 块中呢? - 48klocs
应该使用 'using' 块来避免这样的问题。由于过于复杂,这段代码不应该按照你的方式编写。 - Ilya Palkin
2
@48klocs @Ilya Palkin 这段代码来自于C#参考文献(http://msdn.microsoft.com/en-us/library/vstudio/dszsf989.aspx)。我的目的是为了理解这个结构。我同意在这种情况下,`using`块会是更好的解决方案。 - Kuba Matjanowski
5个回答

11
想象一下,如果发生了其他你没有处理过的异常,比如ArgumentOutOfRangeException,或者如果你想重新抛出异常或者从catch块中抛出一个包装过的异常:
第一个代码块将确保无论是否发生异常,文件都会被关闭。
第二个代码块只有在没有发生异常或者发生了IOException时才会关闭文件。它不处理任何其他情况。
需要注意的是,第一个代码块还确保了如果在catch块内部抛出异常,文件也会被关闭。

7
第一个代码块会在出现未捕获的异常时关闭文件。
第二个代码块仅在没有异常或已捕获所有抛出的异常时才关闭文件。
第一个还确保文件在try中有break,goto,return,continue或任何其他跳转构造使执行移动到try块之外时关闭。 第二个则不会,因此可能导致资源未被关闭。

+1 提到了跳转。个人而言,我倾向于避免在 try 块中使用任何这样的结构,但这是一个好观点。 - p.s.w.g
@p.s.w.g try/finally或者using覆盖整个方法范围并不罕见。在方法的结尾放置一个return返回值通常是有意义的。在大多数情况下,这甚至会在try/using块的末尾,读者期望它在那里,但是返回值表达式可能会使用一些可回收资源,因此将其提取到块结束后会增加更多的复杂性,而不是节省。 - Servy
没错,我经常在using语句内使用return,但通常可以在try/catch中避免。这并没有什么问题,只是个人喜好而已。(尽管goto足以引起批评,不论周围的内容如何) - p.s.w.g

2

在您的示例中,如果您的代码引发的异常不是 System.IO.IOException,则不能保证会运行清理代码。使用 finally 块,无论引发何种类型的异常,其中的代码都将运行。


如果我在这两个例子中都使用 catch(Exception e),它们是否可以被视为相等? - Kuba Matjanowski

1

想象一下,在catch {}中发生了异常,但是在finally中的代码仍然会运行,但是if (file != null){}块不会运行。


1
在这种情况下它是多余的。 如果您例如重新抛出异常并仍希望在块后运行一些代码,则它很有用。
try {
  // do something dangerous
} catch(...) {
  // log the error or something
  throw; // let the exception bubble up to the caller
} finally {
  // this always runs
}
// this only runs if there was no exception

另一个例子是,如果捕获可能因不同的原因抛出异常:
try {
  // do something dangerous
} catch(...) {
  // handle the error
  // log the error, which may cause a different exception
} finally {
  // this runs even if the catch crashed
}
// this only runs if there was no exception, or the code in the catch worked

简单来说,由于代码可能因为许多你甚至不知道的原因而崩溃,将清理工作放在finally块中是很有用的,以确保无论发生什么都会运行。


@Matjanos 它甚至在 goto 上执行吗? - Scott Chamberlain
1
@ScottChamberlain:是的,如果你使用goto跳出trycatch块,finally块仍然会执行。我之前对此并不确定,但经过测试发现确实如此。 - Guffa
我在想,反过来呢?用goto进入try块会发生什么呢?当我们跨越try时,调用堆栈是否会被设置?还是说即使在方法中间进入,仍然会执行finally(以及catch)? - Scott Chamberlain
2
@ScottChamberlain:你不能那样做。一个在try结构体内的标签不在它外部的goto作用域之内。 - Guffa
为什么要点踩?如果你不解释哪里有问题,那么回答就无法得到改进。 - Guffa
显示剩余2条评论

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