ChatGPT解决这个技术问题 Extra ChatGPT

为什么 try {...} finally {...} 好;尝试 {...} 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 块中有 return 语句(在这种情况下,finally 中的清理代码将运行,但 try..catch 之后的代码不会)。

不然最后有什么特别的?

在你试图抓住一只你无法应付的老虎之前,你应该记录你的最终愿望。
Exceptions 文档中的主题可能会提供一些很好的见解。另请查看 Finally Block 示例。

K
Khoth

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


任何考虑到封装的代码都可能只能在引发异常的地方处理异常。简单地把它传回调用堆栈,绝望地希望其他东西能够处理一些任意异常,这是灾难的根源。
在大多数情况下,从应用程序级别(例如,某个配置设置)发生特定异常的原因比在类库级别更明显。
大卫 - 我希望程序快速失败,这样我就可以意识到问题,而不是让程序在未知状态下运行。
如果您的程序在发生异常后处于未知状态,那么您的代码是错误的。
@DavidArno,任何考虑到封装的代码都应该只处理其范围内的异常。其他任何事情都应该传递给其他人处理。如果我有一个应用程序从用户那里获取文件名然后读取文件,并且我的文件阅读器在打开文件时遇到异常,它应该将它们传递(或使用异常并抛出一个新异常),以便应用程序可以说,嘿 - 文件没有打开,让我们提示用户输入另一个文件。文件阅读器不应该能够提示用户或采取任何其他行动作为响应。它的唯一目的是读取文件。
A
Adam Wright

“最后”是“您必须始终做的事情以确保程序状态正常”的声明。因此,如果有任何异常可能会抛出程序状态,那么拥有一个总是好的形式。编译器还竭尽全力确保您的 finally 代码运行。

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

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


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

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

使用第一块代码,异常将被简单地吸收,即使程序状态可能错误,程序也会继续执行。

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

如果没有预料到异常,那么不要这样放置 try..catch 块,当程序进入错误状态并且您不知道原因时,以后将很难调试。


M
Mark Ingram

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

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

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

由于 using 语句自动包装在 try / finally 中,并且流将自动关闭。 (如果您想实际捕获异常,则需要在 using 语句周围放置一个 try / catch)。


这是不正确的。 using 不会用 try/catch 包装代码,它应该说 try/finally
R
Robert Paulson

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

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

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

'finally' 是意图揭示代码。您向编译器和其他程序员声明此代码无论如何都需要运行。如果你有多个 catch 块并且你有清理代码,你需要 finally。如果没有 finally,您将在每个 catch 块中复制您的清理代码。 (干燥原理)

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


C
Chris Lawlor

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

另外,从可读性的角度来看,当我看到一个“try”块时,我假设会有一个相应的“catch”语句。如果您仅使用“尝试”以确保在“最终”块中取消分配资源,则可以考虑使用 'using' statement

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

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


m
manjuv

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

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

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

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


与此答案一样基本,它绝对是最好的答案。养成 try/catch/finally 的习惯很好,即使后两者之一是空的。在非常罕见的情况下,catch 块可能存在并且为空,但至少如果您总是编写 try/catch/finally,您会在阅读代码时看到空块。有一个空的 finally 块也是有帮助的。如果您稍后需要清理,或者需要在异常时调试状态,这将非常有帮助。
O
OwenP

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

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

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


M
Mark Cidade

如果您不知道要捕获什么异常类型或如何处理它,那么使用 catch 语句是没有意义的。你应该把它留给可能有更多关于这种情况的信息的上级来电者知道该怎么做。

如果出现异常,您仍然应该在其中有一个 finally 语句,以便您可以在该异常被抛出给调用者之前清理资源。


R
Ryan

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

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


G
Guy Starbuck

finally 是可选的——如果没有要清理的资源,就没有理由设置“Finally”块。


S
SpoiledTechie.com

取自:here

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

Dim str As Stream = GetStream() If (str.CanRead) Then '读取流的代码 End If

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

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


斯科特——如果您在上面引用的文字位于expertsexchange.com 的付费墙后面,您可能不应该在此处发布。我可能错了,但我敢打赌这不是一个好主意。
B
Bastien Vandamme

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


d
dr.Crow

如果您阅读 C# for programmers,您就会明白,finally 块旨在优化应用程序并防止内存泄漏。

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

例如,当您打开文件或数据库连接时,您的机器将分配内存来满足该事务,并且除非执行了弃置或关闭命令,否则该内存将不会保留。但是如果在交易过程中发生了错误,除非它在 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();
}

K
Kibbee

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


F
Factor Mystic

一方面,捕获您不想处理的异常是一种不好的做法。查看提高 .NET 应用程序性能和可扩展性中的Chapter 5 about .Net Performance。旁注,您可能应该在 try 块内加载流,这样,如果失败,您可以捕获相关的异常。在 try 块之外创建流会破坏其目的。


l
lotsoffreetime

在可能的许多原因中,异常执行起来非常慢。如果这种情况经常发生,您可以轻松地缩短执行时间。


D
David Mohundro

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

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


J
Jared

只要没有引发异常,您的示例之间的有效差异就可以忽略不计。

但是,如果在 'try' 子句中抛出异常,第一个示例将完全吞下它。第二个示例将异常引发到调用堆栈的下一步,因此所述示例的不同之处在于,一个完全掩盖了任何异常(第一个示例),另一个(第二个示例)保留了异常信息以供以后可能处理,而仍在执行“finally”子句中的内容。

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

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

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

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

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


M
Martin Liesén

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