Dispose / finalize pattern:处理托管资源

时间:2015-06-21 10:40:56

标签: c# garbage-collection dispose finalize

让我们假设我有一个名为Base的类有3个属性:

class Base : IDisposable
{
    private string _String;
    private Class1 classe1;
    private int foo;

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

    public virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            Console.WriteLine("Free Managed ressources");
                  //HOW TO FREE _String, class1 and foo ?!          
        }
        Console.WriteLine("Free unmanaged ressources");
    }

    ~Base()
    {
        this.Dispose(false);
    }
}

和一个名为Class1的classe,有2个属性:

class Class1
{
    public int entier { get; set; }
    public string Nom { get; set; }
}

我的问题是:如何在Dispose方法中释放Base的属性? (_String,classe1,foo)

2 个答案:

答案 0 :(得分:3)

  

我的问题是:我如何在Dispose中释放Base的属性   方法 ? (_String,classe1,foo)

你不需要,这是垃圾收集器的工作。实现IDisposable是框架允许您释放已分配的任何非托管资源的一种方式,并且自己配置实现IDisposable的托管对象(后者又支持其他非托管资源) 。

您的一次性工具IDisposable中没有任何托管对象,并且一旦不再有任何对象指向您的Base类,它们将被收集。什么时候会发生?在任意时间,当GC看到第0代中不再有空间时,它需要收集。你无需做任何事情。

实施IDisposable并不意味着“一旦我运行 Dispose(),这个对象将立即被收集”,这仅仅意味着该框架让您有机会收回任何资源它可能没有意识到(例如非托管的)。推荐的方法,如果实现终结器,通过GC.SuppressFinalize抑制对它的调用,保存GC将对象从终结器队列移动到F-Reachable队列的麻烦,从而使其可用于收藏较早。

  

这3个属性何时会从堆中释放出来?垃圾   收藏家不会释放他们,因为我有GC.SuppressFinalize(this)

您对GC的工作方式以及SuppressFinalize的含义存在基本的误解。 GC将在非确定性时间运行,您基本上不应该关心何时发生这种情况。他有责任在你之后进行清理。在实现终结器的对象上调用SuppressFinalize只会在调用终结器时运行时检查的对象头中设置一个位,这将阻止终结器运行

答案 1 :(得分:1)

在这种情况下,你根本不应该实现IDisposable,或者如果它在那里,因为它被认为很可能在将来是必要的,那么它将是一个空的实现。你当然不应该有一个终结者;除非你真的需要100%确定性,否则永远不会有。

在某些情况下,您可能希望实现IDisposable,在某些情况下,您还希望拥有析构函数(这是使用终结函数的C#方式)。< / p>

一个是你拥有的东西,当对象完成时,它是非常重要的,通常是撤消你以前做过的事情,比如释放你获得的句柄,关闭你的连接&# 39; d打开等,但托管内存。 (所有对象都使用托管内存,如果不能再次使用托管内存,所有对象都会清理托管内存,而其他内容需要更多托管内存,这就是< em>托管 in&#34;托管内存&#34;意味着。)

public class SomeClass : IDisposable
{
  private IntPtr _someHandle;
  public SomeClass(string someIdentifier)
  {
    _someHandle = GetHandle(someIdentifier);
  }
  public void Dispose()
  {
    ReleaseHandle(_someHandle);
  }
}

所以,现在每当使用SomeClass的内容完成后,它就会调用Dispose()(可能隐含地通过using块)并清除所有内容很好。

但如果那不会发生怎么办?那么,这就是为什么我们可能有一个终结者:

public class SomeClass : IDisposable
{
  private IntPtr _someHandle;
  public SomeClass(string someIdentifier)
  {
    _someHandle = GetHandle(someIdentifier);
  }
  public void Dispose()
  {
    ReleaseHandle(_someHandle);
    _someHandle = null; // so we know not to release twice.
  }
  ~SomeClass()
  {
    if(_someHandle != null)
      ReleaseHandle(_someHandle);
  }
}

所以,如果Dispose()没有被调用,我们仍然可以进行清理,因为正常的垃圾收集过程:

  1. 意识到你需要更多的记忆。
  2. 查找不再使用的对象。
  3. 回收那些物品的记忆。
  4. 是否添加了以下步骤:

    1. 实现您要回收的内存有一个终结者可以运行的对象。
    2. 将对象放入其他此类对象的队列中。
    3. (在另一个线程上)运行对象的终结器。
    4. 该对象不再是一个&#34;有一个终结者可以运行的对象。按照上面的步骤4,所以下次可以回收它。
    5. 所有这些都有缺点:

      1. 我们无法保证何时会发生这种情况。
      2. 我们没有在步骤3中回收尽可能多的内存,因为有这样的对象。
      3. 垃圾收集是世代的,并且与对象的世代收集很好地结合意味着快速死亡或长时间生活,在GC尝试收集对象的第一次之后死亡最不理想的时间。
      4. 我们可以通过调用Dispose()而不是让终结发生来解决前两个问题,这取决于类的用户,而不是类本身。我们通过让对象知道它不需要最终标记为不再需要的对象来解决第三个问题:

        public class SomeClass : IDisposable
        {
          private IntPtr _someHandle;
          public SomeClass(string someIdentifier)
          {
            _someHandle = GetHandle(someIdentifier);
          }
          public void Dispose()
          {
            ReleaseHandle(_someHandle);
            GC.SuppressFinalize(this);
          }
          ~SomeClass()
          {
            ReleaseHandle(_someHandle);
          }
        }
        

        如果某个对象已经传递给GC.SuppressFinalize(),那么第4步就会发生。

        第二种情况,您可以实现IDisposable的实施目标,即IDisposable个对象作为另一个对象的字段,其中包含&#34;拥有&#34;它(控制它的一生):

        public class SomeOtherClass : IDisposable
        {
          private SomeClass _someObj;
          public SomeOtherClass(string someIdentifier)
          {
            _someObj = new SomeClass(someIdentifier);
          }
          public void Dispose()
          {
            //If base type is disposable
            //call `base.Dispose()` here too.
            _someObj.Dispose();
          }
        }
        

        清理SomeOtherClass因此意味着清理它所拥有的SomeClass字段。请注意,我们在这里有一个终结者。我们不需要终结者,因为它无关紧要;充其量它什么都不做,只是有上面提到的终结者的缺点,更糟糕的是,它会尝试清理_someObj而不知道这是在_someObj清理自己之前还是之后_someObj清除Dispose()排队等待自己清理的方式,它可以假设没有别的东西可以进行清理。

        对于第三种情况,考虑我们是否将这两种情况与一个具有两者一个非释放资源的类和一个一次性类的字段组合在一起。在这里,如果我们public sealed class SomeHybridClass : IDisposable { private IntPtr _someHandle; private SomeClass _someObj; public SomeHybridClass(string someIdentifier) { _someHandle = GetHandle(someIdentifier); _someObj = new SomeClass(someIdentifier); } public void Dispose() { ReleaseHandle(_someHandle); GC.SuppressFinalize(this); _someObj.Dispose(); } ~SomeHybridClass() { ReleaseHandle(_someHandle); } } d,我们想要清理它们,但如果我们最终确定,我们只想清理直接处理的非托管资源:

        public sealed class SomeHybridClass : IDisposable
        {
          private IntPtr _someHandle;
          private SomeClass _someObj;
          public SomeHybridClass(string someIdentifier)
          {
            _someHandle = GetHandle(someIdentifier);
            _someObj = new SomeClass(someIdentifier);
          }
          private void Dispose(bool disposing)
          {
            if(disposing)
            {
              _someObj.Dispose();
            }
            ReleaseHandle(_someHandle);
          }
          public void Dispose()
          {
            Dispose(true);
            GC.SuppressFinalize(this);
          }
          ~SomeHybridClass()
          {
            Dispose(false);
          }
        }
        

        现在,由于此处有重复,因此将它们重构为相同的方法是有意义的:

        Dispose(bool)

        对于第四种情况,想象一下这门课程是否封闭;它的派生类型也需要能够进行清理,因此我们使参数化public class SomeHybridClass : IDisposable { private IntPtr _someHandle; private SomeClass _someObj; public SomeHybridClass(string someIdentifier) { _someHandle = GetHandle(someIdentifier); _someObj = new SomeClass(someIdentifier); } protected virtual void Dispose(bool disposing) { // if this in turn was derived, we'd call // base.Dispose(disposing) here too. if(disposing) { _someObj.Dispose(); } ReleaseHandle(_someHandle); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } ~SomeHybridClass() { Dispose(false); } } 方法受到保护:

        seal

        然而,最后两个例子确实是在解决错误的问题:他们正在解决如何让一个既有一次性类型作为字段又有非托管资源的类的问题,和/或成为发生这种情况的类型层次结构。从来没有遇到过这种情况,你真的好多了;或者有一个处理非托管资源的类(并且protected virtual void Dispose(bool disposing)编辑)在字段中具有一次性类型,并且最终只有处理前两个案件。如果您通过派生SafeHandle来处理非托管资源,那么您实际上只需要担心第二种情况,并且还管理一些困难的边缘情况。

        真的,终结者应该非常非常少地写作,而且当他们写作时,他们应该尽可能简单地写,因为他们有足够的复杂性和他们周围的边缘情况,因为它是。你需要知道如何处理覆盖{{1}}(注意,永远不应该是公开的)来处理那些对某人来说似乎是个好主意的遗产,但是没有非托管和托管的可继承类 - 一次性资源迫使其他人进入该职位。

          

        如何在Dispose方法中释放Base的属性? (_String,classe1,foo)

        现在应该清楚,那些字段(.NET中的属性是一个非常不同的东西)不需要被释放。他们拥有的唯一资源是管理记忆,所以一旦他们无法到达(不是静止的,不是要在某种方法中对他们做某些事情,而且不是。在任何一个类别的某个领域中的某个领域中,或者在其中任何一个领域中的某个领域中的某个领域,等等。)他们的记忆将在需要时自动回收。