c# dispose 和 finalizer 的区别

来源:这里教程网 时间:2026-02-21 17:36:39 作者:

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()
写对、用对、测对。

相关推荐