关于.net:在C#中完成/处理模式

Finalize/Dispose pattern in C#

C 2008

我已经为此做了一段时间了,我对一些问题仍然感到困惑。我的问题如下

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

  • 但是,如果开发一个不直接或间接使用任何非托管资源的类,可以实现IDisposable,这样类的客户机就可以使用'using语句'?

    实现IDisposable以便类的客户机可以使用using语句是可以接受的吗?

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    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,客户机可以这样使用和处置类:

    1
    2
    3
    4
    using(NoGateway objNoGateway = new NoGateway())
    {
        // Do stuff here  
    }

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

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


  • 这里是推荐的IDisposable模式。在编程使用IDisposable的类时,通常应使用两种模式:

    当实现不使用非托管资源的密封类时,只需实现与常规接口实现相同的Dispose方法:

    1
    2
    3
    4
    5
    6
    7
    public sealed class A : IDisposable
    {
        public void Dispose()
        {
            // get rid of managed resources, call Dispose on member variables...
        }
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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中声明终结器;只有当您有实际的非托管资源要处理时,才应该实现终结器。clr处理可终结的对象与处理不可终结的对象不同,即使调用SuppressFinalize

    因此,除非必须声明终结器,否则不应声明终结器,但如果类的继承者直接使用非托管资源,则给类的继承者一个钩子来调用Dispose并实现终结器本身:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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和friends不算数,因为它们声明自己的终结器),那么就不要实现终结器,因为GC处理可终结类的方式不同,即使您稍后禁止使用终结器。还要注意,即使B没有终结器,它仍然调用SuppressFinalize来正确处理任何实现终结器的子类。

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


    执行IDisposable的官方模式很难理解。我相信这个更好:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class BetterDisposableClass : IDisposable {

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

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

      ~BetterDisposableClass() {
        CleanUpNativeResources();
      }

    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class NativeDisposable : IDisposable {

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

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

      ~NativeDisposable() {
        CleanUpNativeResource();
      }

    }

    对于SafeHandle及其衍生产品,这些类别应该非常罕见。

    对于不直接处理非托管资源(即使存在继承)的可释放类,其结果是非常强大的:它们不再需要关注非托管资源。它们很容易实现和理解:

    1
    2
    3
    4
    5
    6
    7
    public class ManagedDisposable : IDisposable {

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

    }


    请注意,任何IDisposable实现都应遵循以下模式(IMHO)。我根据一些优秀的.NET"上帝"的信息开发了这个模式.NET框架设计指南(请注意,由于某种原因,msdn不遵循这个模式!).NET框架设计指南由krzysztof cwalina(当时的clr架构师)和brad abrams(我相信当时的clr程序经理)以及bill wagner([有效的c]和[更有效的c]编写(请在amazon.com上查看这些内容:

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

    我所建立(并为其编写代码片段)的模式如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    #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的继承。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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 );
        }
    }

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


    我同意PM100(并且应该在我之前的文章中明确地说)。

    除非需要,否则不应在类中实现IDisposable。具体来说,您需要/应该实现IDisposable的时间大约有5次:

  • 类显式包含(即,不通过继承)实现IDisposable的任何托管资源,一旦不再使用类,就应该清除这些资源。例如,如果类包含流、dbcommand、datatable等的实例。

  • 类显式包含实现close()方法的任何托管资源,例如IDataReader、IDbConnection等。请注意,这些类中的一些类确实通过具有Dispose()和close()方法实现IDisposable。

  • 类显式包含非托管资源-例如COM对象、指针(是的,可以在托管C中使用指针,但必须在"不安全"块中声明指针,等等。对于非托管资源,还应确保在RCW上调用System.Runtime.InteropServices.Marshal.ReleaseComObject()。尽管从理论上讲,RCW是一个托管包装器,但是仍然有引用计数在封面下进行。

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

  • 您的类包含上述任何组合…

  • 与处理COM对象以及必须使用marshal.releaseComObject()相比,建议使用System.Runtime.InteropServices.SafeHandle类。

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

    一个非常重要的注意事项是,如果您正在使用WCF并清理资源,那么您应该几乎总是避免使用"using"块。那里有很多博客文章,还有一些关于为什么这是一个坏主意的关于msdn的文章。我也在这里发布过-不要将"using()"与wcf代理一起使用


    使用lambda而不是idisposable。

    我从来没有对整个使用/可识别的想法感到兴奋。问题是,它要求调用者:

    • 知道他们必须使用IDisposable
    • 记住使用"using"。

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

    假设我想用一个sqlconnection(应该用using包装的东西)来做一些事情。经典的你会这么做的

    1
    2
    3
    4
    using (Var conn = Factory.MakeConnection())
    {
         conn.Query(....);
    }

    新途径

    1
    2
    3
    4
    Factory.DoWithConnection((conn)=>
    {
        conn.Query(...);
    }

    在第一种情况下,调用方不能简单地使用using语法。在第二种情况下,用户没有选择。没有创建sqlconnection对象的方法,调用方必须调用doWithConnection。

    DowithConnection看起来像这样

    1
    2
    3
    4
    5
    6
    7
    void DoWithConnection(Action<SqlConnection> action)
    {
       using (var conn = MakeConnection())
       {
           action(conn);
       }
    }

    MakeConnection现在是私有的


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

    简短回答:否

    长回答:

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

    • 它的对象是来自经验的它们(例如socket类)
    • 其文件化
    • 他们很谨慎,可以看到类实现了IDisposable

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

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

    所以不要这样做


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

    第一,

    1
    using(NoGateway objNoGateway = new NoGateway())

    实际上相当于:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    try
    {
        NoGateway = new NoGateway();
    }
    finally
    {
        if(NoGateway != null)
        {
            NoGateway.Dispose();
        }
    }

    这听起来可能很荒谬,因为"new"运算符不应返回"null",除非出现内存不足异常。但考虑以下情况:1。调用返回IDisposable资源的FactoryClass或2。如果您有一个可能继承或不继承IDisposable的类型,这取决于它的实现-请记住,我在许多客户端上多次看到IDisposable模式的实现不正确,开发人员只需添加Dispose()方法而不继承IDisposable(bad、bad、bad)。您还可以拥有从属性或方法返回IDisposable资源的情况(同样是坏的、坏的、坏的-不要"放弃您的IDisposable资源")。

    1
    2
    3
    4
    5
    using(IDisposable objNoGateway = new NoGateway() as IDisposable)
    {
        if (NoGateway != null)
        {
            ...

    如果"as"运算符返回空值(或返回资源的属性或方法),并且"using"块中的代码可防止"null",则在尝试调用对空对象的Dispose时,由于"内置"空检查,代码不会爆炸。

    您的答复不准确的第二个原因是由于以下stmt:

    A finalizer is called upon the GC destroying your object

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

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


  • 如果您使用的是其他使用非托管资源的托管对象,则您没有责任确保这些对象最终确定。当对对象调用Dispose时,您的责任是调用这些对象上的Dispose,它就停在那里。

  • 如果你的类不使用任何稀缺的资源,我就不明白为什么你要让类实现IDisposable。只有当你:

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

  • (2次?)WebClient可能使用非托管资源或其他实现IDisposable的托管资源。然而,确切的原因并不重要。重要的是,它实现了IDisposable,因此在处理完对象后,您需要根据该知识来处理它,即使WebClient根本不使用其他资源。


  • 处置模式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    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() { }
    }

    继承示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    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();
            }
        }
    }

    来自msdn的模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    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类的(),则需要使用终结器,之后需要清理本机类型(GC未收集该类型)。在这种情况下,如果用户不调用Dispose(),则包含的WebClient将在nogateway之后由gc进行释放。

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


    1
    using(NoGateway objNoGateway = new NoGateway())

    等于

    1
    2
    3
    4
    5
    6
    7
    8
    9
    try
    {
        NoGateway = new NoGateway();
    }

    finally
    {
        NoGateway.Dispose();
    }

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


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

    1
    2
    3
    public ~MyClass() {
      //dont use this
    }

    大多数情况下,这是因为不知道何时或是否会被调用。Dispose方法更好,特别是如果我们直接使用或处置。

    使用是好的。使用它:)