Tech Point Fundamentals

Wednesday, January 6, 2021

Destructor in C# | Finalizer in C# | Finalize Method Overriding

Destructor in C# | Finalizer in C# | Finalize Method Overriding

Destructor-Or-Finalizer-InCSharp
  In C# destructors are methods inside the class which is used to destroy the instances of that class when they are no longer needed. The .NET framework has an inbuilt mechanism called Garbage Collection to de-allocate memory occupied by the unused objects. The GC internally uses the destruction method to clean up the unused objects. 




Introduction

An instance variable or an object is eligible for destruction when it is no longer reachable and it is no longer possible for any code to use that instance. 

Since C# is a garbage-collected language, meaning that the .Net framework will free the objects that you no longer use, but still there may be some situations where you need to do some manual cleanup. 

Please read about the constructor here.

Destructor (Finalizer)

Destructors are also known as Finalizers. A destructor is a very special member function of a class that is executed whenever an object of its class goes out of scope.

Destructor is used to write the code that needs to be executed while an instance is destroyed i.e garbage collection process.

A destructor is a method with the same name as the name of the class but starting with the character tilde (~) character. A destructor runs only after a class becomes unreachable. 



Destructor Fundamental Points

    1. Destructor has the same name as the class name with the "~" sign. The tilde sign distinguished it from the constructor.
    2. A destructor cannot be called explicitly, they invoked automatically after GC
    3. A C# destructor automatically calls the destructor of its base class.
    4. Unlike constructors, the destructor of the child class called before the parent class, i.e. the destructors are called in the reverse order of the constructor calls.
    5. The destructor method is called recursively for all instances in the inheritance chain, from the most-derived to the least-derived.
    6. We cannot inherit from a sealed class but a sealed class can define destructors in C#.
    7. Internally, the destructor called the Finalize method of the base Object class. We cannot call the base class Finalize() method explicitly.
    8. All the explicit destructors override the Finalize method of the base class. 
    9. Empty destructors should never be used because they are ultimately wasteful and instances get into the finalize queue for no reason. So it results in the loss of performance.



Destructor Fundamental Rules

    1. A destructor has no return type even void.
    2. A destructor cannot have any parameters.
    3. A destructor cannot be overloaded or inherited.
    4. The static classes cannot have any destructors.
    5. An abstract class can also have destructors but we cannot create objects of an abstract class.
    6. A destructor cannot have any modifiers like static, abstract, sealed, virtual, or partial.
    7. Only one destructor is allowed i.e. a class can only have one destructor.
    8. Structures cannot define destructors but constructors are allowed in the structures. Structures are the value type, not a reference type.
    9. By default all the destructors are private, we cannot apply any access modifier explicitly. But Finalize() method of the base Object class is protected.




Finalize - Object class Destructor (Object.Finalize)

In C# all the classes are implicitly derived from the super base class ObjectThis Object class contains a special virtual method, Finalize(), which every class can override.

The Garbage Collector of the .NET framework calls this destructor method prior to the garbage collection of the Objects class. When an object is eligible for destruction, the garbage collector runs the Finalize () method of that object.

When we provide an explicit destructor in a class, during the compilation time, the compiler automatically generates the Finalize() method. That means that a destructor and overridden Finalize() method cannot co-exist in a class.

The Finalize method is called when an object that overrides Finalize is destroyed. A Finalize method can also be used for resurrecting an object i.e making the object accessible again after it has been cleaned up during garbage collection.  However, the object can only be resurrected once; Finalize cannot be called on resurrected objects during garbage collection.

 ~Object();

Example


 



Overriding Finalize Method

Every explicit finalizer or destructor overrides the Finalize() method of the base Object class. The Object.Finalize() method does nothing by default, but you should override Finalize only if necessary, and only to release unmanaged resources such as windows, files, network connections,  database connections, etc

The Object class provides no implementation for the Finalize method, and the garbage collector does not mark types derived from Object for finalization unless they override the Finalize method.

The C# compiler does not allow you to override the Finalize method explicitly. Instead, you provide a finalizer by implementing a destructor for your class. Therefore, a call to a finalizer is implicitly translated to the following code:

Overriding-Finalizer

If you see the IL Code of a class having explicit destructor by ILDASM tool:

Finalizer-ILCode
      
Now it is clear that explicit destructor basically overrides the Finalize method internally and the finalizer implicitly calls the Finalize() method of the base class.



Finalizer vs Garbage Collector

If a class does override the Finalize method, the garbage collector adds an entry for each instance of the type to an internal structure called the Finalization Queue. The finalization queue contains entries for all the objects in the managed heap whose finalization code must run before the garbage collector can reclaim their memory. 

Since GC does not know what’s inside your Finalize() method, it can not call the Finalize() method directly. Because if you have written some resource expensive code in the destructor then it may block garbage collection because of some costly process being executed before the memory for the object is reclaimed. So the finalizers on the objects are called periodically on a separate thread, completely independently from the GC. This way the main GC thread is not blocked. 

Since GC doesn’t call the Finalize method directly from this queue, instead, it removes object reference from the finalizer queue and puts it on the Freachable Queue. Each object reference in the freachable queue identifies an object that is ready to have its Finalize method called. So the finalizable objects stay uncollected for at least 1 more GC round than “normal” objects.

There is a dedicated Finalizer thread created by the CLR in every .NET process. It is responsible for running the finalize method for all objects that opted to implement it. This specialized CLR thread is only responsible for monitoring the freachable queue and when GC adds an item it executes the Finalize method.



When to override the Finalize() Method

One should override the Finalize method for reference types only. The CLR (common language runtime) only finalizes reference types. It ignores finalizers on value types.

One should not implement a Finalize method for managed objects because the garbage collector releases managed resources automatically. But the garbage collector has no knowledge of unmanaged resources such as window handles, or open files and streams.

One should override Finalize for a class that uses unmanaged resources, such as file handles or database connections that must be released when the managed object that uses them is discarded during garbage collection. 

In .NET Framework, finalizers are called when the program exits. But it is also possible to force garbage collection by calling GC.Collect(), but most of the time, this call should be avoided because it may create performance issues.

If your class implements a finalizer, you are guaranteeing that it will stay in memory even after the collection that should have killed it. This decreases overall GC efficiency and ensures that your program will dedicate CPU resources to cleaning up your object.

If you do implement a finalizer, you must also implement the IDisposable interface to enable explicit cleanup, and call GC.SuppressFinalize(this) in the Dispose method to remove the object from the finalization queue. As long as you call Dispose() before the next collection, then it will clean up the object properly without the need for the finalizer to run.



Garbage Collector

The .NET's garbage collector manages the allocation and de-allocation of memory for our application. Every time when we create a new object, the common language runtime allocates memory for the object from the managed heap. 

As long as address space is available in the managed heap, the runtime continues to allocate space for new objects. But, memory is not infinite, so the garbage collector must perform a collection in order to free some memory.

GC keeps track of all the objects and ensures that each object gets destroyed once. It ensures that objects, which are being referenced, are not destroyed. GC destroys the objects only when necessary. 

The garbage collector's optimizing engine determines the best time to perform a collection, based upon the allocations being made.  Garbage collector checks for objects that are no longer being used by the application, if it found an object eligible for destruction, it calls the destruction and reclaims the memory used to store the object.

Because garbage collection is non-deterministic, you do not know precisely when the garbage collector performs finalization. To release resources immediately, you can also choose to implement the dispose pattern and the IDisposable interface.

Following are some common methods of the garbage collector:

GC.Collect():

It forces an immediate garbage collection of all generations. It also freezes the main thread and associated child threads of the application. 

When the GC.Collect() method is called, the runtime performs a blocking garbage collection of all generations. All objects, regardless of how long they have been in memory, are considered for collection; however, objects that are referenced in managed code are not collected. 

It is always recommended not to use GC.Collect() unless there is a specific reason to use it. A GC typically consists of two phases i.e. Mark and Sweep phases followed by a Compaction phase



GC.SuppressFinalize(object):

This method requests that the CLR not call the finalizer for the specified object. The GC.SuppressFinalize() method tells GC that the object has been manually disposed and no more finalization is necessary for the specified object. As soon as GC.SuppressFinalize method called, the object reference is removed from the finalization/freachable queue. The goal is to prevent finalizing the object twice.

Objects that implement the IDisposable interface can call this method from the object's Dispose() implementation to prevent the garbage collector from calling Object.Finalize() on an object that does not require it. 

Usually, this is done to prevent the finalizer from releasing unmanaged resources that have already been freed by the Dispose() implementation. But if obj does not have a finalizer or the garbage collector has already signaled the finalizer thread to run the finalizer, the call to the SuppressFinalize method has no effect.

GC.ReRegisterForFinalize(object):

By default, all objects that implement finalizers are added to the list of objects that require finalization; however, an object might have already been finalized or might have disabled finalization by calling the SuppressFinalize method.

The ReRegisterForFinalize() method requests that the system call the finalizer for the specified object for which SuppressFinalize() has previously been called. This method adds the specified obj to the list of objects that request finalization before the garbage collector frees the object. 

However, calling the ReRegisterForFinalize() method does not guarantee that the garbage collector will call an object's finalizer. But a finalizer can use this method to resurrect itself or an object that it references.



Limitations of Finalizers or Destructors

    1. There is no control over the garbage collection, so the exact time when the finalizer executes is undefined. 
    2. It is not guaranteed that the finalizers of two objects run in any specific order, even if one object refers to the other.
    3. The thread on which the finalizer runs is also unspecified.
    4. The Finalize method might not run to completion or might not run at all under some exceptional circumstances like indefinite blocks or exceptional process termination.
    5. Creating reliable finalizers is often difficult, because you cannot make assumptions about the state of your application, and because unhandled system exceptions such as OutOfMemoryException and StackOverflowException terminate the finalizer.

Live Demo





Conclusion

The conclusion is that destructors are not useful in many C# programs. When our application encapsulates unmanaged resources such as Windows, Files, Network connections, we should use destructors to free those resources. But since the Garbage Collection is non-deterministic, one should use the Dispose method or using-dispose pattern. In the next article, we will learn about them in detail.

No comments:

Post a Comment

Please do not enter any HTML. JavaScript or spam link in the comment box.