为什么最终尝试{...}好{...}好;尝试{...}抓住{}不好吗?
我见过有人说使用不带参数的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之后的代码将不会)。
否则,最后有什么特别之处?
解决方案
"最后"是"必须执行某些操作以确保程序状态正常的声明"。这样,如果有可能异常会抛出程序状态,那么拥有一个总是一种很好的形式。编译器还竭尽全力以确保Final代码运行。
"捕获"是对"我可以从此异常中恢复"的陈述。我们只能从确实可以纠正捕获而没有参数的异常中恢复过来,说"嘿,我可以从任何事情中恢复!",这几乎总是不正确的。
如果有可能从每个异常中恢复过来,那么这实际上是一个语义上的争论,关于我们要声明的意图。但是,事实并非如此,几乎可以肯定的是,我们上方的框架将更好地处理某些异常情况。因此,请最终使用,免费运行清理代码,但仍然让更多知识渊博的处理程序处理该问题。
最大的不同是try ... catch
会吞下异常,从而掩盖了发生错误的事实。 " try..finally"将运行清理代码,然后异常将继续进行,由知道如何处理的异常处理。
如果我们不知道要捕获哪种异常类型或者如何处理它,那么没有catch语句是没有意义的。我们应该将其留给上级呼叫者,该呼叫者可能对情况有更多了解,知道该怎么做。
万一发生异常,我们仍然应该在其中有一个finally语句,以便可以在将该异常引发给调用者之前清理资源。
从可读性的角度来看,它更明确地告诉未来的代码阅读器"此处的内容很重要,无论发生什么事情都必须完成。"很好
同样,空的catch语句往往对它们具有一定的"气味"。它们可能表明开发人员没有考虑可能发生的各种异常以及如何处理它们。
使用final,即使catch语句将异常抛出给调用程序,我们也可以清理资源。在示例包含空catch语句的情况下,几乎没有什么不同。但是,如果我们在捕获中进行了一些处理并抛出了错误,或者甚至根本没有捕获,那么最终仍然会运行。
因为当那一行抛出异常时,我们不会知道。
在第一段代码中,异常将被简单吸收,即使程序的状态可能错误,程序也将继续执行。
在第二个代码块中,将引发异常并冒泡,但仍可以保证reader.Close()
运行。
如果预计不会发生异常,则不要这样放置try..catch块,否则当程序进入不良状态并且我们不知道为什么时,以后很难进行调试。
最后是可选的-如果没有可清理的资源,则没有理由使用"最终"块。
在许多原因中,异常的执行速度非常慢。如果发生这种情况,我们可以轻松地减少执行时间。
取自:这里
在成功执行方法的过程中,通常不应出现引发和捕获异常的情况。在开发类库时,必须在执行可能导致引发异常的操作之前,给客户端代码测试错误条件的机会。例如,System.IO.FileStream提供了一个CanRead属性,可以在调用Read方法之前对其进行检查,以防止引发潜在的异常,如以下代码段所示:
Dim str As Stream = GetStream()
如果(str.CanRead)然后
'读取流的代码
万一
在调用可能引发异常的特定方法之前是否检查对象状态的决定取决于对象的预期状态。如果使用应存在的文件路径和应以读取模式返回文件的构造函数创建FileStream对象,则无需检查CanRead属性;无法读取FileStream将违反所进行的方法调用的预期行为,并应引发异常。相反,如果将方法记录为返回可能可读或者可能不可读的FileStream引用,则建议在尝试读取数据之前检查CanRead属性。
为了说明使用"直到异常运行"编码技术可能导致的性能影响,将强制转换的性能与Cas运算符进行比较,如果强制转换失败,则抛出InvalidCastException。对于强制转换有效的情况,这两种技术的性能相同(请参见测试8.05),但是对于强制转换无效且使用强制转换会导致异常的情况,使用强制转换的速度比使用强制转换慢600倍。作为操作员(请参阅测试8.06)。异常引发技术的高性能影响包括分配,引发和捕获异常的成本,以及后续对异常对象进行垃圾回收的成本,这意味着引发异常的瞬时影响并不高。随着引发更多异常,频繁的垃圾回收成为一个问题,因此频繁使用异常抛出编码技术的总体影响将类似于Test 8.05.
捕获所有异常的try / catch块的问题在于,如果发生未知异常,则程序现在处于不确定状态。这完全违反了快速失败规则,即我们不希望程序在发生异常时继续运行。上面的try / catch甚至可以捕获OutOfMemoryExceptions,但这绝对是程序无法在其中运行的状态。
Try / finally块使我们可以执行清理代码,同时仍然快速失败。在大多数情况下,我们只想在全局级别捕获所有异常,以便可以记录它们,然后退出。
最终无论执行什么。因此,如果try块成功,它将执行,如果try块失败,则它将执行catch块,然后执行finally块。
另外,最好尝试使用以下构造:
using (StreamReader reader=new StreamReader("myfile.txt")) { }
由于using语句会自动包含在try / finally中,因此流将自动关闭。 (如果我们想实际捕获异常,则需要在try语句周围进行try / catch)。
try..finally块仍将引发任何引发的异常。最后,所有要做的工作都是确保在引发异常之前运行清除代码。
带有空catch的try..catch将完全消耗任何异常,并隐藏它发生的事实。读者将被关闭,但无法判断是否发生了正确的事情。如果我们打算将i写入文件怎么办?在这种情况下,我们将不会进入代码的那部分,并且myfile.txt将为空。所有下游方法都可以正确处理吗?当我们看到空文件时,我们是否能够正确猜测它是空的,因为引发了异常?最好抛出异常,让我们知道自己做错了什么。
另一个原因是try..catch这样完成是完全不正确的。我们这样做的意思是:"无论发生什么事,我都能应付。"那StackOverflowException
呢,那之后你可以清理吗?那OutOfMemoryException
呢?通常,我们应该只处理我们期望的异常并且知道如何处理。
对一个人来说,捕获不费力处理的异常是一种不好的做法。从提高.NET应用程序性能和可伸缩性中查阅有关.Net性能的第5章。附带说明,我们可能应该将流加载到try块中,这样,如果失败,我们可以捕获相关的异常。在try块之外创建流会破坏其目的。
只要不抛出异常,示例之间的有效差异就可以忽略不计。
但是,如果在" 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实例。
希望这可以帮助。
我同意这里似乎是共识的观点,空的"捕获"是不好的,因为它掩盖了try块中可能发生的任何异常。
同样,从可读性的角度来看,当我看到" try"块时,我认为将有一个相应的" catch"语句。如果我们仅使用" try"以确保在" finally"块中取消了资源分配,则可以考虑使用" using"语句:
using (StreamReader reader = new StreamReader('myfile.txt')) { // do stuff here } // reader.dispose() is called automatically
我们可以对任何实现IDisposable的对象使用" using"语句。对象的dispose()方法在块的末尾被自动调用。
尽管以下两个代码块是等效的,但它们并不相等。
try { int i = 1/0; } catch { reader.Close(); throw; } try { int i = 1/0; } finally { reader.Close(); }
- "最终"是意图披露代码。我们向编译器和其他程序员声明,无论如何,此代码都需要运行。
- 如果我们有多个catch块并且有清理代码,则最终需要。没有最后,我们将在每个catch块中复制清除代码。 (干原理)
最后块是特殊的。 CLR通过将catch块与catch块分开来识别和处理代码,并且CLR竭尽全力保证finally块将始终执行。这不仅仅是编译器的语法糖。
尝试{} catch {}并不总是不好的。这不是一个常见的模式,但是无论何时我需要关闭资源时,我都会倾向于使用它,例如在线程末尾关闭(可能)打开的套接字。