尝试/最终(没有捕获)气泡会发生异常吗?


115

我几乎肯定答案是肯定的。如果我使用“尝试最终”块但不使用Catch块,则所有异常都会冒泡。正确?

对一般做法有什么想法?

赛斯

Answers:


130

是的,绝对可以。finally当然,假设您的代码块没有抛出异常,在这种情况下,它将有效地“替换”最初抛出的异常。


13
@David:您不能从C#中的finally块返回。
乔恩·斯基特

3
msdn文档还确认了以下答案:或者,您可以捕获在调用堆栈上方的try-finally语句的try块中可能引发的异常。也就是说,您可以在调用包含try-finally语句的方法的方法中,在调用该方法的方法中或在调用堆栈中的任何方法中捕获异常。如果未捕获到异常,则finally块的执行取决于操作系统是否选择触发异常展开操作。
宽带

60

对一般做法有什么想法?

是。要小心。当您的finally块正在运行时,它完全有可能在运行,因为引发了未处理的意外异常。这意味着某些东西已损坏,并且可能发生完全意外的事情。

在这种情况下,可以说根本不应该在finally块中运行代码。最终代码块中的代码可能构建为假设它依赖的子系统是健康的,而实际上它们可能会被严重破坏。finally块中的代码可能会使情况更糟。

例如,我经常看到这种情况:

DisableAccessToTheResource();
try
{
    DoSomethingToTheResource();
}
finally
{
    EnableAccessToTheResource();
}

该代码的作者正在思考“我正在暂时改变世界状况;我需要将状态恢复到被召唤之前的状态”。但是,让我们考虑一下所有可能出错的方法。

首先,调用者可能已禁用对资源的访问;在这种情况下,此代码可能会过早重新启用它。

其次,如果DoSomethingToTheResource引发异常,那么启用访问资源的正确做法是正确的???管理资源的代码意外中断。该代码实际上是“如果管理代码已损坏,请确保其他代码可以尽快调用该已损坏的代码,以使它也可能严重失败 ”。这似乎是个坏主意。

第三,如果DoSomethingToTheResource引发异常,那么我们如何知道EnableAccessToTheResource也不会引发异常?无论多么糟糕,使用资源都可能会影响清除代码,在这种情况下,原始的异常将丢失并且问题将更难以诊断。

我倾向于不使用try-finally块来编写这样的代码:

bool wasDisabled = IsAccessDisabled();
if (!wasDisabled)
    DisableAccessToTheResource();
DoSomethingToTheResource();
if (!wasDisabled)
    EnableAccessToTheResource();

现在除非状态需要改变,否则状态不会改变。现在,调用者的状态不再混乱。现在,如果DoSomethingToTheResource失败,那么我们将不会重新启用访问。我们假设某些东西已被严重破坏,并且不冒险通过尝试保持运行代码来使情况恶化。如果可以的话,让呼叫者解决问题。

那么什么时候运行finally块是个好主意?首先,在预期到异常时。例如,您可能希望锁定文件的尝试可能会失败,因为其他人已将其锁定。在这种情况下,捕获异常并将其报告给用户是有意义的。在这种情况下,减少了关于破坏的不确定性;您不太可能通过清理来使事情变得更糟。

其次,当您要清理的资源是稀缺的系统资源时。例如,在finally块中关闭文件句柄很有意义。(“使用”当然是编写try-finally块的另一种方式。)文件的内容可能已损坏,但是您现在无法执行任何操作。文件句柄最终将被关闭,因此它最好尽快而不是稍后。


7
在错误处理方面,还有更多示例说明了我们作为一个行业还如何真正发挥作用。并不是说我有什么比例外更好的建议了,但是我希望未来会更有可能导致合理而正确地采取正确的行动。
乔恩·斯基特

在vb.net中,Finally块中的代码有可能知道正在处理的异常。如果一个人设置了一个异常层次结构来区分“没有做;否则就好了”和“状态坏了”,那么只有在挂起的异常不是坏异常之一的情况下,才能运行finally块。我喜欢让处理程序/清除例程捕获异常并抛出DisposerFailedException(属于“不良”类别,并将原始异常作为InnerException包含在内)。我希望看到一个标准的iDisposableEx接口,该接口带有Dispose(Ex作为例外)来简化此操作。
超级猫

尽管我理解在某些情况下对象处于不良状态时可能会发生异常,并且尝试进行清理会使情况变得更糟,但我认为应该在清理代码中处理此类问题,可能需要借助委托。例如,一个锁定包装器可以公开一个“ CheckIfSafeToUnlock(Ex as Exception)”函数委托,该函数委托可以在锁定对象处于无效状态时设置,否则可以清除。在释放锁之前,包装程序可以检查委托;如果为非空,则可以运行它并仅在返回true时释放锁。
supercat

如果使用包装器,则委托可以使操纵对象状态的代码在中断时选择适当的清除操作。此类操作可能包括修复数据结构并返回True,引发另一个“更严重的”异常,该异常包装原始异常,或者让原始异常渗透,同时返回False。
超级猫

2
埃里克(Eric),感谢您的出色回答。我想我应该问两个问题,因为您和乔恩都是正确的。无论如何,您都会被投票。感谢您对这个问题的关注。
塞斯·斯皮尔曼
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.