C# > Memory Management > Garbage Collection > Finalizers
Finalizer Example: Resource Cleanup
This code demonstrates the use of a finalizer to release unmanaged resources. Finalizers are used when an object holds resources that the garbage collector (GC) doesn't manage directly (e.g., file handles, network connections). It's crucial to understand that finalizers add complexity and should be used sparingly, primarily when absolutely necessary to clean up unmanaged resources.
Concepts Behind Finalizers
Finalizers, also known as destructors, are methods in a class that are automatically called by the garbage collector when an object is about to be reclaimed. They provide a last-chance opportunity for an object to release unmanaged resources. However, finalizers should not be relied upon for deterministic cleanup. The GC decides when to run them, which might be much later than expected. Excessive reliance on finalizers can negatively impact performance.
Example Code: File Resource Cleanup
This code creates a `FileResource` class that wraps a `FileStream`. The finalizer `~FileResource()` calls the `Dispose(false)` method. The `Dispose()` method, part of the `IDisposable` interface, is also implemented to allow for explicit cleanup. `GC.SuppressFinalize(this)` is called within the `Dispose()` method to prevent the finalizer from being called if the object has already been disposed of. The `Dispose(bool disposing)` method handles both managed and unmanaged resource disposal. Note the commented out section where the FileResource is not disposed. This would trigger the Finalizer.
using System;
using System.IO;
public class FileResource : IDisposable
{
private FileStream _fileStream;
private bool _disposed = false;
public FileResource(string filePath)
{
_fileStream = new FileStream(filePath, FileMode.OpenOrCreate);
Console.WriteLine("FileResource created for: " + filePath);
}
// Finalizer
~FileResource()
{
// Finalize only if Dispose() wasn't called.
Dispose(false);
Console.WriteLine("Finalizer called for FileResource.");
}
// Implement IDisposable.
public void Dispose()
{
Dispose(true);
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SuppressFinalize to
// take this object off the finalization queue
GC.SuppressFinalize(this);
}
// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the finalizer and you should not reference
// other objects except System.Int32 or System.Boolean as they may also
// be finalizing.
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// Dispose managed resources.
if (_fileStream != null)
{
_fileStream.Close();
_fileStream.Dispose();
_fileStream = null;
Console.WriteLine("Managed resources disposed.");
}
}
// Dispose unmanaged resources.
// In this example, _fileStream also encapsulates unmanaged resources,
// which are freed in the 'if (disposing)' block.
_disposed = true;
}
}
public void WriteToFile(string text)
{
if (_disposed)
{
throw new ObjectDisposedException("FileResource", "Cannot write to a disposed FileResource.");
}
byte[] bytes = System.Text.Encoding.ASCII.GetBytes(text);
_fileStream.Write(bytes, 0, bytes.Length);
}
}
public class Example
{
public static void Main(string[] args)
{
FileResource fileResource = new FileResource("example.txt");
fileResource.WriteToFile("Hello, Finalizer!");
fileResource.Dispose(); // Explicit disposal
//FileResource fileResource2 = new FileResource("example2.txt");
//fileResource2.WriteToFile("No Dispose called here.");
// No explicit disposal. Finalizer will (eventually) be called by the GC.
Console.WriteLine("End of Main.");
Console.ReadKey();
}
}
Real-Life Use Case Section
Finalizers are typically used in scenarios where your class interacts with unmanaged resources such as file handles, network sockets, or COM objects. For example, if you're creating a wrapper around a native library written in C++, you'll likely need a finalizer to ensure that the resources allocated by the native library are properly released when the wrapper object is no longer needed. Another use case is cleaning up database connections or releasing memory allocated directly from the operating system. However, in most modern .NET applications, reliance on finalizers should be minimized by utilizing RAII (Resource Acquisition Is Initialization) patterns and the `using` statement, which guarantees deterministic disposal.
Best Practices
Interview Tip
A common interview question is to explain the difference between Dispose() and Finalize(). Dispose() is called explicitly by the programmer to release resources deterministically. Finalize() is called by the garbage collector to release resources non-deterministically. Another good interview tip is to mention that finalizers add overhead to the garbage collection process and can prevent objects from being collected promptly.
When to use them
Use finalizers only when your class directly manages unmanaged resources. In most cases, managed resources can be cleaned up effectively using the `using` statement and the `IDisposable` interface. Consider the performance impact of finalizers, as they can increase the lifespan of objects and slow down garbage collection. Avoid using finalizers to compensate for poor design or lack of proper resource management.
Memory footprint
Objects with finalizers have a larger memory footprint than objects without finalizers. The garbage collector needs to track objects with finalizers separately, adding to the overhead. When an object with a finalizer is garbage collected, it is initially placed on the finalization queue. A dedicated finalizer thread then processes the finalization queue, executing the finalizers of objects. Only after the finalizer has run can the object's memory be reclaimed. Therefore, objects with finalizers stay in memory longer, potentially increasing memory pressure.
Alternatives
The primary alternative to finalizers is implementing the `IDisposable` interface and using the `using` statement. This pattern allows for deterministic cleanup of resources. Another alternative, especially for scenarios where unmanaged resources are accessed frequently, is to encapsulate the unmanaged resource within a safe handle class derived from `SafeHandle`. Safe handles automatically handle resource cleanup using the operating system's resource management mechanisms and integrate seamlessly with the garbage collector.
Pros
Cons
FAQ
-
What happens if an exception is thrown in a finalizer?
If an exception is thrown in a finalizer, the .NET runtime will catch the exception, log it, and terminate the finalizer. The application will continue to run, but the object's resources might not be properly released. Therefore, it's essential to handle exceptions carefully within finalizers and avoid throwing exceptions whenever possible. -
When does the garbage collector call a finalizer?
The garbage collector calls a finalizer when it determines that an object is no longer reachable and is about to be reclaimed. However, the exact timing of finalizer execution is non-deterministic and depends on various factors, including memory pressure and the garbage collection algorithm. Objects that have finalizers are collected in a different garbage collection generation than objects without finalizers.