Implement IDisposable via Composition - Yortw/Yort.Trashy GitHub Wiki
Benefits
While using DisposeAssistant to implement the dispose pattern is more work than using the base classes, it is your only option if you need to inherit from something else, and still provides these benefits;
- Reduces the amount of boiler plate required to implement IDisposable.
- Helps to implement the dispose pattern correctly, including calling SuppressFinalize, ensuring unmanaged resources are disposed even if an error occurs during managed type disposal (including ThreadAbortException) etc.
- Helps ensure "idempotent" disposal, i.e calling Dispose only executes the dispose logic once, subsequent calls to Dispose do nothing
- Helps with thread-safety. The idempotency mentioned above remains true even if multiple threads try to simultaneously dispose an object. The first call will dispose the object, the others will wait for the first thread to finish then return without doing anything. Additionally you can use ObtainBusyToken or EnterBusy/ExitBusy to prevent calls to Dispose from executing while threads are calling other methods on the object.
- Integrated tracking of disposable types.
Implementation
public class MyDisposable : IIsDisposed
{
private int _DisposedState;
private int _BusyCount;
public MyDisposable()
{
// Optional but recommended. Enables integrated tracking of instances of this type.
DisposableTracker.Register(this);
}
public bool IsDisposed { get { return DisposeAssistant.IsDisposed(_DisposedState); } }
public void Dispose()
{
DisposeAssistant.Dispose(this, ref _DisposedState, ref _BusyCount, () => this.Dispose(true));
}
public void Dispose(bool disposing)
{
try
{
if (disposing)
{
// Optional but recommended. Enables integrated tracking of instances of this type.
DisposableTracker.Unregister(this);
//TODO: Dispose any managed objects/resources here.
//If your class is unsealed this should call a
//virtual method that can be overridden by
//derived types to handle their own resources.
}
}
finally
{
//TODO: Dispose any unmanaged resources here.
//If your class is unsealed this should call
//a virtual method to dispose unmanaged resources,
//so derived types can override it to handle
//their own resources.
}
}
// Only required if you have or expect your derived types to have
// unmanaged resources.
~MyDisposable()
{
Dispose(false);
}
}
Extras
Once you've inherited from one of the base classes there are a few methods that can help you with writing your other methods in the class.
Throw ObjectDisposedException When Disposed
You can call ThrowIfDisposed() at the top of your methods to check if the object is disposed and throw an ObjectDisposedException with the correct type information provided.
public void DoSomething()
{
DisposeAssistant.ThrowIfDisposed(this, _DisposedState, _BusyCount);
// Your logic here.
}
Prevent Disposal While a Method Executes
To prevent another thread from disposing your object while your method is executing you can either use ObtainBusyToken (easiest) or Enter/ExitBusy.
Note, ObtainBusyToken and EnterBusy both call ThrowIfDisposed for you, so no need to do both.
Using a token;
public void DoSomething()
{
DisposeAssistant.EnterBusy(this, _DisposedState, ref _BusyCount); //Disposal is blocked one this line executes.
using (var busyToken = DisposeAssistant.ObtainBusyToken(this, _DisposedState, ref _BusyCount, this.ExitBusy))
{
// Your logic goes here.
} // Object allows disposable from this point on.
}
Using enter/exit busy;
public void DoSomething()
{
DisposeAssistant.EnterBusy(this, _DisposedState, ref _BusyCount); //Disposal is blocked one this line executes.
try
{
// Your logic goes here.
}
finally
{
DisposeAssistant.ExitBusy(this, _DisposedState, ref _BusyCount); // Object allows disposable from this point on.
}
}