Dofactory.com
Dofactory.com

C# Command Design Pattern

The Command design pattern encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. 

C# code examples of the Command design pattern is provided in 3 forms:

Frequency of use:
medium-high
C# Design Patterns

UML class diagram

A visualization of the classes and objects participating in this pattern.

Participants

The classes and objects participating in this pattern include:

  • Command  (Command)
    • declares an interface for executing an operation
  • ConcreteCommand  (CalculatorCommand)
    • defines a binding between a Receiver object and an action
    • implements Execute by invoking the corresponding operation(s) on Receiver
  • Client  (CommandApp)
    • creates a ConcreteCommand object and sets its receiver
  • Invoker  (User)
    • asks the command to carry out the request
  • Receiver  (Calculator)
    • knows how to perform the operations associated with carrying out the request.

Structural code in C#

This structural code demonstrates the Command pattern which stores requests as objects allowing clients to execute or playback the requests.

using System;

namespace Command.Structural
{
    /// <summary>
    /// Command Design Pattern
    /// </summary>

    public class Program
    {
        public static void Main(string[] args)
        {
            // Create receiver, command, and invoker

            Receiver receiver = new Receiver();
            Command command = new ConcreteCommand(receiver);
            Invoker invoker = new Invoker();

            // Set and execute command

            invoker.SetCommand(command);
            invoker.ExecuteCommand();

            // Wait for user

            Console.ReadKey();
        }
    }

    /// <summary>
    /// The 'Command' abstract class
    /// </summary>

    public abstract class Command
    {
        protected Receiver receiver;

        // Constructor

        public Command(Receiver receiver)
        {
            this.receiver = receiver;
        }

        public abstract void Execute();
    }

    /// <summary>
    /// The 'ConcreteCommand' class
    /// </summary>

    public class ConcreteCommand : Command
    {
        // Constructor

        public ConcreteCommand(Receiver receiver) :
            base(receiver)
        {
        }

        public override void Execute()
        {
            receiver.Action();
        }
    }

    /// <summary>
    /// The 'Receiver' class
    /// </summary>

    public class Receiver
    {
        public void Action()
        {
            Console.WriteLine("Called Receiver.Action()");
        }
    }

    /// <summary>
    /// The 'Invoker' class
    /// </summary>

    public class Invoker
    {
        Command command;

        public void SetCommand(Command command)
        {
            this.command = command;
        }

        public void ExecuteCommand()
        {
            command.Execute();
        }
    }
}
Output
Called Receiver.Action()

Real-world code in C#

This real-world code demonstrates the Command pattern used in a simple calculator with unlimited number of undo's and redo's. Note that in C# the word 'operator' is a keyword. Prefixing it with '@' allows using it as an identifier.

using System;
using System.Collections.Generic;

namespace Command.RealWorld
{
    /// <summary>
    /// Command Design Pattern
    /// </summary>

    public class Program
    {
        public static void Main(string[] args)
        {
            // Create user and let her compute

            User user = new User();

            // User presses calculator buttons

            user.Compute('+', 100);
            user.Compute('-', 50);
            user.Compute('*', 10);
            user.Compute('/', 2);

            // Undo 4 commands

            user.Undo(4);

            // Redo 3 commands

            user.Redo(3);

            // Wait for user

            Console.ReadKey();
        }
    }

    /// <summary>
    /// The 'Command' abstract class
    /// </summary>

    public abstract class Command
    {
        public abstract void Execute();
        public abstract void UnExecute();
    }

    /// <summary>
    /// The 'ConcreteCommand' class
    /// </summary>

    public class CalculatorCommand : Command
    {
        char @operator;
        int operand;
        Calculator calculator;

        // Constructor

        public CalculatorCommand(Calculator calculator,
            char @operator, int operand)
        {
            this.calculator = calculator;
            this.@operator = @operator;
            this.operand = operand;
        }

        // Gets operator

        public char Operator
        {
            set { @operator = value; }
        }

        // Get operand

        public int Operand
        {
            set { operand = value; }
        }

        // Execute new command

        public override void Execute()
        {
            calculator.Operation(@operator, operand);
        }

        // Unexecute last command

        public override void UnExecute()
        {
            calculator.Operation(Undo(@operator), operand);
        }

        // Returns opposite operator for given operator

        private char Undo(char @operator)
        {
            switch (@operator)
            {
                case '+': return '-';
                case '-': return '+';
                case '*': return '/';
                case '/': return '*';
                default:
                    throw new
             ArgumentException("@operator");
            }
        }
    }

    /// <summary>
    /// The 'Receiver' class
    /// </summary>

    public class Calculator
    {
        int curr = 0;

        public void Operation(char @operator, int operand)
        {
            switch (@operator)
            {
                case '+': curr += operand; break;
                case '-': curr -= operand; break;
                case '*': curr *= operand; break;
                case '/': curr /= operand; break;
            }
            Console.WriteLine(
                "Current value = {0,3} (following {1} {2})",
                curr, @operator, operand);
        }
    }

    /// <summary>
    /// The 'Invoker' class
    /// </summary>

    public class User
    {
        // Initializers

        Calculator calculator = new Calculator();
        List<Command> commands = new List<Command>();
        int current = 0;

        public void Redo(int levels)
        {
            Console.WriteLine("\n---- Redo {0} levels ", levels);
            // Perform redo operations
            for (int i = 0; i < levels; i++)
            {
                if (current < commands.Count - 1)
                {
                    Command command = commands[current++];
                    command.Execute();
                }
            }
        }

        public void Undo(int levels)
        {
            Console.WriteLine("\n---- Undo {0} levels ", levels);
            
            // Perform undo operations

            for (int i = 0; i < levels; i++)
            {
                if (current > 0)
                {
                    Command command = commands[--current] as Command;
                    command.UnExecute();
                }
            }
        }

        public void Compute(char @operator, int operand)
        {
            // Create command operation and execute it

            Command command = new CalculatorCommand(calculator, @operator, operand);
            command.Execute();

            // Add command to undo list

            commands.Add(command);
            current++;
        }
    }
}
Output
Current value = 100 (following + 100)
Current value =  50 (following - 50)
Current value = 500 (following * 10)
Current value = 250 (following / 2)

---- Undo 4 levels
Current value = 500 (following * 2)
Current value =  50 (following / 10)
Current value = 100 (following + 50)
Current value =   0 (following - 100)

---- Redo 3 levels
Current value = 100 (following + 100)
Current value =  50 (following - 50)
Current value = 500 (following * 10)

.NET Optimized code in C#

The .NET optimized code demonstrates the same code as above but uses more modern C# and .NET features.

Here is an elegant C# Command solution.

namespace Command.NetOptimized;

using static System.Console;

using System;
using System.Collections.Generic;

/// <summary>
/// Command Design Pattern
/// </summary>
public class Program
{
    public static void Main()
    {
        // Create user and let her compute
        var user = new User();

        // Issue several compute commands
        user.Compute('+', 100);
        user.Compute('-', 50);
        user.Compute('*', 10);
        user.Compute('/', 2);

        // Undo 4 commands
        user.Undo(4);

        // Redo 3 commands
        user.Redo(3);

        // Wait for user
        ReadKey();
    }
}

/// <summary>
/// The 'Command' interface
/// </summary>
public interface ICommand
{
    void Execute();
    void UnExecute();
}

/// <summary>
/// The 'ConcreteCommand' class
/// </summary>
public class CalculatorCommand( Calculator calculator, 
                                char @operator, 
                                int operand) : ICommand
{

    // Sets operator
    public char Operator { set => @operator = value; }

    // Sets operand
    public int Operand { set => operand = value; }

    // Execute command
    public void Execute()
    {
        calculator.Operation(@operator, operand);
    }

    // Unexecute command
    public void UnExecute()
    {
        calculator.Operation(Undo(@operator), operand);
    }

    // Return opposite operator for given operator
    private static char Undo(char @operator)
    {
        return @operator switch
        {
            '+' => '-',
            '-' => '+',
            '*' => '/',
            '/' => '*',
            _ => throw new ArgumentException("@operator"),
        };
    }
}

/// <summary>
/// The 'Receiver' class
/// </summary>
public class Calculator
{
    private int current = 0;

    // Perform operation for given operator and operand
    public void Operation(char @operator, int operand)
    {
        switch (@operator)
        {
            case '+': current += operand; break;
            case '-': current -= operand; break;
            case '*': current *= operand; break;
            case '/': current /= operand; break;
        }
        WriteLine(
            "Current value = {0,3} (following {1} {2})",
            current, @operator, operand);
    }
}

/// <summary>
/// The 'Invoker' class
/// </summary>
public class User
{
    private readonly Calculator calculator = new();
    private readonly List<ICommand> commands = [];
    private int current = 0;

    // Redo original commands
    public void Redo(int levels)
    {
        WriteLine($"\n---- Redo {levels} levels ");

        // Perform redo operations
        for (int i = 0; i < levels; i++)
        {
            if (current < commands.Count - 1)
            {
                commands[current++].Execute();
            }
        }
    }

    // Undo prior commands
    public void Undo(int levels)
    {
        WriteLine($"\n---- Undo {levels} levels ");

        // Perform undo operations
        for (int i = 0; i < levels; i++)
        {
            if (current > 0)
            {
                commands[--current].UnExecute();
            }
        }
    }

    // Compute new value given operator and operand
    public void Compute(char @operator, int operand)
    {
        // Create command operation and execute it
        var command = new CalculatorCommand(calculator, @operator, operand);
        command.Execute();

        // Add command to undo list
        commands.Add(command);
        current++;
    }
}
Output
Current value = 100 (following + 100)
Current value =  50 (following - 50)
Current value = 500 (following * 10)
Current value = 250 (following / 2)

---- Undo 4 levels
Current value = 500 (following * 2)
Current value =  50 (following / 10)
Current value = 100 (following + 50)
Current value =   0 (following - 100)

---- Redo 3 levels
Current value = 100 (following + 100)
Current value =  50 (following - 50)
Current value = 500 (following * 10)



Last updated on Mar 17, 2024

Want to know more?


Learn how to build .NET applications in 33 days with design patterns, ultra clean architecture, and more.

Learn more about our Dofactory .NET developer package.


Guides


vsn 3.2