C# For C++ Devs: ~A() doesn't act like a destructor

In C++, memory allocated with the 'new' keyword must be deallocated using 'delete' or it is not deallocated until the application finishes. A call to delete results in a call to the destructor for that class. Classes that are allocated on the stack are automatically destroyed, which calls their destructor, when they go out of scope.

Sometimes this 'deterministic' memory allocation/deallocation behavior is exploited by developers using scoped objects on the stack to acquire and then automatically release resources even in the presence of exceptions (this pattern is known as Resource Acquisition Is Initialization - RAII).

Here is a C++ class designed to be used in RAII pattern:

class A
{
public:
A()
{
      // Acquire a resource (e.g. mutex or file)
   }

   ~A()
{
      // Release the resource
}
};

The class is then used as follows:

void f()
{
{
A raii;
      // do some stuff, maybe even throw exceptions
   }
   // raii has gone out of scope, so the destructor has been called. If an exception was thrown A still went out of scope and the destructor was still called
}

C# is a language with automatic garbage collection which means that developers allocate memory but in most cases they don't need to worry about when that memory is deallocated. There is no way to explicitly call the destructor. It is called whenever the garbage collector decides it is necessary to clean up, which is called Finalizing the class. In most cases classes should not implement a destructor.

In C#, it is possible to get somewhat deterministic garbage collection (at least for unmanaged objects like files) by implementing the IDisposable interface and adding a Dispose() method. That method acts much more like C++'s destructor than the equivalent class destructor. The dispose pattern is described pretty well for C# in the MSDN help for IDisposable.

Things to note:

  • The C# destructor will only (and can only) be called by the Finalizer.
  • Dispose() may be called in code.
  • If Dispose() is called before the Finalizer is called, finalization is suppressed using GC.SuppressFinalize(this);.
  • You must be careful not to reference any managed objects if Dispose is called from the destructor (this is achieved in the example by using an extra Dispose() function that takes a bool parameter).
  • It isn't covered in the code, but if you have member variables that implement IDisposable, your class should also implement IDisposable.

Working with unmanaged resources is clearly much more work than working with managed resources.

To implement the same RAII pattern from above in C#, assuming you have set up your class A to implement IDisposable, code with the 'using' statement to ensure Dispose() is called at the end of the block as follows:

using (A raii = new A())
{
   // Do some stuff...
}

This is safe in the presence of exceptions in the same way that the C++ scoped class pattern was above.