在C#中完成/处理模式

时间:2009-05-22 16:44:46

标签: c# .net idisposable finalizer

C#2008

我一直在研究这个问题,我仍然对某些问题感到困惑。我的问题在下面

  1. 我知道如果你处理非托管资源,你只需要一个终结器。但是,如果您使用托管资源来调用非托管资源,您是否还需要实现终结器?

  2. 但是,如果您开发一个不直接或间接使用任何非托管资源的类,您是否可以实现IDisposable以便您的类的客户端可以使用'using statement'?

    实现IDisposable是否可以接受,以便您的类的客户端可以使用using语句?

    using(myClass objClass = new myClass())
    {
        // Do stuff here
    }
    
  3. 我在下面开发了这个简单的代码来演示Finalize / dispose模式:

    public class NoGateway : IDisposable
    {
        private WebClient wc = null;
    
        public NoGateway()
        {
            wc = new WebClient();
            wc.DownloadStringCompleted += wc_DownloadStringCompleted;
        }
    
    
        // Start the Async call to find if NoGateway is true or false
        public void NoGatewayStatus()
        {
            // Start the Async's download
                // Do other work here
            wc.DownloadStringAsync(new Uri(www.xxxx.xxx));
        }
    
        private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            // Do work here
        }
    
        // Dispose of the NoGateway object
        public void Dispose()
        {
            wc.DownloadStringCompleted -= wc_DownloadStringCompleted;
            wc.Dispose();
            GC.SuppressFinalize(this);
        }
    }
    
  4. 关于源代码的问题:

    1. 这里我没有添加终结器,通常GC将调用终结器,终结器将调用Dispose。由于我没有终结器,何时调用Dispose方法?这个类的客户端是否需要调用它?

      所以我的示例中的类名为NoGateway,客户端可以像这样使用和处理类:

      using(NoGateway objNoGateway = new NoGateway())
      {
          // Do stuff here   
      }
      

      当执行到达using块的末尾时,会自动调用Dispose方法,还是客户端必须手动调用dispose方法?即。

      NoGateway objNoGateway = new NoGateway();
      // Do stuff with object
      objNoGateway.Dispose(); // finished with it
      
    2. 我在NoGateway课程中使用了webclient课程。因为webclient实现了IDisposable接口,这是否意味着webclient间接使用非托管资源?是否有一个严格的规则可以遵循这个?我怎么知道一个类使用非托管资源?

13 个答案:

答案 0 :(得分:397)

推荐的IDisposable模式为here。在编写使用IDisposable的类时,通常应该使用两种模式:

实现不使用非托管资源的密封类时,只需像普通接口实现一样实现Dispose方法:

public sealed class A : IDisposable
{
    public void Dispose()
    {
        // get rid of managed resources, call Dispose on member variables...
    }
}

实现未密封的类时,请执行以下操作:

public class B : IDisposable
{    
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }   
        // get rid of unmanaged resources
    }

    // only if you use unmanaged resources directly in B
    //~B()
    //{
    //    Dispose(false);
    //}
}

请注意,我尚未在B中声明终结器;如果您有实际的非托管资源要处置,则应该只实现终结器。即使调用SuppressFinalize,CLR也会以不可终结的对象处理可终结对象。

因此,除非必须,否则不应声明终结器,但是如果他们直接使用非托管资源,则为您的类的继承者提供一个钩子来调用Dispose并自己实现终结器:

public class C : B
{
    private IntPtr m_Handle;

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }
        ReleaseHandle(m_Handle);

        base.Dispose(disposing);
    }

    ~C() {
        Dispose(false);
    }
}

如果您没有直接使用非托管资源(SafeHandle并且朋友不计算,因为他们声明了自己的终结器),那么就不要实现终结器,因为GC以不同的方式处理可终结的类,即使你后来压制终结者。另请注意,即使B没有终结器,它仍然会调用SuppressFinalize来正确处理任何实现终结器的子类。

当一个类实现IDisposable接口时,它意味着某些地方有一些非托管资源,当你使用完该类时应该除掉它们。实际资源封装在类中;您不需要明确删除它们。只需调用Dispose()或将类包装在using(...) {}中,就可以确保在必要时删除任何非托管资源。

答案 1 :(得分:116)

实施IDisposable的官方模式很难理解。我相信这一个是better

public class BetterDisposableClass : IDisposable {

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

  protected virtual void CleanUpManagedResources() { 
    // ...
  }
  protected virtual void CleanUpNativeResources() {
    // ...
  }

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}

even better解决方案是要有一条规则,始终必须为您需要处理的任何非托管资源创建包装类:

public class NativeDisposable : IDisposable {

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

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

  ~NativeDisposable() {
    CleanUpNativeResource();
  }

}

使用SafeHandle及其派生词,这些类应该非常罕见

即使在存在继承的情况下,不直接处理非托管资源的一次性类的结果也很强大:他们不再需要关注非托管资源。他们将简单来实现和理解:

public class ManagedDisposable : IDisposable {

  public virtual void Dispose() {
    // dispose of managed resources
  }

}

答案 2 :(得分:35)

请注意,任何IDisposable实现都应遵循以下模式(IMHO)。我基于来自几个优秀的.NET“神”.NET Framework Design Guidelines的信息开发了这种模式(注意MSDN由于某种原因不遵循这个!)。 .NET Framework设计指南由Krzysztof Cwalina(当时为CLR Architect)和Brad Abrams(我相信当时的CLR项目经理)和Bill Wagner([有效C#]和[更有效的C#])撰写在Amazon.com上寻找这些:

请注意,除非您的类直接包含(不是继承)非托管资源,否则不应该实现Finalizer。一旦你在一个类中实现了Finalizer,即使它从未被调用过,它仍然可以用于额外的集合。它会自动放在Finalization Queue上(在单个线程上运行)。另外,一个非常重要的注意事项......在Finalizer中执行的所有代码(如果你需要实现一个)必须是线程安全的,并且是异常安全的!否则会发生一些事情......(即未确定的行为,如果是异常,则会导致致命的不可恢复的应用程序崩溃)。

我放在一起的模式(并编写了一个代码片段)如下:

#region IDisposable implementation

//TODO remember to make this class inherit from IDisposable -> $className$ : IDisposable

// Default initialization for a bool is 'false'
private bool IsDisposed { get; set; }

/// <summary>
/// Implementation of Dispose according to .NET Framework Design Guidelines.
/// </summary>
/// <remarks>Do not make this method virtual.
/// A derived class should not be able to override this method.
/// </remarks>
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.

    // Always use SuppressFinalize() in case a subclass
    // of this type implements a finalizer.
    GC.SuppressFinalize( this );
}

/// <summary>
/// Overloaded Implementation of Dispose.
/// </summary>
/// <param name="isDisposing"></param>
/// <remarks>
/// <para><list type="bulleted">Dispose(bool isDisposing) executes in two distinct scenarios.
/// <item>If <paramref name="isDisposing"/> equals true, the method has been called directly
/// or indirectly by a user's code. Managed and unmanaged resources
/// can be disposed.</item>
/// <item>If <paramref name="isDisposing"/> 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.</item></list></para>
/// </remarks>
protected virtual void Dispose( bool isDisposing )
{
    // TODO If you need thread safety, use a lock around these 
    // operations, as well as in your methods that use the resource.
    try
    {
        if( !this.IsDisposed )
        {
            if( isDisposing )
            {
                // TODO Release all managed resources here

                $end$
            }

            // TODO Release all unmanaged resources here



            // TODO explicitly set root references to null to expressly tell the GarbageCollector
            // that the resources have been disposed of and its ok to release the memory allocated for them.


        }
    }
    finally
    {
        // explicitly call the base class Dispose implementation
        base.Dispose( isDisposing );

        this.IsDisposed = true;
    }
}

//TODO Uncomment this code if this class will contain members which are UNmanaged
// 
///// <summary>Finalizer for $className$</summary>
///// <remarks>This finalizer will run only if the Dispose method does not get called.
///// It gives your base class the opportunity to finalize.
///// DO NOT provide finalizers in types derived from this class.
///// All code executed within a Finalizer MUST be thread-safe!</remarks>
//  ~$className$()
//  {
//     Dispose( false );
//  }
#endregion IDisposable implementation

以下是在派生类中实现IDisposable的代码。请注意,您不需要在派生类的定义中明确列出IDisposable的继承。

public DerivedClass : BaseClass, IDisposable (remove the IDisposable because it is inherited from BaseClass)


protected override void Dispose( bool isDisposing )
{
    try
    {
        if ( !this.IsDisposed )
        {
            if ( isDisposing )
            {
                // Release all managed resources here

            }
        }
    }
    finally
    {
        // explicitly call the base class Dispose implementation
        base.Dispose( isDisposing );
    }
}

我已在我的博客上发布此实施:How to Properly Implement the Dispose Pattern

答案 3 :(得分:22)

我同意with pm100(并且应该在我之前的帖子中明确说过这一点。)

除非您需要,否则不应在类中实现IDisposable。非常具体,大概有5次你需要/应该实现IDisposable:

  1. 您的类显式包含(即不通过继承)任何实现IDisposable的托管资源,并且应该在您的类不再使用后清除它们。例如,如果您的类包含Stream,DbCommand,DataTable等的实例

  2. 您的类显式包含实现Close()方法的任何托管资源 - 例如IDataReader,IDbConnection等。请注意,其中一些类通过Dispose()和Close()方法实现IDisposable。

  3. 您的类显式包含非托管资源 - 例如一个COM对象,指针(是的,您可以在托管C#中使用指针,但它们必须在'不安全'块中声明,等等。 在非托管资源的情况下,您还应确保在RCW上调用System.Runtime.InteropServices.Marshal.ReleaseComObject()。尽管RCW在理论上是一个托管包装器,但仍然存在引用计数。

  4. 如果您的班级使用强引用订阅活动。您需要从事件中取消注册/分离自己。在尝试取消注册/分离它们之前,始终要确保它们不是空的!

  5. 您的课程包含以上内容的任意组合......

  6. 使用COM对象并且必须使用Marshal.ReleaseComObject()的推荐替代方法是使用System.Runtime.InteropServices.SafeHandle类。

    BCL(基类图书馆小组)在这里有一篇关于它的好文章http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx

    要做的一个非常重要的注意事项是,如果您正在使用WCF并清理资源,那么您应该始终避免使用“使用”块。有很多博客文章和MSDN上的一些关于为什么这是一个坏主意。我也在这里发布了它 - Don't use 'using()' with a WCF proxy

答案 4 :(得分:12)

使用lambdas而不是IDisposable。

我从来没有对整个使用/ IDisposable想法感到兴奋。问题是它需要调用者:

  • 知道他们必须使用IDisposable
  • 记得使用'使用'。

我新的首选方法是使用工厂方法和lambda而不是

想象一下,我想用SqlConnection做一些事情(应该包含在使用中)。经典你会做

using (Var conn = Factory.MakeConnection())
{
     conn.Query(....);
}

新方式

Factory.DoWithConnection((conn)=>
{
    conn.Query(...);
}

在第一种情况下,调用者可能根本不使用using语法。在第二种情况下,用户别无选择。没有创建SqlConnection对象的方法,调用者必须调用DoWithConnection。

DoWithConnection看起来像这样

void DoWithConnection(Action<SqlConnection> action)
{
   using (var conn = MakeConnection())
   {
       action(conn);
   }
}

MakeConnection现已私有

答案 5 :(得分:10)

没有人回答关于你是否应该实施IDisposable的问题,即使你不需要它。

简答:否

答案很长:

这将允许您班级的消费者使用“使用”。我要问的问题是 - 他们为什么要这样做?大多数开发人员不会使用“使用”,除非他们知道必须 - 并且他们如何知道。任

  • 从经验中可以看出它们(例如套接字类)
  • 其记录
  • 他们很谨慎,可以看到该类实现了IDisposable

因此,通过实现IDisposable,你告诉开发人员(至少有些人)这个类包装了必须释放的内容。他们将使用'使用' - 但还有其他情况下无法使用(对象的范围不是本地的);他们将不得不开始担心其他情况下物体的寿命 - 我当然会担心。但这不是必要的

您实现Idisposable以使其能够使用,但除非您告诉他们,否则他们不会使用。

所以不要这样做

答案 6 :(得分:4)

  1. 如果您正在使用其他使用非托管资源的托管对象,则无需确保完成这些对象。您的责任是在对象上调用Dispose时调用Dispose,然后停止在那里。

  2. 如果你的班级没有使用任何稀缺资源,我就不明白你为什么要让你的班级实现IDisposable。你应该这样做,如果你是:

    • 知道你的物体很快就会有稀缺的资源,而不是现在(我的意思是“正如我们仍然在发展,它会在我们完成之前就在这里”,而不是像“我想我们”我需要这个“)
    • 使用稀缺资源
  3. 是的,使用您的代码的代码必须调用对象的Dispose方法。是的,使用您的对象的代码可以使用using,如您所示。

  4. (2再次?)WebClient可能使用非托管资源或实现IDisposable的其他托管资源。然而,确切的原因并不重要。重要的是它实现了IDisposable,所以当你完成它时,通过处理对象就可以对你采取行动,即使事实证明WebClient根本不使用其他资源。

答案 7 :(得分:4)

another answer的某些方面略有不正确,原因有两个:

首先,

using(NoGateway objNoGateway = new NoGateway())

实际上相当于:

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

这可能听起来很荒谬,因为'new'运算符永远不会返回'null',除非你有一个OutOfMemory异常。但请考虑以下情况: 1.您调用FactoryClass返回IDisposable资源或 2.如果你有一个类型可能会或可能不会从IDisposable继承,具体取决于它的实现 - 请记住,我已经看到IDisposable模式在许多客户端多次错误地实现,其中开发人员只是在不继承IDisposable的情况下添加Dispose()方法(坏,坏,坏)。您还可以从属性或方法返回IDisposable资源的情况(再次糟糕,糟糕,糟糕 - 不要'放弃您的IDisposable资源)

using(IDisposable objNoGateway = new NoGateway() as IDisposable)
{
    if (NoGateway != null)
    {
        ...

如果'as'运算符返回null(或返回资源的属性或方法),并且'using'块中的代码防止'null',那么当你尝试在null上调用Dispose时,你的代码不会爆炸对象,因为'内置'空检查。

您的回复不准确的第二个原因是由于以下内容:

  

在GC摧毁你的对象时调用终结器

首先,Finalization(以及GC本身)是非确定性的。 CLR确定何时调用终结器。即开发人员/代码不知道。如果IDisposable模式正确实现(如上所述)并且已调用GC.SuppressFinalize(),则不会调用Finalizer。这是正确实现模式的重要原因之一。由于每个托管进程只有一个Finalizer线程,无论逻辑处理器的数量如何,您都可以通过忘记调用GC.SuppressFinalize()来备份甚至挂起Finalizer线程,从而轻松降低性能。

我在博客上发布了Dispose Pattern的正确实现:How to Properly Implement the Dispose Pattern

答案 8 :(得分:3)

处理模式:

public abstract class DisposableObject : IDisposable
{
    public bool Disposed { get; private set;}      

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

    ~DisposableObject()
    {
        Dispose(false);
    }

    private void Dispose(bool disposing)
    {
        if (!Disposed)
        {
            if (disposing)
            {
                DisposeManagedResources();
            }

            DisposeUnmanagedResources();
            Disposed = true;
        }
    }

    protected virtual void DisposeManagedResources() { }
    protected virtual void DisposeUnmanagedResources() { }
}

继承示例:

public class A : DisposableObject
{
    public Component components_a { get; set; }
    private IntPtr handle_a;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeManagedResources");
          components_a.Dispose();
          components_a = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeUnmanagedResources");
          CloseHandle(handle_a);
          handle_a = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}

public class B : A
{
    public Component components_b { get; set; }
    private IntPtr handle_b;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeManagedResources");
          components_b.Dispose();
          components_b = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeUnmanagedResources");
          CloseHandle(handle_b);
          handle_b = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}

答案 9 :(得分:2)

using(NoGateway objNoGateway = new NoGateway())

相当于

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

在GC摧毁您的物体时调用终结器。这可能与您离开方法的时间完全不同。离开使用块后立即调用IDisposable的Dispose。因此,模式通常用于在您不再需要它们之后立即释放资源。

答案 10 :(得分:2)

1)WebClient是托管类型,因此您不需要终结器。如果您的用户没有使用NoGateway类的Dispose(),并且需要在之后清理本机类型(GC未收集),则需要终结器。在这种情况下,如果用户没有调用Dispose(),则在NoGateway之后,GC将立即处理所包含的WebClient。

2)间接是,但你不必担心它。你的代码是正确的,你无法阻止你的用户忘记Dispose()。

答案 11 :(得分:2)

来自msdn的模式

public class BaseResource: IDisposable
{
   private IntPtr handle;
   private Component Components;
   private bool disposed = false;
   public BaseResource()
   {
   }
   public void Dispose()
   {
      Dispose(true);      
      GC.SuppressFinalize(this);
   }
   protected virtual void Dispose(bool disposing)
   {
      if(!this.disposed)
      {        
         if(disposing)
         {
            Components.Dispose();
         }         
         CloseHandle(handle);
         handle = IntPtr.Zero;
       }
      disposed = true;         
   }
   ~BaseResource()      
   {      Dispose(false);
   }
   public void DoSomething()
   {
      if(this.disposed)
      {
         throw new ObjectDisposedException();
      }
   }
}
public class MyResourceWrapper: BaseResource
{
   private ManagedResource addedManaged;
   private NativeResource addedNative;
   private bool disposed = false;
   public MyResourceWrapper()
   {
   }
   protected override void Dispose(bool disposing)
   {
      if(!this.disposed)
      {
         try
         {
            if(disposing)
            {             
               addedManaged.Dispose();         
            }
            CloseHandle(addedNative);
            this.disposed = true;
         }
         finally
         {
            base.Dispose(disposing);
         }
      }
   }
}

答案 12 :(得分:-4)

据我所知,强烈建议不要使用Finalizer / Destructor:

public ~MyClass() {
  //dont use this
}

大多数情况下,这是由于不知道何时或将被调用。处置方法要好得多,特别是如果我们直接使用或处置。

使用很好。用它:)