In this article, I will be talking about one of the most important topics related to the memory management in .NET. We know that managing the memory is the primary concern of any application. Thus, to help the programmers focus on implementing their functionality, .NET introduced automatic memory management, using Garbage Collector. Garbage collection is the heart of .NET Applications. Garbage collector has a limitation,in that it can clean up only managed resources. Thus, now the question is what is a managed resource and what is an unmanaged resource.
Managed Resource
Managed resource means anything which can be managed by CLR (any code that uses CLR, this can be managed code written in C# or C++).CLR handles the memory management for such resources and automatically clears the memory, when not required.
Unmanaged Resources
Unmanaged resources are generally C or C++ code, libraries or DLL's.These are called unmanaged because the coder has to do the memory management (allocate memory for the object and clean the memory after the object is no longer required). These can file the handles, database connections etc.
Thus, now when we have a basic idea of the Managed and Unmanaged resources, we will move further towards our main topic, how to implement the memory management for the unmanaged resources. CLR provides some help in releasing the memory claimed by the unmanaged resources. For clearing the unmanaged resources, we have a virtual method finalized in System.Object Class.
Finalize Method
Object Class does not provide any implementation to the Finalize method. Unless a class derived from the object class overrides, the Finalize method garbage collector cannot mark it for finalization. Garbage collector maintains a finalization queue for all the objects in the Heap, whose finalization method code must run before Garbage Collector can run to reclaim the memory. Garbage Collector automatically calls the Finalize method, but it is not sure, when Garbage Collector will run and call Finalize method.
Now, I will show you, how to implement Finalize Method. Finalize is a virtual method of an Object Class. It does not have any access modifier. We cannot call Finalize method directly, as there is no keyword like Finalize. To use Finalize, we need to create a destructor. Destructor is a special method, which has the same name as the class name with a tilt prefixed before it. Destructor cannot have any parameters. At compile time, the destructor is converted to Finalize() method. The sample code is given below:
- using System;
- namespace FinalizeDemo
- {
- class Program
- {
- static void Main(string[] args)
- {
- FinalizeDemo d = new FinalizeDemo();
- d = null;
- Console.ReadLine();
- }
- }
- class FinalizeDemo
- {
- public FinalizeDemo()
- {
- Console.WriteLine("Object Created");
-
- }
-
- ~FinalizeDemo()
- {
- Console.WriteLine("Destructor Called.");
- }
- }
- }
Now, we have added a destructor for the class. Let's verify whether it has created a Finalize method for the same. For this purpose, I will be using ILSPY .In ILSPY, we will browse the EXE created .Below is the snapshot for the same. Here, we can see the constructor, but not the destructor. Now, click Finalize method and you will see the destructor. The destructors are converted to Finalize method at the compile time. Now, we will run the code, given above. Even though we assign null to the object, still it’s not garbage collected. To see the destructor being called, run the Application, given above, through the command prompt. See the below snapshot for the same.
Note: Even though we assigned null to the object, we cannot predict when the memory will be de-allocated. To make the memory de-allocated immediately, we can call GC.Collect() method.
In .NET, we have one more way to clear the unmanaged memory.
Dispose Method
Dispose method is also used to unmanage the resources like connections, file etc. This method belongs to IDisposable interface. IDisposable interface has only one method i.e. Dispose. To clear all the unmanaged resources held by a class, we need to inherit that class from IDisposable interface and implement Dispose method. We have to write all the cleanup code in DisposeMethod. Whenever we want to free the resources held by the object, we can call the Dispose method.
- using System;
-
- namespace FinalizeDemo
- {
- class Program
- {
- static void Main(string[] args)
- {
- FinalizeDemo d = new FinalizeDemo();
- d.Dispose();
- d = null;
-
- Console.ReadLine();
- }
- }
- class FinalizeDemo:IDisposable
- {
- public FinalizeDemo()
- {
- Console.WriteLine("Object Created");
-
- }
-
- ~FinalizeDemo()
- {
- Console.WriteLine("Destructor Called.");
- }
-
- public void Dispose()
- {
- Console.WriteLine("Dispose Method Called");
- }
- }
- }
There is a problem in this approach. If the user forgot to call the Dispose method, there will be a memory leak. To overcome this problem, it's recommended to use Dispose and Finalize together. Thus, if a user forgot to call Dispose method ,Garbage Collector can call the Finalize method and clear all the memory, held by the object. Below is the code snippet to implement Dispose and Finalize. Instead of writing the same logic in Dispose method and destructor, we will be creating a Dispose Method, whch accepts a Boolean parameter. This method can be called from the destructor or from Dispose () method.
- using System;
-
- namespace FinalizeDemo
- {
- class Program
- {
- static void Main(string[] args)
- {
- FinalizeDemo d = new FinalizeDemo();
- d.Dispose();
- d = null;
-
- Console.ReadLine();
- }
- }
- class FinalizeDemo:IDisposable
- {
- private bool Disposed = false;
- public FinalizeDemo()
- {
- Console.WriteLine("Object Created");
-
- }
-
- ~FinalizeDemo()
- {
- Console.WriteLine("Destructor Called.");
- Dispose(false);
- }
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
-
- }
- public void Dispose(bool disposing)
- {
- if(!Disposed)
- {
- if (disposing)
- {
- Console.WriteLine("Called From Dispose");
-
- }
- else {
-
- }
- Disposed = true;
-
-
- }
- }
- }
Here, we have taken a Boolean variable Disposed = false. Now, we have two scenarios 1) If a user calls Dispose method: Here, we are checking whether an object has been disposed or not. Now, when we call this method from Dispose method of IDisposable interface, we pass true. In if block, we will write all the clean up code and then outside it, we will set the Disposed variable to true.
2) If a user forgets to call Dispose method: In this case Destructor, we will call the Dispose Method with false and control will go to else block inside Dispose method. Here, we will write all the clean up code.
.NET introduced the using block to take care of calling Dispose method, if a class is implementing IDisposable interface. Hence, it’s a good practice to create an object within the using block.