ChatGPT解决这个技术问题 Extra ChatGPT

Try / finally(没有 Catch)会冒泡异常吗?

我几乎肯定答案是肯定的。如果我使用 Try finally 块但不使用 Catch 块,那么任何异常都会冒泡。正确的?

对一般实践有什么想法吗?

赛斯

那么……什么是“泡沫”?它会抛出还是隐藏?

J
Jon Skeet

是的,它绝对会。当然,假设您的 finally 块不会引发异常,在这种情况下,它将有效地“替换”最初引发的异常。


@David:您不能从 C# 中的 finally 块返回。
msdn documentation 也证实了这个答案:或者,您可以捕获可能在调用堆栈更高的 try-finally 语句的 try 块中引发的异常。也就是说,您可以在调用包含 try-finally 语句的方法的方法中,或在调用该方法的方法中,或在调用堆栈中的任何方法中捕获异常。如果没有捕获到异常,finally 块的执行取决于操作系统是否选择触发异常展开操作。
E
Eric Lippert

对一般实践有什么想法吗?

是的。当心。当你的 finally 块运行时,它完全有可能正在运行,因为抛出了一个未处理的、意外的异常。这意味着某些东西被破坏了,并且可能会发生一些完全出乎意料的事情。

在这种情况下,可以说根本不应该在 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 块的另一种方式。)文件的内容可能已损坏,但现在您无能为力。文件句柄最终将被关闭,所以它最好早点而不是晚点。


在错误处理方面,我们作为一个行业还没有真正采取行动的更多例子。并不是说我有什么比例外更好的建议,但我希望未来有一些东西更有可能导致合理容易地采取正确的行动方针。
在 vb.net 中,Finally 块中的代码可以知道什么异常正在等待处理。如果一个人设置了一个异常层次结构来区分“没有这样做;状态正常”和“状态被破坏”,那么只有在挂起的异常不是坏的异常之一时才可以运行 finally 块。我喜欢让 disposer/cleanup 例程捕获异常并抛出 DisposerFailedException(属于“坏”类别,并将原始异常包含为 InnerException)。我希望看到一个带有 Dispose(Ex as Exception) 的标准 iDisposableEx 接口来促进这一点。
虽然我理解在某些情况下可能会在对象处于错误状态时发生异常并且尝试清理会使事情变得更糟,但我认为应该在清理代码中处理此类问题,可能在委托的帮助下。例如,锁包装器可以公开“CheckIfSafeToUnlock(Ex as Exception)”函数委托,该委托可以在锁定对象处于无效状态时设置,否则清除。在释放锁之前,包装器可以检查委托;如果非空,它可以运行它并仅当它返回 true 时才释放锁。
让包装器使用这样的委托将允许操纵对象状态的代码在它被中断时选择适当的清理操作。此类操作可能包括修复数据结构并返回 True,抛出另一个包装原始异常的“更严重”异常,或者在返回 False 时让原始异常渗透。
埃里克,感谢您的出色回答。我想我应该问两个问题,因为你和乔恩都是正确的。在任何情况下,你都被赞成。感谢您对问题的关注。