Dispose/finalize pattern : disposing managed ressources

239 views Asked by At

Let's imagine I have a class named Base with 3 attributes :

class Base : IDisposable
{
    private string _String;
    private Class1 classe1;
    private int foo;

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

    public virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            Console.WriteLine("Free Managed ressources");
                  //HOW TO FREE _String, class1 and foo ?!          
        }
        Console.WriteLine("Free unmanaged ressources");
    }

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

and a classe named Class1 with 2 attributes :

class Class1
{
    public int entier { get; set; }
    public string Nom { get; set; }
}

My question is : How can I free the attributes of Base in the Dispose method ? (_String, classe1, foo)

2

There are 2 answers

0
Yuval Itzchakov On BEST ANSWER

My question is : How can I free the attributes of Base in the Dispose method ? (_String, classe1, foo)

You don't need to, that's the job of the garbage collector. Implementing IDisposable is a way for the framework to let you release any unmanaged resources you have allocated, and dispose managed objects implementing IDisposable themselves (which in turn hold other unmanaged resources).

None of the managed objects at your disposable implement IDisposable, and they will be collected once there is no longer any objects pointing to your Base class. When will that happen? In an arbitrary time, when the GC see's that there is no longer space in generation 0, and it needs to collect. There is nothing you need to do.

Implementing IDisposable does not mean "this object will be collected immediatly once i run Dispose()", it merely means that the framework gives you a chance to reclaim any resources it might not be aware of (such as unmanaged ones). It is a recommended approach, if one implements a finalizer, to suppress the call to it via GC.SuppressFinalize, saving the GC the trouble of moving your object from the Finalizer Queue to the F-Reachable Queue, hence making it available for collection earlier.

when will these 3 attributes free from the heap ? The garbage collector won't free them because I have GC.SuppressFinalize(this)

You have a basic misunderstanding of how the GC works and what SuppressFinalize means. The GC will run at an non-deterministic time, and you basically shouldn't care when that happens. It's his responsibility to clean up after you. Calling SuppressFinalize on an object implementing a finalizer does nothing more than set a bit in the objects header which the runtime checks when calling finalizers, which will suppress your finalizer from running

1
Jon Hanna On

In this case, you shouldn't implement IDisposable at all, or if it was there because it was deemed very likely that it could be necessary in the future, then it would have an empty implementation. You certainly shouldn't have a finaliser in there; never have one unless you actually need one with 100% certainty.

There are a few cases where you would want to implement IDisposable, and in some of those cases you'd also want to have a destructor (which is the C# way of having a finaliser).

One is where you have something that it is really important to do when the object is finished with, most often undoing something you have previously done, such as releasing a handle that you'd obtained, closing a connection you'd opened, etc. but not managed memory. (All objects use managed memory, and all objects have their managed memory cleaned up for them if they're can't be used again and more managed memory is needed by something else, that's what the managed in "managed memory" means).

public class SomeClass : IDisposable
{
  private IntPtr _someHandle;
  public SomeClass(string someIdentifier)
  {
    _someHandle = GetHandle(someIdentifier);
  }
  public void Dispose()
  {
    ReleaseHandle(_someHandle);
  }
}

So now whenever something that's been using a SomeClass is done with it, it calls Dispose() on it (perhaps implicitly via a using block) and all is cleaned up nicely.

But what if that doesn't happen? Well, that's why we might have a finaliser:

public class SomeClass : IDisposable
{
  private IntPtr _someHandle;
  public SomeClass(string someIdentifier)
  {
    _someHandle = GetHandle(someIdentifier);
  }
  public void Dispose()
  {
    ReleaseHandle(_someHandle);
    _someHandle = null; // so we know not to release twice.
  }
  ~SomeClass()
  {
    if(_someHandle != null)
      ReleaseHandle(_someHandle);
  }
}

So, here if the Dispose() doesn't get called, we still get the clean-up, because the normal garbage-collection process:

  1. Realise you need more memory.
  2. Find objects that aren't going to be used any more.
  3. Reclaim the memory of those objects.

Has the following steps added:

  1. Realise the object whose memory you were going to reclaim has a finaliser to run.
  2. Put the object into a queue of other such objects.
  3. (On a separate thread) run the finaliser of the object.
  4. The object is no longer an object that "has a finaliser to run" as per step 4 above, so next time around it can be reclaimed.

All of this has downsides:

  1. We can't guarantee when, if ever, this will happen.
  2. We didn't get to reclaim as much memory in step 3, because there was such an object.
  3. Garbage collection is generational, and playing nicely with generational collection for an object means either dying quickly or living a long time, dying just after the first time the GC tried to collect an object is pretty much the least optimal time.

We can get around the first two by calling Dispose() rather than letting finalisation happen, which is up to the user of the class, not the class itself. We get around the third by having an object that knows it doesn't need to be finalised mark itself as no longer needing to be:

public class SomeClass : IDisposable
{
  private IntPtr _someHandle;
  public SomeClass(string someIdentifier)
  {
    _someHandle = GetHandle(someIdentifier);
  }
  public void Dispose()
  {
    ReleaseHandle(_someHandle);
    GC.SuppressFinalize(this);
  }
  ~SomeClass()
  {
    ReleaseHandle(_someHandle);
  }
}

If an object has been passed to GC.SuppressFinalize() then step 4 and subsequent don't happen.

The second case where you might what to implement IDisposable is where you have an IDisposable object as a field of another object that "owns" it (controls it's lifetime):

public class SomeOtherClass : IDisposable
{
  private SomeClass _someObj;
  public SomeOtherClass(string someIdentifier)
  {
    _someObj = new SomeClass(someIdentifier);
  }
  public void Dispose()
  {
    //If base type is disposable
    //call `base.Dispose()` here too.
    _someObj.Dispose();
  }
}

Cleaning up a SomeOtherClass hence means cleaning up the SomeClass it has as a field. Note that here we do not have a finaliser here. We can't need a finaliser, because it would have nothing to do; at best it would do nothing and just have the downsides of finalisers mentioned above, at worse it would try to clean up _someObj without knowing whether this would happen before or after _someObj cleaning itself up and with _someObj queued to clean itself up in a way where it can assume nothing else will do the clean-up.

For the third case, consider if we combine the two cases with a class that has both an unmanaged resource it releases and a field which is a disposable class. Here if we are Dispose()d we want to clean up both, but if we are finalised we want to only clean up the unmanaged resource that is dealt with directly:

public sealed class SomeHybridClass : IDisposable
{
  private IntPtr _someHandle;
  private SomeClass _someObj;
  public SomeHybridClass(string someIdentifier)
  {
    _someHandle = GetHandle(someIdentifier);
    _someObj = new SomeClass(someIdentifier);
  }
  public void Dispose()
  {
    ReleaseHandle(_someHandle);
    GC.SuppressFinalize(this);
    _someObj.Dispose();
  }
  ~SomeHybridClass()
  {
    ReleaseHandle(_someHandle);
  }
}

Now, since there's repetition here, it makes sense to refactor them into the same method:

public sealed class SomeHybridClass : IDisposable
{
  private IntPtr _someHandle;
  private SomeClass _someObj;
  public SomeHybridClass(string someIdentifier)
  {
    _someHandle = GetHandle(someIdentifier);
    _someObj = new SomeClass(someIdentifier);
  }
  private void Dispose(bool disposing)
  {
    if(disposing)
    {
      _someObj.Dispose();
    }
    ReleaseHandle(_someHandle);
  }
  public void Dispose()
  {
    Dispose(true);
    GC.SuppressFinalize(this);
  }
  ~SomeHybridClass()
  {
    Dispose(false);
  }
}

And for a fourth case, imagine if this class wasn't sealed; it's derived types also need to be able to do this clean-up, so we make the parameterised Dispose(bool) method protected:

public class SomeHybridClass : IDisposable
{
  private IntPtr _someHandle;
  private SomeClass _someObj;
  public SomeHybridClass(string someIdentifier)
  {
    _someHandle = GetHandle(someIdentifier);
    _someObj = new SomeClass(someIdentifier);
  }
  protected virtual void Dispose(bool disposing)
  {
    // if this in turn was derived, we'd call
    // base.Dispose(disposing) here too.
    if(disposing)
    {
      _someObj.Dispose();
    }
    ReleaseHandle(_someHandle);
  }
  public void Dispose()
  {
    Dispose(true);
    GC.SuppressFinalize(this);
  }
  ~SomeHybridClass()
  {
    Dispose(false);
  }
}

However, these last two examples are really solving the wrong problems: They're solving the problem of how to have a class that has both a disposable type as a field and an unmanaged resource, and/or be part of a type hierarchy with this happening. Really you're much better off never getting into this situation; either have a class that only deals with an unmanaged resource (and is sealed) or has disposable types in fields, and you end up with only having the deal with the first two cases. If you deal with your unmanaged resources by deriving from SafeHandle then you are really only having to worry about the second case, and that also manages some difficult edge cases too.

Really, finalisers should very, very rarely be written, and when they are written they should be written to be as simple as possible, because there's enough complication inherent to them and the edge-cases around them as it is. You need to know how to deal with overriding protected virtual void Dispose(bool disposing) (note, should never be public) to deal with the legacy of when that had seemed like a good idea to someone, but not have inheritable classes with both unmanaged and managed-disposable resources forcing someone else into that position.

How can I free the attributes of Base in the Dispose method ? (_String, classe1, foo)

As should now be clear, those fields (attributes are a very different thing in .NET) don't need to be freed. The only resource they have is managed memory, so once they can't be reached (aren't in a static, aren't about to have something done to them in a method, and aren't in a field of something that is in either of those categories or a field of something that is in a field in either of those, etc.) their memory will be automatically reclaimed when needed.