Loading...
服务热线:136-48097804 购物车(0)
您好,访客 请登陆注册
 

当前位置:资讯中心主页 >C/C++ >文章内容

  • 利用C#实现标准的Dispose模式
  • 来源:作者: 发布时间:2006-11-14 08:00:00
    • 域名注册

    • 域名惊喜价格 cn域名1元注册
    • com域名39.9

      虚拟主机

    • 主机按月支付,低至19元/月
    • 超大流量,可开子站点

      VPS主机

    • 特惠VPS168元/月,4-8M独享带宽保证
    • 独立操作系统,无限开站点
     本文讲解的是你在建立包含内存以外资源的类型,特别是处置非内存资源的时候,怎么编写自己的资源管理代码。

       我们已知道了处置那些占用非受控(unmanaged)资源的对象的重要性,目前应该编写资源管理代码来处置那些包含非内存资源的类型了。整个.NET框架组件都使用一个标准的模式来处理非内存资源。使用你建立的类型的用户也希望你遵循这个标准的模式。标准的处理模式的思想是这样的:当客户端记得的时候使用IDisposable接口释放你的非受控资源,当客户端忘记的时候防护性地使用终结器(finalizer)。他和垃圾收集器(Garbage Collector)一起工作,确保只在必要的时候该对象才受到和终结器相关的性能影响。这是处理非受控资源的一条非常好的途径,因此我们应该完全地认识他。

       类层次体系中的根基类(root base class)必须实现IDisposable接口以释放资源。这个类型还必须添加一个作为防御机制的终结器。所有这些程式都把释放资源的工作委托给一个虚拟的方法,衍生的类能根据自己的资源管理需求来重载该方法。只要衍生的类必须释放自己的资源,并且他必须调用该函数的基类版本的时候,他才需要重载这个虚拟方法。

       开始的时候,如果你的类使用了非内存资源,他就必须含有一个终结器。你不能依赖客户端总是调用Dispose()方法。因为当他们忘记这样做的时候,你就面临资源泄漏的问题。没有调用Dispose是他们的问题,不过你却有过失。用于确保非内存资源被正确地释放的唯一途径是建立终结器。

       当垃圾收集器运行的时候,他即时从内存中删除所有不带终结器的垃圾对象。所有带有终结器的对象仍然存在于内存中。这些对象都被添加到终结队列,垃圾收集器引发一个新线程,周期性地在这些对象上运行终结器。在这些终结程式线程完成自己的工作之后,就能从内存中删除垃圾对象了。需要终结的对象在内存中停留的时间比没有终结器的对象停留的时间长非常多。不过你别无选择。如果要使程式有防护性,在类型包含非受控资源的时候,你必须编写一个终结器。不过也不用担心性能问题。下一步确保了客户端避免和终结相关的性能开销。
    实现IDisposable接口是一种标准的途径,他通知用户和运行时系统持有资源的对象必须及时地释放。IDisposable接口仅仅包含一个方法:

    public interface IDisposable
    {
      void Dispose( );
    }

       你对IDisposable.Dispose()方法的实现(implementation)负责下面四个事务:

       1、释放所有的非受控资源。

       2、释放所有的受控资源(包括未解开事件)。

       3、设置标志表明该对象已被处理过了。你必须在自己的公共方法中检查这种状态标志并抛出ObjectDisposed异常(如果某个对象被处理过之后再次被调用的话)。

       4、禁止终结操作(finalization)。你调用GC.SuppressFinalize(this)来完成这种事务。

       通过实现IDisposable接口你完成了两个事务:你为客户端及时地释放自己持有的所有受控资源提供了机制;你为客户端提供了一种释放非受控资源的标准途径。这是个非常大的进步。当你在类型中实现了Idisposable接口的时候,客户端能避免终结操作的开销,你的类就成为.NET世界中的"良民"了。

       不过在你建立的这种机制中仍然存在一些问题。怎样在衍生类清理自己资源的时候同时也让基类能够清理资源?如果衍生类重载了终结操作,或添加了自己的IDisposable实现,那么这些方法必须调用基类,否则,基类就不能正确地进行清理操作。同样,finalize(终结操作)和Dispose参和分担了一些相同的职责。Finalize方法和Dispose方法的代码几乎相同。而且在重载接口函数后并不像你预料的那样工作。标准的Dispose模式中的第三个方法是个受保护的虚拟辅助函数,他分解出这些一起的事务,并给衍生类添加一个用于释放资源的"钩子(hook)"。基类包含了核心接口的代码。作为对Dispose()或终结操作的响应,该虚拟函数为衍生类清除资源提供了"钩子":

    protected virtual void Dispose( bool isDisposing );

       这个重载的方法实现支持finalize和Dispose的必要事务,由于他是虚拟的,他为所有的衍生类提供了一个入口点。衍生类能重载这个方法,为清除自己的资源提供适当的实现,同时还能调用基类版本。当isDisposing为真(true)的时候,你能清除受控和非受控资源,当isDisposing为假(false)的时候,你只能清除非受控资源。在这两种情况下,你都能调用基类的Dispose(bool)方法,让他清除自己的资源。
       下面有一个简短的例子,他演示了你在实现这种模式的时候所提供的代码框架。MyResourceHog类演示了实现IDisposable接口、终结器的代码,并建立了一个虚拟的Dispose方法:

    public class MyResourceHog : IDisposable
    {
      // 已被处理过的标记
      private bool _alreadyDisposed = false;
      // 终结器。调用虚拟的Dispose方法
      ~MyResourceHog()
      {
       Dispose( false );
      }

      // IDisposable的实现
      // 调用虚拟的Dispose方法。禁止Finalization(终结操作)
      public void Dispose()
      {
       Dispose( true );
       GC.SuppressFinalize( true );
      }

      // 虚拟的Dispose方法
      protected virtual void Dispose( bool isDisposing )
      {
       // 不要多次处理
       if ( _alreadyDisposed )
        return;
       if ( isDisposing )
       {
        // TODO: 此处释放受控资源
       }
       // TODO: 此处释放非受控资源。设置被处理过标记
       _alreadyDisposed = true;
      }
    }

       如果衍生类需要执行另外的清除操作,他应该实现受保护的Dispose方法:

    public class DerivedResourceHog : MyResourceHog
    {
      // 他有自己的被处理过标记
      private bool _disposed = false;

      protected override void Dispose( bool isDisposing )
      {
       // 不要多次处理
       if ( _disposed )
        return;
       if ( isDisposing )
       {
        // TODO: 此处释放受控资源
       }
       // TODO: 此处释放所有受控资源

       // 让基类释放自己的资源。基类负责调用GC.SuppressFinalize( )
       base.Dispose( isDisposing );

       // 设置衍生类的被处理过标记
      _disposed = true;
      }
    }

       请注意,基类和衍生类都包含该对象的被处理过(disposed)标记。这纯粹是起保护作用。复制这个标记能封装构成某个对象的所有类释放资源时产生的所有可能的错误。

       你必须编写防护性的Dispose和finalize。对象的处理能按任意次序进行,你可能会遇见在调用自己类型的成员对象的Dispose()方法之前,该对象已被处理过了。你不应该认为这是问题,因为Dispose()方法会被多次调用。如果他在已被处理过的对象上被调用,他就不执行所有事务。Finalizer(终结器)也有类似的规则。如果你引用的对象仍然存在于内存中,你就没有必要检查空引用(null reference)。不过,你引用的所有对象都可能被处理了,他也可能已被终结了。

       这为我带来了和处理或清除相关的所有方法的最重要的建议:你应该仅仅释放资源,在dispose方法中不要执行所有其他操作。如果你在Dispose或finalize方法中执行其他操作,都可能给对象的生命周期带来严重的不良影响。对象在被构造的时候才"出生",当垃圾收集器收回他们的时候才"死亡"。当你的程式再也不能访问他们的时候,你能认为他们处于"昏睡"状态。如果你不能到达(reach)某个对象,你就不能调用他的方法,对于所有的意图和目的来说,他是死的。不过带有终结器的对象被宣布死亡之前更有最后一口气。终结器除了清理非受控资源之外不应该执行其他所有操作。如果某个终结器由于什么原因使某个对象又能到达了,那么该对象就恢复(resurrected)了。即使他是从"昏睡"状态醒来的,他也是"活着"的。下面是个非常明显的例子:

    public class BadClass
    {
      // 保存某个全局对象的引用
      private readonly ArrayList _finalizedList;
      private string _msg;

      public BadClass( ArrayList badList, string msg )
      {
       // 缓冲该引用
       _finalizedList = badList;
       _msg = (string)msg.Clone();
      }

      ~BadClass()
      {
       // 把该对象添加到列表中。这个对象是可到达的,不再是垃圾了。他回来了!
       _finalizedList.Add( this );
      }
    }

       当某个BadClass对象执行自己的终结器的时候,他向全局列表上添加了对自己的引用。这仅仅使自己可到达了,他活了过来!不过这样操作所带来的问题使所有人都会感到胆怯。该对象已被终结了,因此垃圾收集器相信不用再次调用他的终结器了。你真的需要终结一个被恢复的对象的时候,终结操作却不会发生了。其次,你的一些资源可能不能用了。GC不会把终结器队列中的对象能到达的所有对象从内存中移除,不过他可能已终结了这些对象。如果是这样的话,那些对象一定不能再次使用了。尽管BadClass的成员仍然存在于内存中,他们却像被处理过或被终结了相同。在C#语言中没有控制终结次序的途径。你不能使这种构造工作更可靠。不要尝试!

       除了学院的练习作业之外,我从来没有见到过如此明显地使用被恢复对象的代码。不过我看到有些代码有这个倾向,他们在终结器中试图执行某些实际工作,当终结器调用的某些函数保存了对该对象的引用的时候,他就正在把对象变成活动的状态。原则上我们必须非常仔细地检查finalizer和Dispose方法中所有代码。如果有些代码除了释放资源之外还执行了其他的操作,我们就需要再检查一次。这些操作在未来可能引起程式bug。请移除这些操作,并确保finalizer和Dispose()方法只释放资源,不作其他任务事务。

       在受控环境中,你不必为自己建立的每个类型编写终结器,你只需要为存储非受控类型,或包含了实现IDisposable接口的成员的类型编写终结器。即使你只需要Disposable接口,不必finalizer,也应该同时实现整个模式。否则,你会使衍生类的标准Dispose思想的实现变得非常复杂,从而限制了衍生类的功能。请遵循前面谈到的标准的Dispose思想,这将使你、你的类的用户、从你的类型建立衍生类的用户的生活更加轻松。


  • 以上内容由 华夏名网 搜集整理,如转载请注明原文出处,并保留这一部分内容。

      “华夏名网” http://www.sudu.cn 和 http://www.bigwww.com 是成都飞数科技有限公司的网络服务品牌,专业经营虚拟主机,域名注册,VPS,服务器租用业务。公司创建于2002年,经过6年的高速发展,“华夏名网”已经成为我国一家知名的互联网服务提供商,被国外权威机构webhosting.info评价为25大IDC服务商之一。

    华夏名网网址导航: 虚拟主机 双线主机 主机 域名注册 cn域名 域名 服务器租用 酷睿服务器 vps vps主机

  • (阅读次数:139)
  • 上一篇: C++/CLI中栈对象的设计问题    下一篇: 利用C#实现标准的Dispose模式
  • [收藏] [推荐] [评论] [打印本页] [返回上一页][关闭窗口]
  • 昵称: (为空则显示guest)
  • 评论分数: ★ ★ ★★★ ★★★★ ★★★★★
  • 评论内容:(不能超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规。