« 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://ancientblogs.logos.com/mt-cgi/mt-tb.cgi/232