抛出-捕获逻辑

4
try
{
    try
    {
        throw new Exception("From Try");
    }
    catch
    {
        throw new Exception("From Catch");
    }
    finally
    {
        throw new Exception("From Finally");
    }
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

上述代码的输出结果是:From Finally
为什么不是From Catch呢?
或者说,我如何在外部捕获并记录两个异常?

有点棘手...期待看到这个问题的答案。 - riwalk
8个回答

6
因为 finally 块在 catch 块之后执行,会覆盖异常。
当在处理早期异常时发生异常时,第一个异常将丢失。
如何从外部捕获和记录两个异常?
1.不要在 finally 块中抛出异常。这总是一个坏主意。
2.如果您想在内部 catch 块中记录日志,请使用 throw;或将第一个异常作为新异常的 InnerException 传递。这就是 InnerException 的存在原因。

当然我并不是在 finally 块中手动抛出异常,异常可能在释放资源时在 finally 块中被抛出,这是正常的行为,我希望在那里收到异常通知,这并不与我想要“来自 Catch”的异常冲突。 - DxCK
@dxck,这并不是很正常。释放资源的代码通常不应该抛出异常。你有具体的例子吗? - H H
例如:由于磁盘空间不足,写入临时文件失败,在最终块中我想删除该文件,但是由于IO错误或其他人干扰了文件(例如磁盘清理程序),删除操作也失败了。 - DxCK
在临时文件成功打开后,您可以并且应该编写 finally 部分,以便它不会抛出异常。关闭操作不会抛出异常,在这些条件下,删除操作也不会抛出异常。 - H H
类型 System.ServiceModel.ClientBase 有以下方法:Abort()Close()Dispose()。在调用 Abort() 后,再调用 Close()Dispose() 将会抛出对象已关闭的异常。 - DxCK
显示剩余2条评论

3
这是C#语言规范定义的行为。在try块内抛出的异常会被中止处理,而在finally块内抛出的异常将被处理。相关章节8.9.5 throw语句解释了异常的传播方式:
  • 当前函数成员,在每个包围抛出点的try块中进行检查。对于每个语句S,从最内层的try语句开始,以最外层的try语句结束,评估以下步骤:

    • 如果S的try块包围抛出点,并且S有一个或多个catch子句,则按出现顺序检查catch子句,以定位适合的异常处理程序。第一个指定异常类型或异常类型基类的catch子句被视为匹配项。通用的catch子句(§8.10)被视为任何异常类型的匹配项。如果找到了匹配的catch子句,则通过将控件转移到该catch子句的块来完成异常传播。

    • 否则,如果S的try块或catch块包围抛出点,并且S有一个finally块,则控件将转移到finally块。如果finally块抛出另一个异常,则当前异常的处理将被终止。否则,当控制到达finally块的结束点时,将继续处理当前异常。


1

添加额外的try-catch块,如下所示:

try {
    Exception fromCatch = null;
    try {
        throw new Exception("From Try");
    }
    catch {
        try {
            throw new Exception("From Catch");
        }
        catch (Exception e) {
            // catch failed -> store exception
            fromCatch = e;
        }
    }
    finally {
        try {
            throw new Exception("From Finally");
        }
        catch (Exception e) {
            // i can think of better exception merging... but this shows the idea
            throw new Exception(e.Message, fromCatch);
        }
        // throw fromCatch, in case "From Finally did not happen"
        throw fromCatch;
    }
}
catch (Exception ex) {
    Console.WriteLine(ex.Message);
    if (ex.InnerException != null) {
        Console.WriteLine(ex.InnerException.Message);
    }
}

报告:

From Finally
From Catch

编辑:显然这是第二个问题的答案,因为“为什么”已经得到了充分的回答 :)


0

因为 finally 块总是会被执行。

try 
{ 
    try 
    { 
        throw new Exception("From Try"); 
        // (1) A new exception object A is created here and thrown.
    } 
    catch // (2) Exception object A is catched.
    { 
        throw new Exception("From Catch"); 
        // (3) A new exception object B is created here and thrown.
    } 
    finally // (4) Execution is forced to continue here!
    { 
        throw new Exception("From Finally"); 
        // (5) A new exception object C is created here and thrown.
    } 
} 
catch (Exception ex) // (6) Exception object C is catched.
{ 
    Console.WriteLine(ex.Message); 
} 

在步骤(3)和(5)中,每个新的异常对象都会丢弃先前的异常对象。由于finally块始终被执行,因此剩下的只有步骤(5)中的异常对象C。


0

这是一个非常好的问题,也是一个有点棘手的问题。让我们一步一步地来看:

try
{
    throw new Exception("From Try");
}
catch
{
    throw new Exception("From Catch");
}

在上面的代码中,Exception("From Try")被抛出并被catch子句捕获(到目前为止还很简单)。catch子句会抛出自己的异常,通常我们会期望它会立即被捕获(因为catch嵌套在一个更大的try-catch块中),但是...
finally
{
   throw new Exception("From Finally");
}

finally子句是保证(尝试)执行的,它首先执行,并抛出自己的异常,覆盖之前抛出的Exception("From Catch")异常。

"catch和finally一起常见的用法是在try块中获取和使用资源,在catch块中处理异常情况,并在finally块中释放资源" - MSDN Article

按照这种逻辑,我们应该尽量避免在catch和finally块中编写容易引发异常的代码。如果您担心出现您提出的情况,我建议将异常及其相关信息记录到外部文件中,以供调试参考。


0

finally语句块总是会执行,而且它总是最后一个执行的。因此,内部try语句块完成的最后一件事情就是finally语句块,而finally语句块抛出了一个异常,被外部catch语句块捕获。

不确定我是否理解问题的第二部分。


请注意,finally块并不总是运行。有许多情况下,finally块不会运行。(例如,如果try包含“快速失败”)。 - Eric Lippert
@eric,“fail fast”是什么意思? - pm100
这意味着快速失败。而不是缓慢失败。也就是说,当出现问题时,立即停止一切操作,以免造成更严重的后果。记录故障并尝试关闭文件和保存用户数据等操作可能是个好主意,但在机器人手臂撞倒一万升的布丁之前停止它可能比花费几毫秒钟尝试恢复更好。如果在finally块中放置了一个fail fast,则不会运行其他finally块,进程将立即失败。 - Eric Lippert

0
无论如何都会执行 finally。无论尝试或捕获中是否出现异常,你都会看到 "From Finally"。这实际上是 finally 子句的整个 目的 。因此,您可以在其中放置代码来清理资源等 - 即使有异常也是如此。

0

你的代码在try/catch/finally语句的每个部分都抛出了一个新的异常。当你创建新的错误时,实际上是吞噬了先前的异常。你可以使用类似以下方式将“From Try”消息添加到“From Catch”消息中:

catch(Exception ex)
{
    throw new Exception(ex.Message + ":" + "From Catch");
}

我不知道你怎么能在finally中链接它。


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