您的当前位置:首页正文

《转》c#的内存管理(托管及未托管对象管理)

来源:要发发知识网
class Myclass : IDisposable
{
   public void Dispose() 
   {
      // implementation
   }
}

Dispose()的执行代码显式释放由对象直接使用的所有未托管资源,并在所有实现IDisposable接口的封装对象上调用Dispose()。这样,Dispose()方法在释放未托管资源时提供了精确的控制。
假定有一个类ResourceGobbler,它使用某些外部资源,且执行IDisposable接口。如果要实例化这个类的实例,使用它,然后释放它,就可以使用下面的代码:

ResourceGobbler theInstance = new ResourceGobbler();

   // 这里是theInstance 对象的使用过程
  
theInstance.Dispose();


如果在处理过程中出现异常,这段代码就没有释放theInstance使用的资源,所以应使用try块,编写下面的代码:

ResourceGobbler theInstance = null;
try
{
   theInstance = new ResourceGobbler();
//  这里是theInstance 对象的使用过程
}
finally  
{
  if (theInstance != null) theInstance.Dispose();
}

即使在处理过程中出现了异常,这个版本也可以确保总是在theInstance上调用Dispose(),总是释放由theInstance使用的资源。但是,如果总是要重复这样的结构,代码就很容易被混淆。C#提供了一种语法,可以确保在引用超出作用域时,在对象上自动调用Dispose()(但不是Close())。该语法使用了using关键字来完成这一工作—— 但目前,在完全不同的环境下,它与命名空间没有关系。下面的代码生成与try块相对应的IL代码:

using (ResourceGobbler theInstance = new ResourceGobbler())
{
   //  这里是theInstance 对象的使用过程
}

using语句的后面是一对圆括号,其中是引用变量的声明和实例化,该语句使变量放在随附的复合语句中。另外,在变量超出作用域时,即使出现异常,也会自动调用其Dispose()方法。如果已经使用try块来捕获其他异常,就会比较清晰,如果避免使用using语句,仅在已有的try块的finally子句中调用Dispose(),还可以避免进行额外的缩进。
注意:
对于某些类来说,使用Close()要比Dispose()更富有逻辑性,例如,在处理文件或数据库连接时,就是这样。在这些情况下,常常实现IDisposable接口,再执行一个独立的Close()方法,来调用Dispose()。这种方法在类的使用上比较清晰,还支持C#提供的using语句。

前面的章节讨论了类所使用的释放未托管资源的两种方式:
●        利用运行库强制执行的析构函数,但析构函数的执行是不确定的,而且,由于垃圾收集器的工作方式,它会给运行库增加不可接受的系统开销。
●        IDisposable接口提供了一种机制,允许类的用户控制释放资源的时间,但需要确保执行Dispose()。
一般情况下,最好的方法是执行这两种机制,获得这两种机制的优点,克服其缺点。假定大多数程序员都能正确调用Dispose(),实现IDisposable接口,同时把析构函数作为一种安全的机制,以防没有调用Dispose()。下面是一个双重实现的例子:

public class ResourceHolder : IDisposable
{
      private bool isDispose = false;
      
      // 显示调用的Dispose方法
  public void Dispose() 
      {
           Dispose(true);
          GC.SuppressFinalize(this); 
       }

       // 实际的清除方法
  protected virtual void Dispose(bool disposing) 
      {
            if (!isDisposed)
           {
              if (disposing) 
           { 
                      // 这里执行清除托管对象的操作.
                  }
                  // 这里执行清除非托管对象的操作
            }
    
         isDisposed=true;
      }

      // 析构函数 
      ~ResourceHolder()
      {
            Dispose (false);
      }
}


可以看出,Dispose()有第二个protected重载方法,它带一个bool参数,这是真正完成清理工作的方法。Dispose(bool)由析构函数和IDisposable.Dispose()调用。这个方式的重点是确保所有的清理代码都放在一个地方。
传递给Dispose(bool)的参数表示Dispose(bool)是由析构函数调用,还是由IDisposable.Dispose()调用——Dispose(bool)不应从代码的其他地方调用,其原因是:
●        如果客户调用IDisposable.Dispose(),该客户就指定应清理所有与该对象相关的资源,包括托管和非托管的资源。
●        如果调用了析构函数,在原则上,所有的资源仍需要清理。但是在这种情况下,析构函数必须由垃圾收集器调用,而且不应访问其他托管的对象,因为我们不再能确定它们的状态了。在这种情况下,最好清理已知的未托管资源,希望引用的托管对象还有析构函数,执行自己的清理过程。
isDispose成员变量表示对象是否已被删除,并允许确保不多次删除成员变量。这个简单的方法不是线程安全的,需要调用者确保在同一时刻只有一个线程调用方法。要求客户进行同步是一个合理的假定,在整个.NET类库中反复使用了这个假定(例如在集合类中)。最后,IDisposable.Dispose()包含一个对System.GC. SuppressFinalize()方法的调用。SuppressFinalize()方法则告诉垃圾收集器有一个类不再需要调用其析构函数了。因为Dispose()已经完成了所有需要的清理工作,所以析构函数不需要做任何工作。调用SuppressFinalize()就意味着垃圾收集器认为这个对象根本没有析构函数.

  正确理解以上内容,可以大大优化系统性能,及时释放不需要的数据,不能仅靠C#提供的自动回收机制,也需要程序员使用更灵活的办法!二者合一既能让程序运行飞快,也让系统更加稳定!

转载于:https://www.cnblogs.com/1971ruru/archive/2010/05/20/1740049.html

因篇幅问题不能全部显示,请点此查看更多更全内容