C# finally块抛出异常时行为不一致的原理咨询
C# finally块抛出异常的差异化表现底层逻辑解析
核心前置规则
.NET runtime的异常处理遵循两个核心逻辑:
- 只要异常还在用户代码的栈中向上传播(上层仍有可匹配的catch块),所有待执行的finally块一定会在异常进入上层catch块前执行
- 当异常即将逃逸出所有用户代码(无任何上层catch可捕获)时,runtime会先标记该异常为根未处理异常、输出异常栈信息,再执行剩余待执行的finally块,且finally块中抛出的新异常会被直接丢弃,不会覆盖已标记的根未处理异常
第一段代码(嵌套try/catch)逻辑解析
代码如下:
static void Main(string[] args) { try { try { WriteLine("1 Entered try block"); throw new ArithmeticException("EXC 1 InnerTry"); } catch (Exception e) { WriteLine("2 Entered inner catch block, exception:"); WriteLine(e.Message); throw new ArithmeticException("EXC 2 InnerCatch"); } finally { WriteLine("3 Entered finally block."); throw new ArithmeticException("EXC 3 InnerFinally"); } } catch (Exception e) { WriteLine("4 Entered outer catch block, exception:"); WriteLine(e.Message); } }
执行流程:
- 内层try抛出
EXC 1,被内层catch捕获处理后,抛出EXC 2 EXC 2要向上层传播,首先必须执行同层级的finally块- finally块内抛出
EXC 3,此时异常仍在用户代码栈内传播,上层仍有catch可捕获,因此新抛出的EXC 3会直接覆盖正在传播的EXC 2 EXC 3向上传播被外层catch捕获,因此最终输出的异常信息为EXC 3
对应输出结果:
1 Entered try block 2 Entered inner catch block, exception: EXC 1 InnerTry 3 Entered finally block. 4 Entered outer catch block, exception: EXC 3 InnerFinally
第二段代码(无外层catch)逻辑解析
代码如下:
static void Main(string[] args) { try { WriteLine("1 Entered try block"); throw new ArithmeticException("EXC 1 InnerTry"); } catch (Exception e) { WriteLine("2 Entered inner catch block, exception:"); WriteLine(e.Message); throw new ArithmeticException("EXC 2 InnerCatch"); } finally { WriteLine("3 Entered finally block."); throw new ArithmeticException("EXC 3 InnerFinally"); } }
执行流程:
- try抛出
EXC 1,被catch捕获处理后,抛出EXC 2 EXC 2要向上传播,runtime首先判断:Main是程序入口点,上层无任何用户代码的catch块,该异常属于未处理异常- runtime先输出未处理异常的栈信息(即
Unhandled exception. System.ArithmeticException: EXC 2 InnerCatch那行) - 再执行待执行的finally块,虽然finally里抛出了
EXC 3,但此时runtime已经标记了EXC 2为根未处理异常,后续异常会被直接丢弃,不会有任何输出 - 进程终止
对应输出结果,EXC 3无任何异常信息输出:
1 Entered try block 2 Entered inner catch block, exception: EXC 1 InnerTry Unhandled exception. System.ArithmeticException: EXC 2 InnerCatch at ExceptionExample.Program.Main(String[] args) in D:\csharp\ExceptionExample.cs:line 77 3 Entered finally block.
常见疑问解答
为什么两种场景表现不一致?
核心差异是finally抛出新异常时,上层是否还有用户态catch块可接收异常:
- 有上层catch时,异常仍在用户代码栈传播过程中,新异常会直接覆盖正在传播的旧异常
- 无上层catch时,runtime已经将第一个即将逃逸出用户代码的异常标记为根未处理异常,后续finally抛出的异常会被直接丢弃
finally的具体执行时机是什么?
分两种场景:
- 异常可被上层用户代码catch捕获:finally一定在异常传递到上层catch块之前、栈回溯到上层处理逻辑之前执行
- 异常为无匹配catch的未处理异常:finally会在runtime输出未处理异常栈信息之后、进程终止之前执行,此时异常的栈回溯已经完成
finally是否一定在异常处理栈回溯前执行?
只有当异常还在用户代码栈内传播(有上层catch)时成立。如果异常已经是逃逸出所有用户代码的未处理异常,栈回溯会先完成(输出异常栈),再执行finally块。
内容的提问来源于stack exchange,提问作者Jake1234
相关产品推荐
相关产品推荐

