Dispose是可控、可预测的资源释放,Finalize是GC在不确定时机触发的被动补救机制;前者需实现IDisposable并显式调用,后者为语法糖且不应手动调用。
根本区别就一句话:Dispose() 是你控制的、可预测的资源释放;Finalize(即析构函数 ~ClassName())是 GC 在不确定时间、不确定线程上被动触发的“补救机制”。你不调用 Dispose(),程序可能跑着跑着就卡住或报“无法访问已关闭的文件”这类错误;你不写 Finalize,只要 Dispose() 写对了,系统照样稳如老狗。
Dispose() 必须实现 IDisposable 接口,由你显式调用(比如 obj.Dispose() 或用 using 语句块)Finalize 是 C# 语法糖,编译后变成 protected override void Finalize(),GC 自动调用,你不能直接调用它(也不该尝试)Finalize 至少要等两次垃圾回收:第一次标记 + 放入 freachable 队列,第二次才真正执行 —— 这意味着非托管资源可能被锁住几十毫秒甚至几秒Finalize,它的生命周期会被拉长,拖慢 GC 效率,还可能引发对象间析构顺序错乱(比如 A 析构时访问了已被 B 析构掉的句柄)绝大多数情况下:只实现 Dispose(),**完全不用写 ~ClassName()**。只有当你类里直接持有非托管资源(比如 IntPtr、SafeHandle 子类、P/Invoke 返回的句柄),且没用现成的托管包装(如 FileStream 已帮你管好了),才需要加 Finalize 作为兜底。
HBITMAP、HDC)等 —— 实现 IDisposable,并在 Dispose(true) 中释放它们List、Dictionary 等纯托管对象 —— 完全不需要 IDisposable,更别提 Finalize
IDisposable 成员(比如自己 new 出的 MemoryStream),那你也要实现 IDisposable,并在自己的 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() 执行 —— 即使中间抛异常、提前 return、甚至 Environment.FailFast(),它都守得住。
using (var fs = new FileStream("log.txt", FileMode.Append))
{
f
s.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() 写对、用对、测对。