The IDisposable interface

The finalize method, which we examined in the preceding section, has some performance implications for the system. With the Finalizer method, we are not sure of when the memory will be reclaimed by the garbage collector even after the object is no longer required. This implies that there is a possibility that unused memory will be persisted in a managed heap for longer than the desired amount of time. 

With the IDisposable interface, we can assume control over when the memory is reclaimed for unmanaged resources in the application. The IDisposable interface in C# only has one method, which is Dispose().

In this method, we can perform the same cleanup of unmanaged resources that we did in the Finalizer method. The following is the code implementation of the IDisposable interface:

public class DisposeImplementation : IDisposable
{
public DisposeImplementation()
{
Console.WriteLine("Creating object of DisposeImplementation");
}
~DisposeImplementation()
{
Console.WriteLine("Inside the finalizer of class
DisposeImplementation");
}
public void Dispose()
{
}
}

Notice that in the preceding example, we have declared a DisposeImplementation class and have implemented a IDisposable interface in this class. 

As we are implementing the IDisposable interface, we have defined a Dispose function in the same class. 

With the Dispose method, we need to clear all the unmanaged resources we are using in this class. While this approach is reliable in terms of when the resources will be reclaimed, there are some points we need to understand:

  • It's the programmer's responsibility is to ensure that the Dispose method is called to reclaim the memory.
  • If the programmer misses calling the Dispose method, there is a chance that the unmanaged resources will not be cleared.

Therefore, as a good programming practice, we should use both the Finalize and Dispose methods together in any implementation related to unmanaged resources. This will ensure that if the programmer has missed calling the Dispose method, then the Finalize method will always be there to reclaim the memory of the unmanaged resources. 

Additionally, in order to ensure that we do not duplicate the work in Finalize and Dispose, we can use the approach illustrated in the following example.

For the same class that we used in the preceding implementation, we will declare an isDisposed field. The value of this field is set to false. In the Dispose method, we will reset its value to true to indicate that the cleanup for the unmanaged resources has occurred. 

Now, to make sure that we do not do a cleanup of the resources a second time, we will check the value of this property in the Finalize method. If the Dispose property is set to true, indicating that cleanup has already occurred, then nothing will happen. If the Dispose property is set to false, indicating that cleanup has not occurred, then finalize will do a cleanup of the resources just as before. The following is the code implementation for this:

public class DisposeImplementation : IDisposable
{
private bool isDisposed = false;
public DisposeImplementation()
{
Console.WriteLine("Creating object of DisposeImplementation");
}
~DisposeImplementation()
{
if(!isDisposed)
{
Console.WriteLine("Inside the finalizer of class
DisposeImplementation");
this.Dispose();
}
}
public void Dispose()
{
isDisposed = true;
Console.WriteLine("Inside the dispose of class
DisposeImplementation");
/// Reclaim memory of unmanaged resources
}
}

Now, let's demonstrate these classes in two ways. First, we will call the Dispose method before calling the GC.Collect() method.

Call the Dispose method as follows:

DisposeImplementation d = new DisposeImplementation();
d.Dispose();
d = null;
GC.Collect();
Console.ReadLine();

In the preceding code, in the Dispose method we are setting the value in the flag to true. Apart from setting the flag, we will also be reclaiming memory from unmanaged resources. Therefore, when we call the finalize method, as the value in the flag is already set to true, the block inside the finalize method does not get executed.

The following is the output:

Now, let's consider another scenario in which the programmer forgets to call the Dispose method explicitly. The following is the code snippet for this:

DisposeImplementation d = new DisposeImplementation();
//d.Dispose();
d = null;
GC.Collect();
Console.ReadLine();

In the preceding code, we are not calling the Dispose method, so the value in the flag is set to false. Therefore, when the garbage collector executes the finalize method in object d, it also executes the code block to explicitly call the Dispose method for the same object. 

The following is the output for this:

There is also a property that we can use to suppress calling the finalize method in the Dispose method. We can use this when we are sure that we don't need to verify the resources in the finalize method. The following is the syntax we can use to suppress calling the finalize method:

public void Dispose()
{
isDisposed = true;
GC.SuppressFinalize(this);
Console.WriteLine("Inside the dispose of class
DisposeImplementation");
/// Reclaim memory of unmanaged resources
}

In the preceding code block, we have used GC.SupressFinalize() for the current object. This will remove the references from the finalization queue, ensuring that the finalize method is never triggered for the current object. Therefore, if we execute the same input, we get the following output:

Using this pattern, we can ensure that unmanaged resources are released from memory without compromising the performance of the application.

In the next section, we will look at using the using block as a good practice for when we are dealing with any classes implementing the IDisposable interface.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.148.177