江女士
C#资源回收和IDisposable接口的使用
来源:傅雄思     发布时间: 2019-06-30      浏览次数:382

字号:

源码地址:https://github.com/hiramtan/HiFramework_unity/blob/master/unity/Assets/HiFramework/Extensions/ObjectBase.cs在说资源回收之前先要说明托管资源和非托管资源。1.托管资源由CLR来维护,自动进行垃圾回收,比如数组。2.非托管资源不会进行自动垃圾回收,需要手动释放,比如句柄。但在C#中的非托管资源很多都被封装到.NET类中,当对象释放时内部方法同时释放非托管资源。比如Socket连接,在.Net中被封装为Socket类,反编译Socket类库,看到创建连接对象时其实创建了一个句柄

但是当上层用户使用socket的时候并没有发现有过释放句柄的逻辑,这部分释放逻辑由谁完成的?我们继续反编译Socket的Close方法。

最终将会调用到本篇所要涉及的IDisposable接口。在Disposable中Socket内部逻辑释放了托管资源比如缓存和非托管资源,比如连接句柄。

既然说清楚了托管资源和非托管资源,下面正式说下IDisposable接口的使用。在C#中创建的对象,数组,列表...等等都不需要考虑资源释放的问题,因为它会被CLR的垃圾回收机制自动回收。比如有个ObjectBase类,创建这个对象然后置空,它会一段时间后被自动回收: ObjectBase ob = new ObjectBase(); ob = null;那怎样确定这个对象确实是被正常回收了呢,我们引入它的析构函数:public class ObjectBase { ~ObjectBase() { Console.WriteLine("Dispose finish"); } }现在出现一个问题:创建的对象释放时是随机的不确定时长的等待自动回收机制进行收回,有没有办法主动回收这些资源呢,比如对象占用内存较大,想主动立即释放而不等待主动回收。可以写一个方法,在这个方法里释放引用的资源就可以了,这个方法可以随便起名,当然更规范的是继承IDisposable来实现Disposable方法。比如在这个方法里释放申请的托管资源和非托管资源等,现在ObjectBase类变成如下: public class ObjectBase:IDisposable { ~ObjectBase() { Console.WriteLine("Dispose finish"); }

/// <summary>执行与释放或重置非托管资源关联的应用程序定义的任务。</summary> public void Dispose() { } }现在又出现一个问题,有些用户会调用ob.Dispose()同时释放了托管资源和非托管资源,有些用户只是ob = null什么都没有做等待系统的垃圾回收。能不能有更统一的方法?当然可以,我们把释放资源的逻辑不写在Dispose()方法中,而是写在另一个方法中,比如Dispose(bool disposing)中,让用户主动调用的,或者析构函数都调用这个方法就同时做到了释放资源。这样还有一个问题,当用户主动调用Dispose()时,其实Dispose(bool disposing)被执行了两次(主动调用一次,析构一次)我们添加一个标识,标记他只能被标记一次防止多次调用。public class ObjectBase : IDisposable { private bool disposed = false; ~ObjectBase() { Dispose(false); } /// <summary>执行与释放或重置非托管资源关联的应用程序定义的任务。</summary> public void Dispose() { Dispose(true); } private void Dispose(bool disposing) { if (!disposed) {

} disposed = true; } }再继续,Dispose(bool disposing)为什么传入bool类型的变量?其实这个变量标记的是是否能够安全释放,托管资源由垃圾回收机制回收,当析构函数执行时它所引用的对象说不定早就被回收,比如再去取这个List,然后执行Clear时肯定会报错,因为List早已被回收。当用户调用Dispose接口时,这个对象还未触发垃圾回收,可以随意拿来Clear,所以逻辑又会变成: public class ObjectBase : IDisposable { private bool disposed = false; ~ObjectBase() { Dispose(false); } /// <summary>执行与释放或重置非托管资源关联的应用程序定义的任务。</summary> public void Dispose() { Dispose(true); } private void Dispose(bool disposing) { if (!disposed) { if (disposing) { //释放托管资源 } //释放非托管资源 } disposed = true; } }这样也会存在一个问题:当用户主动调用Dispose时释放非托管的逻辑执行了两次(一次主动调用,一次析构)那我们来通知系统不要执行回收该对象来避免两次执行,至此释放接口的逻辑完成: public class ObjectBase : IDisposable { private bool disposed = false; ~ObjectBase() { Dispose(false); } /// <summary>执行与释放或重置非托管资源关联的应用程序定义的任务。</summary> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (!disposed) { if (disposing) { //释放托管资源 } //释放非托管资源 } disposed = true; } }但是用户使用起来太不方便了,我们封装一下,可以继承该类方便的使用。因为释放托管资源在C#里面操作很频繁,非托管资源大部分被封装只是很少的情况下使用,我们分别把接口封装为强制重写和可重写。 public abstract class ObjectBase : IDisposable { private bool disposed = false; ~ObjectBase() { Dispose(false); } /// <summary>执行与释放或重置非托管资源关联的应用程序定义的任务。</summary> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (!disposed) { if (disposing) { DisposeManaged(); } DisposeUnmanaged(); } disposed = true; }

protected abstract void DisposeManaged();//释放托管资源

protected virtual void DisposeUnmanaged()//释放非托管资源 {

} }

  • 相关内容: