BrandGhost
Command Design Pattern in C#: Complete Guide with Examples

Command Design Pattern in C#: Complete Guide with Examples

Command Design Pattern in C#: Complete Guide with Examples

When you need to turn a method call into a standalone object that can be stored, queued, logged, or undone, the command design pattern in C# is the behavioral pattern built for exactly that job. It encapsulates a request as an object, letting you parameterize clients with different requests, delay execution, support undo operations, and build complex command pipelines. Instead of calling methods directly, you wrap them in command objects -- and that single change unlocks a surprising amount of flexibility.

In this complete guide, we'll walk through everything you need to know about the command design pattern -- from the core participants and a basic implementation to command queuing, undo/redo support, its relationship to CQRS, and practical considerations for production use. By the end, you'll have working C# code examples and a clear understanding of when this pattern is the right choice for your application.

What Is the Command Design Pattern?

The command design pattern is a behavioral pattern from the Gang of Four (GoF) catalog that decouples the object making a request from the object that knows how to perform it. The core idea is simple: instead of invoking operations directly on a receiver, you wrap each operation in a command object that contains all the information needed to perform the action.

Think about a restaurant. You don't walk into the kitchen and tell the chef exactly what to do. You give your order to a waiter, who writes it on a slip and passes it to the kitchen. That slip is the command -- it captures what you want, it can be queued behind other orders, the waiter doesn't need to know how to cook, and the kitchen doesn't need to know who placed the order. The command object is the intermediary that decouples the requester from the executor.

This decoupling solves several problems at once. Commands can be stored in a history for undo support, serialized for logging, batched for transactional execution, or queued for deferred processing. You get all of this flexibility simply by turning a direct method call into an object.

The pattern involves four key participants: the Command interface, Concrete Commands, the Invoker, and the Receiver. Understanding how these participants interact is the foundation for everything else in this guide.

Core Components of the Command Design Pattern

Each participant in the command design pattern has a distinct role. Getting the boundaries right between these roles is what makes the pattern work cleanly.

The Command is an interface or abstract class that declares the method signature for executing the operation. In its simplest form, this is a single Execute method. When you need undo support, you add an Undo method to the interface. This is the abstraction that lets the invoker work with any command without knowing what it does.

The Concrete Command implements the command interface and binds a specific action to a specific receiver. It stores the receiver and any parameters needed to call the receiver's methods. Each concrete command encapsulates exactly one operation -- this keeps commands focused and composable.

The Invoker is responsible for triggering commands. It doesn't know what the commands do. The invoker might execute commands immediately, store them in a history stack, queue them for later, or batch them together. A toolbar button, a menu item, or a job scheduler can all be invokers.

The Receiver contains the business logic that the command delegates to. It knows how to perform the actual operation. The receiver doesn't know anything about commands or invokers -- it's a plain object with methods.

The Client creates and configures command objects, connecting concrete commands to their receivers and assigning them to invokers. The client is where all the wiring happens, and it's the only participant that knows the concrete types of everything involved. This is where dependency injection can help manage the wiring in larger applications.

Implementing the Command Pattern in C#

Let's build a practical example: a text editor with command-based operations. This is a textbook use case for the command pattern because text editors need undo/redo, command history, and the ability to bind different operations to toolbar buttons and keyboard shortcuts interchangeably.

Defining the Command Interface

The command interface declares both Execute and Undo methods, which gives us undo support from the start:

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

Two methods. That's the entire contract. Every command in the system will implement this interface, and the invoker only needs to know about these two operations. The simplicity of this interface is a feature -- it means any operation that can be done and undone fits the contract.

Creating the Receiver

The receiver is the object that contains the actual business logic. In our case, it's a TextDocument that holds content and supports basic text operations:

using System;
using System.Text;

public sealed class TextDocument
{
    private readonly StringBuilder _content = new();

    public string Content => _content.ToString();

    public void InsertText(int position, string text)
    {
        if (position < 0 || position > _content.Length)
        {
            throw new ArgumentOutOfRangeException(
                nameof(position));
        }

        _content.Insert(position, text);
    }

    public void DeleteText(int position, int length)
    {
        if (position < 0 ||
            position + length > _content.Length)
        {
            throw new ArgumentOutOfRangeException(
                nameof(position));
        }

        _content.Remove(position, length);
    }

    public void ReplaceText(
        int position,
        int length,
        string newText)
    {
        DeleteText(position, length);
        InsertText(position, newText);
    }
}

Notice that TextDocument doesn't know anything about commands. It's a plain class with methods that manipulate text. This is how the receiver should look -- focused on its domain responsibility and completely unaware of the command infrastructure wrapping it.

Building Concrete Commands

Each concrete command encapsulates one operation on the document. Here's an insert command:

public sealed class InsertTextCommand : ICommand
{
    private readonly TextDocument _document;
    private readonly int _position;
    private readonly string _text;

    public InsertTextCommand(
        TextDocument document,
        int position,
        string text)
    {
        _document = document;
        _position = position;
        _text = text;
    }

    public void Execute()
    {
        _document.InsertText(_position, _text);
    }

    public void Undo()
    {
        _document.DeleteText(_position, _text.Length);
    }
}

The command stores everything it needs: the receiver, the position, and the text to insert. Execute inserts the text. Undo removes the same number of characters from the same position, effectively reversing the operation. Here's a delete command:

public sealed class DeleteTextCommand : ICommand
{
    private readonly TextDocument _document;
    private readonly int _position;
    private readonly int _length;
    private string _deletedText = string.Empty;

    public DeleteTextCommand(
        TextDocument document,
        int position,
        int length)
    {
        _document = document;
        _position = position;
        _length = length;
    }

    public void Execute()
    {
        _deletedText = _document.Content
            .Substring(_position, _length);
        _document.DeleteText(_position, _length);
    }

    public void Undo()
    {
        _document.InsertText(_position, _deletedText);
    }
}

The delete command needs to remember what it deleted so it can restore the text during undo. This is a common pattern -- concrete commands often need to capture state before execution so they can reverse their effects.

Creating the Invoker with History

The invoker manages command execution and maintains a history stack for undo/redo support:

using System;
using System.Collections.Generic;

public sealed class CommandInvoker
{
    private readonly Stack<ICommand> _undoStack = new();
    private readonly Stack<ICommand> _redoStack = new();

    public void ExecuteCommand(ICommand command)
    {
        command.Execute();
        _undoStack.Push(command);
        _redoStack.Clear();
    }

    public void Undo()
    {
        if (_undoStack.Count == 0)
        {
            Console.WriteLine("Nothing to undo.");
            return;
        }

        ICommand command = _undoStack.Pop();
        command.Undo();
        _redoStack.Push(command);
    }

    public void Redo()
    {
        if (_redoStack.Count == 0)
        {
            Console.WriteLine("Nothing to redo.");
            return;
        }

        ICommand command = _redoStack.Pop();
        command.Execute();
        _undoStack.Push(command);
    }

    public bool CanUndo => _undoStack.Count > 0;

    public bool CanRedo => _redoStack.Count > 0;
}

The invoker is elegant in its simplicity. It executes a command, pushes it onto the undo stack, and clears the redo stack (because new commands invalidate the redo history). Undo pops from the undo stack and pushes onto the redo stack. Redo does the reverse. The invoker has zero knowledge of what the commands actually do -- it just calls Execute and Undo through the interface.

Putting It All Together

Here's how the client wires everything up and uses the system:

using System;

var document = new TextDocument();
var invoker = new CommandInvoker();

var insertHello = new InsertTextCommand(
    document, 0, "Hello, ");
var insertWorld = new InsertTextCommand(
    document, 7, "World!");

invoker.ExecuteCommand(insertHello);
Console.WriteLine(document.Content);
// Output: Hello, 

invoker.ExecuteCommand(insertWorld);
Console.WriteLine(document.Content);
// Output: Hello, World!

invoker.Undo();
Console.WriteLine(document.Content);
// Output: Hello, 

invoker.Redo();
Console.WriteLine(document.Content);
// Output: Hello, World!

var deleteCommand = new DeleteTextCommand(
    document, 5, 8);
invoker.ExecuteCommand(deleteCommand);
Console.WriteLine(document.Content);
// Output: Hello

invoker.Undo();
Console.WriteLine(document.Content);
// Output: Hello, World!

The client creates commands, passes them to the invoker, and the invoker manages execution and history. The document doesn't know about the command infrastructure. The invoker doesn't know about text editing. Each piece does one job. This is the command design pattern working as intended.

Command Queuing and Deferred Execution

One of the most powerful capabilities the command design pattern enables is queuing commands for deferred or batched execution. Because commands are objects, they can be stored in any data structure -- a queue, a list, a priority queue, or even serialized and sent across a network.

A command queue is useful when you want to batch operations, throttle execution, or process commands asynchronously. Here's a simple queue implementation:

using System;
using System.Collections.Generic;

public sealed class CommandQueue
{
    private readonly Queue<ICommand> _commands = new();

    public void Enqueue(ICommand command)
    {
        _commands.Enqueue(command);
    }

    public void ProcessAll()
    {
        while (_commands.Count > 0)
        {
            ICommand command = _commands.Dequeue();
            command.Execute();
            Console.WriteLine("Executed command.");
        }
    }

    public int PendingCount => _commands.Count;
}

This pattern shows up frequently in game development (queuing player actions), task scheduling systems, and macro recording features. You can extend it with priorities, retry logic, or conditional execution -- the command objects carry all the information needed, so the queue only needs to call Execute.

You can also serialize command objects to JSON or another format, send them across a network, and execute them on a different machine. This is the foundation of command-sourcing architectures and distributed command processing.

Undo/Redo Mechanics

The undo/redo implementation we built in the invoker section is the most common reason developers reach for the command pattern. The mechanics are straightforward once you understand the two-stack approach.

Every command that gets executed goes onto the undo stack. When the user triggers undo, you pop the most recent command and call its Undo method, then push it onto the redo stack. Redo pops from the redo stack, re-executes, and pushes back onto the undo stack. When a new command is executed, the redo stack gets cleared because the new action creates a divergent history.

The tricky part is implementing Undo correctly in each concrete command. Some operations are trivially reversible -- inserting text is undone by deleting the same text. But others require the command to snapshot state before execution. The DeleteTextCommand we built earlier demonstrates this: it captures the deleted text during Execute so it can restore it during Undo.

For complex scenarios, you might want to compose multiple commands into a single undoable operation. This is the macro command (or composite command) approach. You can learn more about how composite structures work in the composite design pattern guide. A MacroCommand would implement ICommand and hold a list of child commands. Its Execute runs all children in order, and its Undo runs their Undo methods in reverse order:

using System.Collections.Generic;
using System.Linq;

public sealed class MacroCommand : ICommand
{
    private readonly List<ICommand> _commands;

    public MacroCommand(IEnumerable<ICommand> commands)
    {
        _commands = commands.ToList();
    }

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

    public void Undo()
    {
        for (int i = _commands.Count - 1; i >= 0; i--)
        {
            _commands[i].Undo();
        }
    }
}

This gives you atomic undo for multi-step operations. The invoker treats the macro the same as any other command -- it's just another ICommand.

The Command Pattern and CQRS

Command Query Responsibility Segregation (CQRS) is an architectural pattern that separates read operations from write operations. The "Command" in CQRS shares the same fundamental idea as the command design pattern: encapsulate a write operation as a distinct object with all the data it needs.

In a CQRS architecture, you define command objects for each write operation (like CreateOrderCommand or UpdateCustomerAddressCommand) and send them through a command handler pipeline. The command handler is essentially the receiver from the GoF command pattern -- it knows how to execute the operation. The command bus or dispatcher plays the invoker role, routing commands to the appropriate handler.

The connection is conceptual more than structural. The GoF command pattern is a design-level pattern for encapsulating method calls within a single application. CQRS is an architectural pattern for separating read and write models across an entire system. But they share the same insight: treating operations as first-class objects gives you flexibility in how those operations are dispatched, validated, logged, and processed.

Combining with Other Patterns

The command design pattern works well alongside several other patterns, and knowing these combinations helps you build more flexible systems.

The strategy design pattern and the command pattern are often confused because both encapsulate behavior as objects. The difference is in intent: strategy encapsulates how to do something (an algorithm), while command encapsulates what to do (a request). You might use a strategy to determine the algorithm a command uses during execution, combining both patterns in a single design.

The observer design pattern pairs naturally with the command pattern when you need to notify other parts of the system that a command was executed. The invoker can raise events after executing or undoing a command, letting observers react -- for example, updating a UI element that displays undo history or logging every command execution for audit purposes.

The decorator design pattern can wrap commands with cross-cutting concerns like logging, validation, or authorization. Instead of adding logging code to every concrete command, you create a LoggingCommandDecorator that wraps any ICommand, logs the execution, and delegates to the wrapped command. This keeps individual commands clean and follows inversion of control principles.

Benefits and Drawbacks

The command design pattern offers clear advantages, but it's not the right tool for every situation. Understanding both sides helps you make informed decisions about when to apply it.

Benefits:

  • Decoupling. The invoker doesn't know the receiver. The receiver doesn't know the invoker. Commands are the only bridge between them, and you can swap commands, receivers, and invokers independently.
  • Undo/redo support. Because commands encapsulate operations and their reverse, building undo/redo is a matter of maintaining a history stack.
  • Command queuing and scheduling. Commands are objects, so they can be stored in queues, serialized, or scheduled for later execution.
  • Macro recording. You can record sequences of commands and replay them, which is useful for automation and testing.
  • Extensibility. Adding new operations means adding new command classes. You don't modify existing invokers, receivers, or other commands.

Drawbacks:

  • Class proliferation. Each operation requires its own command class. In systems with many operations, this can lead to a large number of small classes.
  • Complexity for simple cases. If you just need to call a method, wrapping it in a command object adds indirection without much benefit. The pattern shines when you need history, queuing, or decoupling -- not for direct, one-off method calls.
  • State management in undo. Implementing reliable Undo methods can be tricky when commands have complex side effects or interact with external systems. Undoing a database write or an API call requires careful handling that goes beyond simply reversing in-memory state.

Frequently Asked Questions

What is the command design pattern in C#?

The command design pattern in C# is a behavioral design pattern that encapsulates a request as an object. It separates the object that issues the request (the invoker) from the object that performs the work (the receiver) by introducing a command object in between. The command object contains all the data needed to perform the operation, which enables features like undo/redo, command queuing, and macro recording. In C#, you typically define an ICommand interface with Execute and Undo methods and create concrete command classes that implement it.

When should I use the command design pattern?

Use the command design pattern when you need to decouple the sender of a request from the handler, support undo/redo functionality, queue or schedule operations for deferred execution, log operations for auditing, or implement macro recording. It's particularly valuable in applications with rich UI interactions -- like text editors, graphic design tools, or game engines -- where users expect to undo and redo their actions. If you only need to call a method directly and don't need any of these features, the pattern adds unnecessary complexity.

How does the command pattern differ from the strategy pattern?

Both patterns encapsulate behavior as objects, but they serve different purposes. The strategy pattern encapsulates interchangeable algorithms -- it's about how something is done. The command pattern encapsulates requests -- it's about what is done. A strategy typically has no concept of undo and is swapped at runtime to change an algorithm. A command is a self-contained action that carries its parameters, can be stored in a history, and can be reversed. The two patterns can be combined: a command might use a strategy internally to choose which algorithm to apply during execution.

Can I use delegates instead of command objects in C#?

Yes, for simple scenarios. C# delegates and lambda expressions can serve as lightweight commands. You can pass an Action or Func<T> instead of creating a full command class. However, delegates don't naturally support undo, they can't carry named metadata, and they're harder to serialize or inspect. If you need undo/redo, command history, or rich command metadata, concrete command objects give you the structure to support those features. Delegates work well when you only need deferred execution without the additional capabilities.

What is the relationship between the command pattern and CQRS?

CQRS (Command Query Responsibility Segregation) borrows the concept of encapsulating write operations as command objects from the GoF command design pattern. In CQRS, each write operation is represented as a command class sent through a command handler pipeline. However, CQRS is an architectural pattern that separates read and write models across an entire system, while the command design pattern is a design-level pattern for encapsulating method calls within a single application. They share the same insight -- treating operations as first-class objects -- but operate at different levels of abstraction.

How do I implement undo for commands with complex side effects?

For commands that interact with external systems -- databases, APIs, file systems -- undo is more nuanced than reversing in-memory state. One approach is to use compensating actions: instead of literally reversing the original operation, you execute a separate operation that counteracts it (for example, issuing a refund to undo a payment). Another approach is the memento pattern, which captures and restores object state. For database operations, wrapping command execution in a transaction and rolling back on undo is a practical strategy. The key principle is that each command must capture enough context during execution to reverse its effects.

How does the command pattern support macro recording?

Macro recording is a natural extension of the command pattern. Since every user action is represented as a command object, recording a macro is as simple as storing executed commands in a list. Playing the macro back means iterating through the list and calling Execute on each command in sequence. You can wrap the recorded sequence in a MacroCommand (a composite of commands) so the entire macro can be executed, undone, or stored as a single unit. This is how many professional applications -- from text editors to spreadsheet programs -- implement their macro functionality.

Wrapping Up the Command Design Pattern in C#

The command design pattern in C# is a versatile behavioral pattern that transforms method calls into objects you can store, queue, undo, and compose. The pattern's strength lies in its simplicity -- an ICommand interface with Execute and Undo, concrete commands that bind actions to receivers, and an invoker that manages execution and history.

Start by identifying places in your codebase where you need undo support, deferred execution, or decoupling between the code that triggers an action and the code that performs it. If you find yourself passing around callbacks with extra metadata, or maintaining manual undo logic scattered across your application, the command pattern gives you a clean structure to consolidate that behavior. Pair it with the adapter pattern to integrate third-party receivers that don't match your command interface, or with dependency injection to manage the wiring between commands, receivers, and invokers in larger applications. You can find the command pattern alongside other behavioral and structural patterns in the complete list of design patterns.

Observer Design Pattern in C#: Complete Guide with Examples

Master the observer design pattern in C# with practical code examples, event-driven architecture guidance, and real-world use cases.

Command Pattern in C# - What You Need to Implement It

Organize your code with the Command Pattern in C#! Learn what the Command Pattern in C# is and the design principles it follows. Understand the pros and cons!

Composite Design Pattern in C#: Complete Guide with Examples

Master the composite design pattern in C# with practical examples showing tree structures, recursive operations, and uniform component handling.

An error has occurred. This application may no longer respond until reloaded. Reload