Skip to content

Writing your own command type

Run the complete page example.

The basics page creates commands with the static factory methods on ReactiveCommand. Those factories cover almost every case, because they call the same constructors this page covers directly. Write your own command type when every command of a kind needs behavior the factories cannot give you. Examples are recording every parameter a command has run with, logging every run of a combined command, or wrapping a non-reactive command source. This page also covers the interfaces a view model exposes a command through, and SwitchSubscribe, for following a command property that gets replaced.

Derive from ReactiveCommand<TParam, TResult>

ReactiveCommand<TParam, TResult> exposes two constructors to a subclass. The factory methods on ReactiveCommand call these same constructors, but give you no way to intercept each run. A subclass can.

1. Pass the constructors through and override Execute. RecordingCommand<TParam, TResult> hands both of its constructors straight to the base class. Its Execute override adds the parameter to a list, then runs the command as usual.

public sealed class RecordingCommand<TParam, TResult> : ReactiveCommand<TParam, TResult>
{
    private readonly List<TParam> _history = [];

    public RecordingCommand(
        Func<TParam, IObservable<TResult>> execute,
        IObservable<bool>? canExecute,
        ISequencer? outputScheduler)
        : base(execute, canExecute, outputScheduler)
    {
    }

    public RecordingCommand(
        Func<TParam, IObservable<(IObservable<TResult> Result, Action Cancel)>> execute,
        IObservable<bool>? canExecute,
        ISequencer? outputScheduler)
        : base(execute, canExecute, outputScheduler)
    {
    }

    public IReadOnlyList<TParam> History => _history;

    public override IObservable<TResult> Execute(TParam parameter)
    {
        _history.Add(parameter);
        return base.Execute(parameter);
    }
}

2. Use it like any other command. borrow.History lists every book id it has run with, something no ReactiveCommand.Create factory tracks for you.

LibraryDesk desk = new();

using RecordingCommand<int, Book> borrow = new(
    bookId => Signal.Emit(desk.Borrow(bookId)),
    canExecute: null,
    outputScheduler: Sequencer.Immediate);

Book first = await borrow.Execute(1);
Book second = await borrow.Execute(2);

Console.WriteLine(first.Title);
Console.WriteLine(second.Title);
Console.WriteLine(string.Join(", ", borrow.History));

// Output:
// Clean Code
// The Pragmatic Programmer
// 1, 2

The second constructor is for execution logic that cancels through a callback instead of by unsubscribing. It takes a Func<TParam, IObservable<(IObservable<TResult> Result, Action Cancel)>>. That is an observable of a tuple holding the result observable and a Cancel action. ReactiveCommand calls Cancel when the execution is disposed, instead of unsubscribing from Result directly. Use this when the work you are wrapping only knows how to stop through a callback of its own, such as a printer driver.

bool printerToldToStop = false;
ScheduledSignal<string> printerOutput = new(Sequencer.Immediate);

using RecordingCommand<int, string> printReceipt = new(
    bookId => Signal.Emit<(IObservable<string> Result, Action Cancel)>((printerOutput, () => printerToldToStop = true)),
    canExecute: null,
    outputScheduler: Sequencer.Immediate);

IDisposable execution = printReceipt.Execute(1).Subscribe(static _ => { });
execution.Dispose();

Console.WriteLine(printerToldToStop);
Console.WriteLine(string.Join(", ", printReceipt.History));

// Output:
// True
// 1

Disposing execution calls printerToldToStop's setter rather than unsubscribing from printerOutput, because this constructor cancels through the Cancel callback the execute function supplied.

A failed execution's exception reaches ThrownExceptions even when nothing awaits Execute. A plain subscription sees it too, because ThrownExceptions is a separate stream from the command's result.

LibraryDesk desk = new();
List<string> errors = [];

using ReactiveCommand<int, Book> borrow = ReactiveCommand.CreateFromObservable<int, Book>(
    bookId => bookId == 99 ? Signal.Fail<Book>(new InvalidOperationException("No such book.")) : Signal.Emit(desk.Borrow(bookId)),
    outputScheduler: Sequencer.Immediate);
using IDisposable subscription = borrow.ThrownExceptions.Subscribe(error => errors.Add(error.Message));

try
{
    _ = await borrow.Execute(99);
}
catch (InvalidOperationException)
{
    // The awaiting caller sees the error too; ThrownExceptions lets other parts of the view model react as well.
}

Console.WriteLine(errors[0]);

// Output:
// No such book.

Derive from CombinedReactiveCommand<TParam, TResult>

CombinedReactiveCommand<TParam, TResult> exposes three constructors to a subclass, for a canExecute observable, an output ISequencer, or both. ReactiveCommand.CreateCombined calls these same constructors, but gives you no hook to react to every run. LoggingCombinedCommand<TParam, TResult> writes a line each time it runs, using its own IsExecuting.

public sealed class LoggingCombinedCommand<TParam, TResult> : CombinedReactiveCommand<TParam, TResult>
{
    private readonly IDisposable _loggingSubscription;

    public LoggingCombinedCommand(
        IEnumerable<ReactiveCommandBase<TParam, TResult>> childCommands,
        IObservable<bool>? canExecute,
        ISequencer? outputScheduler)
        : base(childCommands, canExecute, outputScheduler) =>
        _loggingSubscription = LogRuns();

    public LoggingCombinedCommand(IEnumerable<ReactiveCommandBase<TParam, TResult>> childCommands, IObservable<bool>? canExecute)
        : base(childCommands, canExecute) =>
        _loggingSubscription = LogRuns();

    public LoggingCombinedCommand(IEnumerable<ReactiveCommandBase<TParam, TResult>> childCommands, ISequencer? outputScheduler)
        : base(childCommands, outputScheduler) =>
        _loggingSubscription = LogRuns();

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _loggingSubscription.Dispose();
        }

        base.Dispose(disposing);
    }

    private IDisposable LogRuns() =>
        IsExecuting.Where(static isExecuting => isExecuting)
            .Subscribe(static _ => Console.WriteLine("Combined command running"));
}

Each constructor passes the child commands to the matching base constructor, with canExecute, outputScheduler or both. Pass null for either one to keep its default.

LibraryDesk desk = new();
_ = desk.Borrow(1);

using ReactiveCommand<RxVoid, int> takeBackLoans = ReactiveCommand.Create(desk.ReturnAll);
using ReactiveCommand<RxVoid, int> countShelf = ReactiveCommand.Create(() => desk.Search(string.Empty).Count);
using LoggingCombinedCommand<RxVoid, int> withCanExecuteAndScheduler = new([takeBackLoans, countShelf], desk.WhenAnyValue(d => d.IsOpen), Sequencer.Immediate);
IList<int> firstRun = await withCanExecuteAndScheduler.Execute();

using ReactiveCommand<RxVoid, int> countShelfAgain = ReactiveCommand.Create(() => desk.Search(string.Empty).Count);
using LoggingCombinedCommand<RxVoid, int> withCanExecute = new([countShelfAgain], desk.WhenAnyValue(d => d.IsOpen));
IList<int> secondRun = await withCanExecute.Execute();

using ReactiveCommand<RxVoid, int> countShelfOnceMore = ReactiveCommand.Create(() => desk.Search(string.Empty).Count);
using LoggingCombinedCommand<RxVoid, int> withScheduler = new([countShelfOnceMore], Sequencer.Immediate);
IList<int> thirdRun = await withScheduler.Execute();

Console.WriteLine(firstRun[1]);
Console.WriteLine(secondRun[0]);
Console.WriteLine(thirdRun[0]);

// Output:
// Combined command running
// Combined command running
// Combined command running
// 4
// 4
// 4

withCanExecuteAndScheduler combines takeBackLoans and countShelf, so its list has both results. The other two combine a single child, so their list holds one.

Derive from ReactiveCommandBase<TParam, TResult>

ReactiveCommandBase<TParam, TResult> is the abstract class ReactiveCommand<TParam, TResult> and CombinedReactiveCommand<TParam, TResult> both build on. Deriving from it directly means implementing CanExecute, IsExecuting, ThrownExceptions, Execute(), Execute(TParam) and Subscribe yourself. It also means implementing two protected hooks, ICommandCanExecute and ICommandExecute. A System.Windows.Input.ICommand caller, such as XAML binding infrastructure, routes through these hooks instead of calling CanExecute and Execute directly. A realistic reason to derive this way is adapting an existing, non-reactive command source into ReactiveUI's contract, rather than writing execution logic that itself produces an IObservable.

DeskAnnouncementCommand wraps a plain Func<string> that produces an announcement, allowed to run only while the desk is open:

protected override bool ICommandCanExecute(object? parameter) => _canExecuteValue;

protected override void ICommandExecute(object? parameter) => _ = Execute();

Binding code that only knows ICommand calls asCommand.CanExecute(null) and asCommand.Execute(null). Both route to these overrides. That is how a command built this way still works as an ICommand, without repeating ReactiveCommandBase's own dispatch logic.

LibraryDesk desk = new();
List<string> canExecuteChanges = [];
using DeskAnnouncementCommand closingSoon = new(desk, static () => "The desk closes in ten minutes.");
using IDisposable subscription = closingSoon.Subscribe(Console.WriteLine);

System.Windows.Input.ICommand asCommand = closingSoon;
asCommand.CanExecuteChanged += (_, _) => canExecuteChanges.Add(asCommand.CanExecute(null) ? "can announce" : "cannot announce");

Console.WriteLine(asCommand.CanExecute(null));
asCommand.Execute(null);
await Task.Yield();

desk.IsOpen = false;

Console.WriteLine(string.Join(", ", canExecuteChanges));

// Output:
// True
// The desk closes in ten minutes.
// cannot announce

Closing the desk raises CanExecuteChanged, which the sample's handler turns into "cannot announce" by reading CanExecute(null) again. DeskAnnouncementCommand raises that event itself, by calling the protected OnCanExecuteChanged method ReactiveCommandBase defines for exactly this purpose.

Subscribe a witness directly

A command is an IObservable<TResult>. A witness is a class that implements IObserver<T> itself, instead of using an operator such as Subscribe(Action<T>). A witness can subscribe to a command the normal way, whether the command is a ReactiveCommand<TParam, TResult> or a hand-written subclass of ReactiveCommandBase<TParam, TResult>. BookReceiptPrinter prints a receipt for every book a command produces:

LibraryDesk desk = new();
BookReceiptPrinter witness = new();

using ReactiveCommand<int, Book> borrow = ReactiveCommand.Create<int, Book>(desk.Borrow);
using IDisposable subscription = borrow.Subscribe(witness);

_ = await borrow.Execute(1);
_ = await borrow.Execute(4);

// Output:
// Printed a receipt for Clean Code
// Printed a receipt for Refactoring

Code that only knows a command through the abstract ReactiveCommandBase<TParam, TResult> subscribes a witness the same way, whether the command underneath was built by a factory or, like DeskAnnouncementCommand, by hand:

LibraryDesk desk = new();
DeskAnnouncementBoard board = new();
using DeskAnnouncementCommand closingSoon = new(desk, static () => "The desk closes in ten minutes.");

ReactiveCommandBase<RxVoid, string> command = closingSoon;
using IDisposable subscription = command.Subscribe(board);

_ = await command.Execute();

// Output:
// Board: The desk closes in ten minutes.

Exposing a command from a view model

A view model property typed as a concrete ReactiveCommand<TParam, TResult> or CombinedReactiveCommand<TParam, TResult> forces every caller to know which one it is. IReactiveCommand<TParam, TResult> hides that: it is the interface both classes implement, exposing Execute, CanExecute, IsExecuting and the command's results as an IObservable<TResult>, whichever concrete type sits behind it.

LibraryDesk desk = new();
DeskConsoleViewModel console = new()
{
    SearchCommand = ReactiveCommand.Create<string, IReadOnlyList<Book>>(desk.Search),
};

IReactiveCommand<string, IReadOnlyList<Book>> search = console.SearchCommand!;
IReadOnlyList<Book> matches = await search.Execute("Fowler");

Console.WriteLine(matches[0].Title);

// Output:
// Refactoring

IReactiveCommand, without the type parameters, drops Execute and the result type entirely. Use it for a method that only needs to know whether a command is busy or ready to run. A status bar is one example. Neither the command's parameter type nor its result type matters there.

LibraryDesk desk = new();
using ReactiveCommand<RxVoid, int> countShelf = ReactiveCommand.Create(() => desk.Search(string.Empty).Count);
using CombinedReactiveCommand<RxVoid, int> endOfDay = ReactiveCommand.CreateCombined([countShelf]);

_ = await countShelf.Execute();
Console.WriteLine(await DescribeAsync(countShelf));

_ = await endOfDay.Execute();
Console.WriteLine(await DescribeAsync(endOfDay));

// Output:
// Not busy, ready
// Not busy, ready
private static async Task<string> DescribeAsync(IReactiveCommand command)
{
    bool isExecuting = await command.IsExecuting.FirstAsync();
    bool canExecute = await command.CanExecute.FirstAsync();
    return $"{(isExecuting ? "Busy" : "Not busy")}, {(canExecute ? "ready" : "not ready")}";
}

DescribeAsync reads the same way for a plain command and a combined one, because both implement IReactiveCommand.

Following a command property that gets replaced

A clerk's console screen might swap in a new search command each time the clerk switches catalogue. SearchCommand on DeskConsoleViewModel is an IReactiveCommand<TParam, TResult>? property that gets reassigned, not a fixed field. Subscribing to this.WhenAnyValue(x => x.SearchCommand) gives you the command instances themselves, one per assignment. You would still have to subscribe to whichever one is current by hand, and re-subscribe every time it changes. SwitchSubscribe does that for you. Give it an observable of commands, or of any inner observable. It follows whichever one the property currently holds. It drops the old one, and it subscribes to the new one the moment the property changes.

%%{init: {"theme": "base", "themeVariables": {"fontFamily": "Roboto, Helvetica, Arial, sans-serif", "fontSize": "15px", "primaryColor": "#DCE9FF", "primaryBorderColor": "#6C8EC4", "primaryTextColor": "#0B2447", "secondaryColor": "#E3F2E8", "secondaryBorderColor": "#7FA88C", "secondaryTextColor": "#12301C", "tertiaryColor": "#F3E5F5", "tertiaryBorderColor": "#A98BB0", "tertiaryTextColor": "#2E1437", "lineColor": "#7B8699", "textColor": "#1B1F27", "noteBkgColor": "#FFF4D6", "noteBorderColor": "#C9A94F", "noteTextColor": "#3A2A00", "actorBkg": "#DCE9FF", "actorBorder": "#6C8EC4", "actorTextColor": "#0B2447", "signalColor": "#7B8699", "signalTextColor": "#1B1F27", "labelBoxBkgColor": "#F1F3F8", "labelBoxBorderColor": "#A7AEBB", "edgeLabelBackground": "#F7F9FC", "clusterBkg": "#F7F9FC", "clusterBorder": "#C9D1DE"}}}%%
flowchart LR
    Property(["SearchCommand property"]):::vm -- "replaced" --> New(["newest command"]):::vm
    Property -. "dropped" .-> Old(["previous command"]):::neutral
    Sub(["SwitchSubscribe"]):::neutral -- "follows" --> New
    classDef vm fill:#E3F2E8,stroke:#7FA88C,color:#12301C
    classDef neutral fill:#F1F3F8,stroke:#A7AEBB,color:#1B1F27

SwitchSubscribe always follows whichever command the property currently holds. The previous command's stream stops mattering the moment a new one replaces it.

1. Subscribe once, follow every command the property holds. SwitchSubscribe(Action<TResult>) subscribes to each command's results in turn.

LibraryDesk desk = new();
DeskConsoleViewModel console = new();
List<string> firstTitles = [];

using IDisposable subscription = console.WhenAnyValue(v => v.SearchCommand)
    .SwitchSubscribe(matches => firstTitles.Add(matches[0].Title));

using ReactiveCommand<string, IReadOnlyList<Book>> byTitle = ReactiveCommand.Create<string, IReadOnlyList<Book>>(desk.Search, Sequencer.Immediate);
console.SearchCommand = byTitle;
_ = await byTitle.Execute("Fowler");

using ReactiveCommand<string, IReadOnlyList<Book>> byAuthor = ReactiveCommand.Create<string, IReadOnlyList<Book>>(desk.Search, Sequencer.Immediate);
console.SearchCommand = byAuthor;
_ = await byAuthor.Execute("Gamma");

Console.WriteLine(string.Join(", ", firstTitles));

// Output:
// Refactoring, Design Patterns

Both byTitle's and byAuthor's results reach firstTitles, even though the subscription was made once, before either command was assigned to SearchCommand.

2. Add error and completion handlers where the command type can raise them. An overload takes onNext, onError and onCompleted, the same three callbacks Subscribe takes.

LibraryDesk desk = new();
DeskConsoleViewModel console = new();
List<string> titles = [];
List<string> notes = [];

using IDisposable subscription = console.WhenAnyValue(v => v.SearchCommand).SwitchSubscribe(
    matches => titles.Add(matches[0].Title),
    error => notes.Add($"Search failed: {error.Message}"),
    () => notes.Add("Command replaced"));

using ReactiveCommand<string, IReadOnlyList<Book>> search = ReactiveCommand.Create<string, IReadOnlyList<Book>>(desk.Search, Sequencer.Immediate);
console.SearchCommand = search;
_ = await search.Execute("Martin");

Console.WriteLine(string.Join(", ", titles));

// Output:
// Clean Code

onCompleted here runs only when the outer property observable itself completes, not each time one command's result stream completes. Swapping in a new command does not call it. A ReactiveCommand's result stream, in fact, never completes on its own. So onCompleted is reachable only if the outer WhenAnyValue observable ends.

3. Pick one of the command's own observables with a selector. SwitchSubscribe(selector, onNext) projects each command to one of its own observables, IsExecuting here, before switching and subscribing. That lets you drive a busy indicator from whichever command is current.

LibraryDesk desk = new();
DeskConsoleViewModel console = new();
List<bool> busyChanges = [];

using IDisposable subscription = console.WhenAnyValue(v => v.SearchCommand)
    .SwitchSubscribe(static cmd => cmd.IsExecuting, busyChanges.Add);

using ReactiveCommand<string, IReadOnlyList<Book>> search = ReactiveCommand.Create<string, IReadOnlyList<Book>>(desk.Search, Sequencer.Immediate);
console.SearchCommand = search;
_ = await search.Execute("Hunt");

Console.WriteLine(string.Join(", ", busyChanges));

// Output:
// False, True, False

The selector overload also takes error and completion handlers, for a command type that can raise them, in the same three-callback shape as step 2.

SwitchSubscribe is not limited to command properties. Any property typed as an IObservable<T> can be swapped and followed the same way. DeskConsoleViewModel.Progress is one example: a running match count that gets replaced each time a new search starts.

DeskConsoleViewModel console = new();
List<int> matchCounts = [];

using IDisposable subscription = console.WhenAnyValue(v => v.Progress).SwitchSubscribe(matchCounts.Add);

ScheduledSignal<int> firstSearch = new(Sequencer.Immediate);
console.Progress = firstSearch;
firstSearch.OnNext(1);
firstSearch.OnNext(2);

ScheduledSignal<int> secondSearch = new(Sequencer.Immediate);
console.Progress = secondSearch;
firstSearch.OnNext(3);
secondSearch.OnNext(5);

Console.WriteLine(string.Join(", ", matchCounts));

// Output:
// 1, 2, 5

Once console.Progress is reassigned to secondSearch, further values from firstSearch (3) are dropped. Only secondSearch's values (5) reach matchCounts.

A selector overload also works on a plain IObservable<T> property, when the value you want is not the property itself but something it exposes. ActiveSession holds a SearchSession record whose MatchCount is the stream that matters:

DeskConsoleViewModel console = new();
List<int> matchCounts = [];

using IDisposable subscription = console.WhenAnyValue(v => v.ActiveSession)
    .SwitchSubscribe(static session => session.MatchCount, matchCounts.Add);

ScheduledSignal<int> firstCount = new(Sequencer.Immediate);
console.ActiveSession = new SearchSession(firstCount);
firstCount.OnNext(1);

ScheduledSignal<int> secondCount = new(Sequencer.Immediate);
console.ActiveSession = new SearchSession(secondCount);
firstCount.OnNext(9);
secondCount.OnNext(4);

Console.WriteLine(string.Join(", ", matchCounts));

// Output:
// 1, 4

SwitchSelect: the observable form

Every SwitchSubscribe overload is built on SwitchSelect, which does the same switching but returns an IObservable<TValue> instead of subscribing for you. Use it when you want to compose the result further, or feed it to ToProperty, rather than subscribing directly.

LibraryDesk desk = new();
DeskConsoleViewModel console = new();
List<bool> busyChanges = [];

IObservable<bool> isBusy = console.WhenAnyValue(v => v.SearchCommand).SwitchSelect(static cmd => cmd.IsExecuting);
using IDisposable subscription = isBusy.Subscribe(busyChanges.Add);

using ReactiveCommand<string, IReadOnlyList<Book>> search = ReactiveCommand.Create<string, IReadOnlyList<Book>>(desk.Search, Sequencer.Immediate);
console.SearchCommand = search;
_ = await search.Execute("Hunt");

Console.WriteLine(string.Join(", ", busyChanges));

// Output:
// False, True, False

SwitchSelect also has an overload for a plain inner observable, without a command selector, for properties like ActiveSession above.

Every ReactiveUI package that ships these types also ships as ReactiveUI.*.Reactive, built from the same source, for apps that use System.Reactive instead.

At a glance

MemberWhat it does
ReactiveCommand<TParam, TResult> constructor (result observable)Base constructor a subclass uses to intercept every run. ReactiveCommand.Create* calls the same one.
ReactiveCommand<TParam, TResult> constructor (cancel callback)Base constructor for execution logic that cancels through a callback rather than by unsubscribing.
CombinedReactiveCommand<TParam, TResult> constructorsThree base constructors a subclass uses, with canExecute, an ISequencer, or both.
ReactiveCommandBase<TParam, TResult>Abstract base every command type shares. That includes CanExecute, IsExecuting, ThrownExceptions, Execute, Subscribe, plus the ICommand hooks a subclass overrides.
ReactiveCommandBase<TParam, TResult>.ICommandCanExecute(object?)Protected hook a subclass overrides. An ICommand caller's CanExecute routes here.
ReactiveCommandBase<TParam, TResult>.ICommandExecute(object?)Protected hook a subclass overrides. An ICommand caller's Execute routes here.
ReactiveCommandBase<TParam, TResult>.Subscribe(IObserver<TResult>)Subscribes a witness directly to a command's results.
ReactiveCommand<TParam, TResult>.Subscribe(IObserver<TResult>)The same subscription on a plain ReactiveCommand<TParam, TResult>.
IReactiveCommand<TParam, TResult>The interface to expose a command through when a view model should not commit to a concrete command type.
IReactiveCommandThe non-generic interface for code that only needs CanExecute and IsExecuting.
SwitchSubscribeSubscribes to the inner observable a property currently holds, following each replacement.
SwitchSelectThe observable form of SwitchSubscribe, for composing further or feeding to ToProperty.