IDisposable in C#

09 september 2022 om 10:00 by ParTech Media - Post a comment

The term ‘dispose’ is typically used when there are items that need to be taken care of after being consumed. Consider a real-life example. Food covers or food waste needs to be disposed of after the food is consumed.

Similarly, in the programming world, we need to dispose of certain elements after performing many operations. Let's see more about it in this post.

Table of contents

  1. About IDisposable
  2. Understanding and Implementing IDisposable
  3. Conclusion

About IDisposable

Before we talk about IDisposable, it’s important to understand two main concepts: Managed and Unmanaged code. Managed code is that which is taken care of by .NET from the moment it is created and destroyed. Unmanaged code, on the other hand, is where .NET makes certain calls through the Win32 API functions to external resources. .Net runtime is not aware of the utilized resource, which needs to be released once the operation is complete. Accessing a database or performing operations in different files through a .NET Code are some examples of unmanaged code.

IDisposable handles the releasing of unmanaged resources without the need for manual intervention to explicitly clean it up.

Understanding and Implementing IDisposable

Let us first understand how an unmanaged resource code is handled without IDisposable. This will let us see the importance of IDisposable.

An operation that we perform can result in either of these two scenarios -

  • The resource is accessed and the operation is completed successfully.
  • Run time errors/exceptions occur which stops you from achieving the desired result.

As you can see, in both these cases, it is necessary to release the resource. This can be achieved through the below code -

StreamReader tr = null;

try
{
    tr = new StreamReader(@"D:\PARTECH\IDisposable.txt");

    string s = tr.ReadToEnd();

    Console.WriteLine(s);
}
catch (Exception ex)
{
    throw ex;
}
finally
{
    if (tr != null)
    {
        Console.Write("Dispose invoked");
        tr.Dispose();
    }

    Console.ReadLine();
}

Here, the try-catch-finally block is introduced where the external resource is accessed in the first line of the try block. Also, the code for completing the operation in the resource is also added to the try block.

To ensure that the resource is released, no matter the outcome, the ‘dispose of the resource’ is called explicitly in the finally block. If the finally block is not created by the developer, then the resource will not be released and thus will not be available for external operation.

This is where IDisposable brings in value. When a class inherits an IDisposable interface, it has to implement the method IDispose. It can also leverage the ‘using’ keyword. This keyword ensures that the resource is locked only in the block where the object is present. Once the code crosses the block, the resource gets released automatically without any extra lines of code. Let us see the implementation of the same.

CallFile();
Console.ReadLine();

void CallFile()
{
    try
    {
        using var tr = new StreamReader(@"D:\PARTECH\IDisposable.txt");
        string s = tr.ReadToEnd();
        Console.WriteLine(s);
    }
    catch (Exception ex)
    {

    }
}

Since we are adopting using (which indirectly makes use of IDisposable), developers need not worry about releasing the resource after the activity.

Now that we have understood the use of IDisposable, let us also look into Finalizers. You are already aware tht Dispose method cleans up the unmanaged resources, can be called explicitly in the finally block, and will be executed automatically through the ‘using’ statement.

Now, imagine a scenario where a developer has implemented the ‘IDisposable interface’ for a class and added the code details through unmanaged resources. Now, he/she, instead of adopting the ‘using’ keyword approach or the try-catch-finally approach or forgets to implement ‘dispose’ in the finally section, the resource gets held up.

To avoid such situations, Finalizers are used. Finalizers are like destructors. They are used to free up the resource object when the user leaves it to GC.

Let us now understand the working of IDisposable and Finalizers with the help of a sample code and examine the results.

class ExperimentingDisposable : IDisposable
{
    public ExperimentingDisposable()
    {
        Console.WriteLine("Unmanaged resource Initialized");
    }

    public void GetData()
    {
        Console.WriteLine("data");
    }

    void HandleUnmanagedResource()
    {
        Console.WriteLine("Handle the unmanaged resource");
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose invoked directly by client.");

        Dispose(true);

        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        Console.WriteLine("Value for input parameter - disposing - " + disposing.ToString());

        HandleUnmanagedResource();
    }

    ~ExperimentingDisposable()
    {
        Console.WriteLine("Finalizer invoked.");

        Dispose(false);
    }
}

The above is a custom class that inherits the interface IDisposable. The class is recreated to look like it is consuming an unmanaged code. The class has a constructor, getdata method to fetch data from the unmanaged code, a method to handle unmanaged resources, a public method dispose, an overridden method dispose of IDisposable, and a destructor at the end.

Let us now consume the class in multiple ways and examine the results.

ExperimentingDisposable disposable = null;
try
{
    disposable = new ExperimentingDisposable();
    disposable.GetData();
}
finally
{
    disposable.Dispose();
}
Console.ReadLine();

The above code is an example of the try-catch-finally block approach where the developer has to invoke the dipose method explicitly to free up the unmanaged resource. Below is the output of the execution.

CallMethod();
Console.ReadLine();

void CallMethod()
{
    using var disposable = new ExperimentingDisposable();
    disposable.GetData();
}

In the above code, we have removed the try-catch-finally block and used the ‘using’ statement instead.

If you observe the result, both the try-catch-finally approach and using produces similar results. The only difference is that the dispose method is called implicitly in the ‘using’ keyword approach.

CallMethod();
GC.Collect(); //Forcing the GC to work just to see the output immediately.
Console.ReadLine();

void CallMethod()
{
    var disposable = new ExperimentingDisposable();
    disposable.GetData();
}

In the above code, we are not going to explicitly call dispose or adopt using. Instead, we leave it to the GC to clear it as required (To check whether it is clearing.

On observing the output, GC makes the call to the finalizer, which internally invokes the dispose method of the class. If the finalizer is not implemented then, GC will not free up the unmanaged resource.

Conclusion

And that’s everything you need to know about IDisposable in C#. As you can see, IDisposable is an important component of C# that helps developers to a great extent. It ensures that the resource gets released automatically without any extra lines of code. So go ahead and use it in your next application or project.

Nieuwste