Well, while being in touch with a number of developers, I always find people don’t understand the real meaning of disposing objects. Lets clear our basic understanding on why we need to dispose objects in .NET before understanding the usefulness of Using block.
Why Disposing is necessary ?
We all know, the code that we run on our managed environment are called managed code. All the objects that we create and automatically picked up by an invisible hand from the program itself called Garbage Collector. The GC is capable of detecting all the objects that do not have reference from the program that is running on, thus sweeping the unnecessary memory from the program and return the memory to the program again.
Now you must be thinking, does it sweep only the objects that are Disposed? Aah.. No, not really.
Difference between Managed and Unmanaged Memory.
In managed world, memory are created in object Heap (a memory separate from Execution memory but exists in the process) and the program have entire idea on the start and end location of the memory it have 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 Page File such that your program can access the memory and get data. This memory is totally unmanaged, and your program can only request to clear the memory and Garbage Collection cannot collect it without your request. In case of 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 if you do not explicitly call to remove it.
For instance, File IO, DataBase Connection Open, Network call, etc 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, as you are calling an unmanaged memory yourself.
What are your option to Deallocate Unmanaged Memory?
Well, by this, 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 which is opened inside a class) and call it whenever GC collects. Well, yes. There is an option to define a destructor inside a class which will be called directly from the GC itself and deallocate memory. Let us look how to write a class with destructor.
public class TestClass
//here you can write File.Open
//here you can write File.Close
Now if you see the above code you can see, we can create an 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 a 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 Garbage Collector collects the unmanaged memory, it generally suspends the Execution Engine (not in case of 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 moves ahead. This will make your object to stay longer as the GC collects the object until it executes again, when it starts by calling destructor of all the queued references and then starts collecting managed objects.
Related Read : Using or Using ?
The usefulness of 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 have to wait for the destructor to call the Close Connection, there would be lot of unused connection remain in the pool for a long time until the GC executes the destructor. .NET solves the problem by giving a simple workaround to write our Dispose method inside the class and a shorthand using block to call the Dispose method automatically. Let us take an example:
public class TestClass : IDisposable
//here you can write File.Open
//here you can write File.Close
public void Dispose()
//Close the file here
Now in the above code what we did is we defined our own method called Dispose which 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 the GC to collect the object rather than putting it to the Finalizer Queue.
Related Read : Using Fixed Keyword in C#
Where Using Block lies in ?
Now, as you can remarkably identify that using a IDisposable is way better than having a Destructor in a class, the C# language gives a syntactic sugar to embrace this functionality and encouraging 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 pretty safe to access, we must wrap this inside a Try/Catch/Finally block ensuring the try to try creating the object instance, and Finally will ensure that at any cost the memory gets disposed. We will write like this :
tclass = new TestClass(); // Here memory gets created
// We use the object tclass here.
tclass.Dispose(); // here memory gets cleared
The Finally will ensure that even though the try block encounters an exception, the unmanaged memory gets disposed. To give a shortcut to the above code, .NET provides an Using block. To rewrite the above code with Using block, we write 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 forget to call Dispose from its code. Thus making the life of the programmer simple.
Things to Remember
- We only need to dispose objects with Unmanaged Memory.
- Destructor loses GC Generation hence it is better to avoid.
- GC.SuppressFinalize will allow the programmer to mark an object that destructor does not needed to be called.
- Dispose is a better pattern which allows the programmer to clear unmanaged memory.
- Using block is a shortcut of try/finally where in the try, it creates an object of the class and in finally the Dispose gets called.
I hope this post will give you better understanding of the Using block and allow you to write better programs in the long run.