为什么尝试{...}最终{...}好;试试{}}抓住{}不好?

时间:2008-09-24 18:10:23

标签: c# .net exception-handling try-catch try-catch-finally

我看到有人说使用不带参数的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之后的代码将不会。)

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

20 个答案:

答案 0 :(得分:344)

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

答案 1 :(得分:58)

“终于”是一个声明“你必须做的事情,以确保程序状态是理智的”。因此,如果异常可能会导致程序状态失效,那么拥有一个总是很好的形式。编译器还竭尽全力确保运行您的Finally代码。

“Catch”是“我可以从此异常中恢复”的声明。你应该只从你真正能够纠正的异常中恢复过来 - 没有争论就说:“嘿,我可以从任何东西中恢复过来!”,这几乎总是不真实的。

如果可以从每个异常中恢复,那么它实际上是一个语义狡辩,关于你宣布你的意图是什么。但是,它不是,而且几乎可以肯定,高于你的帧将更好地处理某些异常。因此,最后使用,让你的清理代码免费运行,但仍然让更多知识渊博的处理程序处理这个问题。

答案 2 :(得分:32)

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

使用第一个代码块,异常只是吸收,即使程序状态可能出错,程序也会继续执行。

使用第二个块时,异常将抛出并冒泡 reader.Close()仍可保证运行。

如果不期望异常,那么不要放一个try..catch块,以后当程序进入错误状态并且你不知道为什么时很难调试。

答案 3 :(得分:21)

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

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

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

由于using语句自动包装在try / finally中,因此流将自动关闭。 (如果你想真正捕获异常,你需要在using语句周围放一个try / catch。)

答案 4 :(得分:8)

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

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

try
{
  int i = 1/0;
}
finally
{
  reader.Close();
}
  1. 'finally'是意图揭示代码。您向编译器和其他程序员声明无论如何都需要运行此代码。
  2. 如果您有多个catch块并且您有清理代码,那么您最终需要。最后,你将在每个catch块中复制清理代码。 (DRY原则)
  3. 最后块是特殊的。 CLR使用finally块分别识别和处理带有finally块的代码,并且CLR竭尽全力保证finally块始终执行。这不仅仅是编译器的语法糖。

答案 5 :(得分:5)

我同意这里似乎达成的共识 - 空的'catch'很糟糕,因为它掩盖了try块中可能发生的任何异常。

另外,从可读性的角度来看,当我看到'try'块时,我假设会有一个相应的'catch'语句。如果您只是使用'try'以确保在'finally'块中取消分配资源,您可以考虑改为'using' statement

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

您可以将'using'语句与任何实现IDisposable的对象一起使用。对象的dispose()方法在块结束时自动调用。

答案 6 :(得分:3)

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

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

另一个原因是try..catch这样完成是完全错误的。你这么说的是,“无论发生什么事,我都能处理它。”怎么样StackOverflowException,你可以在那之后清理一下吗?那么OutOfMemoryException呢?通常,您应该只处理您期望的并且知道如何处理的异常。

答案 7 :(得分:3)

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

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

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

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

答案 8 :(得分:2)

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

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

答案 9 :(得分:2)

最后是可选的 - 如果没有资源可以清理,没有理由拥有“终止”块。

答案 10 :(得分:2)

取自:here

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

Dim str As Stream = GetStream() 如果(str.CanRead)那么   '代码读取流 结束如果

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

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

答案 11 :(得分:2)

如果您不知道要捕获的异常类型或者如何处理它,那么使用catch语句就没有意义了。您应该将其留给更高级的来电者,以便了解有关情况的更多信息。

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

答案 12 :(得分:2)

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

答案 13 :(得分:2)

如果您阅读C# for programmers,您会理解,finally块的设计是为了优化应用程序并防止内存泄漏。

  

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

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

catchfinally的不同之处在于,catch是设计为您提供处理/管理或解释错误的方法。把它想象成告诉你的人"嘿,我抓到了一些坏人,你想让我对他们做些什么?" 而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();
}

答案 14 :(得分:1)

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

答案 15 :(得分:1)

对于一个人来说,捕捉您无需处理的异常是不好的做法。从提高.NET应用程序性能和可伸缩性中查看Chapter 5 about .Net Performance。注意,您应该在try块中加载流,这样,如果失败,您可以捕获相关的异常。在try块之外创建流会使其失败。

答案 16 :(得分:0)

在可能的原因中,异常执行起来很慢。如果发生这种情况,你很容易瘫痪你的执行时间。

答案 17 :(得分:0)

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

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

答案 18 :(得分: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实例。 希望这会有所帮助。

答案 19 :(得分:0)

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