FinalizersImplicit 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 usewhen 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 instancealthough 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:
// Some Code
//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()
//Necessary cleanup code
Note that the generated code above calls the base.Finalize
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-deterministicin 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 resourcesand 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()