IDisposable has been around since the beginning of .Net.
The basic premise is simple..

Developers dont need to manage memory now. The 'Garbage Collector' takes care of reclaiming memory for you.. However the GC is non-deterministic.. you can't predict when it will embark on a 'collection' of unused objects. So far so good..
However there are cases when you want deterministic cleanup, e.g. if DB connections are a premium, you want to reclaim them as soon as possible. Enter IDisposable.. Simple concept. Introduce a public Dispose method that you can call when you deem fit.

Creating types

Summary:

  1. All managed objects do NOT need to implement IDisposable. Just let the GC do its thing. You do NOT need to implement IDisposable if
    1. does NOT directly own any unmanaged resources (e.g. native handles, memory, pipes, etc..)
    2. does NOT directly own any managed members that implement IDisposable
    3. does NOT have special cleanup needs that must run on-demand/asap e.g. unsubscribe from notifiers, close DB Connections, etc.
  2. If you need to implement IDisposable BUT do not own any (direct) unmanaged resources, You should NOT throw in a free finalizer.
    1. Consider if you can make your type sealed. This simplifies the implementation a great deal.
    2. If you must have subtypes, then implement the version with the virtual Dispose(bool) overload as detailed below. Again, rethink if you can seal the type.
  3. If you directly own unmanaged resources that need cleanup, check for a managed wrapper type that you can use. e.g. a SafeFileHandle. If there is, use it and fall back to 2. Still no finalizer
  4. If you reach here, you need a finalizer. Finalizer pulls in IDisposable. Ensure that you have a deterministic Dispose implementation, that makes the finalizer redundant and avoids the associated performance penalties. Log an error in the finalizer to call your attention to cases where the clients have forgotten to call Dispose. Fix them.
    1. Consider creating a managed wrapper type because finalizers are hard to get right. e.g. SafeMyNativeTypeWrapper. Deriving from SafeHandle is not recommended - better left to experts
    2. Use GC.AddMemoryPressure and its counterpart to 'help' the GC if you are allocating significant amounts of native memory. Similarly manage handles via the HandleCollector class (e.g. GDI handles). See this post for details except I'd move the Remove.. calls into Dispose instead of the finalizer.

Programming against types that implement IDisposable

  1. Limit the scope of disposable types to within a method. Wrap them within a using blockto ensure that Dispose is called (reliably) when control leaves the using block.
  2. If you need to hold on to a disposable type i.e. as a member field, you need to implement IDisposable on the container type. e.g. If A owns B owns C owns D, where D implements IDisposable, then A,B and C need to implement IDisposable as well.
  3. Do not dispose objects that you don't own. e.g. if you obtain a reference to an object from a container (e.g. a MEF container or a Form's controls collection) OR a static/global accessor  you don't own the object. Hence you shouldn't call dispose and break other clients with ObjectDisposedException. Leave it to the container to Dispose it.

The long-winded version (with code snippets)

Para1: Avoid implementing IDisposable unless necessary, most objects don't need it.

If your type doesn't need IDispose. You can stop reading here.

Para2: If you need deterministic cleanup, implement IDisposable (mini).

  • All public members need to check for _isDisposed == true & throw an ObjectDisposedException
  • Dispose can be called multiple times : once again use _isDisposed and ignore all calls except the first one
  • Dispose should not throw exceptions
  • Call Dispose for disposable managed objects that this type owns. Corollary: Dispose will creep all the way up the object graph. e.g. If TypeA contains B contains C contains D and D implements IDisposable: A,B,C need to implement IDisposable.
  • (Managed Memory Leaks from Events) - Unsubscribe from all events that this object has active subscriptions to. Long-lived Publishers can keep short-lived subscribers alive and prevent them from being collected. Try: clearing subscribers from your own events might be a good idea- set event/delegate to null. Try: using the WeakEventManager/WeakReference type
  • Seal your type - inheritance needs the full blown Dispose pattern (later on in this post).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sealed class MyType : IDisposable
    {
        // other code
 
        private bool _isDisposed;
 
        public void SomeMethod()
        {
            if (_isDisposed)
                throw new ObjectDisposedException();
            // proceed..
        }
 
        public void Dispose()
        {
            if (_isDisposed)
                return;
 
            // cleanup
 
            _isDisposed = true;
        }
    }

Para3: Avoid finalizers unless necessary.

When are finalizers necessary ?

  • When you have directly owned Unmanaged resources that need to be cleaned up AND there isn't a managed wrapper type that has the finalization routine nailed down e.g. a SafeHandle derivation. If you can, you go back to the previous section.

Finalizers

  • slow down the collection process
  • prolong object lifetime - the object moves into the next generation (whose collection is even less frequent. C# in a Nutshell gives a ratio of Gen 0 10:1  Gen 1)
  • are difficult to get right

Finalizers should

  • not block / throw exceptions
  • must execute quickly
  • not reference other finalizable members (their finalizers may have already run)
  • should log / raise a red flag to indicate Dispose was not called.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
sealed class HasUnmanaged : IDisposable
    {
        public void Dispose()
        {
            Dispose(true);
            // prevent object from being promoted to next Gen/finalizer call
            GC.SuppressFinalize(this);
        }
        ~HasUnmanaged()
        {
            LogSomeoneForgotToCallDispose();
            Dispose(false);
        }
 
        private bool _isDisposed;
        private void Dispose(bool isDisposing)
        {
            if (_isDisposed)
                return;
 
            if (isDisposing)
            {
                // dispose of managed resources(can access managed members)
            }
            // release unmanaged resources
            _isDisposed = true;
        }
    }

Para4: Subclassing a Disposable type

If your type cannot be sealed, then it's time to bring out the big guns. Implement the base type as follows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class BaseType : IDisposable
    {
        Intptr _unmanagedRes = ... // unmanaged resource
        SafeHandle _managedRes = ... // managed resource
    
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        
        ~BaseType()
        {
            Dispose(false);
        }
    
        private bool _isDisposed;
        virtual void Dispose(bool isDisposing)
        {
            if (_isDisposed)
                return;
 
            if (isDisposing)
            {
                // managed resources dispose
                _managedRes.Dispose();
            }
            // unmanaged resource cleanup
            Cleanup(_unmanagedRes);
            // null out big fields if any
            _unmanagedRes = null;
 
            _isDisposed = true;
        }
    }
  • If the derived type has it's own resources that need cleanup, it overrides the virtual member like this
  • If the derived type does not have resources that need cleanup, just inherit from the base. You're done.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class MessyDerived : BaseType
    {
        // ... Derived members
 
        private bool _isDisposed;
        override void Dispose(bool isDisposing)
        {
            try
            {
                if (!_isDisposed)
                {
                    if (isDisposing)
                    {
                        // derived managed resources
                    }
                    // derived unmanaged resources
                    _isDisposed = true;
                }
            }
            finally
            {
                base.Dispose(isDisposing);
            }
        }
    }
 
 
    class SimpleDerived : BaseType
    {
        // ... Derived members
    }

Of course, there will be edge-cases. But for the most part, this should save you a lot of grief
See also - Joe Duffy's post

[Forward]Sweeping the IDisposable minefield的更多相关文章

  1. Forward+ Rendering Framework

    近几天啃各种新技术时又一个蛋疼的副产品...额,算是把AMD的Forward+ Sample抄了一遍吧. 其实个人感觉这个AMD大肆宣传的Forward+跟Intel很早之前提的Tiled-Based ...

  2. 通过IEnumerable和IDisposable实现可暂停和取消的任务队列

    一般来说,软件中总会有一些长时间的操作,这类操作包括下载文件,转储数据库,或者处理复杂的运算. 一种处理做法是,在主界面上提示正在操作中,有进度条,其他部分不可用.这里带来很大的问题, 使用者不知道到 ...

  3. IDisposable的另类用法

    IDisposable是.Net中一个很重要的接口,一般用来释放非托管资源,我们知道在使用了IDisposable的对象之后一定要调用IDisposable.Dispose()方法,或者使用.Net提 ...

  4. Sql Server 聚集索引扫描 Scan Direction的两种方式------FORWARD 和 BACKWARD

    最近发现一个分页查询存储过程中的的一个SQL语句,当聚集索引列的排序方式不同的时候,效率差别达到数十倍,让我感到非常吃惊 由此引发出来分页查询的情况下对大表做Clustered Scan的时候, 不同 ...

  5. JAVA常见面试题之Forward和Redirect的区别

    用户向服务器发送了一次HTTP请求,该请求可能会经过多个信息资源处理以后才返回给用户,各个信息资源使用请求转发机制相互转发请求,但是用户是感觉不到请求转发的.根据转发方式的不同,可以区分为直接请求转发 ...

  6. forward和redirect的区别(转)

    Redirect Forward 不同的request 不同的对象,但是可以渠道上一个页面的内容 send后面的语句会继续执行,除非return Forward后面的语句不会继续发送给客户端 速度慢 ...

  7. jsp/servlet 中sendRedirect,include,forward区别

    1 sendRedirect response.sendRedirect(); 服务器根据逻辑,发送一个状态码,告诉浏览器重新去请求新的地址,一般来说浏览器会用刚才请求的所有参数重新请求,所以sess ...

  8. Vector3.forward

    这里我要说的就是Vector3.forward ,它等价与 new Vector3(0,0,1):它并不是一个坐标,它是一个标准向量,方向是沿着Z轴向前.这样平移一次的距离就是1米, 如果 Vecto ...

  9. jsp动作元素之forward指令

    forward指令用于将页面响应转发到另外的页面.既可以转发到静态的HTML页面,也可以转发到动态的JSP页面,或者转发到容器中的Servlet. forward指令格式如下: <jsp:for ...

随机推荐

  1. 如何使用 Chrome 浏览器调试动态加载的 Javascript 脚本

    在IE中,可以在调试程序的文档列表最下方看到一个"动态脚本"的文件夹,里面可以找到动态加载的脚本,但是...数量繁多,也不能自定义名称... 但是在 Chrome 中,貌似根本找不 ...

  2. ISE中的Force Process Up-to-Date功能:ISE中如何在未综合实现的前提下打开ChipScope ?

    ISE中如何在未综合实现的前提下双击Analyze Design Using ChipScope打开ChipScope ? 有时,你正在ISE中调试程序,在ChipScope中看到了现象,顺手修改了程 ...

  3. 打造 Vue.js 可复用组件

    Vue.js 是一套构建用户界面的渐进式框架.我们可以使用简单的 API 来实现响应式的数据绑定和组合的视图组件. 从维护视图到维护数据,Vue.js 让我们快速地开发应用.但随着业务代码日益庞大,组 ...

  4. Hibernate 建立一对多双向关联关系

    下面内容整理自<精通Hibernate>第二版 注:既然是双向关联."一对多双向关联"和"多对一双向关联"是同一回事. 对象位于内存中,在内存中从一 ...

  5. HTML5学习笔记(十八):闭包

    高阶函数 JavaScript的函数其实都指向某个变量.既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,也可以返回一个函数,这种函数就称之为高阶函数. 函数作为参 ...

  6. InfoQ 趋势报告:架构和设计领域技术演变详解

    https://www.infoq.cn/article/R7lWXd0R4VFf3E0bB*38 本文概述了我们对当前“架构和设计”领域的看法,这个领域侧重于基础设施模式.技术框架模式的实现,以及软 ...

  7. Oracle根据表生成系统流水号

    1.建表tablewater create table TABLEWATER ( tb_id INTEGER not null, vc_table_name ), num_water_no ) )vc ...

  8. redis使用日志(4):如何让外部服务器访问

    开启redis 允许外网IP 访问 在 Linux 中安装了redis 服务,当在客户端通过远程连接的方式连接时,报could not connect错误. 错误的原因很简单,就是没有连接上redis ...

  9. COMPILING ACTIONSCRIPT 3.0 WITH SUBLIME TEXT 2

    At Clock we typically spend our time developing JavaScript and PHP, however, occasionally Flash pres ...

  10. (装机)关于WINRE/ESP/LRS_ESP/MSR/PBR这些分区

    WINRE 1GB 用途:Windows 8 系统恢复模式.这个分区是保存是在Windows 8 系统主体本身被破坏无法正常启动的时候进行系统修复的Windows 8 PE系统.. ESP 260MB ...