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 on August 20, 2008