首页 文章

在C#中完成/处理模式

提问于
浏览
344

C#2008

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

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

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

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

using(myClass objClass = new myClass())
{
    // Do stuff here
}
  • 我在下面开发了这个简单的代码来演示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);
    }
}

关于源代码的问题:

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

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

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

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

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

13 回答

  • 35

    推荐的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() 或将类包装在_1360617中,就可以确保在必要时删除所有非托管资源 .

  • 21

    实施 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及其衍生物,这些类应该非常罕见 .

    即使在存在继承的情况下,不直接处理非托管资源的一次性类的结果也很强大: they don't need to be concerned with unmanaged resources anymore . 它们易于实现和理解:

    public class ManagedDisposable : IDisposable {
    
      public virtual void Dispose() {
        // dispose of managed resources
      }
    
    }
    
  • 2

    请注意,任何IDisposable实现都应遵循以下模式(恕我直言) . 我根据几个优秀的.NET "gods" .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

  • 12

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

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

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

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

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

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

    • 您的课程包含上述任意组合......

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

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

    One very important note to make is that if you are working with WCF and cleaning up resources, you should ALMOST ALWAYS avoid the 'using' block. There are plenty of blog posts out there and some on MSDN about why this is a bad idea. I have also posted about it here - Don't use 'using()' with a WCF proxy

  • 10

    使用lambdas而不是IDisposable .

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

    • 知道他们必须使用IDisposable

    • 记得使用'using' .

    我新的首选方法是使用工厂方法和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 现在是私人的

  • 384

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

    简答:不

    答案很长:

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

    • 从经验中可以看出它们(例如套接字类)

    • 记录在案

    • 他们很谨慎,可以看到该类实现了IDisposable

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

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

    所以不要这样做

  • 4
    • 如果您正在使用其他使用非托管资源的托管对象,则无需确保完成这些对象 . 您的责任是致电Dispose在对象上调用Dispose时它停止的对象 .

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

    • 知道你的对象很快就会有稀缺资源,而不是现在(我的意思是在"we're still developing, it will be here before we're done"中,而不是在"I think we'll need this"中)

    • 使用稀缺资源

    • 是的,使用您的代码的代码必须调用对象的Dispose方法 . 是的,使用您的对象的代码可以使用 using ,如您所示 .

    • (2再次?)WebClient可能使用非托管资源或实现IDisposable的其他托管资源 . 然而,确切的原因并不重要 . 重要的是它实现了IDisposable,因此当你完成它时,即使事实证明WebClient根本不使用任何其他资源,你也可以通过处理该对象来处理这些知识 .

  • -4

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

    第一,

    using(NoGateway objNoGateway = new NoGateway())
    

    实际上相当于:

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

    这可能听起来很荒谬,因为'new'运算符永远不会返回'null',除非你有一个OutOfMemory异常 . 但请考虑以下情况: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(),则不会调用Finalizer . 这是正确实现模式的重要原因之一 . 由于每个托管进程只有一个Finalizer线程,无论逻辑处理器的数量如何,您都可以通过忘记调用GC.SuppressFinalize()来备份甚至挂起Finalizer线程,从而轻松降低性能 .

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

  • 113

    处理模式:

    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();
            }
        }
    }
    
  • 4
    using(NoGateway objNoGateway = new NoGateway())
    

    相当于

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

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

  • 2

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

    2)间接是,但你不必担心它 . 你的代码是正确的,你不能阻止你的用户忘记Dispose()非常容易 .

  • 3

    Pattern from 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

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

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

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

    使用很好 . 用它 :)

相关问题