What is the Command pattern and how can it be implemented in C# to support undo functionality?

M.F.M Fazrin
6 min readJun 2, 2024

--

Taking Control: Demystifying the Command Pattern in C# and Building Undo Functionality

Imagine this: you’re editing a photo in a powerful image editor like Photoshop. You’ve spent the last hour meticulously tweaking colors, adjusting layers, and applying filters. But then, disaster strikes! You accidentally hit the wrong button and your masterpiece reverts to a blurry mess. Panic sets in… but wait! You remember the “Undo” command. With a sigh of relief, you click it, and your painstaking work magically reappears.

This, my friends, is the power of the Command pattern. It’s a design pattern that encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations — like saving your precious photo edits.

In this comprehensive guide, we’ll journey together through the world of the Command pattern in C#. We’ll break down its essence, unravel its benefits, and learn how to implement it step-by-step to build a robust “undo” functionality. By the end, you’ll be wielding the Command pattern like a pro, crafting elegant and maintainable code.

1. Understanding the Problem: Why the Command Pattern?

Before we dive into the specifics, let’s first understand why we even need the Command pattern. Consider a scenario where you’re building a simple text editor. You’ll likely have functions like:

public void BoldText(string text) { ... } 
public void ItalicizeText(string text) { ... }
public void UnderlineText(string text) { ... }

Now, imagine wanting to implement “undo” and “redo” functionality. You’d need to track every single operation and its inverse. This could quickly turn into a tangled mess of conditional statements, especially as your editor grows more complex.

This is where the Command pattern comes to the rescue. It provides an elegant solution by encapsulating each operation (like making text bold, italic, or underlined) as a separate object, making it easy to manage, store, and even reverse these actions.

2. Unveiling the Command Pattern: The Core Components

Let’s break down the Command pattern into its key players:

  • Command: The heart of the pattern, this interface defines the execute method that encapsulates the actual operation to be performed.
  • ConcreteCommand: These classes implement the Command interface, providing the concrete implementation of the Execute method for specific commands (e.g., BoldCommand, ItalicCommand).
  • Invoker: This component (often a button or menu item) holds and invokes the Command object. It knows how to execute a command but doesn’t know what the command actually does.
  • Receiver: The object on which the command is performed. For instance, in a text editor, the Receiver would be the document object.
  • Client: This component creates ConcreteCommand objects and sets their Receiver. It’s responsible for assembling the command object and handing it off to the Invoker.

3. Building our Example: A Simple Text Editor with Undo/Redo

Let’s solidify our understanding by building a simple text editor that supports bolding and undoing/redoing the last action.

Step 1: Defining the Command Interface

public interface ITextCommand
{
void Execute();
void Undo();
}

This interface ensures that all our concrete commands will have methods to execute and undo their actions.

Step 2: Implementing Concrete Commands

public class BoldCommand : ITextCommand
{
private readonly TextEditor _receiver;
private bool _wasBold;

public BoldCommand(TextEditor receiver)
{
_receiver = receiver;
}

public void Execute()
{
_wasBold = _receiver.IsBold;
_receiver.ToggleBold();
}

public void Undo()
{
if (_wasBold != _receiver.IsBold)
{
_receiver.ToggleBold();
}
}
}

This BoldCommand toggles the boldness of the text and stores the previous state to enable the Undo functionality.

Step 3: Creating the Receiver (Text Editor)

public class TextEditor
{
public string Text { get; set; } = "";
public bool IsBold { get; private set; }

public void ToggleBold()
{
IsBold = !IsBold;
}
}

This simplified TextEditor class manages the text and its boldness.

Step 4: Implementing the Invoker

public class EditorInvoker
{
private ITextCommand? _lastCommand;

public void ExecuteCommand(ITextCommand command)
{
command.Execute();
_lastCommand = command;
}

public void UndoLastCommand()
{
if (_lastCommand != null)
{
_lastCommand.Undo();
_lastCommand = null;
}
}
}

This EditorInvoker stores the last executed command and provides methods to execute new commands and undo the last one.

Step 5: Putting It All Together (Client Code)

// Create the receiver and invoker
var textEditor = new TextEditor { Text = "Hello, World!" };
var invoker = new EditorInvoker();

// Create and execute the BoldCommand
var boldCommand = new BoldCommand(textEditor);
invoker.ExecuteCommand(boldCommand);

// Undo the last command
invoker.UndoLastCommand();

Here, we create the necessary objects, execute the BoldCommand, and then undo it using the EditorInvoker.

4. Enhancing Our Editor: Adding More Commands and Redo Functionality

With the basic structure in place, extending our editor is a breeze. Let’s add a command to italicize text and implement redo functionality:

Step 6: Adding a New Command (ItalicizeCommand)

public class ItalicizeCommand : ITextCommand
{
private readonly TextEditor _receiver;
private bool _wasItalic;

public ItalicizeCommand(TextEditor receiver)
{
_receiver = receiver;
}

public void Execute()
{
_wasItalic = _receiver.IsItalic;
_receiver.ToggleItalic();
}

public void Undo()
{
if (_wasItalic != _receiver.IsItalic)
{
_receiver.ToggleItalic();
}
}
}

We create a new ItalicizeCommand analogous to the BoldCommand.

Step 7: Extending the Receiver (TextEditor)

public class TextEditor
{
// ... existing code ...

public bool IsItalic { get; private set; }
public void ToggleItalic()
{
IsItalic = !IsItalic;
}
}

We add the necessary property and method to handle the italic style in our TextEditor.

Step 8: Implementing Redo in the Invoker

public class EditorInvoker
{
private Stack<ITextCommand> _undoStack = new Stack<ITextCommand>();
private Stack<ITextCommand> _redoStack = new Stack<ITextCommand>();

public void ExecuteCommand(ITextCommand command)
{
command.Execute();
_undoStack.Push(command);
_redoStack.Clear(); // Clear redo stack after new execution
}

public void UndoLastCommand()
{
if (_undoStack.Count > 0)
{
ITextCommand command = _undoStack.Pop();
command.Undo();
_redoStack.Push(command);
}
}

public void RedoLastCommand()
{
if (_redoStack.Count > 0)
{
ITextCommand command = _redoStack.Pop();
command.Execute();
_undoStack.Push(command);
}
}
}

We enhance the EditorInvoker to use two stacks: one for undo and one for redo. This allows us to store a history of commands and navigate back and forth.

Step 9: Testing our Enhanced Editor

// ... (previous code) ... 

// Create and execute the ItalicizeCommand
var italicCommand = new ItalicizeCommand(textEditor);
invoker.ExecuteCommand(italicCommand);
// Undo the last command
invoker.UndoLastCommand();
// Redo the last undone command
invoker.RedoLastCommand();

We can now bold, italicize, undo, and redo actions in our simple text editor.

5. Real-World Applications: Where the Command Pattern Shines

The Command pattern’s versatility makes it a valuable tool in various scenarios beyond text editors:

  • GUI Applications: Handling user interactions with buttons, menu items, and other controls.
  • Transaction Management: Encapsulating database transactions to ensure atomicity (either all operations are executed or none).
  • Macro Recording: Recording a sequence of user actions and then replaying them later.
  • Workflow Engines: Representing individual steps in a workflow as commands, allowing for flexibility and control over the execution flow.

6. Advantages of Embracing the Command Pattern

  • Decoupling: Clear separation between the invoker of a command and the receiver, enhancing flexibility and reusability.
  • Encapsulation: Each command encapsulates a specific operation, improving code organization and maintainability.
  • Undo/Redo: Provides a straightforward mechanism for implementing undo and redo functionality, improving user experience.
  • Extensibility: Adding new commands is simple and doesn’t require modifying existing code, adhering to the Open/Closed principle.

7. Potential Drawbacks to Consider

  • Complexity: Can increase the number of classes in a project, potentially adding complexity if overused for very simple operations.
  • Overhead: Introducing a slight overhead due to object creation for each command, although this is usually negligible in most applications.

Conclusion: Mastering the Command Pattern

The Command pattern is a powerful tool for encapsulating requests and providing a structured approach to managing operations in your C# applications. Its ability to decouple invocation from execution, facilitate undo/redo, and enhance code organization makes it invaluable in various software development scenarios. By understanding its core principles and practicing its implementation, you’ll elevate your coding skills and build more robust and maintainable software.

Remember, the best way to truly master the Command pattern is to put it into practice. Experiment with the provided example, extend it with new commands and features, and explore its application in your own projects. The more you work with it, the more you’ll appreciate its elegance and power. Happy coding!

--

--

M.F.M Fazrin
M.F.M Fazrin

Written by M.F.M Fazrin

Senior Software Development Specialist @ Primary Health Care Corporation (Qatar)

No responses yet