finally 是收尾动作的强制执行点,只要控制流进入过 try 块,无论正常结束、抛异常、return 或 break,finally 都会执行(除非进程被强杀);它在 return 计算值之后、方法退出前运行,可修改状态但不能改变已确定的返回值(引用类型字段除外)。

finally 不是“保险丝”,而是“收尾动作的强制执行点”——它不保证资源一定被释放,只保证你写的那几行代码一定会跑完(除非进程被强杀或线程被中止)。
finally 什么时候会执行?
只要控制流进入过 try 块,无论发生什么,finally 都会执行。包括以下情况:
-
try正常执行完毕后 -
try中抛出异常,且有匹配的catch捕获并处理 -
try中抛出异常,但没有catch(未处理异常),程序即将崩溃前(取决于 CLR 异常展开机制,通常会执行) -
try或catch中遇到return、break、continue、goto
关键点:finally 在 return 计算返回值之后、方法真正退出之前运行。这意味着它能修改某些状态,但不能改变已确定的返回值(除非是引用类型字段)。
为什么 file?.Close() 比直接 file.Close() 更安全?
因为 file 可能根本没成功创建,比如 OpenWrite() 抛出 UnauthorizedAccessException 或 DirectoryNotFoundException,此时 file 仍是 null。如果 finally 里直接调用 file.Close(),就会触发新的 NullReferenceException,掩盖原始异常。
- 使用空条件操作符
file?.Close()是最简明的防护 - 更严谨的做法是加
if (file != null)判断,语义更清晰 - 不要在
finally里抛异常——它可能让原本可捕获的异常丢失,或导致应用意外终止
try-finally 和 using 语句怎么选?
using 本质是编译器对 try-finally 的语法糖,专为实现 IDisposable 接口的对象设计。二者等价于:
using (var file = new FileInfo("x.txt").OpenWrite())
{
file.WriteByte(0xF);
}
// 等价于
{
var file = new FileInfo("x.txt").OpenWrite();
try
{
file.WriteByte(0xF);
}
finally
{
file?.Dispose(); // 注意:FileStream.Dispose() 内部会调用 Close()
}
}
- 优先用
using:简洁、不易漏写、自动调用Dispose() - 必须用
try-finally的场景:需要捕获异常(catch),或资源类型没实现IDisposable,或需在finally中做非释放类操作(如日志、重置标志位) - 注意:如果
using块内抛出异常,Dispose()仍会执行;但如果Dispose()自身也抛异常,它会覆盖原异常(除非用throw;重新抛)
finally 中 return 会覆盖 try/catch 的返回值吗?
会,而且这是个极易忽略的陷阱。看这个例子:
static int GetValue()
{
try { return 1; }
catch { return 2; }
finally { return 3; } // 实际返回的是 3
}
-
finally中的return会直接结束方法,丢弃try或catch中已准备好的返回值 - 这会让调试变得困难——表面看逻辑走到了
try,结果却返回了finally的值 - 规范做法:永远不在
finally中写return、throw或任何可能中断流程的语句
真正的资源清理逻辑应该干净、无副作用、不改变控制流——否则就不是“清理”,而是“干扰”。










