.NET和C#异常。合理捕获哪些异常?

12

声明一下,我来自 Java 背景,不怎么用 C#。这两个世界之间有许多共通之处,但当然也存在差异,其中一个就是对异常的思考方式。

最近我回答了一个 C# 问题,建议在某些情况下这样做是合理的:

 try {
   some work
 } catch (Exeption e) {
       commonExceptionHandler();
 }

我得到了一个回复,但我不太理解:

直到.NET 4.0之前,捕获异常非常糟糕。这意味着你会捕获各种低级致命错误,并掩盖错误。这还意味着,在触发此类异常的某种损坏事件中,堆栈上任何打开的finally块都将被执行,因此即使调用callExceptionReporter()函数尝试记录并退出,它可能甚至无法到达该点(finally块可能会再次抛出异常,或者引起更多损坏,或从磁盘或数据库中删除重要内容)。

也许我比我意识到的更加困惑,但我不同意其中的一些观点,请其他人给出评论。

  1. 我知道有很多低级别的异常我们不想忽略。我的commonExceptionHandler()函数可以合理地重新抛出这些异常。这似乎与这个问题的答案一致。它说“根据您的上下文,使用catch(...)可以是可接受的,前提是重新抛出异常。”因此,我得出结论:使用catch(Exception)并不总是有问题,只是在某些情况下悄悄地忽略某些异常是不好的。

  2. 短语“直到.NET 4之前,捕获异常非常糟糕”是什么意思?.NET 4中发生了什么变化?这是指AggregateException吗?它可能使我们能够对我们捕获的异常做一些新的事情,但我认为并没有改变基本的“不要忽略”规则。

  3. 接下来的短语真的让我不安。这可能正确吗?

这也意味着,在触发此类异常的某种损坏事件中,堆栈上任何打开的finally块都将被执行(finally块可能会再次抛出异常,或者引起更多损坏,或从磁盘或数据库中删除重要内容)。

我理解如果某些低级别的代码有:

lowLevelMethod() {
    try {
        lowestLevelMethod();
    } finally {
         some really important stuff
    }
}

在我的代码中,我调用了lowLevel();

 try {
      lowLevel()
 } catch (Exception e) {
       exception handling and maybe rethrowing
 }

无论我是否捕获异常,这都不会对finally块的执行产生任何影响。在我们离开lowLevelMethod()时,finally已经运行了。如果finally要执行任何糟糕的事情,比如损坏我的磁盘,那么它就会执行。我的捕获异常没有任何作用。如果它到达我的异常块,我需要做正确的事情,但我不能是未能执行finally块的原因。


我同意您关于#3的观点,但我无法确定地回答它。我也很想听听。我知道有一些我们无法捕捉的异常(http://msdn.microsoft.com/en-us/library/system.stackoverflowexception.aspx)。可能发帖者是在指先前的.Net-2.0之前的情况。如果在.net 2.0中,这似乎是可以的。 - Noon Silk
如果我们无法捕获它们,那么我的catch块肯定不会造成伤害吧? :-) 我试图理解那些catch块可能会主动造成损害的情况。 - djna
3
同意 :) 但请检查链接,它说在 .net < 2 中捕获 StackOverflowException 是不好的。 - Noon Silk
谢谢,那是一个有趣的链接。 - djna
我同意@James和@Keith的观点;我认为捕获异常的决定与C#/Java二分法无关。 - Jim G.
5个回答

11
针对问题#2: 这位作者所说的是“破坏状态异常”。它们将在.NET 4.0中引入(CLR团队在2008年PDC大会的演讲中宣布)。 无法使用普通catch块捕获破坏状态异常。例如:访问冲突,无效内存。 但您可能想要捕获这些异常:
  1. 在main()函数中——写入日志、退出、关闭插件。
  2. 极少数情况下,当您知道代码会抛出这种异常时(例如与本地代码进行交互的某些情况)。
要做到这一点,您应该在要捕获CorruptedStateException的方法上放置[HandleProcessCorruptedStateException]属性。
要了解更多关于这些异常的内容,请参阅 MSDN文章。

不完全是我所问的问题,但非常有趣的答案!非常感谢。 - djna

9
作为一般规则,除非以下情况之一,否则不应该捕获异常:
  1. 您有一个特定的异常可以处理并采取相应措施。但在这种情况下,您应该始终检查是否应该首先考虑和避免异常。

  2. 您位于应用程序的顶层(例如UI),不希望向用户呈现默认行为。例如,您可能希望出现一个错误对话框,其中包含“请发送我们日志”的样式消息。

  3. 您在处理完异常后重新抛出异常,例如如果您回滚了DB事务。

您的 finally 块总是会执行。您的代码示例是正确的 - 低级方法应只具有 tryfinally。例如,未经管理的调用可能知道它需要处理未经管理的资源,但不会暴露给调用它的 .Net 方法。该调用应在其 finally 块中清除未经管理的资源,并且调用托管方法可以处理异常或直接将其传递。

如果有异常中有您需要处理的内容,则应重新抛出异常,例如:

try {
    conn.BeginTransaction();
    //do stuff
    conn.CommitTransaction();
}
catch (Exception) {
    conn.RollbackTransaction(); //required action on any exception
    throw; //re-throw, don't wrap a new ex to keep the stack trace
}
finally {
    conn.Dispose(); //always dispose of the resource
}

5

我的座右铭是处理你能够(或需要)处理的内容,让其他异常冒泡并在UnhandledException事件中捕获它们。

不过,你说得对,无论try部分是否引发异常,在退出方法之前finally块总是会被调用。所以,你想在外部方法中捕获异常与否完全取决于你... ...这不应该影响finally块的调用。


谢谢。总的来说,我同意“冒泡”思想。这基本上是我在Java中所做的。在我看来,有合理的理由在某些情况下捕获异常(并重新抛出一些异常)。 - djna
抛出自定义本地异常? - James
正确。请参见https://dev59.com/oEbRa4cB1Zd3GeqPyDWy - peSHIr

1

仅回答你的第三个问题:

如果你有

nastyLowLevel() {
  doSomethingWhichMayCorruptSomethingAndThrowsException();
}

MyEvilCatcher() {
  try {
    nastyLowLevel();
  } catch (Exception e) {
    MyExceptionHandler(e);
  }
}

WiseCatcher() {
  try {
    MyEvilCatcher();
  } catch (LowLevelException e) {
    DoSomethingWiseSoFinnalyDontRuinAnything();
  } finally {
    DoSomethingWhichAssumesLowLevelWentOk();
  }
}

我认为你所询问的响应只是指一些低级方法可以使用异常来通知外部方法需要特别注意。如果你在全捕获处理程序中忘记了这些异常并且没有重新抛出它们,那么可能会出现问题。

通常,我更喜欢仔细考虑可能会抛出哪些异常并显式地捕获它们。我只在生产环境的最外层使用“通用处理程序”,以便记录意外异常并以格式良好的方式呈现给客户(包括提示发送日志文件给我们)。


谢谢。我同意你的哲学观点。我在考虑,通过仔细、正确地编写 MyExceptionHandler() 函数,我们实际上增加了正确处理大量 try/catch 代码块的机会。 - djna

0

我认为引用的回答是错误的(或者可能是指2.0而不是4.0)? 对我来说听起来有点虚假。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接