The simplest and most reliable implementation of the Dispose design pattern
It would seem that this template is not just simple, but very simple; it’s detailed in more than one well-known book.
However, until now, even within the framework of one project, it can often be implemented in different ways, creating a zoo from bicycles, crutches and leaks.
I want to share my implementation method, which is based on minimizing the invention of bicycles, minimizing the amount of code and increasing its expressiveness and transparency.
Preconditions
No mixing of managed and unmanaged resources
I never implement it myself and I do not advise colleagues to use the possession of managed and unmanaged resources in one class.
Thus, one class can:
- Do not own resources at all
- Own one unmanaged resource, that is, simply convert it to a managed
- Own one or more managed resources
Inheritance of implementations is undesirable
I do not use class inheritance unless absolutely necessary; the proposed implementation assumes an instance of a sealed class as the object owner of the resources.
This does not mean that it cannot be modified to support inheritance.
Wrappers for unmanaged resources implemented using Janitor.Fody
Update: in the comments it is rightly pointed out that for this purpose it is better to use classes inherited from CriticalFinalizerObject and SafeHandle .
What I used
This plugin for Fody - a free tool for modifying assembly code after compilation - will allow you to not manually write out a thousand and one implementation details necessary for the correct release of resources.
Your code (example from the documentation ):
Post Processing Result:
Your code (example from the documentation ):
public class Sample : IDisposable
{
IntPtr handle;
public Sample()
{
handle = new IntPtr();
}
public void Method()
{
//Some code
}
public void Dispose()
{
//must be empty
}
void DisposeUnmanaged()
{
CloseHandle(handle);
handle = IntPtr.Zero;
}
[DllImport("kernel32.dll", SetLastError=true)]
static extern bool CloseHandle(IntPtr hObject);
}
Post Processing Result:
public class Sample : IDisposable
{
IntPtr handle;
volatile int disposeSignaled;
bool disposed;
public Sample()
{
handle = new IntPtr();
}
void DisposeUnmanaged()
{
CloseHandle(handle);
handle = IntPtr.Zero;
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern Boolean CloseHandle(IntPtr handle);
public void Method()
{
ThrowIfDisposed();
//Some code
}
void ThrowIfDisposed()
{
if (disposed)
{
throw new ObjectDisposedException("TemplateClass");
}
}
public void Dispose()
{
if (Interlocked.Exchange(ref disposeSignaled, 1) != 0)
{
return;
}
DisposeUnmanaged();
GC.SuppressFinalize(this);
disposed = true;
}
~Sample()
{
Dispose();
}
}
Now we can move on to the most common case, for which this article was written.
Implement a Dispose Design Pattern for Managed Resources
Training
First, we need the CompositeDisposable class from the Reactive Extensions library .
It is necessary to add a small extension method to it:
public static void Add(this CompositeDisposable litetime, Action action)
{
lifetime.Add(Disposable.Create(action));
}
Adding a cleanup field
private readonly CompositeDisposable lifetime = new CompositeDisposable();
Implementing the Dispose Method
public void Dispose()
{
lifetime.Dispose();
}
Nothing more and never need to be added to this method.
Clear explicitly constructed resources
Simply add the simplest code directly to the resource allocation location.
It was:
myOwnResourceField = new Resource();
// И где-то при очистке
if (myOwnResourceField != null)
{
myOwnResourceField.Dispose();
myOwnResourceField = null;
}
It became:
lifetime.Add(myOwnedResourceField = new Resource());
Unsubscribing from events
It was:
sender.Event += Handler;
// И где-то при очистке
sender.Event -= Handler
It became:
sender.Event += Handler;
lifetime.Add(() => sender.Event -= Handler);
Unsubscribing from IObservable
It was:
subscription = observable.Subscribe(Handler);
// И где-то при очистке
if (subscription != null)
{
subscription.Dispose();
subscription = null;
}
It became:
lifetime.Add(observable.Subscribe(Handler));
Performing arbitrary cleaning actions
CreateAction();
lifetime.Add(() => DisposeAction());
Checking the state of an object
if (lifetime.IsDisposed)
conclusions
The proposed method:
- universal: guaranteed to cover any managed resources, even such as "when cleaning, run the following code"
- expressive: additional code is small in volume
- familiar: an ordinary class from a very popular library is used, which, in addition, if necessary, is easy to write yourself
- transparent: the cleaning code of each resource is located close to the capture code, most potential leaks will be immediately noticed when reviewing
- degrades performance: adds “memory traffic” by creating new objects
- does not affect the security of using an already "dead" object: own resources will be cleared only once, but any checks with the throwing of an ObjectDisposedException must be done manually
I would be glad if the described method is useful to readers.