« 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

Post a comment




(you may use HTML tags for style)