Look at a TV remote. Each button is a wrapped-up request. Power means "turn the TV on." Volume+ means "increase volume." Channel means "change channel."
The remote has no idea how the TV actually turns on internally. The button just holds the instruction. Press it, and the instruction runs on the TV.
Here's the magic: because each action is now an object — a button — you can do things you couldn't before. Record a sequence of presses, replay them, or undo the last one.
That's the Command pattern: wrap a request as an object, so you can pass it around, queue it, log it, or undo it.
The problem it solves
Normally you call a method directly:
light.TurnOn(); // happens immediately, then vanishes
The action is a fleeting event. You can't store it, queue it, undo it, or ask "what did the user just do?" Once it runs, it's gone. Command turns that verb into a noun — an object you can hold.
The three players
- Receiver — the thing that does the actual work (the TV, the light).
- Command — an object wrapping "call this method on the receiver."
- Invoker — triggers commands but knows nothing about what they do (the remote).
The code
Every command can execute, and optionally undo:
public interface ICommand
{
void Execute();
void Undo();
}
The receiver does the real work:
public class Light
{
public void On() => Console.WriteLine("Light ON");
public void Off() => Console.WriteLine("Light OFF");
}
Each concrete command wraps one action on the receiver — and knows its own reverse:
public class TurnOnCommand : ICommand
{
private readonly Light _light;
public TurnOnCommand(Light light) => _light = light;
public void Execute() => _light.On();
public void Undo() => _light.Off(); // the reverse action
}
public class TurnOffCommand : ICommand
{
private readonly Light _light;
public TurnOffCommand(Light light) => _light = light;
public void Execute() => _light.Off();
public void Undo() => _light.On();
}
The invoker presses buttons and keeps a history — but knows nothing about lights:
public class RemoteControl
{
private readonly Stack<ICommand> _history = new();
public void PressButton(ICommand command)
{
command.Execute();
_history.Push(command); // remember it, so we can undo
}
public void PressUndo()
{
if (_history.Count > 0)
_history.Pop().Undo(); // undo the last thing
}
}
Use it:
var light = new Light();
var remote = new RemoteControl();
remote.PressButton(new TurnOnCommand(light)); // Light ON
remote.PressButton(new TurnOffCommand(light)); // Light OFF
remote.PressUndo(); // Light ON — undo!
The remote replayed and undid actions — because each action was an object it could store on a stack. You cannot do this with plain light.On() method calls. That's the entire point.
What "action as object" unlocks
Once a request is an object, you get superpowers for free:
-
Undo / Redo — keep a history stack and call
.Undo(). Every text editor works this way. - Queuing — put commands in a queue and run them later. This is how background job systems work.
- Scheduling — "run this command at 2 AM."
- Logging and audit — store executed commands to replay or review.
- Macros — bundle several commands into one.
None of these are possible when the action is just a method call that runs and disappears.
Where you've seen it
- Undo/redo in any editor — Word, Photoshop, VS Code — is a command history stack.
- Background job queues (Hangfire, message queues) — each job is essentially a command.
-
ICommandin WPF/MVVM — button bindings are literally this pattern by name. -
The CQRS command side and MediatR's
IRequest— command objects sent to handlers.
Command vs Strategy
Both wrap behavior in an object, but the intent differs:
- Strategy is about how to do one thing, swappable — which shipping algorithm to run. It usually runs immediately, no history.
- Command is about what to do, captured as an object so it can be queued, undone, or logged. The action itself becomes storable data.
Strategy chooses a method. Command captures a request.
One line to remember
Command = TV remote button. Wrap an action as an object so you can store it, queue it, replay it, or undo it — things a plain method call can never do.
Part 11 of a series on must-know design patterns in C#, explained the simple way. Earlier parts covered Singleton, Factory Method, Builder, Adapter, Decorator, Facade, Proxy, Composite, Observer, and Strategy. Next up: Iterator — walking a collection without exposing its internals.
Top comments (0)