« Image Format Error when Loading from a Stream | Main | Unsubscribing from C# events »
August 20, 2008
Leverage using blocks with Scope
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 at August 20, 2008 9:52 AM
Trackback Pings
TrackBack URL for this entry:
http://blog.logos.com/mt-cgi/mt-tb.cgi/232
Comments
Do you guys read Ayende's blog? If not, you should check it out. He had a post very similar to this some time back: http://www.ayende.com/Blog/archive/8065.aspx
Posted by: Rob at August 20, 2008 5:20 PM
Thanks for the link! I do read Ayende from time to time, but I didn't remember that post.
Posted by: Ed Ball at August 21, 2008 8:39 AM
Why...
is Scope sealed?
Its constructor private/why use a static Create()?
Posted by: Lloyd at August 26, 2008 6:40 AM
Scope is sealed because we prefer to seal classes that aren't designed for inheritance.
We use a static Create method because we thought it looked nicer back when we first designed the class. I'm pretty ambivalent about it though; there's no reason it couldn't have a public constructor instead.
Thanks for the questions!
Posted by: Ed Ball at August 26, 2008 10:17 AM