Finalize vs Dispose: Key Differences

09 augustus 2021 om 10:00 by ParTech Media - Post a comment

Modern-day computers are capable of performing multiple tasks simultaneously and users are benefitted the most. One of the key factors that determine the multitasking capability of a machine is its memory.

The higher the memory, the higher the possibility of multitasking. At the same time, the memory has to be handled efficiently so that the applications can utilize it effectively. Say if the memories are not handled efficiently, even if the memory is high, users might end up facing memory-related issues.

Memory management is critical for the effective performance of the APIs, services, front-end applications developed using C#. To handle this, .Net comes with a Garbage Collector(GC) feature which watches for the unused objects in the code and frees up the memory.GC takes care of the memory for all the managed resources. But for the unmanaged resources, it's the responsibility of the application to free up the memory as and when the task gets completed. So, it implicitly becomes the responsibility of the developer to handle these unmanaged resources.

Let us now see in detail how memory management can be handled for unmanaged code in C#.

Table of contents

  1. What are unmanaged resources?
  2. About Dispose and Finalize
  3. How to implement Dispose and Finalize?
  4. Wrapping Up

What are unmanaged resources?

The resources that are entirely maintained by the .Net itself are termed as managed resources. In other words, the memory of these resources is being maintained by the garbage collector.

On the other hand, unmanaged resources are objects that make use of the underlying operating system resources. This includes files, Windows resources, network connections, database connections, etc.

For unmanaged resources, garbage collectors are not aware of when to release the memory of those resources. For example, if an object is created for the database connection, it creates an underlying network connection to the server of the database, which needs to be closed so that the memory of the object can be released. Let's now see in the next section how to clean them up.

About Dispose and Finalize

Resources like file handling, database connections, network connection, etc. are not being handled by the garbage collector. To release the memory of these objects, it is best to implement the Dispose() method which is part of the IDisposable interface.

The Dispose method has to be explicitly called to free up the object memory. Classes that inherit the IDisposable interface provide the option of using statements. These using statements define the scope of the object and dispose them at the end of its scope. Do note that calling Dispose method does not hamper the performance of the application.

Finalize methods are also known as the destructors of the class. When the object becomes out of scope and also when all the references are removed from the object, the finalize method gets called implicitly. And in the inheritance chain, the finalize method of the base class gets executed at the last.

Writing the memory cleaning logic in the finalize method leads the GC to visit twice, which hampers the performance of the application. Also, it is not being executed immediately as they get executed based on GC. So, until it is very essential, Finalize is not recommended to be used.

How to implement Dispose and Finalize?

To implement Dispose and Finalize methods, we are going to build a single solution. Here, we are going to create a custom class that inherits an IDisposable interface, has an implementation for the Dispose method, and similarly a destructor for finalization.

To begin with, create a .Net Console application, provide a valid name for the solution, and choose the required framework version.

Now, the next step is to create a class that inherits IDisposable -

public class DisposeExample: IDisposable

{

public void Dispose()

{

}

}

Here, we are going to use a StringReader whose memory has to be cleared on completion of the reference of the object. And we are going to create another Dispose method that implements the logic to dispose of the reader once the activity is done.

The constructor of the class holds the logic to create a new instance of the StringReader. Also, in the same class, the finalize method is written in the form of the Destructor (which gets converted to finalize method at the end).

Now to understand Dispose and Finalize, two instances of the class are going to be created in two different methods, one with using and another without using. Once the code gets executed, the instance that was created using Dispose gets cleared off once the scope gets over. But, the instance that was created without using memory gets allocated. However, the clearing of the memory happens under the control of GC. Here is the code for the same -

using System;

using System.IO;

namespace PARTECH_DisposeFinalize

{

  class Program

  {

​    static void Main(string[] args)

​    {

​      ClassDisposableUsingDispose();

​      ClassDisposableUsingFinalize();

​      Console.ReadLine();

​    }

​    static void ClassDisposableUsingDispose()

​    {

​      Console.WriteLine(DateTime.Now.ToString() + " Object getting created.");

​      using var disposeExample = new DisposeExample();

​    }

​    static void ClassDisposableUsingFinalize()

​    {

​      Console.WriteLine(DateTime.Now.ToString() + " Object getting created.");

​      var disposeExample = new DisposeExample();

​    }

  }

  public class DisposeExample : IDisposable

  {

​    bool alreadyDisposed = false;

​    private StringReader _reader;

​    public DisposeExample()

​    {

​      _reader = new StringReader("");

​    }

​    public void Dispose()

​    {

​      Dispose(true);

​      GC.SuppressFinalize(this);

​    }

​    protected virtual void Dispose(bool disposing)

​    {

​      if (!alreadyDisposed && disposing)

​      {

​        if (_reader != null)

​        {

​          _reader.Dispose();

​          Console.WriteLine(DateTime.Now.ToString() + " Object getting disposed.");

​        }

​        alreadyDisposed = true;

​      }

​    }

​	//Finalize code

​    ~DisposeExample()

​    {

​      Dispose(false);

​    }

  }

}

Wrapping Up

Freeing of unused objects of Managed resources is taken care of by GC. Whereas, in unmanaged code, developers have to take care of the memory management and release them as and when required.

Nieuwste