为什么try{…}最后{…}好;尝试{…} catch{}坏?

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

否则,最后有什么特别的?

91793 次浏览

“Finally”是一个声明,“你必须总是做一些事情来确保程序状态正常”。因此,如果存在异常可能会抛出程序状态,那么拥有一个异常总是很好的形式。编译器还会竭尽全力确保Finally代码运行。

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

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

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

如果您不知道要捕获什么异常类型或如何处理它,那么使用catch语句是没有意义的。你应该把它留给上级,他们可能对情况有更多的了解,知道该怎么做。

在那里仍然应该有一个finally语句,以防出现异常,这样就可以在异常抛出给调用者之前清理资源。

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

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

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

因为当这一行引发异常时,你不会知道。

对于第一个代码块,异常将简单地为吸收,即使程序的状态可能是错误的,程序也将继续执行。

对于第二个块,异常将是,并冒泡reader.Close()仍然保证运行。

如果不期望出现异常,那么就不要使用try. catch块,这样当程序进入糟糕的状态而你不知道原因时,就很难进行调试。

Finally是可选的——如果没有资源可以清理,就没有理由使用“Finally”块。

可能有很多原因,其中之一就是异常执行非常慢。如果这种情况经常发生,很容易缩短执行时间。

取自:在这里

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

Dim str As Stream = GetStream() If (str.CanRead) Then '代码来读取流 如果< / p >

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

为了说明使用“运行到异常”编码技术可能导致的性能影响,将强制转换(如果强制转换失败则抛出InvalidCastException)的性能与c# as操作符(如果强制转换失败则返回null)进行比较。在强制转换有效的情况下,这两种技术的性能是相同的(参见测试8.05),但在强制转换无效并且使用强制转换会导致异常的情况下,使用强制转换比使用as操作符慢600倍(参见测试8.06)。异常抛出技术的高性能影响包括分配、抛出和捕获异常的成本,以及异常对象的后续垃圾收集成本,这意味着抛出异常的瞬时影响没有这么高。由于抛出的异常越来越多,频繁的垃圾收集就成了一个问题,因此频繁使用异常抛出编码技术的总体影响将类似于Test 8.05。

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

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

最后无论如何都会执行。如果你的try块成功,它就会执行,如果你的try块失败,它就会执行catch块,然后是finally块。

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

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

由于using语句被自动包装在try / finally语句中,流将自动关闭。(如果想要真正捕获异常,则需要在using语句周围加上try / catch)。

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

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

另一个原因是这样做的try. catch是完全错误的。你这样做的意思是,“无论发生什么,我都能处理好。”那StackOverflowException呢,你能清理一下吗?那OutOfMemoryException呢?一般来说,您应该只处理您期望并知道如何处理的异常。

首先,捕获您懒得处理的异常是一种糟糕的实践。检查提高。net应用程序的性能和可伸缩性中的第五章关于。net性能。旁注,您可能应该在try块中加载流,这样,如果它失败,您就可以捕获相关异常。在try块之外创建流会破坏它的目的。

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

但是,如果在'try'子句中抛出异常,则第一个示例将完全忽略它。第二个示例将向调用堆栈的下一个步骤抛出异常,因此所述示例的区别在于,一个示例完全掩盖了任何异常(第一个示例),而另一个示例(第二个示例)保留了异常信息,以便后续处理,同时仍然执行'finally'子句中的内容。

例如,如果您将代码放在第一个示例的'catch'子句中,该子句抛出异常(无论是最初引发的异常还是新抛出的异常),则阅读器清理代码永远不会执行。最后执行` catch'子句中发生的不管

因此,'catch'和'finally'之间的主要区别是,'finally'块的内容(少数例外)可以被认为是保证来执行,即使遇到意外异常,而'catch'子句之后的任何代码(但在'finally'子句之外)都不会带有这样的保证。

顺便说一句,Stream和StreamReader都实现了IDisposable,并且可以包装在一个“using”块中。'Using'块在语义上等同于try/finally(没有'catch'),所以你的例子可以更简洁地表达为:

using (StreamReader reader = new  StreamReader("myfile.txt"))
{
int i = 5 / 0;
}
< p >…它会在StreamReader实例超出作用域时关闭并销毁它。

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

此外,从可读性的角度来看,当我看到一个“try”块时,我假设会有一个对应的“catch”语句。如果你只是使用'try'来确保资源在'finally'块中被取消分配,你可以考虑使用“使用”的声明:

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();
}
  1. 'finally'是意图揭示代码。您向编译器和其他程序员声明,无论如何都需要运行这段代码。
  2. 如果您有多个捕获块,并且有清理代码,那么您最终需要。如果没有最后,您将在每个catch块中重复清理代码。(干燥原理)

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

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

添加catch子句只是为了重新抛出异常,这是一种糟糕的实践。

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

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

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

catchfinally的不同之处在于,catch的设计是为你提供处理/管理或解释错误的方法。把它想象成一个人告诉你“嘿,我抓住了一些坏人,你想让我对他们做什么?” 而finally的设计是为了确保你的资源被正确放置。想想某人,不管有没有坏人,他都会确保你的财产仍然安全。< / p >

你应该让他们俩永远合作下去。

例如:

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

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

如果你的方法不知道如何处理异常,但需要在异常发生后进行清理,请使用Try..Finally

通过这种方式,异常被传播到调用方法,如果调用方法中有任何合适的Catch语句,则进行处理。如果当前方法或任何调用方法中没有异常处理程序,则应用程序将崩溃。

通过Try..Finally,可以确保在将异常传播给调用方法之前完成局部清理。