Skip to main content

.NET Command Pattern

Introduction

The Command Pattern is one of the behavioral design patterns that turns a request into a stand-alone object containing all information about the request. This transformation allows you to pass requests as method arguments, delay or queue a request's execution, and support undoable operations.

In .NET desktop applications, the Command Pattern proves particularly useful when implementing:

  • User interface elements like menu items and buttons
  • Multi-level undo/redo mechanisms
  • Task scheduling and queuing systems
  • Transactional behavior

By the end of this tutorial, you'll understand how to implement the Command Pattern in your .NET applications and leverage its power to create more maintainable and flexible code.

Understanding the Command Pattern Structure

The Command Pattern involves several components:

  1. Command: An interface or abstract class with an Execute() method.
  2. Concrete Command: Implements the Command interface and defines the binding between a Receiver object and an action.
  3. Invoker: Asks the command to carry out the request.
  4. Receiver: Knows how to perform the operations.
  5. Client: Creates a ConcreteCommand and sets its receiver.

Basic Implementation

Let's start with a simple implementation of the Command Pattern in C#:

csharp
// Command interface
public interface ICommand
{
void Execute();
}

// Receiver
public class Light
{
public void TurnOn()
{
Console.WriteLine("Light is now ON");
}

public void TurnOff()
{
Console.WriteLine("Light is now OFF");
}
}

// Concrete Command for turning the light on
public class LightOnCommand : ICommand
{
private Light _light;

public LightOnCommand(Light light)
{
_light = light;
}

public void Execute()
{
_light.TurnOn();
}
}

// Concrete Command for turning the light off
public class LightOffCommand : ICommand
{
private Light _light;

public LightOffCommand(Light light)
{
_light = light;
}

public void Execute()
{
_light.TurnOff();
}
}

// Invoker
public class RemoteControl
{
private ICommand _command;

public void SetCommand(ICommand command)
{
_command = command;
}

public void PressButton()
{
_command.Execute();
}
}

Now, let's see how to use this pattern:

csharp
class Program
{
static void Main(string[] args)
{
// Create the receiver
Light livingRoomLight = new Light();

// Create commands
ICommand lightOn = new LightOnCommand(livingRoomLight);
ICommand lightOff = new LightOffCommand(livingRoomLight);

// Create the invoker
RemoteControl remote = new RemoteControl();

// Turn on the light
remote.SetCommand(lightOn);
remote.PressButton(); // Output: Light is now ON

// Turn off the light
remote.SetCommand(lightOff);
remote.PressButton(); // Output: Light is now OFF

Console.ReadKey();
}
}

Adding Undo Functionality

One of the major benefits of the Command Pattern is the ability to implement undo operations. Let's extend our example to support this:

csharp
// Enhanced Command interface with undo capability
public interface ICommand
{
void Execute();
void Undo();
}

// Enhanced Concrete Command with undo capability
public class LightOnCommand : ICommand
{
private Light _light;

public LightOnCommand(Light light)
{
_light = light;
}

public void Execute()
{
_light.TurnOn();
}

public void Undo()
{
_light.TurnOff();
}
}

// Enhanced Concrete Command with undo capability
public class LightOffCommand : ICommand
{
private Light _light;

public LightOffCommand(Light light)
{
_light = light;
}

public void Execute()
{
_light.TurnOff();
}

public void Undo()
{
_light.TurnOn();
}
}

// Enhanced Invoker with undo capability
public class RemoteControl
{
private ICommand _command;
private Stack<ICommand> _commandHistory = new Stack<ICommand>();

public void SetCommand(ICommand command)
{
_command = command;
}

public void PressButton()
{
_command.Execute();
_commandHistory.Push(_command);
}

public void PressUndoButton()
{
if (_commandHistory.Count > 0)
{
ICommand lastCommand = _commandHistory.Pop();
lastCommand.Undo();
}
}
}

Usage with undo:

csharp
class Program
{
static void Main(string[] args)
{
// Create the receiver
Light livingRoomLight = new Light();

// Create commands
ICommand lightOn = new LightOnCommand(livingRoomLight);
ICommand lightOff = new LightOffCommand(livingRoomLight);

// Create the invoker
RemoteControl remote = new RemoteControl();

// Turn on the light
remote.SetCommand(lightOn);
remote.PressButton(); // Output: Light is now ON

// Turn off the light
remote.SetCommand(lightOff);
remote.PressButton(); // Output: Light is now OFF

// Undo the last command (turn light back on)
remote.PressUndoButton(); // Output: Light is now ON

Console.ReadKey();
}
}

Practical Example: Text Editor Commands

Let's implement a more practical example - a simplified text editor with undo/redo functionality:

csharp
// The Document class (Receiver)
public class Document
{
private StringBuilder _content = new StringBuilder();
private string _name;

public Document(string name)
{
_name = name;
}

public string Content => _content.ToString();

public void Append(string text)
{
_content.Append(text);
Console.WriteLine($"Added '{text}' to document {_name}");
Console.WriteLine($"Current content: {Content}");
}

public void Delete(int length)
{
if (_content.Length >= length)
{
string deletedText = _content.ToString().Substring(_content.Length - length);
_content.Remove(_content.Length - length, length);
Console.WriteLine($"Deleted '{deletedText}' from document {_name}");
Console.WriteLine($"Current content: {Content}");
}
}
}

// Command interface
public interface ICommand
{
void Execute();
void Undo();
string Description { get; }
}

// Concrete command for adding text
public class AppendTextCommand : ICommand
{
private Document _document;
private string _text;

public AppendTextCommand(Document document, string text)
{
_document = document;
_text = text;
}

public string Description => $"Append '{_text}'";

public void Execute()
{
_document.Append(_text);
}

public void Undo()
{
_document.Delete(_text.Length);
}
}

// Concrete command for deleting text
public class DeleteTextCommand : ICommand
{
private Document _document;
private string _deletedText;
private int _length;

public DeleteTextCommand(Document document, int length)
{
_document = document;
_length = length;
_deletedText = document.Content.Length >= length
? document.Content.Substring(document.Content.Length - length)
: string.Empty;
}

public string Description => $"Delete {_length} characters";

public void Execute()
{
_document.Delete(_length);
}

public void Undo()
{
_document.Append(_deletedText);
}
}

// Command History Manager
public class CommandHistory
{
private Stack<ICommand> _undoStack = new Stack<ICommand>();
private Stack<ICommand> _redoStack = new Stack<ICommand>();

public void ExecuteCommand(ICommand command)
{
command.Execute();
_undoStack.Push(command);
_redoStack.Clear(); // Once a new command is executed, we can't redo anymore
}

public void Undo()
{
if (_undoStack.Count > 0)
{
ICommand command = _undoStack.Pop();
command.Undo();
_redoStack.Push(command);
Console.WriteLine($"Undid: {command.Description}");
}
else
{
Console.WriteLine("Nothing to undo");
}
}

public void Redo()
{
if (_redoStack.Count > 0)
{
ICommand command = _redoStack.Pop();
command.Execute();
_undoStack.Push(command);
Console.WriteLine($"Redid: {command.Description}");
}
else
{
Console.WriteLine("Nothing to redo");
}
}
}

// TextEditor (Invoker)
public class TextEditor
{
private Document _document;
private CommandHistory _history = new CommandHistory();

public TextEditor(string documentName)
{
_document = new Document(documentName);
}

public void AppendText(string text)
{
ICommand command = new AppendTextCommand(_document, text);
_history.ExecuteCommand(command);
}

public void DeleteText(int length)
{
ICommand command = new DeleteTextCommand(_document, length);
_history.ExecuteCommand(command);
}

public void Undo()
{
_history.Undo();
}

public void Redo()
{
_history.Redo();
}

public string GetContent()
{
return _document.Content;
}
}

Usage of our text editor:

csharp
class Program
{
static void Main(string[] args)
{
TextEditor editor = new TextEditor("myDocument.txt");

// Add some text
editor.AppendText("Hello ");
editor.AppendText("World!");
// Output:
// Added 'Hello ' to document myDocument.txt
// Current content: Hello
// Added 'World!' to document myDocument.txt
// Current content: Hello World!

// Undo the last command
editor.Undo();
// Output:
// Deleted 'World!' from document myDocument.txt
// Current content: Hello
// Undid: Append 'World!'

// Redo the undone command
editor.Redo();
// Output:
// Added 'World!' to document myDocument.txt
// Current content: Hello World!
// Redid: Append 'World!'

// Add more text
editor.AppendText(" How are you?");
// Output:
// Added ' How are you?' to document myDocument.txt
// Current content: Hello World! How are you?

// Undo twice
editor.Undo();
// Output:
// Deleted ' How are you?' from document myDocument.txt
// Current content: Hello World!
// Undid: Append ' How are you?'

editor.Undo();
// Output:
// Deleted 'World!' from document myDocument.txt
// Current content: Hello
// Undid: Append 'World!'

Console.WriteLine("Final content: " + editor.GetContent());
// Output: Final content: Hello

Console.ReadKey();
}
}

Command Pattern in WPF Applications

In WPF desktop applications, the Command Pattern is built into the framework through the ICommand interface. Here's how to implement a custom command:

csharp
using System;
using System.Windows.Input;

public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;

public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}

public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}

public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}

public void Execute(object parameter)
{
_execute(parameter);
}
}

Using it in a ViewModel:

csharp
public class MainViewModel : INotifyPropertyChanged
{
private string _message;
public string Message
{
get { return _message; }
set
{
_message = value;
OnPropertyChanged(nameof(Message));
}
}

public ICommand ShowMessageCommand { get; private set; }
public ICommand ClearMessageCommand { get; private set; }

public MainViewModel()
{
ShowMessageCommand = new RelayCommand(
param => Message = "Hello from Command Pattern!",
param => string.IsNullOrEmpty(Message)
);

ClearMessageCommand = new RelayCommand(
param => Message = string.Empty,
param => !string.IsNullOrEmpty(Message)
);
}

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

And in XAML:

xml
<Window x:Class="CommandPatternDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Command Pattern Demo" Height="200" Width="300">
<StackPanel Margin="10">
<TextBlock Text="{Binding Message}" Height="50" />
<Button Content="Show Message" Command="{Binding ShowMessageCommand}" Margin="0,10" />
<Button Content="Clear Message" Command="{Binding ClearMessageCommand}" />
</StackPanel>
</Window>

Macro Commands

Sometimes you may want to execute multiple commands at once. This is where macro commands come in:

csharp
// Macro command - a command that executes multiple commands
public class MacroCommand : ICommand
{
private List<ICommand> _commands = new List<ICommand>();

public MacroCommand(IEnumerable<ICommand> commands)
{
if (commands != null)
{
_commands.AddRange(commands);
}
}

public void AddCommand(ICommand command)
{
_commands.Add(command);
}

public void Execute()
{
foreach (var command in _commands)
{
command.Execute();
}
}

public void Undo()
{
// Execute undo in reverse order
for (int i = _commands.Count - 1; i >= 0; i--)
{
_commands[i].Undo();
}
}
}

Usage:

csharp
// Create receivers
Light livingRoomLight = new Light();
Stereo stereo = new Stereo();
TV tv = new TV();

// Create commands
ICommand lightOn = new LightOnCommand(livingRoomLight);
ICommand stereoOn = new StereoOnCommand(stereo);
ICommand tvOn = new TVOnCommand(tv);

// Create macro command
MacroCommand homeTheaterOn = new MacroCommand(new List<ICommand> { lightOn, stereoOn, tvOn });

// Create invoker
RemoteControl remote = new RemoteControl();

// Turn everything on with one button press
remote.SetCommand(homeTheaterOn);
remote.PressButton();
// Output:
// Light is now ON
// Stereo is now ON
// TV is now ON

// Undo (will turn off TV, then stereo, then light)
remote.PressUndoButton();
// Output:
// TV is now OFF
// Stereo is now OFF
// Light is now OFF

Summary

The Command Pattern is a powerful design pattern that helps you encapsulate requests as objects. We've covered:

  1. The basic structure of the Command Pattern (Command, Receiver, Invoker)
  2. How to add undo/redo functionality
  3. A practical text editor example with command history
  4. How to use the built-in command functionality in WPF
  5. Implementing macro commands for executing multiple commands at once

Benefits of the Command Pattern:

  • Decoupling: Objects sending commands don't need to know how to perform the operation
  • Extensibility: You can add new commands without changing existing code
  • Undo/Redo: Support for reversible operations
  • Queuing and Scheduling: Commands can be stored and executed later
  • Logging: Each command can log its actions for audit purposes

Exercises

  1. Extend the text editor example to include a "Replace Text" command
  2. Create a simple calculator application using the Command Pattern for addition, subtraction, multiplication, and division operations
  3. Implement a drawing application where each drawing action (draw line, rectangle, circle) is a command with undo capability
  4. Expand the WPF example to include logging of commands to a file
  5. Create a batch processing system that queues commands and executes them sequentially

Additional Resources

Happy coding! The Command Pattern will certainly help you create more maintainable and flexible .NET desktop applications.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)