CLR内存管理之释放非托管资源

上篇文章中我提到,CLR通过栈指针给变量分配内存空间,通过GC来释放不再引用的内存空间。GC虽然减少了程序员处理内存的困难,但它也有局限性,它不能处理像文件句柄、网络连接、数据库连接这样的非托管资源。在定义一个类时,我们使用两种机制来自动释放非托管资源:

1,声明一个析构函数(或终结器finalizer),作为类的一个成员

2,在类中实现System.IDisposable接口

析构函数:

析构函数的声明格式如下

class Hotel
{
       ~Hotel()
        {
            //destructor implementation
        }

}

在GC销毁对象之前,会调用对象的析构函数。C#编译器在编译析构函数时,会隐式地把析构函数的代码编译为等价于Finalize()方法的代码,从而确保执行父类的Finalize()方法。下面模拟了编译器编译析构函数生成的c#代码:

protected override void Finalize()
{
     try
      {
         //destructor implementation
      }
      finally
      {
           base.Finalize();
      }
}

如上所示,在~Hotel()析构函数中的实现代码封装在Finalize()方法的一个try语句块中。对父类的Finalize()方法的调用放在finally块中,确保该调用的执行。
有经验的C++开发人员会大量使用析构函数,用于清理资源,提供调试信息或执行一些其他的操作。这是因为,C++中,销毁对象时,它的析构函数会立即执行。但是,C#中,我们使用析构函数的次数却少之又少。C#析构函数的问题在于它的不确定性,由于GC的工作方式,我们无法确定C#析构函数何时会执行。因此,我们不敢在析构函数中放置一些需要在特定时刻执行的代码。对象如果占用了宝贵而重要的资源,应该尽快释放这些资源,此时就不能指望GC来自动清理资源了。另一个问题是,C#析构函数的实现会延迟对象从内存中删除的时间。没有析构函数的对象会在GC的一次处理中从内存中删除。但有析构函数的对象却需要两次的处理才能销毁。第一次调用不会删除,第二次调用时才会执行删除操作。另外,CLR使用一个线程来执行所有对象的Finalize()方法,如果频繁使用析构函数,并让它们执行长时间的清理任务,对性能的影响就会非常显著。

实现IDisposable接口:

在C#中,推荐使用System.IDisposable接口代替析构函数。IDisposable接口为释放非托管资源提供了一种确定的机制,并避免产生析构函数固有的与GC相关的问题。IDisposable接口的实现如下:

class Hotel: IDisposable
 {
        public void Dispose()
        {
            //implementation            
        }
}

Dispose()方法的实现代码显式地释放由对象直接使用的非托管资源,并在所有也实现IDisposable接口的封装对象上调用Dispose()方法。这样,Dispose()方法就为释放非托管资源提供了一种精确的控制。
看下面的代码:

Hotel hotel = new Hotel();
hotel.Dispose();

该代码会调用hotel对象的Dispose()方法,但是如果执行过程出现异常,这段代码就不会释放对象使用的资源。所以,我们对它加以改进:

            Hotel hotel = null;
            try
            {
                hotel = new Hotel();
            }
            finally
            {
                if (hotel != null)
                {
                    hotel.Dispose();
                }
            }

这样就能确保,即使执行过程出现异常,也总能保证在hotel上调用Dispose()方法。但是如果我们的项目中重复使用这样的结构,代码就会变得非常杂乱。C#提供了一种语法,使用using关键字,生成与上面等同的代码:

           using (Hotel hotel = new Hotel())
            {
                //operation
            }

是不是很熟悉,我们在使用数据库连接和网络连接时经常用到这种写法。它也可以保证在出现异常时调用Dispose()方法。
在实际应用中,我们需要综合使用以上两种方法来进行非托管资源的释放工作:

 class Hotel: IDisposable
    {
        private bool isDisposed = false;

        ~Hotel()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposting)
        {
            if (!isDisposed)
            {
                if (disposting)
                {
                    //Cleanup managed objects by calling their Dispose()
                }
                //Cleanup unmanaged objects
            }
            isDisposed = true;
        }
        public void Operate()
        {
            if (isDisposed)
            {
                throw new ObjectDisposedException("Hotel");
            }
            //implementation
        }        
    }

在该示例中,Dispose(bool disposting)方法执行真正的清理工作。它接受一个参数disposting,用来标示调用是来自Dispose()方法还是析构函数。在调用Dispose()方法时,我们用到了GC.SuppressFinalize(this);它会告诉GC,在调用当前对象的Dispose()方法时,不再执行对象的析构函数。在Operate()方法中,会根据变量isDisposed来确定对象是否已经被销毁,如果被销毁,会抛出一个ObjectDisposedException类型的异常。

 

转载于:https://www.cnblogs.com/yonghuisoft/archive/2013/01/20/2868429.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值