如何实现标准的dispose

简介: 如何实现标准的dispose

前面的文章我们说过,如果对象包含非托管资源那么就必须要正确的清理,现在我们就来说一下如何清理。针对非托管资源 .NET 会采用一套标准的模式来完成清理工作。也就是说如果开发人员自己编写的类中存在非托管资源,那么这个类的使用者就会认为这个类遵循 .NET 的垃圾清理模式。标准的 dispose 模式即实现了 IDisposable 接口,又实现了 finalizer ,这样就可以在客户端忘记调用 IDisposable.Dispose 的情况下也可以释放资源。


Tip:在 .NET 中访问非托管资源还可以通过 System.Runtime.Interop.SafeHandle 的派生类来访问,该类正确实现了标准的 dispose 。


零、基类与子类需要注意

在详细讲解具体如何正确实现 dispose 模式前我们要了解基类与子类需要注意的内容。


1.基类

  • 实现 IDisposable 接口,以便释放资源;
  • 如果类本身包含非托管资源,那么就需要添加 finalizer 来防止客户端忘记调用 Dispose 方法;
  • Dispose 方法和 finalizer 资源释放必须交给虚方法,这样子类才可以重写释放资源的方法。


2.子类

  • 假如子类需要自己释放资源,那就必须重写基类所定义的释放资源的虚方法,如果基类不存在这个虚方法那就不需要重写;
  • 假如子类中存在使用非托管资源的情况,那就必须实现 finalizer ;
  • 重写基类释放资源的函数时,一定要调用基类的同名函数。

一、详解

当我们编写的类中存在必须释放的资源的时候,我们就必须实现 IDisposable 接口,这个接口只包含一个无返回值的无参 Dispose 方法。在实现该方法时又如下几个方面需要注意的:


  1. 释放所有不再使用的非托管资源;
  2. 释放所有不再使用的托管资源;
  3. 设置状态标志,表示对象已被清理过,如果有代码调用被清理过的对象那么就可以通过这个标志得知,进而手动抛出 ObjectDisposedException 异常;
  4. 通过 GC.SupperssFinalize(this) 来阻止垃圾回收器重复清理已被清理过的对象。


虽然实现 Dispose 方法在保证释放托管资源和非托管资源的情况下又能保证程序性能不会下降,但是它依然存在问题。子类在清理自身资源的同时还必须保证基类资源也被清理掉。这时大部分开发人员能想到的解决方法就是重写 finalizer并调用基类释放资源的方法,或者给 Dispos 方法添加新的逻辑并调用基类释放资源的方法。这两种方法都有类似的任务需要完成,因此这两种方法包含了大量重复的代码,这时我们就需要将这两种方法中重复的代码提取到一个 protected 级别的虚函数种,这样基类只需写好核心逻辑,子类重写这个方法用来释放自己的资源就可以了。一般我们会将这个方法定义成如下的样子:

protected virtual void Dispose(bool isDisposing)

在上述代码中如果 isDisposing 为 true,则代表托管资源和非托管资源都要清理,如果为 false 则代表只清理非托管资源。不管是 true 还是 false 我们都需要在子类中调用基类的 Dispose(bool) 方法。下面我们通过一段代码来看一下我们该怎么实现上述所说的内容。

public class DemoBase:IDisposable
{
    private bool baseDisposed = false;
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    protected virtual void Dispose(bool isDisposing)
    {
        if(baseDisposed)
        {
            return;
        }
        if(isDisposing)
        {
            //清理托管资源
        }
        //清理非托管资源
        baseDisposed=true;
    }
    public void Method()
    {
        if(baseDisposed)
        {
            throw new ObjectDisposedException("Demo","资源已被释放!");
        }
        //more code
    }
}
public class Demo:DemoBase
{
    private bool disposed = false;
    protected override void Dispose(bool isDisposing)
    {
        if(baseDisposed)
        {
            return;
        }
        if(isDisposing)
        {
            //清理托管资源
        }
        //清理非托管资源
        base.Dispose(isDisposing);
        disposed=true;
    }
    //more code
}

上述代码中 Demo 类继承自 DemoBase,并重写了 Dispose 方法。我们在代码中看到基类和子类都使用了标志状态(baseDisposed,disposed),这里为什么不使用同一个标志状态呢?主要是因为子类在释放资源的时候有可能会把标志状态改为 true,这时在运行基类的 Dispose(bool) 方法基类会认为资源已经释放过了。


Tip:Dispose(bool) 和 finalizer 都必须编写的很可靠,同时具备幂等性。也就是说无论多少次调用 Dispose(bool) 的效果都是一样的。并且我们在释放资源的时候我们不应该进行除了释放资源以外的操作。


目录
相关文章
|
计算机视觉 Python
yolov5+deepsort目标检测与跟踪(毕业设计+代码)
yolov5+deepsort目标检测与跟踪(毕业设计+代码)
|
11月前
|
缓存 安全 数据安全/隐私保护
如何根据请求场景选择 GET 或 POST 请求方法?
【10月更文挑战第27天】根据不同的请求场景,综合考虑数据传输目的、安全性、数据量大小、幂等性要求以及缓存需求等因素,合理地选择GET或POST请求方法,能够更好地实现客户端与服务器之间的数据交互,提高系统的性能和安全性。
408 64
|
11月前
|
应用服务中间件 nginx Docker
Dockerfile
【11月更文挑战第01天】
169 10
7-3|windows删除目录下的所有文件的命令
7-3|windows删除目录下的所有文件的命令
|
JSON Kubernetes Linux
k8s备份恢复实践--velero
使用Velero备份k8资源到minio,阿里云oss,七牛云Kodo
618 8
|
存储 Kubernetes Perl
在K8S中,如何知道Pod的数据存储在哪里?
在K8S中,如何知道Pod的数据存储在哪里?
|
负载均衡 应用服务中间件 nginx
|
网络协议 物联网 开发工具
MQTT常见问题之MQTT无权连接如何解决
MQTT(Message Queuing Telemetry Transport)是一个轻量级的、基于发布/订阅模式的消息协议,广泛用于物联网(IoT)中设备间的通信。以下是MQTT使用过程中可能遇到的一些常见问题及其答案的汇总:
|
存储 Kubernetes 测试技术
k8s使用ceph实现动态持久化存储
简介 本文章介绍如何使用ceph为k8s提供动态申请pv的功能。ceph提供底层存储功能,cephfs方式支持k8s的pv的3种访问模式ReadWriteOnce,ReadOnlyMany ,ReadWriteMany ,RBD支持ReadWriteOnce,ReadOnlyMany两种模式 访问模式只是能力描述,并不是强制执行的,对于没有按pvc声明的方式使用pv,存储提供者应该负责访问时的运行错误。
3579 0
|
对象存储
oss上传图片的图片名和url路径后缀不一致问题分析与说明
oss上传图片的图片名和url路径后缀不一致问题分析与说明
1022 0