您当前的位置: 首页 > 

顺其自然~

暂无认证

  • 2浏览

    0关注

    1317博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

IDisposable

顺其自然~ 发布时间:2020-12-02 14:08:00 ,浏览量:2

IDisposable

MSDN的解释:IDisposable Interface:Provides a mechanism for releasing unmanaged resources(提供释放非托管资源的机制)。

定义一种释放分配的资源的方法。

Dispose

dispose是处理、处置的意思。

IDisposable下面有一个方法:void Dispose();

Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources。

执行(与释放或重置非托管资源相关的)应用程序定义的任务。

 

首先我们需要明确什么是C#(或者说.NET)中的资源,打码的时候我们经常说释放资源,那么到底什么是资源,简单来讲,C#中的每一种类型都是一种资源,而资源又分为托管资源和非托管资源,那这又是什么?!

托管资源:由CLR管理分配和释放的资源,也就是我们直接new出来的对象;

非托管资源:不受CLR控制的资源,也就是不属于.NET本身的功能,往往是通过调用跨平台程序集(如C++)或者操作系统提供的一些接口,比如Windows内核对象、文件操作、数据库连接、socket、Win32API、网络等。

假设我们要使用FileStream,我们通常的做法是将其using起来,或者是更老式的try…catch…finally…这种做法,因为它的实现调用了非托管资源,所以我们必须用完之后要去显式释放它,如果不去释放它,那么可能就会造成内存泄漏(所谓内存泄露就是虽然资源已经用完了,但是资源占用的这块内存一直被占用,不被释放,导致可用内存减少)。

这听上去貌似很简单,但我们编码的时候可能很多时候会忽略掉释放资源这个问题,.NET的垃圾回收又如何帮我们释放非托管资源,接下来我们一探究竟吧,一个标准的释放非托管资源的类应该去实现IDisposable接口:

public class MyClass:IDisposable
{
    /// 执行与释放或重置非托管资源关联的应用程序定义的任务。
    public void Dispose()
    {
    }
}

我们实例化的时候就可以将这个类using起来:(mc只在{}中起作用)

using(var mc = new MyClass())
{
}

看上去很简单嘛,但是,要是就这么简单的话,也没有这篇文章的必要了。如果要实现IDisposable接口,我们其实应该这样做:

  1. 实现Dispose方法;

  2. 提取一个受保护的Dispose虚方法,在该方法中实现具体的释放资源的逻辑;

  3. 添加析构函数;

  4. 添加一个私有的bool类型的字段,作为释放资源的标记

接下来,我们来实现这样的一个Dispose模式:

public class MyClass : IDisposable
{
    /// 
    /// 模拟一个非托管资源
    /// 
    private IntPtr NativeResource { get; set; } = Marshal.AllocHGlobal(100);
    /// 
    /// 模拟一个托管资源
    /// 
    public Random ManagedResource { get; set; } = new Random();
    /// 
    /// 释放标记
    /// 
    private bool disposed;
    /// 
    /// 为了防止忘记显式的调用Dispose方法
    /// 
    ~MyClass()
    {
        //必须为false
        Dispose(false);
    }
    /// 执行与释放或重置非托管资源关联的应用程序定义的任务。
    public void Dispose()
    {
        //必须为true
        Dispose(true);
        //通知垃圾回收器不再调用终结器
        GC.SuppressFinalize(this);
    }
    /// 
    /// 非必需的,只是为了更符合其他语言的规范,如C++、java
    /// 
    public void Close()
    {
        Dispose();
    }
    /// 
    /// 非密封类可重写的Dispose方法,方便子类继承时可重写
    /// 
    /// 
    protected virtual void Dispose(bool disposing)
    {
        if (disposed)
        {
            return;
        }
        //清理托管资源
        if (disposing)
        {
            if (ManagedResource != null)
            {
                ManagedResource = null;
            }
        }
        //清理非托管资源
        if (NativeResource != IntPtr.Zero)
        {
            Marshal.FreeHGlobal(NativeResource);
            NativeResource = IntPtr.Zero;
        }
        //告诉自己已经被释放
        disposed = true;
    }
}

如果不是虚方法,那么就很有可能让开发者在子类继承的时候忽略掉父类的清理工作,所以,基于继承体系的原因,我们要提供这样的一个虚方法。

其次,提供的这个虚方法是一个带bool参数的,带这个参数的目的,是为了释放资源时区分对待托管资源和非托管资源,而实现自IDisposable的Dispose方法调用时,传入的是true,而终结器调用的时候,传入的是false,当传入true时代表要同时处理托管资源和非托管资源;而传入false则只需要处理非托管资源即可。

那为什么要区别对待托管资源和非托管资源?在这个问题之前,其实我们应该先弄明白:托管资源需要手动清理吗?不妨将C#的类型分为两类:一类实现了IDisposable,另一类则没有。前者我们定义为非普通类型,后者为普通类型。非普通类型包含了非托管资源,实现了IDisposable,但又包含有自身是托管资源,所以不普通,对于我们刚才的问题,答案就是:普通类型不需要手动清理,而非普通类型需要手动清理。

而我们的Dispose模式设计思路在于:如果显式调用Dispose,那么类型就该按部就班的将自己的资源全部释放,如果忘记了调用Dispose,那就假定自己的所有资源(哪怕是非普通类型)都交给GC了,所以不需要手动清理,所以这就理解为什么实现自IDisposable的Dispose中调用虚方法是传true,终结器中传false了。

同时我们还注意到了,虚方法首先判断了disposed字段,这个字段用于判断对象的释放状态,这意味着多次调用Dispose时,如果对象已经被清理过了,那么清理工作就不用再继续。

但Dispose并不代表把对象置为了null,且已经被回收彻底不存在了。但事实上,对象的引用还可能存在的,只是不再是正常的状态了,所以我们明白有时候我们调用数据库上下文有时候为什么会报“数据库连接已被释放”之类的异常了。

所以,disposed字段的存在,用来表示对象是否被释放过。

关注
打赏
1662339380
查看更多评论
立即登录/注册

微信扫码登录

0.3020s