Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Garbage collection and resource deallocation

0.00/5 (No votes)
2 Nov 2004 1  
An article about IDisposable and the Garbage Collector in .NET.

Introduction

In non-garbage collected languages like C++, programmers must take care of memory and resource management, you know exactly when your objects are destroyed, implicitly (scope rules) or explicitly (object deletion). The counterpart is that working with pointers commonly leads to memory leaks and resources never freed. Opposed to this, the .NET environment implements a garbage collector that automates memory management and handles unused object destruction, making the programmer's life simpler. Objects are not destroyed when they are out of scope, and you can�t destroy them explicitly. It�s the GC�s job to destroy them and free the memory used, this is called �collecting� and it is done under certain circumstances that won�t be explained here. This means that the GC collects when needed, and you have no control of the destruction of your objects.

Therefore, there are no special considerations to take when writing a class that doesn't make use of resources. But when your class holds a critical resource, you must release, you just can't rely on the GC because you can�t determine when it will run. The GC, upon collection, calls the finalizer of the object (in C#, the finalizer is the object destructor), thus, this is not a good place to release the resources owned by an object. In the .NET class library, the garbage collector is implemented as System.GC in the assembly mscorlib.

Implementing IDisposable

There is an interface in .NET you must implement to "mark" your class in order to provide a mechanism to release resources. This interface is IDisposable (its full name is System.IDisposable and can be found in assembly mscorlib). And it has only one method you must implement: Dispose(). This method does the cleaning work in your class. The destructor should call Dispose() to ensure a clean exit in case you forgot to explicitly call it, but not if it has already been called. A proposed implementation is:

   public class A : IDisposable
   {
      protected bool disposed = false;
   
      public A()
      {
         // acquire resource

      }

      ~A()
      {
         Dispose(false);
      }

      public void doSomething()
      {
         if (disposed)
            throw new ObjectDisposedException("object's name");
         else
         {
            // do something

         }
      }

      public void Dispose()
      {
         Dispose(true);
         GC.SuppressFinalize(this);
      }
      
      protected virtual void Dispose(bool disposing)
      {
         if (!disposed)
         {
            if (disposing)
            {
               // Call Dispose() on managed objects

            }
            // release unmanaged resource(s) held by this object

            disposed = true;
         }
      }
   }

A virtual method Dispose(bool) manages resource deallocation for both managed and unmanaged resources. disposing is a parameter to tell who called the method. Dispose() will call Dispose(true) and the destructor will call Dispose(false). When instantiating class A, all resource acquisition is made in the constructor, and when we are done with the object, we need to call Dispose(true) from the public method Dispose() to release all (both managed and unmanaged) resources. The destructor, called by the GC, also calls Dispose(false) to clean unmanaged resources to ensure there are no leaks in our application. Calling GC.SuppressFinalize() tells the GC not to call the object's destructor, for it just does what we have already done. Dispose(bool disposing) is declared as virtual so our derived classes can call it. disposed is a flag to avoid calling Dispose() more than once and to avoid using a released resource, an ObjectDisposedException is thrown if this happens. Note that this implementation is not thread-safe, race conditions can happen.

The need to implement Dispose(bool) arises because an object may hold both managed objects that need to be disposed and unmanaged resources. So, if you call Dispose(true), you take care of all resources owned and that's all, but if you don't, the GC will take care and the owner object should no longer call Dispose(true) on each of the owned managed objects because they may be already finalized. But you must always clean unmanaged resources by calling Dispose(false).

Derived classes can easily extend this implementation:

   public class B : A
   {
      public B()
      {
         // acquire more resources

      }

      ~B()
      {
      }

      protected override void Dispose(bool disposing)
      {
         if (!disposed)
         {
            if (disposing)
            {
               // Call Dispose() on our managed objects

            }
            // release unmanaged resources acquired in our constructor

            base.Dispose(disposing);
         }
      }
   }

Creating an instance of B calls B's constructor, which in turn calls A's default constructor before exiting. B's destructor does nothing, it only follows the destruction chain calling A's destructor (in fact, we can obviate it, and when the GC claims the object, it will call A's destructor because it's inherited by B). Anyway, we reach A's destructor where Dispose(false) is called. But Dispose(bool) is overridden, it was declared as virtual in A so the right Dispose(bool) in the right class is called in A's destructor. If it weren't declared as virtual, A.Dispose(bool) would be called thereby not calling B.Dispose(bool) and never releasing B's unmanaged resources.

Taking a closer look to our example, the output of Ildasm.exe says:

(~A() is translated as A.Finalize() when compiled to IL).

.method family hidebysig virtual instance void Finalize() cil managed
{
  // Code size       17 (0x11)

  .maxstack  2
  .try
  {
    IL_0000:  ldarg.0
    IL_0001:  ldc.i4.0
    IL_0002:  callvirt   instance void Test.A::Dispose(bool)
    IL_0007:  leave.s    IL_0010
  }  // end .try

  finally
  {
    IL_0009:  ldarg.0
    IL_000a:  call       instance void [mscorlib]System.Object::Finalize()
    IL_000f:  endfinally
  }  // end handler

  IL_0010:  ret
} // end of method A::Finalize

The compiler automatically generates code to control exceptions, it puts our code inside a try/finally block ensuring chain destruction by calling the base class destructor inside the finally block. Also, if the virtual call to Dispose(bool) throws an exception and it is not handled, the program continues with the execution flow. If exceptions are not properly handled inside every Dispose() method, resource leaks can occur.

The using statement

C# has the using statement that allows you to acquire one or multiple resources, use them in a block, and automatically call Dispose() in each of them.

   public class AppClass
   {
      public static void Main()
      {
         using (B a = new B())
         {
            Console.WriteLine(a.ToString());
         };
      }
   }

This block of code translates to IL in the following manner:

.method public hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       30 (0x1e)

  .maxstack  1
  .locals init ([0] class Test.B a)
  IL_0000:  newobj     instance void Test.B::.ctor()
  IL_0005:  stloc.0
  .try
  {
    IL_0006:  ldloc.0
    IL_0007:  callvirt   instance string [mscorlib]System.Object::ToString()
    IL_000c:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_0011:  leave.s    IL_001d
  }  // end .try

  finally
  {
    IL_0013:  ldloc.0
    IL_0014:  brfalse.s  IL_001c
    IL_0016:  ldloc.0
    IL_0017:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_001c:  endfinally
  }  // end handler

  IL_001d:  ret
} // end of method AppClass::Main

That is the same as coding:

   B a = new B();
   try
   {
      Console.WriteLine(a.ToString());
   }
   finally
   {
      if (a != null)
         a.Dispose();
   }

using also allows declaring more than one identifier of the same type:

   using (B a1 = new B(), a2 = new B())
   {
      // do something

   }

The using statement executes the statement block disposing the objects when the end of the statement is reached or when an exception is thrown.

Conclusions

The non-deterministic object destruction provided by the GC forces us to take special care when working with objects wrapping resources:

  • Implementing IDisposable. IDisposable provides the contract a class must implement for correct resource use.
  • Dispose() calls Dispose(true) to release all resources and tells the GC not to call the destructor.
  • Declaring Dispose(bool) as virtual and calling the inherited method from the derived classes.
  • Ensuring Dispose() invocation through the using statement.
  • Calling Dispose(false) in the destructor. Just in case...
  • Calling Dispose() more than once shouldn�t do anything.
  • Making use of a disposed object should throw ObjectDisposedException.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here