dnoll
posted this
30 November 2016
- Last edited 30 November 2016
Implement the Dispose Pattern
Objects that have unmanaged resources - or managed resources that need to be cleaned up - often implement the IDisposable interface. When a developer creates a Disposable object in a using block, that object's Dispose method is called when the code within the using block is finished executing. For objects that need to be cleaned up, implementing IDisposble is the recommended course of action.
However, it is sometimes necessary to implement your own Finalizer/Destructor in a managed object. If your object contains a custom Finalizer, and implements IDisposable, you must implement the "Dispose Pattern", which extends the functionality of the default IDisposable functionality. Without using this pattern, it is likely your system will leak memory over time as objects are added to the Finalizer queue, but never finalized.
Below is the Dispose Pattern recommended by Microsoft (the class name in this case is "CustomObject"):
protected virtual void Dispose(bool disposing) {
// Release unmanaged memory
if (disposing) {
// Release other objects
}
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
~ CustomObject() {
Dispose(false);
}
There are a couple important things to notice here.
First, in addition to the Dispose method required by the IDisposable interface, a second Dispose method is added that accepts a boolean for "disposing". This allows us to have two entry points for our clean-up code (Dispose(), or ~CustomObject()) that both lead to the same place. It is important to remember that:
- When calling the second Dispose method from the original method, you must pass in "True" as the boolean parameter, to signal that this operation was triggered while the object was being disposed.
- When calling the second Dispose method from the Finalizer, you must pass "False" as the boolean parameter, to signal that this operation was triggered by the destructor and not a call to Dispose().
It's up to Developers how they want to handle each case, but being able to identify the caller is vital to making the right choices about unmanaged resources when cleaning up an object.
Second, note that Dipose(bool disposing) is a protected method that is only ever called from the Finalizer and Dispose(). Dispose(bool disposing) should be considered an implementation detail, and all objects referencing this CustomObject from the outside should call Dispose() when looking to release the object.
Finally, the public Dispose() method must call GC.SuppressFinalize(this) after returning from its call to Dispose(bool disposing). From a memory management perspective, this is the most important item. If an object implements both IDisposable and a custom Finalizer, the object has the potential to remain on the Finalizer queue for the rest of the programs runtime, essentially leaking memory everytime a new object of that type is created. Calling SuppressFinalize notifies the Finalizer queue that this object no longer needs to be cleaned up, and will allow it to drop off of the queue.
During development and troubleshooting, you can add breakpoints to these methods to diagnose issues with memory management. If you notice that your breakpoints are not being hit when you are expecting your object to be cleaned up, you may have a memory leak somewhere in your program that is preventing the Garbage Collector from working with your object.