Trick-hot command pattern in C#

I’m working on some code that needs an undo feature, so naturally I turned to the command pattern.  I pulled out GoF to glance at their naming conventions and started to type, all the while thinking “there has to be a better way to do this.” Here’s what I think is a better way.

Recall that the command pattern specifies an abstract “Command” type with an Exceute method. This is only needed in languages without first-class functions: C# gives us delegates, which are pretty much equivalent in this case.  So we’ll skip the custom class and just use them.

But the extended, more useful form of the pattern also has an UnExecute method, used for undo.  This lets you push the commands onto a stack after executing them.  Undo is then simply a matter of popping the most recent command off the stack and UnExecute()ing it.  The command instance retains enough state information to undo what it had previously done.

We can do all that through the clever application of closures.  I’ve chosen to implement the commands as static methods, but they could also be implemented as regular methods, even virtual if you’d like to be able to route around to different implementations. Here’s a fictitious example:

static class Commands
    public static Action HAlign(Item stationary, Item movable)
        int oldX = movable.X;
        movable.X = stationary.X;

        return delegate()
            movable.X = oldX;

Executing the command is just a method call, and enabling undo is as simple as pushing the returned Action (just a delegate with no parameters or return type) onto an undo stack. (Stack<Action> will do nicely) The returned delegate captures, via closure, all the state in needs for undo to work properly.

That’s it! There are a few small land-mines in the details of how closures work (especially w.r.t. non-local or iteration variables), but the general technique is very simple.

2 thoughts on “Trick-hot command pattern in C#

Leave a Reply

Please log in using one of these methods to post your comment: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s