Finalize/Dispose pattern in C#
C 2008
我已经为此做了一段时间了,我对一些问题仍然感到困惑。我的问题如下
我知道,如果要处理非托管资源,只需要一个终结器。但是,如果使用调用非托管资源的托管资源,是否仍需要实现终结器?
但是,如果开发一个不直接或间接使用任何非托管资源的类,可以实现
实现IDisposable以便类的客户机可以使用using语句是可以接受的吗?
1 2 3 4 |
我在下面开发了这个简单的代码来演示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块的末尾时,是否自动调用Dispose方法,或者客户端必须手动调用Dispose方法?即
1 2 3 | NoGateway objNoGateway = new NoGateway(); // Do stuff with object objNoGateway.Dispose(); // finished with it |
我正在我的
这里是推荐的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); //} } |
请注意,我没有在
因此,除非必须声明终结器,否则不应声明终结器,但如果类的继承者直接使用非托管资源,则给类的继承者一个钩子来调用
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); } } |
如果您不直接使用非托管资源(
当一个类实现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(); } } |
对于
对于不直接处理非托管资源(即使存在继承)的可释放类,其结果是非常强大的:它们不再需要关注非托管资源。它们很容易实现和理解:
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); } } |
没有人回答关于是否应该实现IDisposable的问题,即使您不需要它。
简短回答:否
长回答:
这将允许类的消费者使用"using"。我要问的问题是——他们为什么要这样做?大多数开发人员不会使用"使用",除非他们知道他们必须-以及他们如何知道。要么
- 它的对象是来自经验的它们(例如socket类)
- 其文件化
- 他们很谨慎,可以看到类实现了IDisposable
因此,通过实现IDisposable,您可以告诉devs(至少一些)这个类包含了必须发布的内容。他们会使用"使用",但在其他情况下,使用是不可能的(对象的范围不是本地的);在其他情况下,他们必须开始担心对象的生命周期——我肯定会担心。但这不是必要的
您实现IDisposable以使它们能够使用,但除非您告诉它们使用,否则它们不会使用。
所以不要这样做
另一个答案的某些方面有点不正确,原因有两个:
第一,
1 |
实际上相当于:
1 2 3 4 5 6 7 8 9 10 11 |
这听起来可能很荒谬,因为"new"运算符不应返回"null",除非出现内存不足异常。但考虑以下情况:1。调用返回IDisposable资源的FactoryClass或2。如果您有一个可能继承或不继承IDisposable的类型,这取决于它的实现-请记住,我在许多客户端上多次看到IDisposable模式的实现不正确,开发人员只需添加Dispose()方法而不继承IDisposable(bad、bad、bad)。您还可以拥有从属性或方法返回IDisposable资源的情况(同样是坏的、坏的、坏的-不要"放弃您的IDisposable资源")。
1 2 3 4 5 |
如果"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方法。是的,使用对象的代码可以使用
(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 |
等于
1 2 3 4 5 6 7 8 9 |
在销毁对象的GC上调用终结器。这可能和你离开方法的时候完全不同。离开using块后立即调用Dispose of IDisposable。因此,模式通常是在您不再需要资源后立即使用释放资源。
据我所知,强烈建议不要使用终结器/析构函数:
1 2 3 | public ~MyClass() { //dont use this } |
大多数情况下,这是因为不知道何时或是否会被调用。Dispose方法更好,特别是如果我们直接使用或处置。
使用是好的。使用它:)