惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
有赞技术团队
有赞技术团队
J
Java Code Geeks
H
Hackread – Cybersecurity News, Data Breaches, AI and More
美团技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Hugging Face - Blog
Hugging Face - Blog
人人都是产品经理
人人都是产品经理
酷 壳 – CoolShell
酷 壳 – CoolShell
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
Check Point Blog
博客园 - 【当耐特】
The GitHub Blog
The GitHub Blog
Recent Announcements
Recent Announcements
The Cloudflare Blog
Microsoft Azure Blog
Microsoft Azure Blog
腾讯CDC
Vercel News
Vercel News
IT之家
IT之家
MyScale Blog
MyScale Blog
博客园_首页
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
罗磊的独立博客

博客园 - 误会馋

日志模块 Nginx 安装过程 C# 版本控制 PostgreSQL 数据库权限、角色总结 C#中的 Awaiter C#中的协变、逆变 C#中 IAsyncEnumerable 与 IAsyncEnumerator 的使用 Vue 中 vuex 的使用 Vue 中 vue-router 的使用 前后端登录时密码的加密 Vue 中 axios 的使用和跨域问题的解决 发布 ASP.NET Core WebAPI 至 IIS 关于 Workstation Pro 的基础知识 Docker 部署 gitlab 服务器迁移 实现持久化 C# 中 == 和 .Equals() 之间的区别 实现克隆 关于扩展服务 启用扩展
net C# 如何理解和实现 Dispose 方法
误会馋 · 2026-03-25 · via 博客园 - 误会馋

.net C# 如何理解和实现 Dispose 方法

1、接口 IDisposable

接口 IDisposable 包含了一个名为 Dispose 的方法。

namespace System;

public interface IDisposable
{
    void Dispose();
}

实现了接口 IDisposable 的类,才可以使用 using 语句进行调用,以实现资源的释放,否则将报错错,提示:using 语句中使用的类型必须实现 System.IDisposable

public class DbHelper : IDisposable
{
    //...
    
    public void Dispose()
    {
        //...
    }
}

// 使用 using 语句进行调用
using (var helper = new DbHelper())
{
    //...
}

// 使用 using 语句编译后等价于
DbHelper helper = null;
try
{
    helper = new DbHelper();
    //...
}
finally
{
    helper?.Dispose();
}

2、析构函数

C# 编译器不会为没有显式定义析构函数的类自动生成析构函数。只有当你显式定义了析构函数(~ClassName())时,编译器才会生成 Finalize 方法【析构函数是 C# 语法糖,最终编译为 Finalize 方法】。

析构函数的调用时机不可控。析构函数将仅用于释放非拖管资源。拖管资源由 GC 进行释放,释放时机和顺序不可控。若拖管资源再由析构函数来释放,则可能导致程序崩溃:已释放的资源(不存在的资源)在析构函数中再次被释放。

功能上,析构函数与 Dispose 方法重复进行了相同的工作。但二者角色不同,在实际的开发实践中,析构函数通常用于对忘记主动调用 Dispose 方法的补救。

因此,最佳实践是,将释放工作交给另一个方法 void Dispose(bool disposing),然后通过disposing 进行区分,到底调用是来自 Dispose 方法还是来自析构函数。示例如下:

public class DbHelper : IDisposable
{
    //...
    
    public void Dispose()
    {
        Dispose(true);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // 释放托管资源
            // ...
        }

        // 释放非托管资源(无论手动/GC 都必须执行)
        // ...
    }

    ~DbHelper()
    {
        Dispose(false);
    }
}

3、实现幂等

所谓幂等,即函数被调用次数不同,不影响结果。即,无论调用多少次,结果完全一样,不会报错、不会崩溃。

Dispose 方法必须实现幂等,因为代码里可能不小心多次调用 Dispose 方法。如果不做幂等,程序会报错、崩溃、资源重复释放。最佳实践中,资源释放工作交给了 void Dispose(bool disposing),因此,void Dispose(bool disposing) 实现幂等即可。

实现幂等的方式,比较简单,即如果已经释放资源,则不再进行资源释放工作,通过字段 private bool _disposed = false; 来实现。

public class DbHelper : IDisposable
{
    //...
    
    public void Dispose()
    {
        Dispose(true);
    }

    // 实现幂等的关键字段:
    private bool _disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        //如果已经释放资源,则不再进行资源释放工作
        if (_disposed) { return; }

        if (disposing)
        {
            // 释放托管资源
            // ...
        }

        // 释放非托管资源(无论手动/GC 都必须执行)
        // ...

        _disposed = true;
    }

    ~DbHelper()
    {
        Dispose(false);
    }
}

4、继承时的资源释放

继承时,子类是需要释放父类实现中所占用的资源。因此,void Dispose(bool disposing) 最好应用 protected virtual 进行修饰,以便子类调用。

另,子类的 void Dispose(bool disposing) 也应具有幂等特性。在该方法上,父类与子类,应各自维护自己的幂等特性。

当类不需要被继承时( sealed 类),可以简化 Dispose 模式,即不需要用 virtual 修饰方法。

以下是子类示例:

public class Derived : DbHelper
{
    // 其他 Derived 类特有的成员

    // 注意:不要重新实现 Dispose() 方法,因为基类已经实现了 IDisposable
    // 如果重新实现,会隐藏基类的 Dispose(),导致通过基类引用和子类引用调用 Dispose() 时行为不一致,破坏多态性。
    // 只需重写 void Dispose(bool disposing) 方法即可
    //public void Dispose()
    //{
    //    Dispose(true);
    //}

    // 子类也应使 override 的 Dispose(bool disposing)实现幂等
    private bool _disposed = false;

    protected override void Dispose(bool disposing)
    {
        //如果已经释放资源,则不再进行资源释放工作
        if (_disposed) { return; }
        
        if (disposing)
        {
            // 释放 Derived 类特有的托管资源
            // ...
        }

        // 释放 Derived 类特有的非托管资源(无论手动/GC 都必须执行)
        // ...

        // 调用基类的 Dispose 方法,确保基类资源也得到释放
        base.Dispose(disposing);

        _disposed = true;
    }

    // 子类不必实现析构函数,因为父类有析构函数。在析构的过程中,子类析构函数执行先于父类析构函数。
    // 但无论如何,父类析构函数总会执行,又因为 override 了 void Dispose(bool disposing) 的缘故,
    // 父类的析构函数中 Dispose(false) 语句,由于类的多态特性,将调用子类的 void Dispose(bool disposing) 方法。
    // 仅调用 Dispose(false) 的子类析构函数,是冗余的。
    //~Derived()
    //{
    //    Dispose(false);
    //}
}

5、性能改善

有析构函数的对象会被放入 Finalization 队列,增加 GC 负担。

Dispose 方法用于主动地立即地资源释放,当我们主动释放资源后,垃圾回收器并不知情,因此需要知会垃圾回收器,请求不必调用终结器。

当类没有实现析构函数,不通过其进行兜底时,则不必知会垃圾回收器。如果没有析构函数,GC.SuppressFinalize(this);则是无意义的。

高性能场景:避免使用析构函数。

public void Dispose()
{
    Dispose(true);
    
    // 请求系统不要调用这个对象的终结器,以提高性能
    GC.SuppressFinalize(this);
}

6、其他注意事项

(1)异常处理

禁止在 Dispose 中抛异常。因为可能会导致(编译生成的等效的) finally 块执行中断,进而资源泄漏,违背释放资源的初衷。

(2)设置为 null

纯托管对象(如 List<T>),不需要手动释放,GC 会自动回收,设置为 null 不会立即释放内存,但可以断开引用,帮助 GC 更早发现对象不可达。其他的,如实现 IDisposable 的托管对象、非托管资源以及静态资源等,直接设置为 null 是无效的释放资源。

(3)字段 _disposed

它作为对象的字段存在,布尔类型,在系统调用析构函数时,它仍然没有消失,仍处理生命周期内。

(4)ObjectDisposedException

主动调用 Dispose 方法后,对象可能还在其生命周期中。如果此时调用对象的其他方法,则可能产生未知的错误。因此,需要在其他方法中检查是否已经释放资源。

public class DbHelper : IDisposable
{
    private bool _disposed = false;
    
    public void ExecuteQuery(string sql)
    {
        if (_disposed)
        {
            throw new ObjectDisposedException(nameof(DbHelper));
        }
        
        // 正常执行逻辑
    }
}

如果可行,最好在调用 Dispose 方法后,立即将对象设置为 null。

if (disposing)
{
    _managedResource?.Dispose();
    _managedResource = null;
}
(5)线程安全

当需要时,void Dispose(bool disposing) 应实现其线程安全,避免多线程同时进入该方法从而导致错误发生。

protected virtual void Dispose(bool disposing)
{
    lock (_lockObject)
    {
        if (_disposed) { return; }
        
        if (disposing)
        {
            // 释放 Derived 类特有的托管资源
            // ...
        }

        // 释放 Derived 类特有的非托管资源
        // ...

        base.Dispose(disposing);
        
        _disposed = true;
    }
}

除使用上述锁方式外,还可使用 Interlocked 进行。

// 使用 Interlocked 的轻量级实现
private int _disposed = 0;

protected virtual void Dispose(bool disposing)
{
    if (Interlocked.Exchange(ref _disposed, 1) == 0)
    {
        if (disposing)
        {
            // 释放托管资源
        }

        // 释放 Derived 类特有的非托管资源
        // ...

        // 释放非托管资源
        base.Dispose(disposing);
    }
}

(6)异步释放

.NET Core 3.0+ 引入了异步释放模式,对于需要异步释放资源的场景(如异步文件操作、网络连接等)非常重要。

通过实现接口 IAsyncDisposable 的 DisposeAsync 方法的方式,提供了一种异步释放非托管资源的机制。

关于 异步释放资源的实现 与注意事项,此处略。

namespace System
{
    public interface IAsyncDisposable
    {
        // 返回结果: 一个 task ,用于异步释放操作.
        ValueTask DisposeAsync();// 非托管资源释放、异步释放或重置
    }
}
(7)一般情况

一般来说,实现了接口IDisposable 的类的实例,其释放应放在 disposing = true 中进行,因为它们已具有析构函数进行兜底。例如,数据库的连接的关闭。

部分类(如 DbConnection)有 Close 方法,本质是 Dispose 的封装。

if (disposing)
{
    _managedResource?.Dispose();
    _managedResource = null;

    SqliteConn.Close();
}

7、完整示例

public class DbHelper : IDisposable
{
    //...
    public void ExecuteQuery(string sql)
    {
        if (_disposed)
        {
            throw new ObjectDisposedException(nameof(DbHelper));
        }
        
        // 正常执行逻辑
    }
    
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private bool _disposed = false;
    protected readonly object _lockObject = new object();

    protected virtual void Dispose(bool disposing)
    {
        lock (_lockObject)
        {
            if (_disposed) { return; }

            if (disposing)
            {
                // 释放托管资源
                // ...
            }

            // 释放非托管资源
            // ...

            _disposed = true;
        }
    }

    ~DbHelper()
    {
        Dispose(false);
    }
}

public class Derived : DbHelper
{
    // 其他 Derived 类特有的成员
    //...

    private bool _disposed = false;

    protected override void Dispose(bool disposing)
    {
        //基类和子类使用相同的锁对象,保证整个释放过程是原子的
        lock (_lockObject)
        {
            if (_disposed) { return; }
            
            if (disposing)
            {
                // 释放 Derived 类特有的托管资源
                // ...
            }

            // 释放 Derived 类特有的非托管资源
            // ...

            base.Dispose(disposing);
            
            _disposed = true;
        }
    }
}