What is the best way to implement .NET IDisposable classes?

Forgive me in advance if this question is too frank, but I saw similar posts about the discussions here, so I decided that I would take a decisive step.

Anyway, I read several MSDN help pages and various other blogs on the proper use of IDisposable classes. I feel like I'm good at things, but I need to wonder if there is a flaw in the proposed class structure:

 public class DisposableBase : IDisposable { private bool mDisposed; ~DisposableBase() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!mDisposed) { if (disposing) { // Dispose managed resources mManagedObject.Dispose(); } // Dispose unmanaged resources CloseHandle(mUnmanagedHandle); mUnmanagedHandle = IntPtr.Zero; mDisposed = true; } } } 

Anytime the above should serve as the base class, you rely on the executor of this subclass to override the Dispose (bool) method appropriately if necessary. In short, derived classes must ensure that they invoke the base Dispose (bool) method from their overridden version. If not, the unmanaged resources of the base class can never be freed, which will lead to victory over the main goal of the IDisposable interface.

We all know the advantages of virtual methods, but it seems that in this case their design does not justify itself. In fact, I think this drawback of virtual methods often manifests itself when trying to design visual components and similar structures of base / derived classes.

Consider the following change using a protected event rather than a protected virtual method:

 public class DisposeEventArgs : EventArgs { public bool Disposing { get; protected set; } public DisposeEventArgs(bool disposing) { Disposing = disposing; } } public class DisposableBase : IDisposable { private bool mDisposed; protected event EventHandler<DisposeEventArgs> Disposing; ~DisposableBase() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } // This method is now private rather than protected virtual private void Dispose(bool disposing) { if (!mDisposed) { // Allow subclasses to react to disposing event AtDisposing(new DisposeEventArgs(disposing)); if (disposing) { // Dispose managed resources mManagedObject.Dispose(); } // Dispose unmanaged resources CloseHandle(mUnmanagedHandle); mUnmanagedHandle = IntPtr.Zero; mDisposed = true; } } private void AtDisposing(DisposeEventArgs args) { try { EventHandler<DisposeEventArgs> handler = Disposing; if (handler != null) handler(this, args); } catch { } } } 

In this design, the Dispose (bool) base class method will always be called, regardless of whether the subclasses signed for the Disposing event or not. The biggest drawback that I see in this redesigned setting is that there is no predefined order when event listeners are called. This can be problematic if there are several levels of inheritance, for example. A SubclassA listener can be started in front of its child SubclassB listener. Is this flaw serious enough to invalidate my revised design?

This design dilemma makes me wish there was some kind of modifier for methods that was similar to virtual , but which would ensure that the base class method was always called, even if a subclass redefined this function. If there is a better way to achieve this, I will greatly appreciate your suggestions.

+4
source share
5 answers

Here you use event when you really want to use the inheritance mechanism of type virtual . For such scenarios, where I want my implementation to always be called, but you want to enable base class customization, I use the following pattern

 private void Dispose(bool disposing) if (mDisposed) { return; } if (disposing) { mManagedObject.Dispose(); } // Dispose unmanaged resources CloseHandle(mUnmanagedHandle); mUnmanagedHandle = IntPtr.Zero; mDisposed = true; DisposeCore(disposing); } protected virtual void DisposeCore(bool disposing) { // Do nothing by default } 

With this template, I guarantee that my base Dispose class will always be called. Derived classes cannot stop me, just forgetting to call the base method. They can still select the dispose pattern by overriding DisposeCore , but they cannot break the base class contract.

+5
source

A derived class can simply reimplement IDisposable and thus prohibit calling the dispose method, so you also cannot guarantee that.

Personally, I would not use a single template. I prefer to build on SafeHandle and similar mechanisms, instead of implementing the finalizers myself.

+2
source

Consider it obvious that Dispose is not being called so that someone catches it. Of course, Debug.WriteLine is called only when the code is compiled with the DEBUG compiler directive.

 public class DisposableBase : IDisposable { private bool mDisposed; ~DisposableBase() { if (!mDisposed) System.Diagnostics.Debug.WriteLine ("Object not disposed: " + this + "(" + GetHashCode() + ")"; Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } 
+2
source

You can break it:

  • A destructor (finalizer) is needed only for unmanaged resources.
  • Using Safehandle can turn an intangible resource into a managed resource.
  • Ergo: You don't need a destructor. This reduces the Dispose pattern.

The reference project uses virtual void Dispose(bool) to solve the problem of the Base / Derived class. This puts a strain on the derived class to call base.Dispose(disposing) , the gist of your question. I use 2 approaches:

1) Prevent it. With a closed base class you don't have to worry.

 sealed class Foo:IDisposable { void Dispose() { _member.Dispose(); } } 

2) Check it out. Like @ j-agent's answer, but conditional. When performance can be a problem, you don't want finalizers to be in production code:

 class Foo:IDisposable { void Dispose() { Dispose(true); } [Conditional("TEST")] // or "DEBUG" ~Foo { throw new InvalidOperation("somebody forgot to Dispose") } } 
+1
source

The destructor will be called regardless of whether any subclass will override Dispose () (maybe through redefinition or new), but your destructor will be called (~ DisposableBase ()), so I put your logic to clear there may be a good starting point point.

Here is an interesting article about destructors: http://www.c-sharpcorner.com/UploadFile/chandrahundigam/UnderstandingDestructors11192005021208AM/UnderstandingDestructors.aspx

0
source

All Articles