12

There is a LOT of info around about the "standard full" IDisposable implementation for disposing of unmanaged resources - but in reality this case is (very) rare (most resources are already wrapped by managed classes). This question focuses on a mimimal implementation of IDisposable for the much more common "managed resources only" case.

1: Is the mimimal implementation of IDisposable in the code below correct, are there issues?

2: Is there any reason to add a full standard IDisposable implementation (Dispose(), Dispose(bool), Finalizer etc) over the minimal implimentation presented?

3: Is it OK/wise in this minimal case to make the Dispose virtual (since we are not providing Dispose(bool))?

4: If this minimal implementation is replaced with a full standard implementation that includes a (useless, in this case) finalizer - does this change how the GC handles the object? Are there any downsides?

5: The example includes Timer and event handlers as these cases are particularly important not to miss as failing to dispose them will keep objects alive and kicking (this in the case of Timer, eventSource in case of the event handler) until the GC gets round to disposing them in its time. Are there any other examples like these?

class A : IDisposable {
    private Timer timer;
    public A(MyEventSource eventSource) {
        eventSource += Handler
    }

    private void Handler(object source, EventArgs args) { ... }

    public virtual void Dispose() {
        timer.Dispose();
        if (eventSource != null)
           eventSource -= Handler;
    }
}

class B : A, IDisposable {
    private TcpClient tpcClient;

    public override void Dispose() {
        (tcpClient as IDispose).Dispose();
        base.Dispose();
    }   
}

refs:
MSDN
SO: When do I need to manage managed resources
SO: How to dispose managed resource in Dispose() method in C#
SO: Dispose() for cleaning up managed resources

Ricibob
  • 7,505
  • 5
  • 46
  • 65

3 Answers3

8
  1. The implementation is correct, there are no issues, provided no derived class directly owns an unmanaged resource.

  2. One good reason to implement the full pattern is the "principle of least surprise". Since there is no authoritative document in MSDN describing this simpler pattern, maintenance developers down the line might have their doubts - even you felt the need to ask StackOverflow :)

  3. Yes it's OK for Dispose to be virtual in this case.

  4. The overhead of the unnecessary finalizer is negligible if Dispose has been called, and is correctly implemented (i.e. calls GC.SuppressFinalize)

The overwhelming majority of IDisposable classes outside the .NET Framework itself are IDisposable because they own managed IDisposable resources. It's rare for them to directly hold an unmanaged resource - this only happens when using P/Invoke to access unmanaged resources that aren't exposed by the .NET Framework.

Therefore there is probably a good argument for promoting this simpler pattern:

  • In the rare cases that unmanaged resources are used, they should be wrapped in a sealed IDisposable wrapper class that implements a finalizer (like SafeHandle). Because it's sealed, this class doesn't need the full IDisposable pattern.

  • In all other cases, the overwhelming majority, your simpler pattern could be used.

But unless and until Microsoft or some other authoritative source actively promotes it, I'll continue to use the full IDisposable pattern.

Ricibob
  • 7,505
  • 5
  • 46
  • 65
Joe
  • 122,218
  • 32
  • 205
  • 338
  • This. I am a fan of using the full pattern, too, but I can see the point of the OP. Anyway, +1. – Jan Dörrenhaus Sep 24 '13 at 11:42
  • 1
    An excellent answer - thank you. There seems to be a disproportionate of documentation about full IDispose implimentation for UNmanaged resources when, as you stated, this is infact VERY rare - and good point about sealed wrapper for this case. I just wanted to check that my understanding of what the full implementation reduces down to in the case of managed resources only was correct. Thanks. – Ricibob Sep 24 '13 at 13:26
  • @Ricibob, I for one would love to see Microsoft provide via MSDN some very explicit guidance, including what to do when you have multiple resources to dispose, and one of the Dispose methods throws. Should you use try/catch blocks to try to Dispose as many as possible of your owned resources? IMHO you probably should, but there is an example in the Framework (System.ComponentModel.Container) that will not do so. – Joe Sep 24 '13 at 13:50
  • Yes exception handling in Dispose a whole other issue. Also not yet mentioned above is issue of unncecessary finalizer. I often see a standard implimentation of a finalizer for the full implementation of the case above - but it was my understanding that it is a. unnecessary and b. can provide a performance hit as adding a finalizer effects how the GC deals with object. Is there a downside to adding a useless finalizer in the above case? – Ricibob Sep 24 '13 at 14:28
  • I think the overhead of the unnecessary finalizer is negligible if Dispose has been called, and is correctly implemented (i.e. calls GC.SuppressFinalize). – Joe Sep 24 '13 at 15:43
  • @Joe: Construction of an object with a finalizer incurs more costs than constructing an object without one, even if the finalizer never ends up actually executing. More importantly, classes should almost never encapsulating both managed resources and resources that require a cleanup finalizer. Instead, each resource that needs finalizer cleanup should be segregated into its own managed-resource class. Those facts imply that if a class encapsulates a managed resource, neither it *nor any descendants* should use `finalize` for cleanup. – supercat Sep 24 '13 at 22:09
  • @supercat, I agree that a finalizer adds some cost, but believe this cost is almost always negligible. – Joe Sep 25 '13 at 05:23
  • @Joe: "A million here, a million there, and eventually it adds up to real money". If one only overrides `Object.Finalize` in cases where there would be something useful for it to do, the cost will be negligible. If one does it on all of one's classes, the costs can add up. Overriding a finalizer on the theory that it might need to do something someday is a YRAGNI ("You're really not gonna need it"). Classes which perform cleanup actions in their finalizers should be designed *from the ground up* with that in mind; the fact that a class doesn't perform cleanup actions today... – supercat Sep 25 '13 at 14:56
  • ...means neither it nor derived classes should do them tomorrow either. In any case, my concern is that saying "the cost is negligible" might encourage people to declare pointless finalizers on every class as a matter of habit; such a habit would be detrimental to code maintainability and stability, since it would make it harder to distinguish classes which were properly designed for finalizer-based cleanup and those which were not. – supercat Sep 25 '13 at 15:06
  • @supercat, Declaring it on classes that are not IDisposable doesn't make sense of course. But I think I agree with you - hence the remark about sealed wrapper classes above, which are the only ones that need a finalizer. I'm not sure I fully understand what you're recommending though, as in your answer you include GC.SuppressFinalize in your recommended Dispose implementation, which is not needed if you have no finalizer. – Joe Sep 25 '13 at 16:39
  • @Joe: If some of the resources encapsulated by a class might have finalizers, it's a good idea to at minimum include `GC.KeepAlive` in the dispose. For classes which don't implement `Finalize`, `GC.SuppressFinalize` will early-exist without having to do any work and is thus equivalent to `GC.KeepAlive`. The choice is largely arbitrary, but `GC.SuppressFinalize` may be helpful if a derived class implements an "alarm bell" finalizer (one whose purpose is not to perform cleanup, but trigger a notification that an object was wrongfully abandoned). – supercat Sep 25 '13 at 17:06
2

Another option is to refactor your code to avoid inheritance and make your IDisposable classes sealed. Then the simpler pattern is easy to justify, as the awkward gyrations to support possible inheritance are no longer necessary. Personally I take this approach most of the time; in the rare case where I want to make a non-sealed class disposable, I just follow the 'standard' pattern. One nice thing about cultivating this approach is that it tends to push you toward composition rather than inheritance, which generally makes code easier to maintain and test.

Dan Bryant
  • 27,329
  • 4
  • 56
  • 102
  • Totally agree. Im from a C++ background - where inhertiance seemed to the prefered approach - but gradually learning compostion does make code simpler. – Ricibob Sep 26 '13 at 08:57
0

My recommended Dispose pattern is for a non-virtual Dispose implementation to chain to a virtual void Dispose(bool), preferably after something like:

int _disposed;
public bool Disposed { return _disposed != 0; }
void Dispose()
{
  if (System.Threading.Interlocked.Exchange(ref _disposed, 1) != 0)
    Dispose(true);
  GC.SuppressFinalize(this); // In case our object holds references to *managed* resources
}

Using this approach will ensure that Dispose(bool) only gets called once, even if multiple threads try to invoke it simultaneously. Although such simultaneous disposal attempts are rare(*), it's cheap to guard against them; if the base class doesn't do something like the above, every derived class must have its own redundant double-dispose guard logic and likely a redundant flag as well.

(*) Some communications classes which are mostly single-threaded and use blocking I/O allow Dispose to be called from any threading context to cancel an I/O operation which is blocking its own thread [obviously Dispose can't be called on that thread, since that thread can't do anything while it's blocked]. It's entirely possible--and not unreasonable--for such objects, or objects which encapsulate them, to have an outside thread try to Dispose them as a means of aborting their current operation at just the moment they were going to be disposed by their main thread. Simultaneous Dispose calls are likely to be rare, but their possibility doesn't indicate any "design problem" provided that the Dispose code can act upon one call and ignore the other.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • 1
    Personally I find this pattern overkill for the vast majority of IDisposable classes for which multithreaded Dispose is almost a contradiction in terms. Adding interlocked increment is cheap in terms of execution time, but perhaps not cheap in terms of time spent by a maintenance programmer wondering why it's there. Neither do I find your hypothetical example convincing: IMHO exposing an API to allow other threads to interrupt a blocking I/O operation doesn't need the Dispose pattern - arguably an explicit API such as "InterruptBlockingIO" is better, and the blocked thread can do the Dispose – Joe Sep 25 '13 at 05:29
  • A volatile bool isDisposed simpler? Also in the strictly managed resources case (MOST cases!) there is no need to do already displosed check - because all we are doing is calling Dispose on members or base - and those Dispose methods should already look after double call. – Ricibob Sep 25 '13 at 08:14
  • @Ricibob: Using `Interlocked.Exchange` will 100% guard against double invocation; a volatile `bool` won't. As for the necessity of such guarding, while I would agree that one can usually get away without it, it's cheap, and I'd suggest that it's generally easier to provide such protection than prove it's not needed. Suppose, for example, that `Foo` holds two managed "double-dispose guarded" resources and they must be disposed in order. Thread 1 calls `Foo.Dispose` and starts disposing the first object when Thread 2 calls `Foo.Dispose`. – supercat Sep 25 '13 at 15:15
  • @Ricibob: It would be possible for Thread 2 to dispose the second object before Thread 1 finishes disposing the first. Such situations aren't likely to arise, but that doesn't mean it's easy to prove that they won't. Adding the `Interlocked.Exchange` makes it much easier to prove that all parts of the `Dispose` will be done in the proper sequence, though you've got me thinking that another pattern might be better, since code might expect all conditions associated with `Dispose` to apply before it returns. – supercat Sep 25 '13 at 15:23