为什么最终尝试{…} {…}好?尝试{…}抓住{}不好吗?


201

我见过有人说使用不带参数的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语句(在这种情况下,最终的清理代码将运行,但是try..catch之后的代码不会)。

否则,最后有什么特别之处?


7
在尝试捉住无法处理的老虎之前,应记录您的最终愿望。

文档中的“ 例外”主题可能提供一些很好的见解。还要看一看Final Block示例。
Athafoud

Answers:


357

最大的区别是try...catch它将吞没异常,从而掩盖了发生错误的事实。try..finally将运行您的清理代码,然后异常将继续进行,由知道如何处理的异常处理。


11
考虑到封装编写的任何代码都可能只能在引发异常时处理异常。简单地将其传递回调用堆栈,以期希望其他事物将能够处理一些人为异常,这是灾难的根源。
David Arno

3
在大多数情况下,为什么在应用程序级别(例如某个配置设置)会发生特定的异常,而不是在类库级别中会更明显。
Mark Cidade

88
David-我希望程序能够快速失败,这样我就可以知道问题所在,而不是让程序以未知状态运行。
Erik Forbes

6
如果您的程序在发生异常后处于未知状态,那么您正在执行错误的代码。
Zan Lynx

41
@DavidArno,任何使用封装编写的代码都应仅在其范围内处理异常。其他任何东西都应该传递给别人处理。如果我有一个从用户那里获取文件名的应用程序,然后读取了文件,并且我的文件阅读器在打开文件时遇到了异常,则它应该将它们传递(或使用该异常并抛出一个新异常),以便该应用程序可以说,嘿-文件未打开,让我们提示用户输入其他文件。文件阅读器不应提示用户或采取任何其他措施来响应。它的唯一目的是读取文件。
iheanyi 2014年

62

“最后”是“必须执行某些操作以确保程序状态正常的声明”。这样,如果有可能异常会抛出程序状态,那么拥有一个总是一种很好的形式。编译器还竭尽全力以确保您的Final代码运行。

“捕获”是“我可以从此异常中恢复”的说明。您应该只从确实可以纠正的异常中恢复-不带参数的捕获说“嘿,我可以从任何东西中恢复!”,这几乎总是不正确的。

如果这是可能的,从每一个异常中恢复的话,那就真的是一个语义狡辩,你正在声明你的意图是什么。但是,事实并非如此,几乎可以肯定的是,您上方的帧将更好地处理某些异常。因此,请最终使用,免费运行清理代码,但仍然让更多知识渊博的处理程序处理该问题。


1
您的观点普遍存在,但不幸的是忽略了另一个重要情况:明确使不变性可能不再成立的对象无效。通用模式是代码获取锁,对对象进行一些更改并释放锁。如果在进行了部分但不是全部更改后发生异常,则该对象可能处于无效状态。尽管应该有恕我直言更好的替代方法,但我知道没有比捕获对象状态可能无效,明确使状态无效并重新抛出时发生的任何异常更好的方法了。
supercat

32

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

在第一段代码中,异常将被简单吸收,即使程序状态错误,程序也将继续执行。

与第二块,异常会被抛出,并冒泡,但reader.Close()仍然保证运行。

如果预计不会发生异常,则不要这样放置try..catch块,否则当程序进入不良状态并且您不知道为什么时,以后将很难调试。


21

最后无论执行什么。因此,如果您的try块成功,它将执行;如果您的try块失败,则它将执行catch块,然后执行finally块。

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

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

由于using语句会自动包含在try / finally中,因此流将自动关闭。(如果要实际捕获异常,则需要在try语句周围进行try / catch)。


5
这是不正确的。using不使用try / catch来包装代码,应该说try / finally
pr0nin

8

以下两个代码块是等效的,但它们并不相等。

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

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

最后块是特殊的。CLR识别并处理带有finally块与catch块分开的代码,并且CLR竭尽全力以确保finally块将始终执行。这不仅仅是编译器的语法糖。


5

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

同样,从可读性的角度来看,当我看到“ try”块时,我认为将有一个相应的“ catch”语句。如果仅使用“尝试”以确保在“最终”块中取消分配资源,则可以考虑使用“使用”语句

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

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


4

使用Try..Catch..Finally,如果你的方法知道如何在本地处理异常。异常发生在“尝试”中,“捕获”中发生,然后在“最终”中完成清理。

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

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

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


1
就这个答案而言,它是最基本的,绝对是最好的答案。最好只是尝试一下try / catch / finally,即使后两者之一留空。在极少数情况下,catch块可能存在并且为空,但至少如果您始终编写try / catch / finally,则在阅读代码时会看到空块。同样,使用空的finally块也很有帮助。如果以后需要清理,或者需要在异常时调试状态,这将非常有用。
杰西·威廉姆斯

3

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

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

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


2

如果您不知道要捕获哪种异常类型或如何处理它,那么没有catch语句是没有意义的。您应该将其留给上级呼叫者,该呼叫者可能对情况有更多了解,知道该怎么做。

万一发生异常,您仍然应该在其中有一个finally语句,以便可以在将该异常引发给调用者之前清理资源。


2

从可读性的角度来看,它更明确地告诉未来的代码阅读器“此处的内容很重要,无论发生什么事情都必须完成。” 很好

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



2

取自:这里

在成功执行方法的过程中,不应例行地引发和捕获异常。开发类库时,必须在执行可能导致引发异常的操作之前,给客户端代码测试错误条件的机会。例如,System.IO.FileStream提供了一个CanRead属性,可以在调用Read方法之前对其进行检查,以防止引发潜在的异常,如以下代码段所示:

Dim str As Stream = GetStream()If(str.CanRead)然后'代码以读取流End If

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

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


2
斯科特(Scott)-如果您在上面引用的文本在expertexexchange.com的付费专栏后面,您可能不应在此处发布。我对此可能是错的,但我敢打赌这不是一个好主意。
Onorio Catenacci


2

如果您为程序员阅读C#,您将会理解,finally块旨在优化应用程序并防止内存泄漏。

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

例如,当您打开文件或数据库连接时,您的计算机将分配内存来满足该事务,并且除非执行了dispose或close命令,否则不会保留该内存。但是,如果在事务处理期间发生错误,则除非该命令位于该try.. finally..块内,否则不会终止该命令。

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

您应该允许这两个人一起工作。

例如:

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();
}

1

使用final,即使您的catch语句将异常抛出给调用程序,您也可以清理资源。在您的示例包含空catch语句的情况下,几乎没有什么不同。但是,如果您在捕获中进行了一些处理并抛出了错误,或者甚至根本没有捕获,那么最终仍然会运行。


1

对一个人来说,捕捉不麻烦处理的异常是一种不好的做法。从提高.NET应用程序性能和可伸缩性中查阅有关.Net性能的第5章。附带说明,您可能应该将流加载到try块中,这样,如果失败,您可以捕获相关异常。在try块之外创建流会破坏其目的。


0

在许多原因中,异常的执行速度非常慢。如果发生这种情况,您可以轻松地减少执行时间。


0

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

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


0

只要不抛出异常,示例之间的有效差异就可以忽略不计。

但是,如果在“ try”子句中引发了异常,则第一个示例将完全吞没它。第二个示例将异常引发到调用堆栈的下一个步骤,因此,上述示例的区别在于,一个完全遮盖了任何异常(第一个示例),另一个(第二个示例)保留了异常信息以供以后处理时使用仍在执行“ finally”子句中的内容。

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

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

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

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

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


0

尝试{…} catch {}并不总是不好。这不是常见的模式,但是无论何时我需要关闭资源时,我都会倾向于使用它,例如在线程末尾关闭(可能是)打开的套接字。

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.