Making sure that cleanup code is called even in the face of an exception is usually the job of try-finally blocks.
public class Command
{
// ...
public void Execute()
{
try
{
IsWorking = true;
ExecuteCore();
}
finally
{
IsWorking = false;
}
}
}
The standard way to provide cleanup code for a class is to implement IDisposable, which provides a Dispose method that does the cleanup. Since writing try-finally blocks properly is a pain, the using statement in C# makes this much easier. But what about cleanup code that isn’t in a Dispose method? Can the using statement help us with that? Of course; all you need is a specialized type that implements IDisposable. For efficiency, you can even use a struct.
public void Execute()
{
using (new IsWorkingScope(this))
{
IsWorking = true;
ExecuteCore();
}
}
private struct IsWorkingScope : IDisposable
{
public IsWorkingScope(Command command)
{
m_command = command;
}
public void Dispose()
{
m_command.IsWorking = false;
}
readonly Command m_command;
}
Of course, defining a specialized type isn’t very convenient. Sometimes you want to define your cleanup code as a lambda or an anonymous delegate. For this, we use the Scope class.
public void Execute()
{
using (Scope.Create(() => IsWorking = false))
{
IsWorking = true;
ExecuteCore();
}
}
The implementation of Scope is very straightforward; the following is the minimal implementation that we started with. We decided to use a static Create method because we thought it looked better than using a constructor.
public sealed class Scope : IDisposable
{
public static Scope Create(Action fnDispose)
{
return new Scope(fnDispose);
}
public void Dispose()
{
if (m_fnDispose != null)
{
m_fnDispose();
m_fnDispose = null;
}
}
private Scope(Action fnDispose)
{
m_fnDispose = fnDispose;
}
Action m_fnDispose;
}
We added a Cancel method when we realized that it is sometimes useful to conditionally not execute the cleanup code.
public void Cancel()
{
m_fnDispose = null;
}
The Transfer method is a useful way of returning a Scope that would otherwise be disposed by an enclosed using block.
public Scope Transfer()
{
Scope scope = new Scope(m_fnDispose);
m_fnDispose = null;
return scope;
}
Finally, the Empty static field makes it easy to return a Scope that does nothing when disposed.
public static readonly Scope Empty = new Scope(null);
There are those that would consider Scope to be a misuse of the dispose pattern, but we have found it extremely useful.
Posted by Ed Ball on August 20, 2008