Ronald
Ronald

Reputation: 1542

Please, some clarifications on C# IDisposable

I have seen the code below a lot of times in different threads and different forums. This one in particular I picked up from How does GC and IDispose work in C#?.

class MyClass : IDisposable
{
    ...

    ~MyClass()
    { 
        this.Dispose(false);
    }

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

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        { /* dispose managed stuff also */ }

        /* but dispose unmanaged stuff always */
    }
}

My questions are:

  1. Is it necessary to create an explicit destructor? The class inherits from IDisposable and during GC cleanup Dispose() will be eventually executed.

  2. What is the significance of the parameter 'disposing' in Dispose(bool disposing)? Why is it necessary to differentiate between disposing of managed and unmanaged objects?

Upvotes: 9

Views: 278

Answers (5)

Henk Holterman
Henk Holterman

Reputation: 273631

1 Is it necessary to create an explicit destructor?

Only in the rare case that you are directly owning an unmanaged resource.

The class inherits from IDisposable and during GC cleanup Dispose() will be eventually executed.

The IDisposable interface only enables the use in using(){}blocks. The GC will eventually call Dispose() but that will be (too) late. Note that it will use disposing==false and only try to clean up unmanaged stuff. Which you most likely don't have.

2 What is the significance of the parameter 'disposing' in Dispose(bool disposing)? Why is it necessary to differentiate between disposing of managed and unmanaged objects?

Because there is no need to Dispose() the managed resources when you are Disposing. The algorithm of the GC ensures that your managed resources are already being GC'ed themselves. Calling their Dispose() is at best harmless.

Note that this code is based on the standard implementation pattern. If you leave out the destructor the only reason for the overloaded Dispose(bool) is possible inheritance.

A shorter version, note the sealed :

sealed class MyClass : IDisposable
{
   private FileStream MyManagedResource;

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

       /* dispose managed stuff  */
       if (MyManagedResource != null)
          MyManagedResource.Dispose();  // this is why we do it all
   }

   // ~MyClass() { }

}

Upvotes: 3

Jon Skeet
Jon Skeet

Reputation: 1503140

The garbage collector itself knows nothing about IDisposable. It will not dispose anything for you - all it will do is call the finalizer.

The point of having the overload with a "disposing" parameter is that the finalizer will call Dispose(false) to indicate that it's been called from the finalizer and managed objects don't need any clean-up, whereas if you call Dispose explicitly (e.g. via a using statement) that will end up calling Dispose(true).

Part of the point of this pattern is that it's extensible for derived classes - only the base class finalizer needs to call Dispose, and everything else will piggy-back on that by overriding Dispose(bool) if necessary.

However, unless you've actually got direct access to unmanaged resources - or expect derived classes to - you probably don't need a finalizer at all. If you do need fairly direct access, then SafeHandle helps to avoid the need to write a finalizer. You should almost never need to write a finalizer these days. Personally I rarely implement IDisposable myself, and when I do it's typically from a sealed class (as I like to seal classes where possible) - and it almost never involves a finalizer... so I just write a single Dispose method to implement the interface and leave it at that. Much simpler.

The full advice for implementing IDisposable in every situation you can imagine is extremely long-winded and complicated. I think it's well worth trying to limit yourself to simpler situations wherever possible.

Upvotes: 6

Jon
Jon

Reputation: 437734

The destructor is the reason that during GC cleanup Dispose will be executed, as you mention. So you need it to ensure that the object is eventually disposed if the programmer did not explicitly dispose of it earlier.

The very reason for the existence of IDisposable is to return unmanaged resources to the system. This is the reason that you have the "disposed unmanaged stuff always" comment: unmanaged resources should be disposed both when the programmer explicitly calls Dispose and when the finalizer is executed (however, note that the parameterless Dispose method will explicitly prevent the finalizer from being executed -- this prevents double-disposing of resources and is also good for performance).

The disposing parameter serves to differentiate between the explicit Dispose (by the programmer) and the implicit disposal of resources triggered inside the finalizer. If your class has members of type IDisposable then it's more than likely that disposing your object should also dispose of those other member objects, so the code also runs the "dispose managed stuff also" branch. On the other hand, if you did not dispose of the object explicitly (and it's the finalizer, run by the GC, that does this) then these other objects may have already been garbage-collected. You would not want to touch them if this is the case.

Upvotes: 0

Tigran
Tigran

Reputation: 62265

  1. No you do not need to implement a destructor. Actually this is strongly NON recommended by Microsoft itself.
  2. disposing used to identify exact caller of Dispose method.

Upvotes: 1

Marc Gravell
Marc Gravell

Reputation: 1063884

1: no; that is common only in classes that directly wrap an external unmanaged resource, such as windows-handles; or if it is possible that some subclass will do such. If you are only handling managed objects, adding a finalizer is actually a bad thing, in that it impacts the wat collection works

2: it tells the Dispose(bool) code whether it is currenty in garbage collection (if false). When being collected, you shouldn't touch any other objects outside your own, as they may already be gone. However, if you are being electively disposed (i.e. true) you might want to clean up a few encapsulated managed objects; call .Close() on a connection you are wrapping, for example

Upvotes: 2

Related Questions