In this article, I will be talking about one of the most important topics related to memory management in. NET. We know that managing 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. The 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, 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 DLLs. 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 that we have a basic idea of the Managed and Unmanaged resources, we will move further toward our main topic, how to implement memory management for 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 the 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. The garbage collector maintains a finalization queue for all the objects in the Heap, whose finalization method code must run before the Garbage Collector can run to reclaim the memory. The Garbage Collector automatically calls the Finalize method, but it is not sure when the Garbage Collector will run and call the 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 the 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. The 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 the Finalize method and you will see the destructor. The destructors are converted to the 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 is 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 of Method
Dispose method is also used for unmanaged resources like connections, files, etc. This method belongs to the 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 the IDisposable interface and implement the 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 with this approach. If the user forgets 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 forgets to call the Dispose method, the 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 the Dispose method and destructor, we will be creating a Dispose Method, which accepts a Boolean parameter. This method can be called from the destructor or from 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
{
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");
// Clear all the managed resources here
}
else
{
// Clear all the unmanaged resources here
}
Disposed = true;
}
}
}
}
Here, we have taken a Boolean variable Disposed = false. Now, we have two scenarios
- If a user calls the Dispose method: Here, we are checking whether an object has been disposed of or not. Now, when we call this method from the Dispose method of the IDisposable interface, we pass true. In the if block, we will write all the cleanup code, and then outside it, we will set the Disposed variable to true.
- If a user forgets to call the Dispose method: In this case Destructor, we will call the Dispose Method with false, and control will go to the else block inside the Dispose method. Here, we will write all the cleanup code.
.NET introduced the use block to take care of calling the Dispose method if a class is implementing the IDisposable interface. Hence, it’s a good practice to create an object within the using block.