繁体   English   中英

C#中Finalize/Dispose方法的使用

[英]Use of Finalize/Dispose method in C#

C# 2008

我已经研究了一段时间了,我仍然对在代码中使用 finalize 和 dispose 方法感到困惑。 我的问题如下:

  1. 我知道在处理非托管资源时我们只需要一个终结器。 但是,如果存在调用非托管资源的托管资源,它是否仍然需要实现终结器?

  2. 但是,如果我开发一个不直接或间接使用任何非托管资源的类,我是否应该实现IDisposable以允许该类的客户端使用“using 语句”?

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

关于源代码的问题:

  1. 这里我没有添加终结器,一般情况下终结器会被GC调用,终结器会调用Dispose。 由于我没有终结器,我什么时候调用 Dispose 方法? 是类的客户端必须调用它吗?

    因此,我在示例中的类称为 NoGateway,客户端可以像这样使用和处置该类:

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

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

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

推荐的 IDisposable 模式在这里 在编写使用 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(...) {}确保根据需要删除任何非托管资源。

实现IDisposable的官方模式很难理解。 我相信这个更好

public class BetterDisposableClass : IDisposable {

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

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

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}

一个更好的解决方案是制定一个规则,即您必须始终为需要处理的任何非托管资源创建包装类:

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
  }

}

请注意,任何 IDisposable 实现都应遵循以下模式(恕我直言)。 我根据几个优秀的 .NET“神”的信息开发了这个模式.NET 框架设计指南(注意 MSDN 由于某种原因没有遵循这个!)。 .NET Framework 设计指南是由 Krzysztof Cwalina(当时的 CLR 架构师)和 Brad Abrams(我相信当时的 CLR 程序经理)和 Bill Wagner([Effective C#] 和 [More Effective C#](只需看看在 Amazon.com 上查找这些:

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

我整理的模式(并为其编写了代码片段)如下:

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

我已经在我的博客上发布了这个实现: 如何正确实现处置模式

我同意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. 您的课程包含上述内容的任意组合...

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

BCL(基类库团队)在这里有一篇很好的博客文章http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx

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

使用 lambdas 而不是 IDisposable。

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

  • 知道他们必须使用 IDisposable
  • 记住使用“使用”。

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

想象一下,我想用 SqlConnection 做一些事情(应该用 using 包装的东西)。 传统上你会做

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现在是私有的

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

简短回答:否

长答案:

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

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

因此,通过实现 IDisposable,您是在告诉开发人员(至少一些)这个类包含了一些必须发布的东西。 他们将使用 'using' - 但在其他情况下使用是不可能的(对象的范围不是本地的); 在其他情况下,他们将不得不开始担心对象的生命周期——我肯定会担心。 但这不是必须的

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

所以不要这样做

配置模式:

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

由于两个原因,另一个答案的某些方面略有不正确:

第一的,

using(NoGateway objNoGateway = new NoGateway())

实际上相当于:

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

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

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

如果“as”运算符返回 null(或返回资源的属性或方法),并且“using”块中的代码防止“null”,则在尝试对 null 对象调用 Dispose 时,您的代码不会因为以下原因而崩溃“内置”空检查。

你的回复不准确的第二个原因是因为下面的stmt:

在 GC 销毁您的对象时调用终结器

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

我在我的博客上发布了一个正确的处置模式实现如何正确实现处置模式

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

  2. 如果您的班级不使用任何稀缺资源,我不明白您为什么要让您的班级实现 IDisposable。 您应该只在以下情况下这样做:

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

  4. (又是 2 次?)WebClient 很可能使用非托管资源或其他实现 IDisposable 的托管资源。 然而,确切的原因并不重要。 重要的是它实现了 IDisposable,因此当您处理完对象时,您需要根据该知识采取行动,即使事实证明 WebClient 根本不使用其他资源。

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

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

2)间接是的,但您不必担心。 你的代码是正确的,你不能防止你的用户很容易忘记 Dispose()。

using(NoGateway objNoGateway = new NoGateway())

相当于

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

在 GC 销毁您的对象时调用终结器。 这可能与您离开方法的时间完全不同。 在您离开 using 块后立即调用 IDisposable 的 Dispose。 因此,该模式通常用于在您不再需要资源后立即使用 using 来释放资源。

据我所知,强烈建议不要使用终结器/析构函数:

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

大多数情况下,这是由于不知道何时或是否会调用它。 dispose 方法要好得多,特别是如果您直接使用或 dispose。

使用是好的。 用它 :)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM