一、概述资源管理
什么是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针对值类型变量是可以做到资源释放的,但是对于引用类型没有实际意义,值为空但是内存空间仍然存在。