在C#中完成/处理模式

C#2008

我已经有一段时间了,对于一些问题我还是感到困惑。 我的问题如下

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

  2. 但是,如果您直接或间接地开发一个不使用任何非托pipe资源的类,是否可以实现IDisposable以便您的类的客户端可以使用“使用语句”?

    实施IDisposable只是为了让你的class级的客户可以使用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方法? 是class上的客户吗?

    所以我的例子中的类被称为NoGateway,客户端可以像这样使用和处理类:

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

    当执行到达使用块的末尾时,会自动调用Dispose方法,还是客户端不得不手动调用dispose方法? 即

     NoGateway objNoGateway = new NoGateway(); // Do stuff with object objNoGateway.Dispose(); // finished with it 
  2. 我在NoGateway类中使用NoGateway类。 由于webclient实现了IDisposable接口,这是否意味着webclient间接使用非托pipe资源? 对此有没有严格的规定? 我怎么知道一个类使用非托pipe资源?

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

在实现不使用非托pipe资源的密封类时,只需像使用常规接口一样实现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声明一个finalizer。 你应该只实现一个终结器,如果你有实际的非托pipe资源来处置。 即使已调用SuppressFinalize ,CLR也会以不同于可终结对象的方式处理可终结对象。

所以,除非必须声明一个终结器,否则你不应该声明一个终结器,但是如果他们直接使用非托pipe资源,你可以给你的类的inheritance者一个钩子来调用你的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); } } 

如果您没有直接使用非托pipe资源( SafeHandle和朋友不计算,因为它们声明了自己的终结器),那么不要实现终结器,因为GC会以不同的方式处理可终结类,即使后来禁止终结器。 还要注意的是,即使B没有终结器,它仍然调用SuppressFinalize来正确处理任何实现终结器的子类。

当一个类实现了IDisposable接口时,这意味着某个地方有一些非托pipe资源,在完成这个类的使用后应该删除掉。 实际资源被封装在类中; 你不需要明确地删除它们。 简单地调用Dispose()或者using(...) {}包装类将确保任何非托pipe资源都被删除。

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

 public class BetterDisposableClass : IDisposable { public void Dispose() { CleanUpManagedResources(); CleanUpNativeResources(); GC.SuppressFinalize(this); } protected virtual void CleanUpManagedResources() { // ... } protected virtual void CleanUpNativeResources() { // ... } ~BetterDisposableClass() { CleanUpNativeResources(); } } 

一个更好的解决scheme是有一个规则,你总是必须为你需要处理的任何非托pipe资源创build一个包装类:

 public class NativeDisposable : IDisposable { public void Dispose() { CleanUpNativeResource(); GC.SuppressFinalize(this); } protected virtual void CleanUpNativeResource() { // ... } ~NativeDisposable() { CleanUpNativeResource(); } } 

使用SafeHandle及其衍生工具,这些类应该非常less见

即使在inheritance存在的情况下,不直接处理非托pipe资源的一次性类的结果也非常强大: 它们不再需要关心非托pipe资源 。 他们将很容易实施和理解:

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

请注意,任何IDisposable实现应遵循以下模式(恕我直言)。 我基于来自几个优秀的.NET“众神” .NET框架devise指南的信息开发了这种模式(请注意,由于某些原因,MSDN并不遵循这个原则!)。 .NET框架devise指南是由Krzysztof Cwalina(当时的CLR架构师)和Brad Abrams(我相信当时的CLR程序经理)和Bill Wagner([Effective C#]和[More Effective C#](只要在Amazon.com上寻找这些:

请注意,除非您的课程直接包含(不inheritance)非托pipe资源,否则不应执行终结器。 一旦你在一个类中实现了一个Finalizer,即使它从来没有被调用,它仍然保证额外的收集。 它自动放置在Finalization Queue(在单个线程上运行)上。 另外,一个非常重要的注意事项…所有在Finalizer中执行的代码(如果需要实现的话)必须是线程安全的并且是exception安全的! 坏事情会发生,否则…(即未确定的行为,并在例外的情况下,致命的不可恢复的应用程序崩溃)。

我放在一起的模式(并写了一个代码片段)如下:

 #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的inheritance。

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

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

我同意pm100(在我以前的文章中应该明确地说过)。

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

  1. 你的类明确地包含(即不是通过inheritance)实现IDisposable的任何pipe理资源,并且一旦你的类不再使用,应该清除它。 例如,如果你的类包含一个Stream,DbCommand,DataTable等的实例

  2. 您的类明确包含实现Close()方法的任何托pipe资源 – 例如IDataReader,IDbConnection等。请注意,其中一些类通过使用Dispose()和Close()方法来实现IDisposable。

  3. 你的类明确地包含了一个非托pipe资源 – 例如一个COM对象,指针(是的,你可以在托pipe的C#中使用指针,但是它们必须在'不安全的块'中声明。在非托pipe资源的情况下,你也应该确保在RCW上调用System.Runtime.InteropServices.Marshal.ReleaseComObject()。尽pipeRCW在理论上是一个托pipe包装,但仍然有引用计数在下面进行。

  4. 如果你的类使用强引用来订阅事件。 您需要取消注册/从事件中分离出来。 在试图取消注册/分离它们之前,一定要确保它们不是空的。

  5. 你的课程包含上述的任何组合

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

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

需要注意的一点是,如果你正在使用WCF和清理资源,你应该总是避免使用'block'。 有很多博客文章和MSDN上的一些关于为什么这是一个坏主意。 我也在这里发布 – 不要在WCF代理中使用'using()'

使用lambdas而不是IDisposable。

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

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

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

想象一下,我想用SqlConnection做一些事情(应该用一个包装的东西)。 经典,你会做的

 using (Var conn = Factory.MakeConnection()) { conn.Query(....); } 

新方法

 Factory.DoWithConnection((conn)=> { conn.Query(...); } 

在第一种情况下,调用者可以简单地不使用使用语法。 在第二种情况下,用户没有select。 没有创buildSqlConnection对象的方法,调用者必须调用DoWithConnection。

DoWithConnection看起来像这样

 void DoWithConnection(Action<SqlConnection> action) { using (var conn = MakeConnection()) { action(conn); } } 

MakeConnection现在是私人的

即使你不需要它,也没人回答你是否应该实现IDisposable的问题。

简短的回答:不

很长的回答:

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

  • 它的obviuos他们从经验(例如套接字类)
  • 其logging
  • 他们很谨慎,可以看到类实现了IDisposable

所以通过实现IDisposable,你告诉开发者(至less是某些人),这个类包装了一些必须被释放的东西。 他们将使用“使用” – 但也有其他情况下使用是不可能的(对象的范围不是本地的); 他们将不得不开始担心在其他情况下的对象的一生 – 我会担心的。 但是这不是必要的

你实现了Idisposable使他们能够使用,但他们不会使用,除非你告诉他们。

所以不要这样做

  1. 如果您正在使用使用非托pipe资源的其他托pipe对象,则确保这些资源已完成并不是您的责任。 当你的对象被调用Dispose时,你的责任就是调用Dispose对象,并停在那里。

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

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

  4. (2再次?)WebClient很可能使用非托pipe资源或其他实施IDisposable的托pipe资源。 确切的原因并不重要。 最重要的是它实现了IDisposable,所以当你完成它的处理时,即使事实certificateWebClient根本不使用其他资源,它也会依靠这些知识来处理这些知识。

@Icey,

其实你的反应是微不足道的,原因有二:

第一,

 using(NoGateway objNoGateway = new NoGateway()) 

实际上相当于:

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

这听起来很荒谬,因为除非有OutOfMemoryexception,否则“新”操作符不应该返回“null”。 但是请考虑以下情况:1.调用返回IDisposable资源的FactoryClass,或者2.如果您有一个types可能会或可能不会从IDisposableinheritance,具体取决于其实现 – 请记住,我已经看到IDisposable模式实现的错误数量很多在许多客户端开发人员只需添加一个Dispose()方法而无需从IDisposable(坏,坏,坏)inheritance。 你也可以从一个属性或方法返回一个IDisposable资源的情况(再次坏,坏,坏 – 不要“放弃你的IDisposable资源)

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

如果'as'操作符返回null(或者返回资源的属性或方法),并且'using'代码中的代码可以防止'null',那么当您尝试调用Dispose时,代码不会因为'内置'空检查。

你的答复不准确的第二个原因是由于以下的规定:

GC会调用终结器来销毁对象

首先,定稿(以及GC本身)是非确定性的。 CLR决定何时调用终结器。 即开发者/代码不知道。 如果IDisposable模式是正确实现的(正如我上面发布的),GC.SuppressFinalize()已被调用,Finalizer将不会被调用。 这是正确实施模式的一个重要原因。 由于每个托pipe进程只有一个Finalizer线程,不pipe逻辑处理器的数量如何,通过忘记调用GC.SuppressFinalize()来备份甚至挂起Finalizer线程,可以轻易地降低性能。

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

configuration模式:

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

inheritance的例子:

 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 { NoGateway.Dispose(); } 

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

1)WebClient是一个托pipetypes,所以你不需要一个终结器。 如果您的用户没有处理您的NoGateway类的Dispose(),并且本机types(不由GC收集)需要在之后进行清理,则需要终结器。 在这种情况下,如果用户没有调用Dispose(),则在NoGateway之后,包含的WebClient将由GC处理。

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

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

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

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

大多数情况下,这是由于不知道什么时候或将如何调用。 configuration方法要好得多,特别是如果我们使用或直接configuration。

使用是好的。 用它 :)