net C# 如何理解和实现 Dispose 方法
目录[*].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;
}
}
}
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页:
[1]