重构实现IDisposable的全局对象
重构实现IDisposable的全局对象
C# 2008
我已经在工作中使用finalize和dispose方法一段时间了,但我仍然对它们在代码中的使用感到困惑。我的问题如下:
- 我知道只有在处理非托管资源时才需要使用终结器。但是,如果有托管资源调用非托管资源,是否仍需要实现终结器?
- 如果我开发的类不使用任何非托管资源(直接或间接),是否应该实现IDisposable以允许该类的客户端使用“using语句”?
是否可行实现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 }
在执行到达using块的结尾时,Dispose方法是否会自动调用,还是客户端必须手动调用dispose方法?
NoGateway objNoGateway = new NoGateway(); // Do stuff with object objNoGateway.Dispose(); // finished with it
- 我在我的NoGateway类中使用WebClient类。 因为WebClient实现了IDisposable接口,这是否意味着WebClient间接使用非托管资源?是否有硬性规定要遵循?如何知道一个类是否使用非托管资源?
实现IDisposable
的官方模式很难理解。我相信这个better更好:
public class BetterDisposableClass : IDisposable { public void Dispose() { CleanUpManagedResources(); CleanUpNativeResources(); GC.SuppressFinalize(this); } protected virtual void CleanUpManagedResources() { // ... } protected virtual void CleanUpNativeResources() { // ... } ~BetterDisposableClass() { CleanUpNativeResources(); } }
一个更好的解决方案是,对于任何需要处理的非托管资源,您总是必须创建一个包装类:
public class NativeDisposable : IDisposable { public void Dispose() { CleanUpNativeResource(); GC.SuppressFinalize(this); } protected virtual void CleanUpNativeResource() { // ... } ~NativeDisposable() { CleanUpNativeResource(); } }
使用SafeHandle
及其派生类,这些类应该非常少见。
对于不直接处理非托管资源的可处理类,即使存在继承,结果也非常强大:它们不再需要关注非托管资源。它们将变得简单易懂:
public class ManagedDisposable : IDisposable { public virtual void Dispose() { // dispose of managed resources } }
推荐使用的IDisposable模式在这里。当编写使用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()
或将类包装在using(...) {}
中即可确保任何非托管资源被适当地删除。