Dispose模式是.NET中很基础也很重要的一个模式,今天重新复习一下相关的东西并记录下来。

  • 什么是Dispose模式?
  • 什么时候我们该为一个类型实现Dispose模式
  • 使用Dispose模式时应该注意什么?
  • 怎样实现一个Dispose模式?
  • Dispose模式的例子

什么是Dispose模式?

要知道什么是Dispose模式必须先了解两个概念:资源GC的基本原理。资源指的是一些类似于Windows句柄,数据库连接的非内存的一些本地资源,这些资源无法被运行时本身管理,需要程序员自己去申请和释放。当一些托管类型中对这些资源进行一定的封装的时候,我们就需要妥善的去处理他们,比如说在不用的时候释放到这些资源以免造成资源泄露。但是你怎么知道什么时候不用呢?无法知道,这个只有使用这个类型的人知道,所以我们需要在Finalize方法中对资源进行释放,这个有点像C++中的析构函数,但是不一样的是.NET中的Finalize方法是由运行时来调用而非用户。

扯了这么多,貌似只要在Finalize方法中释放掉资源就行了,那Dispose模式又是干嘛的?

正如上面提到的,Finalize方法只能由运行时来调用,用户无法控制什么时候这个方法会被调用。了解一下GC的基本工作原理,我们会发现具有Finalize方法的垃圾对象会在垃圾回收的时候放入一个“垃圾队列”,但是这个垃圾队列并不是说下一次垃圾回收的时候就会调用,而是会在下一次内存压缩(compact)的时候才会去调用。[1]你不知道这货啥时候才会去调用那个Finalize方法,也就是说很可能你这个对象会存活很久(占用内存),并且这个资源一直得不到释放(占用资源)!

我写了下面这个简单的代码做了一下测试,只是为了验证一下并不是多次GC就会去调用Finalize方法的。

internal class Resource
{
public void DoSomething()
{
}
~Resource()
{
Console.WriteLine("In Finalize");
}
} internal class Program
{
private static void Main(string[] args)
{
var res = new Resource();
res.DoSomething();
int i = ;
while (i-- > )
{
GC.Collect();
}
Console.Read();
}
}

那么既然Finalize方法的调用无法控制,于是就引入了Dispose模式,实现IDispose方法来提供一个Dispose方法允许调用者手动去调用Dispose方法释放资源。

什么时候我们该为一个类型实现Dispose模式

我们已经大概知道什么是Dispose模式了,那么我们应该在什么时候去为我们的类型实现这种模式呢?在以下这几种情况中我们应该为一个类型实现Dispose模式(非完全列表哦)

1. 类型对一些本地资源进行封装,比如类型暴露了一些文件操作的接口,并在内部对文件进行了操作。

2. 如果类型内部有一个Dispose成员,你需要保证这个成员必须在你的对象的生命周期内得到释放。比如说我实现了一个记日志的类,类的内部有一个成员变量是FileStream,我就需要实现Dispose模式来保证这个FileStream能够被释放。

3. 如果你要实现一个基类,基类本身没有本地资源。但是他的子类有那么必须要实现Dispose模式,典型例子就是Stream。[2]

当你在考虑为一个基类实现或者不实现Dispose模式的时候需要特别小心,可能会存在一个版本问题以后改动的时候比较麻烦。比如说,一个叫做BaseA的类开始的时候没有实现IDispose接口,但是过了一段时间以后需要实现这个接口,那么就需要找出所有继承自这个BaseA的内在他们那里实现IDispose方法并且调用基类的Dispose方法。如果不全部修改的话,可能会出现子类总的Dispose方法没有去调用基类中的Dispose方法导致基类中的资源得不到释放。另一种相反的情况也类似,比如说第一个版本的基类实现了,以后如果以后要去掉的话也必须将所有子类中调用基类的地方去掉。

使用Dispose模式时应该注意什么?

Dispose Pattern是篇好文章,好好读一下肯定有收获。下面只是摘录自这个文章外加一点点自己的东西,纯属个人翻译练习,推荐看原文。

  • 为包含Disposable实例的类型实现Dispose模式,妥善的去处理那个Disposable的资源。
  • 如果一个类型中包含一些需要显示去释放并且没有Finalizer的实例,那么请为这个类型实现Dispose模式和Finalize方法。
  • 考虑为一些类本身不包含非托管资源,但是它的子类基本都需要包含非托管资源的类型实现Dispose模式。
  • 实现一个签名为protected virtual void Dispose(bool disposing),并将所有释放逻辑放在其中。其他诸如Dispose(),Close(),Finalizer都来调用这个方法来释放。
  • 实现IDispose接口方法的时候,内部去调用Dispose(false),然后再调用GC.SuppressFinalize(this)方法告诉垃圾回收器不需要调用这个实例的Finalize方法直接可以释放
  • 不要将无参数的Dispose方法设置为虚方法,子类应该override的是带参数的那个Dispose方法。
  • 除了Dispose()和Dispose(bool)以外不要重载Dispose方法。
  • 允许Dispose(bool)被多次调用,该方法应该在第一次调用后直接返回。
  • 避免在Dispose(bool)中抛出错误,除非发生了很严重的错误,比如说进程发生了严重的溢出,数据不一致。
  • 当实例已经被Dispose以后,但是外部仍然尝试调用实例的一些方法时,应该抛出ObjectDisposedException来告知调用者。
  • 如果说Close合适你的类型的释放动作的话,考虑添加Close()方法。如果你这么做了,请将Dispose()中的内容搬到Close()方法中,并且将Dispose()方法显示生命为IDispose接口的实现。
  • 尽可能去避免实现Finalizer,因为会影响性能和代码的复杂度。类型本身应该负责资源的释放,可以参考SafeHandler类型。
  • 不要给值类型添加Finalizer,只有引用类型CLR才会去调用Finalize方法。即使你实现了也会被忽略掉。
  • 如果你为一个类型实现了Finalizer,那么请也给这个类型实现Dispose方法。
  • 将Finalize方法访问级别设置为proctected。C++, VB, C#编译器会强制执行这一限制。
  • 如果你的Finalize方法需要在进程出现严重错误强制终止的情况下依然被调用的话,那么你需要考虑创建或者使用critical finallizable object(类型继承自CriticalFinalizerObject)。

怎么实现一个Dispose模式

参考MSDN[2],我这里找了一个.NET提供的类型做例子来看一下。FileStream。我将一些关键代码扣出来说明一下。

public abstract class Stream : IDisposable
{
/// <summary>Closes the current stream and releases any resources (such as sockets and file handles)
/// associated with the current stream.</summary>
public virtual void Close()
{
//调用带参的Dispose方法来清理资源,传入true表示是显示调用Dispose方法
//若子类重载则会调用子类的Dispose方法
this.Dispose(true);
//告诉GC不需要执行本对象的Finalize方法
GC.SuppressFinalize(this);
} /// <summary>Releases all resources used by the <see cref="T:System.IO.Stream" />.</summary>
public void Dispose()
{
this.Close();
} /// <summary>Releases the unmanaged resources used by the <see cref="T:System.IO.Stream" />
/// and optionally releases the managed resources.</summary>
/// <param name="disposing">true to release both managed and unmanaged resources;
/// false to release only unmanaged resources.</param>
//dispose为ture释放托管和非托管的资源,因为是显示调用,其他对象的字段可以安全访问并释放。
//dispose为false的时候只释放非托管资源,因为托管的一些对象可能已经被释放掉了。
protected virtual void Dispose(bool disposing)
{
if (disposing && this._asyncActiveEvent != null)
{
this._CloseAsyncActiveEvent(Interlocked.Decrement(ref this._asyncActiveCount));
}
}
} public class FileStream : Stream
{
private SafeFileHandle _handle; //重载父类中带参的Dispose方法
protected override void Dispose(bool disposing)
{
try
{
//判断是否已经被释放,不多次释放
if (this._handle != null && !this._handle.IsClosed && this._writePos > )
{
this.FlushWrite(!disposing);
}
}
finally
{
//判断是否已经被释放,不多次释放
if (this._handle != null && !this._handle.IsClosed)
{
this._handle.Dispose();
}
this._canRead = false;
this._canWrite = false;
this._canSeek = false;
base.Dispose(disposing);
}
} //垃圾回收时候调用这个方法释放资源
~FileStream()
{
if (this._handle != null)
{
//传入false表示是Finalize方法中调用
this.Dispose(false);
}
} public virtual void Flush(bool flushToDisk)
{
//判断是否已经被释放,如果已经被释放则会抛出异常
if (this._handle.IsClosed)
{
//抛出 ObjectDisposedException 异常
__Error.FileNotOpen();
}
//此处省略N行代码
}
}

Dispose模式的例子
什么你是来找现成的代码的?Google去!

Reference:

1. Jeffrey Richter,
CLR Via C#, 3rd Edition.

2. MSDN, Dispose
Pattern, http://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx

[学习笔记] Dispose模式的更多相关文章

  1. 《Head first设计模式》学习笔记 – 迭代器模式

    <Head first设计模式>学习笔记 – 迭代器模式 代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示. 爆炸性新闻:对象村餐厅和对象村煎饼屋合并了!真是个 ...

  2. C#学习笔记---Dispose(),Finalize(),SuppressFinalize

    http://www.cnblogs.com/eddyshn/archive/2009/08/19/1549961.html 在.NET的对象中实际上有两个用于释放资源的函数:Dispose和Fina ...

  3. 学习笔记——解释器模式Interpreter

    解释器模式,其实就是编译原理中的语法解释器,如果用在项目中,可以用于实现动态脚本的解析,也就是说项目可以支持用户脚本扩展. 但实际上,这种运行时解释,效率很慢,如果不是很需要的话,不建议使用. 一种简 ...

  4. jquery学习笔记---插件开发模式和结构

    JQuery插件开发http://www.cnblogs.com/damonlan/archive/2012/04/06/2434460.html github教程:https://github.co ...

  5. Java-马士兵设计模式学习笔记-命令模式

    一.概述 命令模式 二.代码 1.Client.java public class Client { public void request(Server server){ server.addCom ...

  6. Java-马士兵设计模式学习笔记-桥接模式

    一.概述 1.桥接模式的应用情况:(1)两个维度扩展(2)排列组合 二.代码 1.Gift.java public class Gift { protected GiftImpl giftImpl; ...

  7. 《Head First 设计模式》学习笔记——工厂模式 + 抽象工厂模式

    设计模式 工厂模式:定义一个创建对象的接口,但由子类决定要实例化的是哪一个.工厂方法让类把实例化推迟到子类. 所谓的"决定",并非指模式同意子类本身在执行时做决定,而是指在编写创建 ...

  8. 《Head First 设计模式》学习笔记——状态模式

    在软件开发过程中.应用程序可能会依据不同的情况作出不同的处理. 最直接的解决方式是将这些全部可能发生的情况全都考虑到.然后使用if... ellse语句来做状态推断来进行不同情况的处理. 可是对复杂状 ...

  9. 《Head First 设计模式》学习笔记——模板方法模式

    模板方法模式是类的行为模式.准备一个抽象类,将部分逻辑以详细方法以及详细构造函数的形式实现.然后声明一些抽象方法来迫使子类实现剩余的逻辑.不同的子类能够以不同的方式实现这些抽象方法,从而对剩余的逻辑有 ...

随机推荐

  1. Karma +Jasmine+ require JS进行单元测试并生成测试报告、代码覆盖率报告

    1. 关于Karma Karma是一个基于Node.js的JavaScript测试执行过程管理工具(Test Runner). 该工具可用于测试所有主流Web浏览器,也可集成到CI(Continuou ...

  2. php 学习使用

    https://netbeans.org/kb/docs/php/wish-list-lesson1_zh_CN.html#register-mysql http://www.php100.com/h ...

  3. Selenium2+python自动化3-解决pip使用异常

    一.pip出现异常 有一小部分童鞋在打开cmd输入pip后出现下面情况:Did not provide a commandDid not provide a command?这是什么鬼?正常情况应该是 ...

  4. ASP.NET ZERO 学习 事件总线

    用于注册和触发客户端的全局事件. 介绍 Pub/sub事件模型广泛用于客户端,ABP包含了一个简单的全局事件总线来 注册并 触发事件. 注册事件 可以使用abp.event.on来注册一个全局事件.一 ...

  5. haploview出现"invalid affected status"的解决方法

    haploview弹出这种错误是因为haploview的缺失值默认为0,而plink文件的缺失值一般用"-9"表示,当ped文件的缺失值为"-9"时,haplo ...

  6. Android Studio 初使用

    Android Studio 更改Eclipse快捷键 Android Studio 更改编码 Android Studio 导包

  7. java核心知识点学习----创建线程的第三种方式Callable和Future CompletionService

    前面已经指出通过实现Runnable时,Thread类的作用就是将run()方法包装成线程执行体,那么是否可以直接把任意方法都包装成线程执行体呢?Java目前不行,但其模仿者C#中是可以的. Call ...

  8. php字符串赋值到js的坑

    很早以前的一个比较坑的问题,今天又遇到了,记录一下,免得以后再次入坑. 把php赋值到view层时,如果不是直接渲染到页面,而是赋值给变量.字符如果有回车或者换行就会出现问题. 示例: <?ph ...

  9. 学习springMVC框架配置遇到的问题-数据写入不进数据库时的处理办法

    配置完了,运行,数据写入不到数据库中,就应该想UserAction 中的handleRequest()方法有没有进去,然后就设置断点.如果发现程序没有进去,就再想办法进去.

  10. XE3随笔8:关于乱码

    以下例子都会出现乱码, 虽然都可以有变通的方案, 但如果不乱码就太好了! unit Unit1; interface uses   Windows, Messages, SysUtils, Varia ...