Table of Contents

Command — Command

A minimal, allocation-light Command pattern:

  • Encapsulates an action (Do) and optional Undo over a context TCtx
  • Executes synchronously or asynchronously via ValueTask
  • Composes multiple commands into a macro that executes in order and undoes in reverse
  • Thread-safe after Build(); uses in parameters for structs

Quick start

using PatternKit.Behavioral.Command;

// Single command
var cmd = Command<MyCtx>.Create()
    .Do(static (in MyCtx c, CancellationToken _) => { Console.WriteLine($"hello {c.Name}"); return default; })
    .Undo(static (in MyCtx c, CancellationToken _) => { Console.WriteLine($"undo {c.Name}"); return default; })
    .Build();

var ctx = new MyCtx("Ada");
await cmd.Execute(ctx);             // hello Ada
await cmd.TryUndo(ctx, out var t);  // t completes; prints "undo Ada"

// Macro
var a = Command<MyCtx>.Create().Do(static (in MyCtx c, _) => { Console.Write("A"); return default; }).Build();
var b = Command<MyCtx>.Create().Do(static (in MyCtx c, _) => { Console.Write("B"); return default; }).Build();

var macro = Command<MyCtx>.Macro().Add(a).Add(b).Build();
await macro.Execute(ctx); // prints AB
public readonly record struct MyCtx(string Name);

API (at a glance)

public sealed class Command<TCtx>
{
    public delegate ValueTask Exec(in TCtx ctx, CancellationToken ct);

    public static Builder Create();
    public ValueTask Execute(in TCtx ctx, CancellationToken ct);
    public ValueTask Execute(in TCtx ctx); // ct = default

    public bool TryUndo(in TCtx ctx, CancellationToken ct, out ValueTask undoTask);
    public bool TryUndo(in TCtx ctx, out ValueTask undoTask); // ct = default
    public bool HasUndo { get; }

    public sealed class Builder
    {
        public Builder Do(Exec handler);            // required
        public Builder Do(Action<TCtx> handler);    // sync adapter
        public Builder Undo(Exec handler);          // optional
        public Builder Undo(Action<TCtx> handler);  // sync adapter
        public Command<TCtx> Build();
    }

    public static MacroBuilder Macro();

    public sealed class MacroBuilder
    {
        public MacroBuilder Add(Command<TCtx> cmd);
        public MacroBuilder AddIf(bool condition, Command<TCtx> cmd);
        public Command<TCtx> Build(); // executes in order; Undo runs in reverse
    }
}

Design notes

  • Uses in parameters to avoid copies of struct contexts.
  • ValueTask everywhere keeps the sync-fast path allocation-free.
  • Macro execution is optimized for the fast path: it returns immediately if all steps complete synchronously; otherwise it awaits only when needed.

Error behavior

  • Execute/Undo propagate exceptions from user handlers.
  • TryUndo returns false if no Undo was configured.

Testing

See PatternKit.Tests/Behavioral/Command/CommandTests.cs for TinyBDD scenarios covering Do/Undo and macro ordering.