Benefits of Using Dispose For .NET Objects

Well, while being in touch with a number of developers, I always find that people don’t understand the real meaning of disposing objects. Let’s clarify our basic understanding of why we need to dispose of objects in .NET before understanding the usefulness of a using block.

Why Disposing is necessary

We all know, the code that we run on our managed environment is called managed code. All the objects that we create are automatically picked up by an invisible hand from the program itself, called the Garbage Collector. The GC is capable of detecting all the objects that do not have references from the program that it is running on, thus sweeping the unnecessary memory from the program and returning the memory to the program again.

Now you must be thinking, does it sweep only the objects that are disposed of? Aah.. No, not really.

Difference between Managed and Unmanaged Memory.

In the managed world, memory is created in the object Heap (a memory separate from execution memory but in the process) and the program has an entire idea on the start and end location of the memory it has been using. But while creating a program, we do not really create only managed memory. Consider you are opening a file from your program. There is a request from your program being made to open a file, and load it to the Page File such that your program can access the memory and get data. This memory is totally unmanaged, and your program can only request that the the memory be cleared and the Garbage Collection cannot collect it without your request.  In the case of a file open, the memory is cleared when we call "File.Close". Thus there is a gap between the managed and unmanaged memory boundaries. The Garbage Collector can only collect the memory that .NET has allocated and unmanaged memory will remain allocated if you do not explicitly remove it.

For instance, file IO, opening a database connection, a network call, and so on all are all calling an external process running in the Kernel of the operating system to allocate / deallocate memory. Therefore in such cases, you need an explicit memory deallocation inside your program. You can also consider a PInvoke as an example here, since you are calling an unmanaged memory yourself.

What are your option to Deallocate Unmanaged Memory?

Well, by now you already know that managed memory does not require anything from the programmer to deallocate. For unmanaged memory, you can create a method that can deallocate the memory (for instance "File.Close" that is opened inside a class) and call it whenever GC collects. Well, yes. There is an option to define a destructor inside a class that will be called directly from the GC itself and deallocate memory. Let us look at how to write a class with a destructor.

Public class TestClass
{
    public TestClass()
    {
        //here you can write File.Open
        Console.WriteLine("Constructor");
    }
    ~TestClass()
    {
        //here you can write File.Close
        Console.WriteLine("Destructor");
    }
}

Now you can see in the code above that we can create unmanaged memory inside the constructor, and clear the memory in the Destructor of the class so that the unmanaged memory used by the class gets cleared.

But there is a catch! As you know, your TestClass is managed memory, the object instance of the TestClass would only be cleared by the Garbage Collector. The destructor is called only by the Garbage Collector itself. Now the problem is, when the Garbage Collector collects the unmanaged memory, it generally suspends the Execution Engine (not in the case of the Background GC introduced recently) while collecting, and it cannot call your destructor while in the middle of the Collection. Hence, it maintains a new list of all objects that needs the Destructor to be called. So when your TestClass is found by the GC, it puts the object reference in the Finalizer Queue, and roceeds. This will make your object to stay longer since the GC collects the object until it executes again, when it starts by calling the destructor of all the queued references and then starts collecting managed objects.

The usefulness of the Disposable pattern in Memory Management

Disposable pattern in Memory Management

The Unmanaged memory is expensive. Take an instance of a Database Connection on a Distributed Database system. If your program must wait for the destructor to close the connection, there would be a lot of unused connection remaining in the pool for a long time until the GC executes the destructor. .NET solves the problem with a simple workaround to write our Dispose method inside the class and a shorthand using a block to call the Dispose method automatically. Let us take an example:

public class TestClass : IDisposable
{
    public TestClass()
    {
        //here you can write File.Open
        Console.WriteLine("Constructor");
    }
    ~TestClass()
    {
        //here you can write File.Close
        Console.WriteLine("Destructor");
    }    
    public void Dispose()
    {
        //Close the file here
        GC.SuppressFinalize(this);
    }
}

Now in the code above what we did is we defined our own method called Dispose that we would call from Managed Code and the GC.SuppressFinalize will ensure that the Destructor will not be called when a Dispose is already called . Thus ensuring that the GC collects the object rather than putting it into the Finalizer Queue.


Where the Using Block is

Now, as you can remarkably identify that using a IDisposable is a better way than having a Destructor in a class, the C# language gives a syntactic sugar to embrace this functionality and encourages the use of IDisposable rather the destructor. Now let us think how we could have used the class TestClass.

TestClass tclass = new TestClass(); // The line creates an unmanaged memory instance.
// use the tclass to access the unmanaged memory
tclass.Dispose(); //Clear the unmanaged memory.

We also know that unmanaged memory is not entirely safe to access, we must wrap this inside try/catch/finally blocks ensuring the try to try to create the object instance, and the finally will ensure that at any cost the memory is disposed of. We will write it like this:

TestClass tclass;
Try
{
     tclass = new TestClass(); // Here memory gets created
     // We use the object tclass here.
}
Finally
{
   tclass.Dispose(); // here memory gets cleared
}


The finally will ensure that even though the try block encounters an exception, the unmanaged memory is disposed of. To provide a shortcut to the code above, .NET provides a using statement. To rewrite the code above with a using statement, we write it like this:

using(TestClass tclass = new TestClass()) // this is here memory is created
{
 // We use tclass here..
} // this is where dispose called


Thus you can see, the transformation has made the code so much more readable, ensuring the programmer never forgets to call Dispose from the code. Thus making the life of the programmer simple.

Things to Remember
  • We only need to dispose of objects of unmanaged memory.
  • Destructor loses GC Generation hence it is better to avoid.
  • GC.SuppressFinalize will allow the programmer to mark an object that the destructor does not needed to be called.
  • Dispose is a better pattern that allows the programmer to clear unmanaged memory.
  • A using block is a shortcut of try/finally where in the try, it creates an object of the class and in finally the Dispose is called.

I hope this article will provide you better understanding of the Using block and allow you to write better programs in the long run.
  

Up Next
    Ebook Download
    View all
    Learn
    View all