Dispose/finalize 模式:处置托管资源

Dispose/finalize pattern : disposing managed ressources

假设我有一个名为 Base 的 class 具有 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)

My question is : How can I free the attributes of Base in the Dispose method ? (_String, classe1, foo)

你不需要,那是垃圾收集器的工作。实施 IDisposable 是框架允许您释放任何已分配的 非托管 资源,并自行处置实施 IDisposable 的托管 object 的一种方式(反过来持有其他非托管资源)。

None 的托管 object 在您的一次性工具 IDisposable,一旦不再有任何 object 指向您的 Base class。那什么时候会发生?任意时刻,当GC看到第0代不再有space,需要回收。您无需做任何事情。

实施 IDisposable 并不意味着 "this object will be collected immediatly once i run Dispose()",它仅意味着该框架让您有机会回收它可能不知道的任何资源(例如非托管资源).这是一种推荐的方法,如果实现终结器,通过 GC.SuppressFinalize 抑制对它的调用,从而节省 GC 将 object 从终结器 Queue 移动到 F-Reachable Queue,因此可以更早地用于 collection。

when will these 3 attributes free from the heap ? The garbage collector won't free them because I have GC.SuppressFinalize(this)

您对 GC 的工作原理以及 SuppressFinalize 的含义存在基本的误解。 GC 将在 non-deterministic 时 运行,您基本上不应该关心这种情况何时发生。在你之后清理是他的责任。在实现终结器的 object 上调用 SuppressFinalize 只不过在 objects header 中设置了一个位, 运行time 在调用终结器时会检查它,这将从 运行ning

中抑制你的终结器

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

在某些情况下,您可能希望实现 IDisposable,在某些情况下,您还希望拥有一个析构函数(这是 C# 实现终结器的方式)。

一个是当对象完成时你有一些非常重要的事情要做,最常见的是撤消你以前做过的事情,比如释放你获得的句柄,关闭你的连接' d 打开等但 不是 管理的内存。 (所有对象都使用托管内存,如果所有对象都无法再次使用,并且其他对象需要更多托管内存,那么所有对象都会为它们清理托管内存,这就是 managed 在 "managed memory" 中表示)。

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. 回收那些对象的内存。

添加了以下步骤:

  1. 实现您要回收其内存的对象具有 运行 的终结器。
  2. 将该对象放入其他此类对象的队列中。
  3. (在单独的线程上)运行 对象的终结器。
  4. 该对象不再是上述第 4 步 "has a finaliser to run" 的对象,因此下次可以回收它。

所有这些都有缺点:

  1. 我们无法保证这种情况何时会发生。
  2. 我们没有在第 3 步中回收尽可能多的内存,因为有这样一个对象。
  3. 垃圾收集是分代的,与对象的分代收集配合得很好意味着要么死得快,要么活得久,在 GC 第一次尝试收集后 就死了一个对象几乎是最不理想的时间。

我们可以通过调用 Dispose() 而不是让终结发生来绕过前两个,这取决于 class 的用户,而不是 class 本身。我们通过让一个知道它不需要被最终确定的对象来解决第三个问题,将自己标记为不再需要:

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 对象作为另一个对象的字段 "owns" 它(控制它的生命周期):

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 排队等待以一种它可以假设没有其他东西会进行清理的方式进行自我清理。

对于第三种情况,考虑我们是否将这两种情况与 class 结合起来,其中 两者都 它释放的非托管资源和一个一次性 class。这里如果我们是Dispose()d我们想清理两者,但是如果我们最终确定我们只想清理直接处理的非托管资源:

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);
  }
}

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

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);
  }
}

对于第四种情况,想象一下如果这个 class 没有被密封;它的派生类型也需要能够进行这种清理,因此我们将参数化的 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);
  }
}

然而,这最后两个例子实际上解决了错误的问题:他们解决了如何拥有一个 class 的问题,该 class 具有作为字段的一次性类型和非托管资源,and/or 成为类型层次结构的一部分。真的,永远不要陷入这种情况要好得多;要么有一个 class 处理非托管资源(并且是 sealed) 具有一次性类型字段,你最终只能处理前两种情况。如果您通过从 SafeHandle 派生来处理您的非托管资源,那么您实际上只需要担心第二种情况,并且它也可以管理一些困难的边缘情况。

真的,终结者应该非常非常少地被写,当他们被写的时候,他们应该被写得尽可能简单,因为他们和他们周围的边缘情况有足够的内在复杂性。您需要知道如何处理重写 protected virtual void Dispose(bool disposing)(注意,永远不应该是 public),以处理对某些人来说这似乎是个好主意但无法继承的遗留问题 class具有非托管和托管一次性资源,迫使其他人进入该位置。

How can I free the attributes of Base in the Dispose method ? (_String, classe1, foo)

现在应该清楚了,不需要释放那些字段(属性在 .NET 中是完全不同的东西)。他们拥有的唯一资源是托管内存,所以一旦无法访问它们(不在静态中,不打算在方法中对他们做某事,也不在某个领域在这些类别中的任何一个或其中任何一个字段中的某个字段中,等等)它们的内存将在需要时自动回收。