204

我见过有人说不带参数使用 catch 是不好的形式,特别是如果那个 catch 没有做任何事情:

StreamReader reader=new  StreamReader("myfile.txt");
try
{
  int i = 5 / 0;
}
catch   // No args, so it will catch any exception
{}
reader.Close();

但是,这被认为是好的形式:

StreamReader reader=new  StreamReader("myfile.txt");
try
{
  int i = 5 / 0;
}
finally   // Will execute despite any exception
{
  reader.Close();
}

据我所知,将清理代码放在 finally 块中和将清理代码放在 try..catch 块之后的唯一区别是,如果你的 try 块中有 return 语句(在这种情况下,finally 中的清理代码将运行,但 try..catch 之后的代码不会)。

不然最后有什么特别的?

4

20 回答 20

365

最大的区别是它try...catch会吞下异常,隐藏发生错误的事实。try..finally将运行您的清理代码,然后异常将继续运行,由知道如何处理它的东西处理。

于 2008-09-24T18:12:51.533 回答
63

“最后”是“您必须始终做的事情以确保程序状态正常”的声明。因此,如果有任何异常可能会抛出程序状态,那么拥有一个总是一种很好的形式。编译器还竭尽全力确保您的 finally 代码运行。

“Catch”是“我可以从这个异常中恢复”的声明。你应该只从你真正可以纠正的异常中恢复——不带参数的 catch 说“嘿,我可以从任何事情中恢复!”,这几乎总是不正确的。

如果有可能从每个异常中恢复,那么这真的是一个语义上的狡辩,关于你声明你的意图是什么。然而,事实并非如此,而且几乎可以肯定,您的框架之上的框架将能够更好地处理某些异常。因此,使用 finally,让您的清理代码免费运行,但仍然让更多知识渊博的处理程序处理该问题。

于 2008-09-24T18:12:34.950 回答
33

因为当单行抛出异常时,您不会知道。

使用第一块代码,异常将被简单地吸收,即使程序状态可能错误,程序也会继续执行。

对于第二个块,将抛出异常并冒泡,但reader.Close()保证运行。

如果没有预料到异常,那么不要这样放置 try..catch 块,当程序进入错误状态并且您不知道原因时,以后将很难调试。

于 2008-09-24T18:14:23.900 回答
21

finally 无论如何都会执行。因此,如果您的 try 块成功,它将执行,如果您的 try 块失败,它将执行 catch 块,然后执行 finally 块。

此外,最好尝试使用以下构造:

using (StreamReader reader=new  StreamReader("myfile.txt"))
{
}

由于 using 语句自动包装在 try / finally 中,并且流将自动关闭。(如果您想实际捕获异常,则需要在 using 语句周围放置一个 try / catch)。

于 2008-09-24T18:14:50.907 回答
7

虽然以下 2 个代码块是等价的,但它们并不相等。

try
{
  int i = 1/0; 
}
catch
{
  reader.Close();
  throw;
}

try
{
  int i = 1/0;
}
finally
{
  reader.Close();
}
  1. 'finally' 是意图揭示代码。您向编译器和其他程序员声明此代码无论如何都需要运行。
  2. 如果你有多个 catch 块并且你有清理代码,你需要 finally。如果没有 finally,您将在每个 catch 块中复制您的清理代码。(干燥原理)

finally 块是特殊的。CLR 将 finally 块与 catch 块分开识别和处理代码,并且 CLR 竭尽全力保证 finally 块将始终执行。它不仅仅是来自编译器的语法糖。

于 2008-09-24T20:55:21.970 回答
5

我同意这里似乎达成的共识——空的“catch”是不好的,因为它掩盖了 try 块中可能发生的任何异常。

另外,从可读性的角度来看,当我看到一个“try”块时,我假设会有一个相应的“catch”语句。如果您只使用“尝试”以确保在“最终”块中取消分配资源,则可以考虑使用“使用”语句

using (StreamReader reader = new StreamReader('myfile.txt'))
{
    // do stuff here
} // reader.dispose() is called automatically

您可以对任何实现 IDisposable 的对象使用“使用”语句。对象的 dispose() 方法在块结束时自动调用。

于 2008-09-24T18:28:06.740 回答
5

如果Try..Catch..Finally您的方法知道如何在本地处理异常,请使用 。异常发生在 Try 中,在 Catch 中处理,然后在 finally 中完成清理。

如果您的方法不知道如何处理异常,但一旦发生就需要清理,请使用Try..Finally

这样,如果调用方法中有任何合适的 Catch 语句,异常就会传播到调用方法并进行处理。如果当前方法或任何调用方法中没有异常处理程序,则应用程序崩溃。

通过Try..Finally确保在将异常传播到调用方法之前完成本地清理。

于 2016-09-20T05:56:32.500 回答
3

try..finally 块仍然会抛出任何引发的异常。finally所做的只是确保在引发异常之前运行清理代码。

带有空 catch 的 try..catch 将完全消耗任何异常并隐藏它发生的事实。读者将被关闭,但不知道是否发生了正确的事情。如果您的意图是将i写入文件怎么办?在这种情况下,您将无法访问该部分代码,并且myfile.txt将为空。所有下游方法都正确处理了吗?当您看到空文件时,您是否能够正确猜测它是空的,因为抛出了异常?最好抛出异常并让它知道你做错了什么。

另一个原因是这样的 try..catch 是完全不正确的。你这样做的意思是,“无论发生什么,我都能应付。” 怎么样StackOverflowException,那你能清理干净吗?怎么样OutOfMemoryException?通常,您应该只处理您期望并知道如何处理的异常。

于 2008-09-24T18:16:01.010 回答
2

如果您不知道要捕获什么异常类型或如何处理它,那么使用 catch 语句是没有意义的。你应该把它留给可能有更多关于这种情况的信息的上级来电者知道该怎么做。

如果出现异常,您仍然应该有一个 finally 语句,以便您可以在该异常被抛出给调用者之前清理资源。

于 2008-09-24T18:13:20.737 回答
2

从可读性的角度来看,它更明确地告诉未来的代码阅读者“这里的东西很重要,无论发生什么都需要完成。” 这很好。

此外,空的 catch 语句往往对它们有某种“气味”。它们可能表明开发人员没有考虑可能发生的各种异常以及如何处理它们。

于 2008-09-24T18:14:15.993 回答
2

finally 是可选的——如果没有要清理的资源,就没有理由设置“Finally”块。

于 2008-09-24T18:14:39.597 回答
2

取自:这里

引发和捕获异常不应作为成功执行方法的一部分而经常发生。在开发类库时,必须让客户端代码有机会在执行可能导致引发异常的操作之前测试错误情况。例如,System.IO.FileStream 提供了一个 CanRead 属性,可以在调用 Read 方法之前对其进行检查,从而防止引发潜在的异常,如下面的代码片段所示:

Dim str As Stream = GetStream() If (str.CanRead) Then '读取流的代码 End If

在调用可能引发异常的特定方法之前是否检查对象状态的决定取决于对象的预期状态。如果使用应该存在的文件路径和应该以读取模式返回文件的构造函数创建 FileStream 对象,则不需要检查 CanRead 属性;无法读取 FileStream 将违反所进行的方法调用的预期行为,并且应该引发异常。相反,如果方法被记录为返回可能或可能不可读的 FileStream 引用,建议在尝试读取数据之前检查 CanRead 属性。

为了说明使用“运行直到异常”编码技术可能导致的性能影响,将在转换失败时抛出 InvalidCastException 的转换的性能与 C# as 运算符进行比较,如果转换失败则返回空值。这两种技术的性能在强制转换有效的情况下是相同的(参见测试 8.05),但是对于强制转换无效并且使用强制转换导致异常的情况,使用强制转换比使用作为操作员(见测试 8.06)。抛出异常技术的高性能影响包括分配、抛出和捕获异常的成本以及异常对象后续垃圾回收的成本,这意味着抛出异常的瞬时影响并没有这么高。随着更多的异常被抛出,

于 2008-09-24T18:14:42.823 回答
2

添加一个 catch 子句只是为了重新抛出异常是不好的做法。

于 2012-09-27T12:48:15.527 回答
2

如果你为程序员阅读 C#,你就会明白,finally 块是为了优化应用程序和防止内存泄漏而设计的。

CLR 不能完全消除泄漏...如果程序无意中保留对不需要的对象的引用,可能会发生内存泄漏

例如,当您打开文件或数据库连接时,您的机器将分配内存来满足该事务,并且除非执行了弃置或关闭命令,否则不会保留该内存。try.. finally..但是如果在交易过程中发生了错误,除非它在块内,否则将不会终止进行中的命令。

catch不同于从finally某种意义上说,catch 的设计目的是让你有方法来处理/管理或解释它自己的错误。把它想象成一个告诉你“嘿,我抓到了一些坏人,你想让我对他们做什么?”的人。whilefinally旨在确保您的资源正确放置。想想一个人,不管有没有坏人,他都会确保你的财产仍然安全。

你应该让这两者永远合作。

例如:

try
{
  StreamReader reader=new  StreamReader("myfile.txt");
  //do other stuff
}
catch(Exception ex){
 // Create log, or show notification
 generic.Createlog("Error", ex.message);
}
finally   // Will execute despite any exception
{
  reader.Close();
}
于 2016-04-29T10:33:10.840 回答
1

使用 finally,您可以清理资源,即使您的 catch 语句将异常抛出给调用程序。您的示例包含空的 catch 语句,几乎没有什么区别。但是,如果在你的 catch 中,你做了一些处理并抛出错误,或者甚至根本没有 catch,finally 仍然会运行。

于 2008-09-24T18:14:18.473 回答
1

一方面,捕获您不想处理的异常是一种不好的做法。从提高 .NET 应用程序性能和可扩展性中查看关于 .Net 性能的第 5 章。旁注,您可能应该在 try 块中加载流,这样,如果失败,您可以捕获相关的异常。在 try 块之外创建流会破坏其目的。

于 2008-09-24T18:18:17.713 回答
0

在可能的许多原因中,异常执行起来非常慢。如果这种情况经常发生,您可以轻松地缩短执行时间。

于 2008-09-24T18:14:41.390 回答
0

捕获所有异常的 try/catch 块的问题在于,如果发生未知异常,您的程序现在处于不确定状态。这完全违反了快速失败规则——如果发生异常,您不希望程序继续运行。上面的 try/catch 甚至会捕获 OutOfMemoryExceptions,但这绝对是您的程序不会运行的状态。

Try/finally 块允许您在快速失败的同时执行清理代码。在大多数情况下,您只想在全局级别捕获所有异常,以便您可以记录它们,然后退出。

于 2008-09-24T18:14:48.940 回答
0

只要没有引发异常,您的示例之间的有效差异就可以忽略不计。

但是,如果在 'try' 子句中抛出异常,第一个示例将完全吞下它。第二个示例将异常引发到调用堆栈的下一步,因此所述示例的不同之处在于,一个完全掩盖了任何异常(第一个示例),另一个(第二个示例)保留了异常信息以供以后可能处理,而仍在执行“finally”子句中的内容。

例如,如果您将代码放在第一个引发异常的示例的“catch”子句中(无论是最初引发的异常,还是新异常),读取器清理代码将永远不会执行。无论'catch' 子句中发生什么,最终都会执行。

因此,'catch' 和 'finally' 之间的主要区别在于,'finally' 块的内容(有一些罕见的异常)可以被认为是保证执行,即使面对意外的异常,而后面的任何代码“catch”子句(但在“finally”子句之外)不会提供这样的保证。

顺便说一句,Stream 和 StreamReader 都实现了 IDisposable,并且可以包装在“使用”块中。“使用”块是 try/finally 的语义等价物(没有“catch”),因此您的示例可以更简洁地表示为:

using (StreamReader reader = new  StreamReader("myfile.txt"))
{
  int i = 5 / 0;
}

...当 StreamReader 实例超出范围时,它将关闭并处理它。希望这可以帮助。

于 2008-09-24T18:28:06.240 回答
0

try {...} catch{} 并不总是坏事。这不是一种常见的模式,但是当我无论如何都需要关闭资源时,我确实倾向于使用它,比如在线程结束时关闭(可能)打开的套接字。

于 2008-09-24T22:26:20.223 回答