【问题标题】:Use of Finalize/Dispose method in C#在 C# 中使用 Finalize/Dispose 方法
【发布时间】:2023-04-10 08:34:02
【问题描述】:

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 方法?即

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

【问题讨论】:

  • 解决这个资源释放问题真的需要这种复杂的设计模式吗?

标签: c# .net idisposable finalizer


【解决方案1】:

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

首先,

using(NoGateway objNoGateway = new NoGateway())

其实等价于:

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

这可能听起来很荒谬,因为除非您有 OutOfMemory 异常,否则“new”运算符不应该返回“null”。但请考虑以下情况: 1. 您调用返回 IDisposable 资源的 FactoryClass 或 (坏,坏,坏)。您还可能遇到从属性或方法返回 IDisposable 资源的情况(同样糟糕,糟糕,糟糕 - 不要“放弃您的 IDisposable 资源)

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

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

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

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

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

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

【讨论】:

  • 你确定要写NoGateway = new NoGateway();NoGateway != null吗?
  • 这是指stackoverflow.com/a/898856/3195477 吗?现在没有以“Icey”这个名字发布的答案
  • @DaveInCaz 看起来是正确的。我在任何地方都没有看到“Icey”,但我的回复内容似乎是针对您上面链接提供的答案。也许他改变了他的用户名?
  • @DaveBlack 很酷,谢谢。我刚刚将其编辑到文本中。
【解决方案2】:

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

使用 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

【讨论】:

  • 我相信有第 5 种情况:如果你的类使用强引用订阅事件,那么你应该实现 IDisposable 并从 Dispose 方法中的事件中注销自己。
  • 你好滴滴巴士。是的,你是对的。我忘了那个。我已经修改了我的答案,将其作为案例包括在内。谢谢。
  • dispose 模式的 MSDN 文档添加了另一种情况:“考虑在本身不包含非托管资源或一次性对象但可能有子类型的类上实现基本 Dispose 模式。一个很棒的System.IO.Stream 类就是一个例子。虽然它是一个不包含任何资源的抽象基类,但它的大多数子类都有,因此,它实现了这种模式。”
【解决方案3】:

推荐的 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(...) {} 中即可确保在必要时删除所有非托管资源。

【讨论】:

  • 我同意thecoop。请注意,如果您只处理托管资源,则不需要终结器(实际上,您不应该尝试从终结器中访问托管对象(“t​​his”除外),因为没有保证的顺序GC 将清理对象。此外,如果您使用 .Net 2.0 或更高版本,您可以(并且应该)使用 SafeHandles 来包装非托管句柄。Safehandles 大大减少了您为托管类编写终结器的需要。blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx
  • 我认为最好在终结器中调用 MessageBox.Show("Error, " + GetType().Name + " not dedicated") ,因为应始终释放一次性对象,如果您未能做到这一点,最好尽早告知这一事实。
  • @erikkallen 这是个玩笑吗? :)
  • 因为在 CLR 中需要额外的计算工作来跟踪具有活动终结器的类。 - 实现终结器会导致这种情况发生。调用 GC.SuppressFinalize 意味着运行时不应调用终结器。无论如何,它仍然是 Gen2。如果您不处理托管资源,请不要添加终结器。密封或未密封的类修饰符与这一点无关。
  • @Ritch:引用?这不一定是坏事。如果您正在实施IDisposable,那么它可能会持续一段时间。您正在节省 CLR 必须从 Gen0 -> Gen1 -> Gen2 复制它的工作
【解决方案4】:

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

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

我整理的模式(并为其编写了一个代码 sn-p)如下:

#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

【讨论】:

  • 有人也可以为派生类添加模式(从这个基类派生)
  • @akjoshi - 我已经更新了上面的模式以包含派生的一次性类的代码。另请注意,永远不要在派生类中实现终结器...
  • 微软似乎喜欢在处理方法的末尾设置“处理”标志,但这对我来说似乎是错误的。对“Dispose”的冗余调用应该什么都不做;虽然人们通常不会期望 Dispose 被递归调用,但如果尝试 Dispose 一个由于构造或其他操作期间发生的异常而处于无效状态的对象,则可能会发生这种情况。我认为在非虚拟包装函数中对整数 IsDisposed 标志使用 Interlocked.Exchange 会更安全。
  • @DaveBlack:如果您的基类不使用非托管资源,但您的派生类使用,该怎么办?那么你必须在派生类中实现终结器吗?如果是这样,如果您无权访问源,您怎么知道基类尚未实现它?
  • @DaveBlack "我根据几个优秀的 .NET "神" 的信息开发了这个模式 "如果其中一位神是 Jon Skeet,那么我会听从你的建议。
【解决方案5】:

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

【讨论】:

  • 在 lambdas 中包装东西可能是一个好方法,但它有局限性。事实上,对于一个类的所有消费者都会使用“使用”块的情况,这并不算太糟糕,但它不允许方法将 IDisposable 存储在类字段中(直接或类似迭代器的情况) )。
  • @supercat 您可以争辩说不允许存储占用资源的东西是一件好事。我在这里提出的借用模型迫使您精益使用资源
  • 这可能是一件好事,但它也可能使一些非常合理的操作变得非常困难。例如,假设一个数据库读取器类型,而不是实现 IEnumerable,公开了一个方法 DoForAll(Action&lt;T&gt;) where T:IComparable&lt;T&gt;,在每条记录上调用指定的委托。给定两个这样的对象,它们都将按排序顺序返回数据,一个集合如何输出存在于一个集合中但不存在于另一个集合中的所有项目?如果类型实现了IEnumerable&lt;T&gt;,则可以执行合并操作,但这不适用于DoForAll
  • 我能想出合并两个 DoForAll 集合而不必首先将一个集合完全复制到其他结构中的唯一方法是使用两个线程,这会更加笨拙资源,而不是简单地使用几个 IEnumerable 并小心释放它们。
  • -1:很好地回答了一个没有被问到的问题。这将是“如何使 IDisposable 对象的使用更容易”的一个很好的答案
【解决方案6】:

处理模式:

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

【讨论】:

    【解决方案7】:

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

    【讨论】:

    • @Kyle:谢谢!我也喜欢它:-) 有后续here
    • 虽然我要注意的一件事是它不会阻止被第二次调用。
    • @HuseyinUslu:这只是模式的本质。您当然可以添加 disposed 标志并进行相应检查。
    • @didibus:添加disposed标志很简单,在处理之前检查它并在处理后设置它。看看here 的想法。您还应该在类的任何方法之前检查标志。说得通?复杂吗?
    • +1 for “更好的解决方案是制定一个规则,即您始终必须为需要处理的任何非托管资源创建包装类”。我在 VLC 的一个插件中偶然发现了这个,从那以后我就一直在使用它。省去了很多麻烦...
    【解决方案8】:

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

    【讨论】:

      【解决方案9】:

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

      简短回答:否

      长答案:

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

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

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

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

      所以不要这样做

      【讨论】:

      • 我不明白为什么开发人员不会在实现 IDisposable 的对象上使用 using/dispose(除非程序即将退出)。
      • 关键是开发人员必须编写所有调用来处理所有导致它被取消引用的代码路径。因此,例如,如果我将一个实例放入字典中,当我从字典中删除条目时,我必须调用 dispose。在这种情况下不需要很多麻烦 - 不需要处理对象
      • @pm100 Re: 不必要地实现 IDisposable -- codeproject.com/KB/dotnet/idisposable.aspx 有详细的文章讨论了一些您可能想要考虑的罕见情况(我敢肯定,非常罕见)。简而言之:如果您可以预见未来或派生对象中对 IDisposable 的需求,那么您可能会考虑在基类中将 IDisposable 实现为“无操作”以避免某些派生对象需要的“切片”问题处置和其他人没有。
      【解决方案10】:

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

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

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

      使用很好。使用它:)

      【讨论】:

      • 您应该点击 thecoop 答案中的链接。是的,使用/Dispose 更好,但 Disposable 类绝对应该实现两者。
      • 有趣的是,我从微软读过的所有文档——例如框架设计指南——说永远不要使用析构函数。始终使用 IDisposable。
      • 只需区分使用类和编写类,再读一遍。
      【解决方案11】:
      1. 如果您正在使用正在使用非托管资源的其他托管对象,则确保这些对象已完成不是您的责任。您的责任是在对您的对象调用 Dispose 时对这些对象调用 Dispose,并且它会停在那里。

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

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

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

      【讨论】:

        【解决方案12】:

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

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

        【讨论】:

          【解决方案13】:
          using(NoGateway objNoGateway = new NoGateway())
          

          等价于

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

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

          【讨论】:

          • GC 销毁对象时不会调用终结器。如果 "Finalize" 被覆盖,那么当 GC 否则会销毁对象 时,它将被放置在需要终结的对象队列中,暂时创建对它的强引用 - 至少暂时--“复活”它。
          猜你喜欢
          • 1970-01-01
          • 2019-03-05
          • 2012-12-08
          • 1970-01-01
          • 2013-09-20
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多