Search code examples
garbage-collectiondisposeidisposablefinalizer

IDisposable + finalizer pattern


Looking at the IDisposable pattern + Finalizer pattern, there is something I don't understand:

public class ComplexCleanupBase : IDisposable
{
    private bool disposed = false; // to detect redundant calls

    public ComplexCleanupBase()
    {
        // allocate resources
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // dispose-only, i.e. non-finalizable logic
            }

            // shared cleanup logic
            disposed = true;
        }
    }

    ~ComplexCleanupBase()
    {
        Dispose(false);
    }

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

From my understanding the pattern should be implemented like above.

1) Calling Dispose() fires GC.SuppressFinalize(this), which means that the object should not be put on the finalizer queue as its already properly disposed? That helps to free up the object faster?

2) But what if I am not calling Dispose() on this object at all? In that case the finalizer should kick in, correct? But Dispose(false); does absolutely nothing (only setting disposed = true). Is this intended? It feels like as if something is missing...


Solution

  • Question 1: Yes, if GC.SuppressFinalize is not called the object will be placed on the finalizer queue AND will move up a generation (if not already gen 2), which means that the memory for that object will not be reclaimed until the next pass of GC for the new generation.

    Question 2: Your comment //shared cleanup logic is where the shared cleanup logic will go, this is where things other than setting disposed = true would happen.

    Also, an aside: if your dispose logic should only be called once, consider acquiring a lock, an uncontested lock is very fast in .Net:

    public class ComplexCleanupBase : IDisposable
    {
      private volatile bool disposed = false; // to detect redundant calls
      private readonly object _mutex = new object();
    
      protected virtual void Dispose(bool disposing)
      {
        if (!Monitor.TryEnter(_mutex))
        {
          // We're already being disposed.
          return;
        }
    
        try
        {
          if (!disposed)
          {
            if (disposing)
            {
                // dispose-only, i.e. non-finalizable logic
            }
    
            // shared cleanup logic
            disposed = true;
          }
        }
        finally
        {
          Monitor.Exit(_mutex);
        }
      }
      ... other methods unchanged
    }