C# GC.SuppressFinalize: When and Why to Call It
c# gc suppressfinalize: Learn when and why to call GC.SuppressFinalize in C#, how it interacts with finalizers and IDisposable, and common pitfalls.
c# gc suppressfinalize requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
The GC.SuppressFinalize method is one of those C# APIs that appears in almost every IDisposable implementation, yet its purpose is often misunderstood. Calling it incorrectly can lead to resource leaks or subtle bugs. This article explains what it does, why the standard dispose pattern includes it, and when you should—or should not—call it.
The Problem Finalizers Are Designed to Solve
A finalizer is a method that the garbage collector runs before reclaiming an object's memory. It is declared like a destructor in C#:
public class ResourceHolder { ~ResourceHolder() { // Clean up unmanaged resources here } }
Finalizers exist to handle unmanaged resources—file handles, network sockets, database connections—that the runtime does not manage automatically. If an object holds such a resource and the developer forgets to release it explicitly, the finalizer provides a safety net.
But finalization is not free. Objects with finalizers are placed on the finalization queue when they are created. When the garbage collector determines they are unreachable, it does not reclaim their memory immediately. Instead, it moves them to the f-reachable queue and a dedicated finalizer thread runs their finalizers. Only after that does the memory become eligible for collection. This means finalizable objects survive at least one extra garbage collection, which increases memory pressure and slows down the entire process.
What GC.SuppressFinalize Does
GC.SuppressFinalize tells the garbage collector that an object's finalizer no longer needs to be run. The method takes the object as a parameter and removes it from the finalization queue. Once called, the finalizer will not execute, even if the object is later collected.
The typical call looks like this:
public void Dispose() { GC.SuppressFinalize(this); }
Why would you want to suppress finalization? Because if your Dispose method already performs all the cleanup that the finalizer would have done, there is no reason to run the finalizer later. Suppressing it avoids the extra cost of finalization and prevents the object from lingering in memory for an additional collection cycle.
The Standard Dispose Pattern
The canonical dispose pattern combines a finalizer and Dispose so that cleanup happens whether the developer calls Dispose explicitly or relies on the finalizer as a fallback. The pattern looks like this:
public class ResourceHolder : IDisposable { private bool _disposed; private IntPtr _handle; // unmanaged resource public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { // Release managed resources } // Release unmanaged resources CloseHandle(_handle); _disposed = true; } ~ResourceHolder() { Dispose(false); } }
The Dispose(bool) overload is the core cleanup logic. When called with true, it releases both managed and unmanaged resources. When called with false—which happens only from the finalizer—it releases only unmanaged resources, because managed resources may already have been collected.
The key point is that GC.SuppressFinalize(this) is called at the end of the public Dispose method. This ensures that if the developer explicitly disposes the object, the finalizer will not run later. If the developer forgets to call Dispose, the finalizer will eventually run and clean up unmanaged resources, preventing a leak.
When to Call SuppressFinalize
You should call GC.SuppressFinalize in any Dispose method that fully handles cleanup. This is almost always the right choice for classes that implement IDisposable and have a finalizer.
But there are cases where you should not call it:
- If your class has no finalizer. If you don't define a finalizer, calling
SuppressFinalizeis harmless but unnecessary. The garbage collector already knows there is nothing to finalize. - If
Disposedoes not actually release all resources. If yourDisposemethod only partially cleans up and relies on the finalizer to finish the job, suppressing finalization would cause a leak. This is rare but can happen if you have a base class with a finalizer and a derived class that overridesDisposeincorrectly. - If you are not sure whether cleanup succeeded. Some cleanup operations can fail. If
Disposethrows an exception before releasing a resource, the finalizer should still run as a fallback. In that case, do not callSuppressFinalizeuntil you are certain all cleanup is complete.
Performance and Finalization Cost
The most direct performance impact of GC.SuppressFinalize is that it removes the object from the finalization queue. This has two benefits:
- The object is reclaimed in a single garbage collection. Without suppression, a finalizable object survives at least two collections: one to move it to the f-reachable queue and another to reclaim its memory after the finalizer runs.
- The finalizer thread is not invoked. Finalizer threads run at a lower priority and can introduce latency spikes. Suppressing finalization avoids that overhead entirely.
For objects that are frequently created and disposed, such as stream wrappers or database connections, the difference can be measurable. The exact impact depends on how often the object is collected and how busy the finalizer thread is. But in general, calling SuppressFinalize in Dispose is a low-cost way to reduce GC pressure.
It is worth noting that GC.SuppressFinalize itself is a very fast operation—it simply marks the object as not needing finalization. The cost is negligible compared to the finalization it prevents.
Common Mistakes and Edge Cases
One common mistake is calling SuppressFinalize too early. If you call it before all resources are released, the finalizer will never run, and you risk leaking unmanaged resources. Always place the call after the cleanup logic has completed successfully.
Another mistake is forgetting to call SuppressFinalize in derived classes. When you override Dispose in a derived class, you must call GC.SuppressFinalize again, even if the base class already did. This is because the derived class may have its own finalizer. The standard pattern handles this by having the derived class call the base Dispose method and then call SuppressFinalize on itself.
Here is an example of a derived class:
public class DerivedResourceHolder : ResourceHolder { private bool _disposed; protected override void Dispose(bool disposing) { if (_disposed) return; if (disposing) { // Release derived-class managed resources } // Release derived-class unmanaged resources _disposed = true; base.Dispose(disposing); } ~DerivedResourceHolder() { Dispose(false); } }
Notice that the derived class has its own finalizer and its own _disposed flag. When Dispose is called on a derived instance, it runs the derived cleanup, then calls base.Dispose(disposing), which runs the base cleanup. The public Dispose method in the base class then calls GC.SuppressFinalize(this). Because this refers to the derived instance, the derived finalizer is also suppressed. That is correct.
However, if the derived class did not have a finalizer, it would not need to call SuppressFinalize itself. The base class call would suffice. The rule is simple: call SuppressFinalize only when the class defines a finalizer.
Interaction with Inheritance and Derived Classes
When a class hierarchy involves finalizers, the interaction between Dispose and SuppressFinalize becomes more delicate. The base class's Dispose method calls GC.SuppressFinalize(this), and because this is the actual runtime type, it suppresses the finalizer of the most derived class. That finalizer, if present, would have called Dispose(false) on the derived class, which would have called the base cleanup as well. By suppressing it, you are telling the runtime that the explicit Dispose(true) path already handled everything.
This works only if every class in the hierarchy correctly implements the dispose pattern. If a derived class introduces a new unmanaged resource but forgets to override Dispose or add a finalizer, that resource may leak when the object is disposed. The base class cannot know about resources in derived classes.
A practical approach for complex hierarchies is to mark the Dispose(bool) method as protected virtual and ensure each level calls the base implementation. The public Dispose method should be non-virtual and should call GC.SuppressFinalize once. This keeps the suppression logic in one place while allowing each class to add its own cleanup.
Another edge case is when an object is resurrected in its finalizer. If a finalizer assigns this to a static or instance field, the object becomes reachable again and will not be collected. If you have already called SuppressFinalize, the finalizer will not run, so resurrection cannot happen. This is usually desirable, but it means you cannot rely on finalization for any last-minute state restoration after Dispose has been called.
Finally, consider the case where Dispose is called multiple times. The standard pattern uses a _disposed flag to ensure cleanup runs only once. GC.SuppressFinalize is idempotent, so calling it multiple times is harmless. But the flag prevents the cleanup logic from running more than once, which is important for correctness.
In summary, GC.SuppressFinalize is a simple method with a clear purpose: it tells the garbage collector that the object's finalizer is no longer needed because cleanup has already been performed. Use it in every Dispose implementation that fully handles resource release, and make sure it is called after all cleanup has succeeded. Understanding how it interacts with finalizers and inheritance will help you avoid the subtle bugs that can arise from improper use.