详解IDisposable和Finalize的意义

本文主要讨论IDisposable和Finalize如何释放资源,以及微软提供并推荐的一个标准IDisposable编程模式。

本文写的通俗易懂,如果你还看不懂,就自己去反省一下为什么自己水平这么烂....

IDisposable接口主要提供一个Dispose方法,该方法用来释放资源,准确的说是用来释放非托管资源。理论上不是必须有 IDisposable 接口,你也能自己设计出一个释放非托管资源的方法,但是使用这个接口的话,大家都统一使用,比较好理解,并且使用这个接口的类,可以使用using这种语法糖。

Finalize其实就是析构函数,学过C++的都知道啥叫析构函数,C#也一样。你必须显式的定义一个析构函数,这样,C#编译器才会生成一个Finalize方法。可以使用工具查看IL代码得到验证,IL代码显示了Finalize方法的结果是一个try finally结果,和using类似。

既然 IDisposable和Finalize都是回收非托管资源,那么为什么微软要提供雷同的功能呢?


IDisposable和Finalize确实雷同,但也有一些差别。

Dispose方法是主动调用(使用using也算主动调用),主动释放非托管资源。而析构函数是由GC回收托管资源的时候调用,因此何时调用是不可掌控的。因此完全依靠析构函数会导致出现非托管资源迟迟得不到释放,如果资源过大,堆积在内存增加内存泄露的风险。


也许有人会说,那我只要把非托管资源放到Dispose方法中,保证释放,那就不需要使用析构函数了。完全正确。但是前提是你保证释放了。假设你写了一个框架或者类库,其中使用了非托管资源,而且也必须显式调用Dispose才能释放掉。但是,假如某个2B程序员使用这个类库,他根本没考虑要手动释放资源,因此他在用你的类库后,发现经常内存动不动就满了或者连接数满了,然后就开骂你是各种2B,写的东西是一坨垃圾。


为了避免背这个黑锅,也避免被2B骂成2B,你可以依靠析构函数设置一个底线,就是在GC启动的时候也能触发一个动作去回收非托管资源,你就需要在Finalize中去做这些事情。


因此对于释放非托管资源,可以归结2个要点:

1,只实现Dispose方法,无法保证人人都会调用此方法,万一没调用,背黑锅的就是你。

2,只实现析构函数,可以保证回收非托管资源,但是无法掌握释放时机,因为GC何时触发析构函数是不确定的。运气不好的话,非托管资源长时间都得不到释放。

只有这2个方法同时使用,才能最大限度的释放非托管资源。因此微软提出了一个 IDisposable 编程模式。具体模式参考http://msdn.microsoft.com/en-us/library/system.idisposable.aspx


在这模式中:

如果用户调用了Dispose,则会调用Dispose(true),以此表示释放托管资源和非托管资源。并且注意GC.SuppressFinalize();方法很有必要,这个方法是压制了析构函数的调用,因为非托管资源以及释放,析构函数中的释放语句也没有必要执行了,一不小心可能还会导致错误。


如果用户没有调用Dispose,那么GC在回收资源的时候,会触发析构函数,注意,此时传入的Dispose(false);因为析构函数只负责释放非托管资源,不建议用析构函数去释放托管资源,因为析构函数调用时机是不确定的,托管资源很可能已经被释放掉了,从而导致一些异常的发生。

采用微软的模式简单有效,当然你也可以实现自己的模式,这就看各人需要了。

你可能感兴趣的:(垃圾回收,finalize,dispose)