登录
/
注册
首页
论坛
其它
首页
科技
业界
安全
程序
广播
Follow
关于
导读
排行榜
资讯
发帖说明
登录
/
注册
账号
自动登录
找回密码
密码
登录
立即注册
搜索
搜索
关闭
CSDN热搜
程序园
精品问答
技术交流
资源下载
本版
帖子
用户
软件
问答
教程
代码
写记录
写博客
小组
VIP申请
VIP网盘
网盘
联系我们
发帖说明
道具
勋章
任务
淘帖
动态
分享
留言板
导读
设置
我的收藏
退出
腾讯QQ
微信登录
返回列表
首页
›
业界区
›
安全
›
net C# 如何理解和实现 Dispose 方法
net C# 如何理解和实现 Dispose 方法
[ 复制链接 ]
芮梦月
昨天 19:09
程序园永久vip申请,无限下载程序园所有程序/软件/数据/等
目录
.net C# 如何理解和实现 Dispose 方法
1、接口 IDisposable
2、析构函数
3、实现幂等
4、继承时的资源释放
5、性能改善
6、其他注意事项
(1)异常处理
(2)设置为 null
(3)字段 _disposed
(4)ObjectDisposedException
(5)线程安全
(6)异步释放
(7)一般情况
7、完整示例
.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),不需要手动释放,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;
}
}
}
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
net
如何
理解
实现
Dispose
相关帖子
【译】 如何使用 .NET MAUI 构建 iOS 小部件
基于.NET AgentFramework开发OpenClaw智能体框架
前端如何防止用户重复提交表单?4 种可靠方案(附防坑指南)
.NET对象转JSON,到底有几种方式?
基于.NET的Windows窗体编程之WinForms入门简介
vue甘特图vxe-gantt如何设置日期轴显示为周模式
网关实现电力配电柜 Modbus 数据转 DNP3 协议传输项目案例
飞书如何搜索文档?我用AI打造高效飞书搜索网站助手
C# .NET 周刊|2026年3月1期
回复
使用道具
举报
提升卡
置顶卡
沉默卡
喧嚣卡
变色卡
千斤顶
照妖镜
相关推荐
业界
【译】 如何使用 .NET MAUI 构建 iOS 小部件
0
998
剩鹄逅
2026-03-21
安全
基于.NET AgentFramework开发OpenClaw智能体框架
0
337
秤陷曲
2026-03-23
代码
前端如何防止用户重复提交表单?4 种可靠方案(附防坑指南)
0
536
茹静曼
2026-03-24
业界
.NET对象转JSON,到底有几种方式?
0
313
吮槌圯
2026-03-24
安全
基于.NET的Windows窗体编程之WinForms入门简介
0
815
吟氅
2026-03-24
代码
vue甘特图vxe-gantt如何设置日期轴显示为周模式
0
848
撵延兵
2026-03-25
安全
网关实现电力配电柜 Modbus 数据转 DNP3 协议传输项目案例
0
133
巫雪艷
2026-03-26
安全
飞书如何搜索文档?我用AI打造高效飞书搜索网站助手
0
212
俞秋荣
2026-03-26
业界
C# .NET 周刊|2026年3月1期
0
534
涣爹卮
2026-03-26
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
|
立即注册
回复
本版积分规则
回帖并转播
回帖后跳转到最后一页
签约作者
程序园优秀签约作者
发帖
芮梦月
昨天 19:09
关注
0
粉丝关注
28
主题发布
板块介绍填写区域,请于后台编辑
财富榜{圆}
3934307807
991125
anyue1937
9994892
kk14977
6845359
4
xiangqian
638210
5
神泱
9522
6
韶又彤
9916
7
宋子
9878
8
荪俗
9018
9
闰咄阅
9995
10
蓬森莉
9870
查看更多
今日好文热榜
195
Electron41+Vite8.0+DeepSeek桌面端AI助手|
674
OPUS编解码器在audio DSP上的移植和应用
469
SpringCloud进阶--Seata与分布式事务
987
大模型私有化部署指南:从“一键安装”到“
624
大模型私有化部署指南:从“一键安装”到“
885
SpringCloud进阶--Seata与分布式事务
171
SpringCloud进阶--Seata与分布式事务
785
Oracle SQL经典练习50题 | 附答案
205
gitru:一个由 Rust 打造的零依赖 Git 提交
929
gitru:一个由 Rust 打造的零依赖 Git 提交
266
gitru:一个由 Rust 打造的零依赖 Git 提交
855
gitru:一个由 Rust 打造的零依赖 Git 提交
426
gitru:一个由 Rust 打造的零依赖 Git 提交
694
ESP32S3 USB MSC 调试全过程记录
639
瑞芯微工业级芯加持,正点原子RK3562J开发
56
gitru:一个由 Rust 打造的零依赖 Git 提交
784
营销自动化数据驱动 - 多源数据 OLAP 架构
534
C# .NET 周刊|2026年3月1期
151
【GUI-Agent】阶跃星辰 GUI-MCP 解读---(5)
869
LangChain教程-3、Langchian进阶