Dispose 是你主动喊“收摊”,Finalize 是 GC 偷偷帮你扫尾
根本区别就一句话:
Dispose()是你控制的、可预测的资源释放;
Finalize(即析构函数
~ClassName())是 GC 在不确定时间、不确定线程上被动触发的“补救机制”。你不调用
Dispose(),程序可能跑着跑着就卡住或报“无法访问已关闭的文件”这类错误;你不写
Finalize,只要
Dispose()写对了,系统照样稳如老狗。
Dispose()必须实现
IDisposable接口,由你显式调用(比如
obj.Dispose()或用
using语句块)
Finalize是 C# 语法糖,编译后变成
protected override void Finalize(),GC 自动调用,你不能直接调用它(也不该尝试) GC 调用
Finalize至少要等两次垃圾回收:第一次标记 + 放入
freachable队列,第二次才真正执行 —— 这意味着非托管资源可能被锁住几十毫秒甚至几秒 如果对象有
Finalize,它的生命周期会被拉长,拖慢 GC 效率,还可能引发对象间析构顺序错乱(比如 A 析构时访问了已被 B 析构掉的句柄)
什么时候必须写 Dispose?什么时候可以不写 Finalize?
绝大多数情况下:只实现
Dispose(),**完全不用写
~ClassName()**。只有当你类里直接持有非托管资源(比如
IntPtr、
SafeHandle子类、P/Invoke 返回的句柄),且没用现成的托管包装(如
FileStream已帮你管好了),才需要加
Finalize作为兜底。 ✅ 推荐场景:封装了数据库连接、文件句柄、Socket、GDI 对象(
HBITMAP、
HDC)等 —— 实现
IDisposable,并在
Dispose(true)中释放它们 ❌ 典型误用:类里只用了
List<string></string>、
Dictionary<int object></int>等纯托管对象 —— 完全不需要
IDisposable,更别提
Finalize⚠️ 注意:如果你的类包含一个
IDisposable成员(比如自己 new 出的
MemoryStream),那你也要实现
IDisposable,并在自己的
Dispose()里调用它的
Dispose()
标准 Dispose 模式里,disposing
参数到底控制什么?
这个布尔参数不是摆设 —— 它决定了当前调用是不是来自你手动写的
Dispose()(
true),还是来自 GC 的
Finalize(
false)。这是区分“能安全操作托管资源”和“只能碰非托管资源”的唯一开关。
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// ✅ 这里可以安全调用其他托管对象的 Dispose()
// ✅ 可以释放事件订阅、取消定时器、关闭托管流
_stream?.Dispose();
_timer?.Dispose();
}
// ⚠️ 这里只能释放非托管资源!
// ❌ 绝对不要在这里访问任何托管对象(比如 _stream.Length),因为它们可能已被 GC 回收
if (_handle != IntPtr.Zero)
{
CloseHandle(_handle);
_handle = IntPtr.Zero;
}
disposed = true;
}
}
当 disposing == true:说明是人调的
Dispose(),托管资源大概率还活着,可以放心清理 当
disposing == false:说明是 GC 在 finalizer 线程上调的,此时托管资源可能已不可用,只处理
IntPtr、
SafeHandle、
CloseHandle这类底层操作 漏掉
GC.SuppressFinalize(this)是高频失误:一旦手动调过
Dispose(),就该立刻告诉 GC “别再费劲调我的析构函数了”,否则等于白写优化
为什么 using 语句比 try/finally + Dispose() 更可靠?
因为
using编译后会自动插入
try/finally并确保
Dispose()执行 —— 即使中间抛异常、提前 return、甚至
Environment.FailFast(),它都守得住。
using (var fs = new FileStream("log.txt", FileMode.Append))
{
fs.Write(buffer, 0, buffer.Length);
// 即使这里 throw new InvalidOperationException();
// fs.Dispose() 仍会被调用
}
using要求类型必须实现
IDisposable,否则编译失败 —— 这本身就是一层静态检查 避免手写
try/finally时漏掉
Dispose()或在
catch后忘记调用(尤其多层嵌套时) 注意:如果构造函数就抛异常(比如文件被占用),
using块内变量为
null,但
Dispose()不会调用 —— 所以资源分配失败本身不依赖
Dispose清理
Finalize 和 Dispose 的边界其实很清晰:前者是留给系统兜底的退路,后者是你作为开发者该扛起的责任。现实中,95% 的 .NET 开发者一辈子都不需要亲手写
~ClassName()—— 但每个用到文件、数据库、网络、图形资源的人,都得把
Dispose()写对、用对、测对。
