C#基础笔记——资源管理

一、概述资源管理

什么是C#(或者说是.NET)的资源?

简单的说C#的每一种类型都代表一种资源。而资源又分为两类:

托管资源:由CLR管理分配和释放发资源,即从CLR里new出来的对象。

非托管资源:不受CLR管理的对象,如:Windows内核对象、文件、数据库连接、套接字和COM对象等。

如若使用非托管资源可以通过两种方式释放资源:

1.通过析构函数(Finalizer)来释放资源,.NET Framework提供了一个Object.Finalize方法,它允许在一个对象被GC要求回收它,所使用的内存时,正确清理非托管资源,但是它的使用有损性能。

2.继承IDisposable接口,如果使用了expensive的非托管资源,则推荐使用显式释放资源继承IDisposable接口方法。

二、Dispose模式实现托管资源显式释放和非托管资源释放实现

    public class SampleClass : IDisposable
    {
        
        private IntPtr nativeResource = Marshal.AllocHGlobal(100);//创建一个非托管资源,IntPtr:一个句柄平台特定类型
        private AnotherResource managedResource = new AnotherResource();//创建一个托管资源
        private bool disposed = false;// 声明一个处理标识

        /// <summary>
        /// 实现IDisposable接口中的Dispose方法
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            //通知垃圾回收机制GC不再调用终结器(析构器)
            GC.SuppressFinalize(this);
        }

        public void Close()
        {
            Dispose();
        }

        /// <summary>
        /// 必须方法,阻止程序猿忘记了显式调用Dispose方法
        /// ~SampleClass()被成为终结器(析构器)
        /// </summary>
        ~SampleClass()
        {
            Dispose(false);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (disposed)
            {
                return;
            }
            if (disposing)
            {
                //清理托管代码
                if (managedResource!=null)
                {
                    managedResource.Dispose();
                    managedResource = null;
                }
                if (nativeResource!=null)
                {
                    Marshal.FreeHGlobal(nativeResource);
                    nativeResource = IntPtr.Zero;
                }
                //类型知道自己已经被释放
                disposed = true;
            }
        }

        public void SamplePublicMethod()
        {
            if (disposed)
            {
                throw new ObjectDisposedException("SampleClass", "SampleClass is disposed. ");
            }
        
        }
    }

    public class AnotherResource
    {
        internal void Dispose()
        {
            //
        }
    }

上面的代码给我们留下了如下疑问:

1. 如果类型需要显式释放资源,那么一定要继承IDispose接口吗?

  答案是肯定的

2. 我们如何使用SampleClass并让他在合适的时候释放呢?

  这里我推选使用语法糖using,编译器会自动为我们生产调用Dispose方法的IL代码:

 using (SampleClass sampleClass = new SampleClass())
 {
      //
 }

  在IL中上面的代码相对于:

SampleClass sampleClass;
try 
{
    sampleClass=new  SampleClass();
    //
}
finally
{
     sampleClass.Dispose();
}    

  如果存在两个类型一致的对象,可以按照以下形式完成:

using (SampleClass sampleClass = new SampleClass(),sampleanothorClass = new SampleClass())
{
  //
}

  如果两个类型不一致,则可以按照如下形式使用:

using (SampleClass sampleClass = new SampleClass())
using (SampleAnothorClass sampleAnothorClass = new SampleAnothorClass())
{
     //
}

3.~SampleClass()有什么作用?

  该方法是终结器,基于终结器会被垃圾回收器调用的特点,它会用作资源释放的补救措施。我们知道在.NET中每次使用new创建对象时,CLR会在堆上分配内存,一旦对象不在被引用,就会回收他们的内存。对于没有继承IDisposable接口的类对象,垃圾回收器直接释放对象所在内存;而实现了Dispose模式的类型,在每次创建对象的时候,CLR都会将该对象的指针放在终结列表中,垃圾回收器在回收该对象内存前,会首先将终结列表中的指针放到一个freachable队列中。同时CLR会分配专门的线程读取freachable队列内容,并调用对象的终结器(如:.~SampleClass()),这时对象才真正被标识为垃圾,并在下次进行垃圾回收时释放对象占用内存。

4. Dispose方法是否可以被多次调用,若多次调用是否会因为首次已经释放资源引起异常?

  这里的Dispose方法是可以被多次调用的,为了避免重复调用一起的异常我们引入了private bool disposed = false,在首次是否资源后将disposed赋予true值,再次被调用时直接做空返回,不会再次执行释放资源代码。

5. 为什么Dispose模式提取了一个受保护的虚拟方法?

  这里我考虑到SampleClass类可能被当作基类继承,这样父类也和子类同样需要使用Dispose方法因此定义为virtual方法。防止子类遗忘对基类的Dispose调用,所以使用protected类型。若子类继承父类的Dispose模式可以通过base.Dispose(disposing) 方式基类释放资源。

6. 为什么不用null赋值的形式进行资源释放呢?

  null针对值类型变量是可以做到资源释放的,但是对于引用类型没有实际意义,值为空但是内存空间仍然存在。

原文地址:https://www.cnblogs.com/Abel-Zhang/p/ResourceManagement.html