Why is try {…} finally {…} good; try {…} catch{} bad?
我见过有人说不带参数使用catch是不好的形式,特别是如果catch不起任何作用:
1 2 3 4 5 6 7 8 | StreamReader reader=new StreamReader("myfile.txt"); try { int i = 5 / 0; } catch // No args, so it will catch any exception {} reader.Close(); |
然而,这被认为是良好的形式:
1 2 3 4 5 6 7 8 9 | StreamReader reader=new StreamReader("myfile.txt"); try { int i = 5 / 0; } finally // Will execute despite any exception { reader.Close(); } |
据我所知,将清理代码放入finally块和在try..catch块后放入清理代码之间的唯一区别是,在try块中是否有返回语句(在这种情况下,finally中的清理代码将运行,但try..catch之后的代码将不运行)。
否则,最后有什么特别的?
最大的区别是,
"finally"是"为了确保程序状态正常,必须始终执行的操作"的语句。因此,如果异常有可能脱离程序状态,那么拥有一个异常总是很好的形式。编译器也会花费大量的时间来确保最终的代码能够运行。
"catch"是"我可以从这个异常中恢复"的语句。你应该只从你真正能纠正的异常中恢复——catch不带参数地说:"嘿,我能从任何事情中恢复!"这几乎总是不真实的。
如果能够从每一个异常中恢复过来,那么这将是一个语义上的争论,关于你声明你的意图是什么。然而,事实并非如此,而且几乎可以肯定的是,您上面的框架能够更好地处理某些异常情况。因此,使用finally,让清理代码免费运行,但仍然让更多的知识处理程序处理这个问题。
因为当那一行抛出异常时,你不会知道。
对于第一块代码,异常将被简单地吸收,即使程序的状态可能是错误的,程序也将继续执行。
在第二个块中,异常将被抛出并冒泡,但
如果不期望出现异常,那么不要放置try..catch块,这样,当程序进入错误状态,并且您不知道原因时,很难稍后进行调试。
不管怎样,最终都会被执行。因此,如果try块成功,它将执行;如果try块失败,它将执行catch块,然后执行finally块。
此外,最好尝试使用以下构造:
1 2 3 |
由于using语句自动包装在try/finally中,因此流将自动关闭。(如果要实际捕获异常,则需要在using语句周围放置一个try/catch)。
虽然以下两个代码块是等效的,但它们并不相等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | try { int i = 1/0; } catch { reader.Close(); throw; } try { int i = 1/0; } finally { reader.Close(); } |
最后,模块是特殊的。clr识别并处理带有finally块的代码,并将其与catch块分开,clr将花费大量时间来确保finally块始终执行。它不仅仅是来自编译器的语法糖分。
我同意这里的共识——空的"catch"很糟糕,因为它掩盖了try块中可能发生的任何异常。
另外,从可读性的角度来看,当我看到一个"try"块时,我假设会有一个对应的"catch"语句。如果只使用"try"以确保在"finally"块中取消分配资源,则可以考虑使用"using"语句:
1 2 3 4 | using (StreamReader reader = new StreamReader('myfile.txt')) { // do stuff here } // reader.dispose() is called automatically |
可以将"using"语句与实现IDisposable的任何对象一起使用。对象的dispose()方法将在块的末尾自动调用。
try..finally块仍将引发任何异常。所有
带有空catch的try..catch将完全消耗任何异常,并隐藏它发生的事实。读者将被关闭,但不知道是否发生了正确的事情。如果你想把我写到文件里怎么办?在这种情况下,您将无法到达代码的该部分,myfile.txt将为空。所有的下游方法都能正确处理这个问题吗?当您看到空文件时,是否能够正确地猜测它是空的,因为抛出了异常?最好抛出异常,让它知道你做错了什么。
另一个原因是这样的尝试..捕获操作完全不正确。你这样做的意思是,"不管发生什么,我都能处理好。"那么以东十一〔一〕号呢,你能在那之后清理一下吗?那
如果您的方法知道如何在本地处理异常,请使用
如果您的方法不知道如何处理异常,但在异常发生后需要进行清理,请使用
通过此方法,异常将传播到调用方法,并在调用方法中存在任何合适的catch语句时进行处理。如果当前方法或任何调用方法中没有异常处理程序,则应用程序崩溃。
由
如果您不知道要捕获什么异常类型或如何处理它,那么使用catch语句是没有意义的。你应该把它留给一个更高的来电者,他们可能有更多的情况信息来知道该怎么做。
在出现异常的情况下,您应该仍然有一个finally语句,这样您就可以在将异常抛出给调用方之前清理资源。
从可读性的角度来看,它更明确地告诉未来的代码阅读器"这里的东西很重要,不管发生什么,都需要做。"这很好。
此外,空的catch语句对它们也有一定的"味道"。它们可能表明开发人员没有考虑可能发生的各种异常以及如何处理它们。
finally是可选的——如果没有要清理的资源,就没有理由使用"finally"块。
拍摄地:这里
作为成功执行方法的一部分,引发和捕获异常不应该经常发生。在开发类库时,客户机代码必须有机会在执行可能导致引发异常的操作之前测试错误条件。例如,system.io.filestream提供了一个can read属性,可以在调用read方法之前检查该属性,以防止引发潜在的异常,如以下代码段所示:
dim str as stream=getstream()。如果(str.canread),则'要读取流的代码结束如果
在调用可能引发异常的特定方法之前,是否检查对象状态的决定取决于对象的预期状态。如果使用应存在的文件路径和应以读取模式返回文件的构造函数创建了一个filestream对象,则不必检查canread属性;无法读取filestream将违反所做方法调用的预期行为,并应引发异常。相反,如果将方法记录为返回可能可读或不可读的文件流引用,则建议在尝试读取数据之前检查canread属性。
为了说明使用"运行到异常"编码技术可能导致的性能影响,将在强制转换失败时引发InvalidCastException的强制转换的性能与在强制转换失败时返回空值的c as运算符进行比较。对于强制转换有效的情况(见测试8.05),这两种技术的性能是相同的,但对于强制转换无效的情况,使用强制转换会导致异常,使用强制转换比使用as运算符慢600倍(见测试8.06)。异常抛出技术的高性能影响包括分配、抛出和捕获异常的成本,以及异常对象后续垃圾收集的成本,这意味着抛出异常的即时影响不那么高。随着更多的异常被抛出,频繁的垃圾收集成为一个问题,因此频繁使用异常抛出编码技术的总体影响将类似于测试8.05。
添加catch子句只是为了重新引发异常是不好的做法。
如果您要为程序员阅读C,您会理解,最后一个块的设计是为了优化应用程序并防止内存泄漏。
The CLR does not completely eliminate leaks... memory leaks can occur if program inadvertently keep references to unwanted objects
例如,当您打开一个文件或数据库连接时,您的计算机将分配内存以满足该事务,并且除非执行释放或关闭命令,否则不会保留该内存。但是,如果在事务处理过程中发生了错误,则继续执行的命令将不会终止,除非它位于
你应该允许这两个人一起工作。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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(); } |
好吧,首先,抓住你不想处理的异常是不好的做法。请参阅第5章,从改善.NET应用程序性能和可伸缩性中了解.NET性能。注意,您可能应该在try块中加载流,这样,如果该流失败,您就可以捕获相关的异常。在try块外部创建流会破坏其目的。
最后,即使catch语句向调用程序抛出异常,也可以清理资源。对于包含空catch语句的示例,没有什么区别。但是,如果在catch中,您进行一些处理并抛出错误,或者甚至根本没有catch,那么最终仍将运行。
其中可能有许多原因,异常的执行非常缓慢。如果经常发生这种情况,您可以很容易地削弱执行时间。
只要不抛出异常,示例之间的有效差异就可以忽略不计。
但是,如果在"try"子句中引发异常,则第一个示例将完全吞没它。第二个示例将把异常提升到调用堆栈的下一步,因此所述示例中的不同之处在于,一个完全掩盖了任何异常(第一个示例),另一个(第二个示例)保留了异常信息,以备日后处理,同时仍在执行"finally"子句中的内容。
例如,如果要将代码放入引发异常的第一个示例的"catch"子句中(最初引发的异常或新的异常),则读取器清理代码将永远不会执行。不管"catch"子句中发生了什么,最终执行。
因此,"catch"和"finally"之间的主要区别是,"finally"块的内容(有一些罕见的异常)可以被认为是保证执行的,即使面对意外的异常,而"catch"子句后面的任何代码(但在"finally"子句之外)都不会带有这样的保证。
顺便说一下,流和流阅读器都实现了IDisposable,并且可以包装在"using"块中。使用"blocks"是try/finally(no"catch")的语义等价物,因此您的示例可以更简洁地表示为:
1 2 3 4 |
…当streamreader实例超出范围时,它将关闭并处理它。希望这有帮助。
尝试…捕捉并不总是坏的。这不是一个常见的模式,但我确实倾向于在需要关闭资源时使用它,不管是什么,比如在线程的末尾关闭一个(可能的)打开的套接字。
捕获所有异常的try/catch块的问题是,如果发生未知异常,则程序现在处于不确定状态。这完全违反了快速失败规则——如果发生异常,您不希望程序继续运行。上面的try/catch甚至会捕获outofmemoryExceptions,但这绝对是一种程序不会运行的状态。
Try/Finally块允许您在快速失败的同时执行清理代码。在大多数情况下,您只希望在全局级别捕获所有异常,这样您就可以登录它们,然后退出。