devxlogo

When and How to Use Dispose and Finalize in C#

When and How to Use Dispose and Finalize in C#

hen the .NET framework instantiates an object, it allocates memory for that object on the managed heap. The object remains on the heap until it’s no longer referenced by any active code, at which point the memory it’s using is “garbage,” ready for memory deallocation by the .NET Garbage Collector (GC). Before the GC deallocates the memory, the framework calls the object’s Finalize() method, but developers are responsible for calling the Dispose() method.

The two methods are not equivalent. Even though both methods perform object cleanup, there are distinct differences between them. To design efficient .NET applications, it’s essential that you have a proper understanding of both how the .NET framework cleans up objects and when and how to use the Finalize and Dispose methods. This article discusses both methods and provides code examples and tips on how and when to use them.

System Requirements
To implement the techniques discussed in the article, the minimum requirements are:

  • .NET framework version 1.1 or higher
  • Operating System: Windows XP/2000 or higher

An Insight into the Dispose and Finalize Methods
The .NET garbage collector manages the memory of managed objects (native .NET objects) but it does not manage, nor is it directly able to clean up unmanaged resources. Managed resources are those that are cleaned up implicitly by the garbage collector. You do not have to write code to release such resources explicitly. In contrast, you must clean up unmanaged resources (file handles, database collections, etc.) explicitly in your code.

There are situations when you might need to allocate memory for unmanaged resources from managed code. As an example, suppose you have to open a database connection from within a class. The database connection instance is an unmanaged resource encapsulated within this class and should be released as soon as you are done with it. In such cases, you’ll need to free the memory occupied by the unmanaged resources explicitly, because the GC doesn’t free them implicitly.

Briefly, the GC works as shown below:

  • It searches for managed objects that are referenced in managed code.
  • It then attempts to finalize those objects that are not referenced in the code.
  • Lastly, it frees the unreferenced objects and reclaims the memory occupied by them.

The GC maintains lists of managed objects arranged in “generations.” A generation is a measure of the relative lifetime of the objects in memory. The generation number indicates to which generation an object belongs. Recently created objects are stored in lower generations compared to those created earlier in the application’s life cycle. Longer-lived objects get promoted to higher generations. Because applications tend to create many short-lived objects compared to relatively few long-lived objects, the GC runs much more frequently to clean up objects in the lower generations than in the higher ones.

Keep this information about the .NET garbage collector in mind as you read the remainder of the article. I’ll walk you through the Finalize method first, and then discuss the Dispose method.

Finalizers?Implicit Resource Cleanup
Finalization is the process by which the GC allows objects to clean up any unmanaged resources that they’re holding, before the actually destroying the instance. An implementation of the Finalize method is called a “finalizer.” Finalizers should free only external resources held directly by the object itself. The GC attempts to call finalizers on objects when it finds that the object is no longer in use?when no other object is holding a valid reference to it. In other words, finalizers are methods that the GC calls on “seemingly dead objects” before it reclaims memory for that object.

The GC calls an object’s finalizer automatically, typically once per instance?although that’s not always the case (see the Author’s Note below for more information). The framework calls finalizers on a secondary thread handled by the GC. You should never rely on finalizers to clean up managed resources. A class that has no finalizer implemented but is holding references to unmanaged objects can cause memory leaks, because the resources might become orphaned if a class instance is destroyed before releasing the unmanaged objects.

Author’s Note: Although the GC usually calls an object’s finalizer only once, you can change that by writing code to re-register the instance using the ReRegisterForFinalize method and not subsequently calling the GC.SuppressFinalize method on the instance.

You must implement finalizers very carefully; it’s a complex operation that can carry considerable performance overhead. The performance overhead stems from the fact that finalizable objects are enlisted and removed from the finalization queues, which are internal data structures containing pointers to instances of classes that implement a finalizer method. When pointers to these objects are placed in this data structure, the object is said to be enlisted in the Finalization Queue. Note that the GC periodically scans this data structure to locate these pointers. When it finds one, it removes the pointer from the queue and appends the pointer at the end of another queue called the freachable queue.

Further, finalizable objects tend to get promoted to the higher generations and hence stay in memory for a relatively longer period of time. Note that the GC works more frequently in the lower generations than in the higher ones.

The time and order of execution of finalizers cannot be predicted or pre-determined. This is why you’ll hear that the nature of finalization is “non-deterministic.” Further, due to the non-deterministic nature of finalization the framework does not and cannot guarantee that the Finalize method will ever be called on an instance. Hence, you cannot rely upon this method to free up any un-managed resources (such as a file handle or a database connection instance) that would otherwise not be garbage collected by the GC.

Note that you cannot call or override the Finalize method. It is generated implicitly if you have a destructor for the class. This is shown in the following piece of C# code:?

   class Test   {       // Some Code       ~Test    {       //Necessary cleanup code    }   }   

In the preceding code, the ~Test syntax declares an explicit destructor in C#, letting you write explicit cleanup code that will run during the finalize operation.

The framework implicitly translates the explicit destructor to create a call to Finalize:

   protected override void Finalize()   {       try       {           //Necessary cleanup code       }       finally       {           base.Finalize();       }   }   

Note that the generated code above calls the base.Finalize method.

You should note the following points should when implementing finalizers:

  • Finalizers should always be protected, not public or private so that the method cannot be called from the application’s code directly and at the same time, it can make a call to the base.Finalize method
  • Finalizers should release unmanaged resources only.
  • The framework does not guarantee that a finalizer will execute at all on any given instance.
  • Never allocate memory in finalizers or call virtual methods from finalizers.
  • Avoid synchronization and raising unhandled exceptions in the finalizers.
  • The execution order of finalizers is non-deterministic?in other words, you can’t rely on another object still being available within your finalizer.
  • Do not define finalizers on value types.
  • Don’t create empty destructors. In other words, you should never explicitly define a destructor unless your class needs to clean up unmanaged resources?and if you do define one, it should do some work. If, later, you no longer need to clean up unmanaged resources in the destructor, remove it altogether.

To close out this section, Finalize() is a non-explicit way to clean up resources. Because you can’t control when (or even if) the GC calls Finalize, you should treat destructors only as a fallback mechanism for releasing unmanaged resources. Instead, the approved way to release unmanaged resources is to make your class inherit from the IDisposable interface and implement the Dispose() method.

The Dispose Method?Explicit Resource Cleanup
Unlike Finalize, developers should call Dispose explicitly to free unmanaged resources. In fact, you should call the Dispose method explicitly on any object that implements it to free any unmanaged resources for which the object may be holding references. The Dispose method generally doesn’t free managed memory?typically, it’s used for early reclamation of only the unmanaged resources to which a class is holding references. In other words, this method can release the unmanaged resources in a deterministic fashion.

However, Dispose doesn’t remove the object itself from memory. The object will be removed when the garbage collector finds it convenient. It should be noted that the developer implementing the Dispose method must call GC.SuppressFinalize(this) to prevent the finalizer from running.

Note that an object should implement IDisposable and the Dispose method not only when it must explicitly free unmanaged resources, but also when it instantiates managed classes which in turn use such unmanaged resources. Implementing IDisposable is a good choice when you want your code, not the GC, to decide when to clean up resources. Further, note that the Dispose method should not be called concurrently from two or more different threads as it might lead to unpredictable results if other threads still have access to unmanaged resources belonging to the instance.

The IDisposable interface consists of only one Dispose method with no arguments.

   public interface IDisposable   {      void Dispose();   }

The following code illustrates how to implement the Dispose method on a class that implements the IDisposable interface:

   class Test : IDisposable   {     private bool isDisposed = false;        ~Test()     {       Dispose(false);     }        protected void Dispose(bool disposing)     {       if (disposing)       {         // Code to dispose the managed resources of the class       }       // Code to dispose the un-managed resources of the class          isDisposed = true;     }        public void Dispose()     {       Dispose(true);       GC.SuppressFinalize(this);     }   }      

In the preceding code, when the Boolean variable disposed equals true, the object can free both managed and unmanaged resources; but if the value equals false, the call has been initiated from within the finalizer (~Test) in which case the object should release only the unmanaged resources that the instance has reference to.

The Dispose/Finalize Pattern
Microsoft recommends that you implement both Dispose and Finalize when working with unmanaged resources. The correct sequence then would be for a developer to call Dispose. The Finalize implementation would run and the resources would still be released when the object is garbage collected even if a developer neglected to call the Dispose method explicitly. Francesco Balena writes in his blog “the Dispose/Finalize pattern should be used only when your type invokes unmanaged code that allocates unmanaged resources (including unmanaged memory) and returns a handle that you must use eventually to release the resource. Both dispose and finalize must chain up to their parent objects by calling their parent’s respective methods after they have disposed or finalized their own members”.

Simply put, cleanup the unmanaged resources in the Finalize method and the managed ones in the Dispose method, when the Dispose/Finalize pattern has been used in your code.

As an example, consider a class that holds a database connection instance. A developer can call Dispose on an instance of this class to release the memory resource held by the database connection object. After it is freed, the Finalize method can be called when the class instance needs to be released from the memory. According to MSDN, “Finalize provides a backup to prevent resources from permanently leaking if the programmer fails to call Dispose“. Please refer to the following link:

Suppressing Finalization
After the Dispose method has been called on an object, you should suppress calls to the Finalize method by invoking the GC.SuppressFinalize method as a measure of performance optimization. Note that you should never change the order of calls in the finalization context (first Dispose(true) and then GC.SupressFinalize) to ensure that the latter gets called if and only if the Dispose method has completed its operation successfully.

The following code illustrates how to implement both the Dispose and Finalize pattern for a class.

   public class Base: IDisposable   {     private bool isDisposed = false;         public void Dispose()       {         Dispose(true);         GC.SuppressFinalize(this);       }      protected virtual void Dispose(bool disposing)       {         if(!isDisposed)         {          if (disposing)           {             // Code to dispose the managed resources              // held by the class          }         }              // Code to dispose the unmanaged resources         // held by the class       isDisposed = true;       base.Dispose(disposing);      }      ~Base()      {         Dispose (false);      }   }

You should not reimplement IDisposable for a class that inherits from a base class in which IDispose has already been implemented. The following code snippet may help you understand this concept:

   public class Base: IDisposable   {     private bool isDisposed = false;         public void Dispose()       {         Dispose(true);         GC.SuppressFinalize(this);       }      protected virtual void Dispose(bool disposing)       {         if(!isDisposed)         {          if (disposing)           {             // Code to dispose managed resources              // held by the class          }         }              // Code to dispose unmanaged resources         // held by the class       isDisposed = true;       base.Dispose(disposing);      }      ~Base()      {         Dispose (false);      }   }      public class Derived: Base   {         protected override void Dispose(bool disposing)       {         if (disposing)          {            // Code to cleanup managed resources held by the class.         }                     // Code to cleanup unmanaged resources held by the class.                 base.Dispose(disposing);      }   // Note that the derived class does not // re-implement IDisposable   }

In the preceding code, what if the Dispose method were to throw an exception? In that case, the Finalize method would exit prematurely, and the memory would never be reclaimed. Hence, in such situations, it is advisable to wrap the Dispose method in a try-catch block. This will prevent finalization exceptions from orphaning the object.

Note the following points when implementing disposable types:

  • Implement IDisposable on every type that has a finalizer
  • Ensure that an object is made unusable after making a call to the Dispose method. In other words, avoid using an object after the Dispose method has been called on it.
  • Call Dispose on all IDisposable types once you are done with them
  • Allow Dispose to be called multiple times without raising errors.
  • Suppress later calls to the finalizer from within the Dispose method using the GC.SuppressFinalize method
  • Avoid creating disposable value types
  • Avoid throwing exceptions from within Dispose methods

In a managed environment, the GC takes care of freeing unused objects. In contrast, in unmanaged languages such as C, developers had to release unused objects explicitly that were created dynamically in the heap. However, a proper understanding of both the Dispose and Finalize methods goes a long way toward designing efficient applications.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist