A finalizer is a special, non-deterministic method invoked by the .NET Garbage Collector (GC) to perform cleanup operations before an object’s memory is reclaimed. It serves as a fallback mechanism to ensure unmanaged resources are released if explicit disposal does not occur.Documentation Index
Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt
Use this file to discover all available pages before exploring further.
Syntax and Compilation
A finalizer is declared using a tilde (~) followed by the class name. It cannot have access modifiers, takes no parameters, and cannot be overloaded. Finalizers are restricted to reference types (class or record class); they cannot be defined on value types (struct).
System.Object.Finalize() method. It automatically wraps the finalizer body in a try-finally block to ensure the base class finalizer is always called.
Because explicitly overriding Finalize() or calling base.Finalize() is strictly forbidden in C# and results in compiler errors, the compiler generates Intermediate Language (IL) that is logically equivalent to the following pseudo-code:
Execution Mechanics and the CLR
Finalizer execution is entirely managed by the Common Language Runtime (CLR) and is non-deterministic, meaning you cannot predict exactly when it will run. The lifecycle of an object with a finalizer involves specific internal CLR data structures:- Allocation and the Finalization Queue: When an object implementing a finalizer is instantiated, the CLR allocates the object on the managed heap and places a pointer to it in an internal structure called the Finalization Queue.
- First GC Pass (Marking): When the GC performs a collection, it identifies unreachable objects. If an unreachable object is found in the Finalization Queue, its memory is not immediately reclaimed.
- The F-Reachable Queue: The GC moves the object’s pointer from the Finalization Queue to the F-Reachable (Freachable) Queue. At this point, the object is temporarily “resurrected” because the F-Reachable queue acts as a GC root, holding a strong reference to it.
- Finalizer Thread Execution: A dedicated, high-priority CLR background thread monitors the F-Reachable queue. When populated, this thread dequeues the objects and executes their finalizer methods sequentially.
- Second GC Pass (Reclamation): Once the finalizer completes, the object is removed from the F-Reachable queue. It is now truly unreachable. During the next garbage collection cycle that covers the object’s generation, its memory is finally reclaimed.
Critical Restrictions and Safety
Writing finalizers requires strict adherence to safety rules due to the environment in which the finalizer thread operates:- No Managed Object References: Finalizers must never reference or call methods on other managed objects. When an object is moved to the F-Reachable queue, that queue acts as a GC root. This keeps the finalizable object and its entire referenced object graph alive in memory. Therefore, the memory of referenced managed objects is not reclaimed at this stage. However, because the GC does not guarantee the order of finalization, the finalizers of those referenced objects may have already executed, leaving them in an invalid or disposed state. Accessing them can lead to unpredictable behavior or
ObjectDisposedException. - Fatal Exceptions: Unhandled exceptions thrown within a finalizer will immediately terminate the application process. They bypass standard application exception handling mechanisms. All logic within a finalizer must be strictly defensive and fail-safe.
Performance Implications
Because objects with finalizers require at least two garbage collection cycles to be fully destroyed, they inherently incur a performance penalty. During the first GC pass, the object survives collection and is consequently promoted to the next GC generation (e.g., from Generation 0 to Generation 1). This promotion extends the object’s lifespan significantly, as higher generations are collected much less frequently. Furthermore, the finalizer thread operates sequentially; a blocked or infinitely looping finalizer will halt the execution of all subsequent finalizers in the queue, potentially leading to memory leaks and application crashes.Modern Alternative: SafeHandle
In modern .NET development, writing custom finalizers is largely obsolete. Microsoft strongly recommends usingSystem.Runtime.InteropServices.SafeHandle to wrap unmanaged resources instead of implementing custom finalizers.
SafeHandle provides guaranteed execution and protects against asynchronous exceptions (such as thread aborts or out-of-memory conditions) that could otherwise interrupt finalization and corrupt state. By delegating unmanaged resource management to a SafeHandle, developers can avoid the complexities, performance penalties, and safety risks associated with custom finalizers.
The Standard Dispose Pattern
When a custom finalizer is strictly necessary, it is idiomatically implemented alongside theIDisposable interface using the standard Dispose(bool disposing) pattern.
This pattern provides a centralized mechanism to safely separate managed resource cleanup (which is safe only during explicit disposal) from unmanaged resource cleanup (which is safe during both explicit disposal and finalization). When explicit disposal succeeds, GC.SuppressFinalize is called to remove the object from the Finalization Queue, bypassing the F-Reachable queue entirely and avoiding the finalization performance penalty.
Master C# with Deep Grasping Methodology!Learn More





