views:

668

answers:

4

I can never remember all the rules for implementing the IDisposable interface, so I tried to come up with a base class that takes care of all of this and makes IDisposable easy to implement. I just wanted to hear your opinion if this implementation is ok as it is or whether you see something I could improve. The user of this base class is supposed to derive from it and then implement the two abstract methods ReleaseManagedResources() and ReleaseUnmanagedResources(). So here is the code:

public abstract class Disposable : IDisposable
{
    private bool _isDisposed;
    private readonly object _disposeLock = new object();

    /// <summary>
    /// called by users of this class to free managed and unmanaged resources
    /// </summary>
    public void Dispose() {
        DisposeManagedAndUnmanagedResources();
    }

    /// <summary>
    /// finalizer is called by garbage collector to free unmanaged resources
    /// </summary>
    ~Disposable() { //finalizer of derived class will automatically call it's base finalizer
        DisposeUnmanagedResources();
    }

    private void DisposeManagedAndUnmanagedResources() {
        lock (_disposeLock) //make thread-safe
            if (!_isDisposed) { //make sure only called once
                try { //suppress exceptions
                    ReleaseManagedResources();
                    ReleaseUnmanagedResources();
                }
                finally {
                    GC.SuppressFinalize(this); //remove from finalization queue since cleaup already done, so it's not necessary the garbage collector to call Finalize() anymore
                    _isDisposed = true;
                }
            }
    }

    private void DisposeUnmanagedResources() {
        lock (_disposeLock) //make thread-safe since at least the finalizer runs in a different thread
            if (!_isDisposed) { //make sure only called once
                try { //suppress exceptions
                    ReleaseUnmanagedResources();
                }
                finally {
                    _isDisposed = true;
                }
            }
    }

    protected abstract void ReleaseManagedResources();
    protected abstract void ReleaseUnmanagedResources();

}
+2  A: 

EDIT Yes, I misread the portion about separating disposing managed and unmanaged resources (still on first cup of coffee).

However the lock is still almost certainly not necessary. Yes, during a passive dispose the code will be running on the finalizer thread which is different than the thread it originally ran on. However if the object is being finalized in this fashion the CLR has already determined there are no existing references to this object and hence collected it. So there is no other place which can be calling your dispose at that time and hence there is no reason for a lock.

A couple of other style comments.

Why make the methods abstract? By doing so you are forcing derived classes to implement methods to dispose of managed and unmanaged resources even if they don't have any to dispose. True there is no point in deriving from this class if you don't have anything to dispose. But I think it's fairly common to only have one or the other but not both. I would make them virtual vs. abstract.

Also you prevent double dispose but you do nothing to warning the developer that they are double disposing an object. I realize the MSDN documentation says that double dispose should be essentially a no-op but at the same time under what circumstances should this be legal? In general it's a bad idea to access an object after it's been disposed. A double dispose necessitates a disposed object being reused and is likely a bug (this can happen if finalization is not suppressed in an active dispose but that's also a bad idea). If I were implementing this I would throw on double dispose to warn the developer they were using an object incorrectly (that is using it after it was already disposed).

JaredPar
WTF? Did you read my code? My code does exactly what you describe. Also I have to lock the dipose code because at least the finalizer runs in a different thread than my application!
Hermann
@Hermann, yes I misread that portion. The WTF is a bit unnecessary though.
JaredPar
@JaredPar, sorry for the WTF, but I had the feeling you did not read my code at all. Thanks for pointing out that there is no need for the lock in the finalizer. I'll still keep it in the Dispose method because it doesn't hurt when it's not used and it's good in those rare cases when Dispose is in fact called by a user on differen threads (which is possible after all).Regarding double dispose, MSDN says: "a Dispose method should be callable multiple times without throwing an exception."
Hermann
@Hermann, interesting on the MSDN doc. IMHO the documentation is incorrect. There is no reason I can think off of the top of my head that Dispose should be double call safe. I would consider that to be a mis-use of the original object. After all to double dispose one must be using an object after it's originally disposed which is not legal. Either that or an active dispose neglected to suppress finalization.
JaredPar
@Hermann (cont) In regards the lock though, it does hurt when it's used. You will suffer a performance penalty for entering the lock (albeit small but why have a small performance penalty to support a scenario that is not supported in the first place?). Also if there are rare cases where it's called on multiple threads then you almost certainly have a bug. I like to think of this as a question of usage. Namely "Under what circumstances should 2 threads be disposing of the same object?"
JaredPar
The Multiple-Dispose-should-be-harmless advice is kind of useful, it allows more complex sharing situations where every holder can take a shot at Dispose. It's kind of rare though. And all other actions should throw.
Henk Holterman
+8  A: 

I can't really comment on the correctness of your code, but I question whether you'll find having the Disposable class at the base of your inheritance tree as flexible as it needs to be. It's won't be much use when you want to inherit from something you don't own. I think that IDispoasble is left as an interface because of the many different domains across which it might be used. Providing a concrete implementation at the foot of the inheritance hierarchy would be... limiting.

spender
Good point. Deriving from Disposable doesn't allow to derive from any other class and that makes Disposable useless in many cases.
Vadim
Well of course, but then I would just copy the code out of this base class. And in those cases I own the code, I can just derive from it.
Hermann
+3  A: 

You are accessing the managed object _disposeLock in the finalizer. It may have already been garbage-collected by then. Not sure what the implications of this would be as you are only using it to lock.

Thread-safety seems like overkill. I don't think you need to worry about contention between the GC thread and your application thread.

Otherwise it looks correct.

Joe
Thanks Joe, yes I'll take out the lock in the finalizer.
Hermann
No, it can _not_ have been collected, since the current Disposable is holding a reference to it.
Henk Holterman
"No, it can _not_ have been collected" - this is wrong. It can have been collected - from MSDN: "The finalizers of two objects are not guaranteed to run in any specific order, even if one object refers to the other. That is, if Object A has a reference to Object B and both have finalizers, Object B might have already finalized when the finalizer of Object A starts. " (see http://msdn.microsoft.com/en-us/library/system.object.finalize.aspx)
Joe
Joe, a finalizer does not collect. Please check the difference. The reference _will_ be valid. And yes, if it had been an IDisposbale it could have been Disposed. Not relevant for locking.
Henk Holterman
A: 

If you are concerned about the thread safety then I'd use the lightweight interlocked operations rather than a heavyweight lock:

class MyClass: IDispose {
  int disposed = 0;

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

  public virtual void Dispose(bool disposing)
  {
     if (0 == Thread.InterlockedCompareExchange(ref disposed, 1, 0))
     {
       if (disposing)
       {
          // release managed resources here
       }
       // release unmanaged resources here
     }
  }

  ~MyClass()
  {
    Dispose(false);
  }
}
Remus Rusanu
On second thought the thread that fails the compareexchange still has to wait for the one that succeeded otherwise it can delete memory from under under the live if branch so it would still require syncronization (ie. ManualResetEvent), making it pretty much just as heavy as a lock.
Remus Rusanu