【问题标题】:How to dispose managed resource in Dispose() method in C#?如何在 C# 的 Dispose() 方法中处理托管资源?
【发布时间】:2011-01-18 02:00:00
【问题描述】:

我知道 Dispose() 是为非托管资源设计的,当不再需要该资源时应该释放该资源,而无需等待垃圾收集器完成对象。

但是,在处理对象时,它会抑制垃圾收集器的终结(GC.SuppressFinalize(this); 在下面的代码中)。这意味着如果对象包含托管资源,我们也必须处理它,因为垃圾收集器不会清理它。

在下面的示例代码中(来自 MSDN),“Component”是一个托管资源,我们为此资源调用 dispose() (component.Dispose())。我的问题是,我们如何为托管资源的 Component 类实现此方法?我们应该使用 Collect() 之类的东西来戳垃圾收集器来清理这部分吗?

任何想法将不胜感激。谢谢。

下面是我正在查看的来自 MSDN 的代码:

using System;
using System.ComponentModel;

// The following example demonstrates how to create
// a resource class that implements the IDisposable interface
// and the IDisposable.Dispose method.

public class DisposeExample
{
// A base class that implements IDisposable.
// By implementing IDisposable, you are announcing that
// instances of this type allocate scarce resources.
public class MyResource: IDisposable
{
    // Pointer to an external unmanaged resource.
    private IntPtr handle;
    // Other managed resource this class uses.
    private Component component = new Component();
    // Track whether Dispose has been called.
    private bool disposed = false;

    // The class constructor.
    public MyResource(IntPtr handle)
    {
        this.handle = handle;
    }

    // Implement IDisposable.
    // Do not make this method virtual.
    // A derived class should not be able to override this method.
    public void Dispose()
    {
        Dispose(true);
        // This object will be cleaned up by the Dispose method.
        // Therefore, you should call GC.SupressFinalize to
        // take this object off the finalization queue
        // and prevent finalization code for this object
        // from executing a second time.
        GC.SuppressFinalize(this);
    }

    // Dispose(bool disposing) executes in two distinct scenarios.
    // If disposing equals true, the method has been called directly
    // or indirectly by a user's code. Managed and unmanaged resources
    // can be disposed.
    // If disposing equals false, the method has been called by the
    // runtime from inside the finalizer and you should not reference
    // other objects. Only unmanaged resources can be disposed.
    private void Dispose(bool disposing)
    {
        // Check to see if Dispose has already been called.
        if(!this.disposed)
        {
            // If disposing equals true, dispose all managed
            // and unmanaged resources.
            if(disposing)
            {
                // Dispose managed resources.
                component.Dispose();
            }

            // Call the appropriate methods to clean up
            // unmanaged resources here.
            // If disposing is false,
            // only the following code is executed.
            CloseHandle(handle);
            handle = IntPtr.Zero;

            // Note disposing has been done.
            disposed = true;

        }
    }

    // Use interop to call the method necessary
    // to clean up the unmanaged resource.
    [System.Runtime.InteropServices.DllImport("Kernel32")]
    private extern static Boolean CloseHandle(IntPtr handle);

    // Use C# destructor syntax for finalization code.
    // This destructor will run only if the Dispose method
    // does not get called.
    // It gives your base class the opportunity to finalize.
    // Do not provide destructors in types derived from this class.
    ~MyResource()
    {
        // Do not re-create Dispose clean-up code here.
        // Calling Dispose(false) is optimal in terms of
        // readability and maintainability.
        Dispose(false);
    }
}
public static void Main()
{
    // Insert code here to create
    // and use the MyResource object.
}
}

【问题讨论】:

  • 错误前提:“这意味着如果对象包含托管资源,我们也必须注意这一点,因为垃圾收集器不会清理它。”那是错误的。

标签: c# .net garbage-collection dispose resources


【解决方案1】:

那个一次性模式是confusing。这是a better way 来实现它:

第 1 步。 创建一个一次性类来封装您拥有的每个非托管资源。这应该很少见,大多数人没有非托管资源需要清理。这个类only cares (pdf) 关于它的非托管资源,应该有一个终结器。实现如下所示:

public class NativeDisposable : IDisposable {

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

  protected virtual void CleanUpNativeResource() {
    // ...
  }

  ~NativeDisposable() {
    CleanUpNativeResource();
  }

  // ...

  IntPtr _nativeResource;

}

第 2 步。 当该类包含其他一次性类时,创建一个一次性类。这很容易实现,您不需要终结器。在您的 Dispose 方法中,只需在其他一次性用品上调用 Dispose。在这种情况下,您不需要关心非托管资源:

public class ManagedDisposable : IDisposable {

  // ...

  public virtual void Dispose() {
    _otherDisposable.Dispose();
  }

  IDisposable _otherDisposable;

}

示例中的“组件”可能是其中之一,具体取决于它是封装非托管资源还是仅由其他一次性资源组成。

另外请注意,禁止终结并不意味着您禁止垃圾收集器清理您的实例;这只是意味着当垃圾收集器在您的实例中运行时,它不会调用为其定义的终结器。

【讨论】:

  • 我希望我能给你更多的支持。 Microsoft 一次性使用托管和非托管资源的模式是愚蠢的;任何具有终结器的类都不应持有对执行终结器任务不需要的任何对象的强引用。如果一个类拥有任何托管资源,这意味着 99% 的确定它不应该有终结器,因为终结器很可能不需要这些资源。那么,为什么要提供派生类添加终结器的可能性呢?
  • 我不知道 public sealed 似乎适合 NativeDisposable 类。 Instances 应该由创建它们的代码持有,既不从公众接收也不向公众公开。未密封类的主要“危险”是期望从外部代码接收Thing 的代码可能会被赋予DerivedThing,但如果代码既不公开也不接受来自外部的引用,这种危险不会'不适用。
  • @supercat:你说得对!我现在不知道为什么我首先这样做,但我已经回滚了。谢谢!
【解决方案2】:

这意味着如果对象包含托管资源,我们也必须注意这一点,因为垃圾收集器不会清理它。

这是错误的。垃圾收集器仍会清理您的托管资源。终结器也严格用于清理非托管资源,因此 SuppressFinalize() 调用不会伤害您。

由于您是 IDisposable 模式的新手,我预计您的下一个困惑点是:编写终结器。在 C# 中,您应该只在处理一种全新的非托管资源时编写终结器。因此,例如,如果您有一个将 System.Data.SqlClient.SqlConnection 类型包装为数据访问层一部分的类,您应该为该类型编写终结器,因为您仍然处理同一种底层非托管资源:sql server 数据库连接。该资源的终结器已由基本 SqlConnection 类型处理。

另一方面,如果您正在为一种全新的数据库引擎构建 ADO.Net 提供程序,则需要在连接类中实现终结器,因为以前从未这样做过。

【讨论】:

  • 我也有同样的问题,并看到了您对终结器的评论。因此,为了清楚起见,假设我有一个使用 SerialPort 对象(非托管,是吗?)的库类 (Controller) 和一个使用此类的 WinForms 应用程序。我想确保关闭应用程序时 SerialPort 实例已关闭。我的Controller 课程中是否需要终结器?我遵循了 MSDN 示例格式,但你说这听起来可能不需要?谢谢!
  • @Jon 如果您在谈论 System.IO.Ports.SerialPort,那么该类是包装非托管资源的 托管 类型。您确实不需要 Controller 类型的终结器,因为 SerialPort 类型已经有终结器来最终清理任何非托管资源。但是您的 Controller 类型应该实现 IDisposable,并且当您这样做时,您还将释放任何串行端口成员。
  • 谢谢乔尔。哦,好吧,所以我的班级应该像上面的 MSDN 示例一样保留两种 Dispose() 方法格式?只是省略了终结器?我会把controllerSerialPort.Dispose() 放在Dispose(bool)if(disposing) 块中吗?
【解决方案3】:

也许更清楚一点。 GC.SuppressFinalize(this) 只影响 this 指针引用的对象,而不影响对象的任何成员。也就是说 SuppressFinalize 不会递归地应用于对象的成员。当垃圾收集器为 Disposed 对象回收内存时,很可能没有对对象字段的活动引用。由于您没有对对象的所有字段调用 GC.SuppressFinalize,因此垃圾收集器将对这些对象调用 finalize 方法(如果它们存在)。当它这样做时,这完全取决于运行时,通常你应该让它做它的事情。

【讨论】:

    【解决方案4】:

    对不起,如果我误解了您的问题!!,但是如果您的类只是引用了其他托管类,并且对这些对象的引用不需要处置,那么您的类不一定需要实现 IDisposable。

    【讨论】:

      猜你喜欢
      • 2013-05-12
      • 1970-01-01
      • 2019-08-02
      • 2020-02-15
      • 2023-03-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多