在C#中完成/处理模式

ant*_*009 369 .net c# idisposable finalizer

C#2008

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

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

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

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

    using(myClass objClass = new myClass())
    {
        // Do stuff here
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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);
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

关于源代码的问题:

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

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

    using(NoGateway objNoGateway = new NoGateway())
    {
        // Do stuff here   
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

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

the*_*oop 414

推荐的IDisposable模式在这里.在编写使用IDisposable的类时,通常应该使用两种模式:

在实现不使用非托管资源的密封类时,您只需实现与正常接口实现一样的Dispose方法:

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

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

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);
    //}
}
Run Code Online (Sandbox Code Playgroud)

请注意,我还没有宣布终结者B; 如果您有实际的非托管资源要处置,则应该只实现终结器.CLR以不可终结的对象处理可终结对象,即使SuppressFinalize被调用也是如此.

因此,除非必须,否则不应声明终结器,但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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • @erikkallen是个笑话吗?:) (93认同)
  • 我同意thecoop.请注意,如果您只处理托管资源,则不需要终结器(事实上,您不应该尝试从终结器中访问托管对象("此"除外),因为没有保证顺序GC将清理对象.此外,如果您使用.Net 2.0或更高版本,您可以(并且应该)使用SafeHandles来包装非托管句柄.安全手柄可以大大减少为托管类编写终结器的需要.http:// blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx (26认同)
  • 我认为最好在终结器中调用MessageBox.Show("Error","+ GetType().Name +"not dispos"),因为一次性对象应该总是被丢弃,如果你没有这样做,那就是最好尽早提醒这个事实. (5认同)
  • @Ritch:引用?这不一定是坏事; 如果你正在实施`IDisposable`,那么无论如何它可能会暂停一段时间.你正在保存CLR必须从Gen0 - > Gen1 - > Gen2复制它的努力 (3认同)
  • 因为CLR需要额外的计算工作来跟踪具有活动终结器的类. - 实现终结器会导致这种情况发生.调用GC.SuppressFinalize意味着运行时不应调用Finalizer.无论如何,它仍然是Gen2.如果您不处理托管资源,请不要添加终结器.密封或未密封的类修饰符与该点无关. (2认同)
  • @thecoop:净效果是每个具有未抑制的终结器的对象至少存活一个集合比其他必要的更长,并且可终结对象持有直接或间接引用的每个对象将至少与可终结对象一样长本身. (2认同)

Jor*_*dão 123

实施的官方模式IDisposable很难理解.我相信这个更好:

public class BetterDisposableClass : IDisposable {

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

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

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}
Run Code Online (Sandbox Code Playgroud)

一个更好的解决方案是有一个规则,你总是要创建你需要处理的任何非托管资源的包装类:

public class NativeDisposable : IDisposable {

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

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

  ~NativeDisposable() {
    CleanUpNativeResource();
  }

}
Run Code Online (Sandbox Code Playgroud)

有了SafeHandle它的衍生物,这些类应该是非常罕见的.

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

public class ManagedDisposable : IDisposable {

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

}
Run Code Online (Sandbox Code Playgroud)

  • @HuseyinUslu:这只是模式的_essence_.你当然可以添加一个`dispos`标志并进行相应的检查. (5认同)
  • 虽然我想要注意的一件事是它不会阻止被第二次调用. (4认同)
  • @didibus:添加一个`dispos`标志很简单,在处理之前检查它并在处理后设置它.看看[这里](http://pastebin.com/ujrPRphM)的想法.您还应该在该类的任何方法之前检查该标志.说得通?这很复杂吗? (2认同)

Dav*_*ack 37

请注意,任何IDisposable实现都应遵循以下模式(恕我直言).我根据几个优秀的.NET"神" .NET Framework设计指南的信息开发了这个模式(请注意,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
Run Code Online (Sandbox Code Playgroud)

以下是在派生类中实现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 );
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • @akjoshi - 我已经更新了上面的模式,包括派生的一次性类的代码.另请注意,永远不要在派生类中实现Finalizer ...... (3认同)
  • 微软似乎喜欢在置位方法的末尾设置"dispos"标志,但这对我来说似乎不对.对"Dispose"的冗余调用应该什么都不做; 虽然人们通常不会期望Dispose被递归调用,但是如果试图通过在构造或其他操作期间发生的异常来处理已经处于无效状态的对象,则会发生这样的事情.我认为在非虚拟包装函数中对整数`IsDisposed`标志使用`Interlocked.Exchange`会更安全. (3认同)

Dav*_*ack 23

我同意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上的一些关于为什么这是一个坏主意.我也在这里发布了它 - 不要在WCF代理中使用'using()'

  • 我相信还有第五种情况:如果您的类使用强引用订阅事件,那么您应该实现IDisposable并从Dispose方法中的事件中取消注册. (3认同)

pm1*_*100 12

使用lambdas而不是IDisposable.

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

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

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

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

using (Var conn = Factory.MakeConnection())
{
     conn.Query(....);
}
Run Code Online (Sandbox Code Playgroud)

新方法

Factory.DoWithConnection((conn)=>
{
    conn.Query(...);
}
Run Code Online (Sandbox Code Playgroud)

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

DoWithConnection看起来像这样

void DoWithConnection(Action<SqlConnection> action)
{
   using (var conn = MakeConnection())
   {
       action(conn);
   }
}
Run Code Online (Sandbox Code Playgroud)

MakeConnection 现在是私人的

  • 在lambdas中包装东西可能是一个很好的方法,但它有限制.事实上,一个类的所有消费者都会使用"使用"块,但是它会禁止一种方法将IDisposable存储在类字段中(直接或者像迭代器这样的情况),这并不算太糟糕. ). (2认同)

pm1*_*100 10

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

简答:不

答案很长:

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

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

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

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

所以不要这样做

  • @ pm100 Re:不必要地实现IDisposable - http://www.codeproject.com/KB/dotnet/idisposable.aspx上有详细的文章讨论了一些罕见的情况,你可能想要考虑这个(非常罕见,我'当然).简而言之:如果您可以预见将来或在派生对象中需要IDisposable,那么您可能会考虑在基类中实现IDisposable作为"无操作",以避免在某些派生对象需要时"切片"问题处置和其他人没有. (3认同)

And*_*ski 5

配置模式:

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() { }
}
Run Code Online (Sandbox Code Playgroud)

继承的例子:

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)