Skip to content

Commands

Run the complete page example.

A screen has buttons: Save, Search, Delete. Each one runs some logic in the view model, and the view needs to know more than just "run this": can the button be pressed right now, is the work still running, and did it fail? ICommand is the .NET interface a button binds to. ReactiveCommand implements it and adds what a reactive view model needs: a result, a running flag and a place for errors to go.

A ReactiveCommand<TParam, TResult> is a command that takes a TParam argument and produces a TResult. It is also an IObservable<TResult>: a stream, a source of values that arrive over time. You subscribe to a stream to receive its values, and every subscription is an IDisposable you should dispose when you no longer need it. RxVoid is a type that carries no data, similar to void. Use it for a command's parameter or result when there is nothing to pass or nothing to report.

Create and run a command

1. Create the command. ReactiveCommand.Create builds a command from a synchronous method. The command below clears a filter box and gives back the empty string.

using TodoListViewModel viewModel = new(InMemoryTodoStore.CreateSeeded());
viewModel.FilterText = "bill";
using ReactiveCommand<RxVoid, string>? clearFilter = ReactiveCommand.Create(() => viewModel.FilterText = string.Empty);

_ = await clearFilter.Execute();

Console.WriteLine($"[{viewModel.FilterText}]");
[]

2. Create an asynchronous command the same way. ReactiveCommand.CreateFromTask wraps a method that returns a Task, so you can use async/await inside it. Calling Execute() on either kind of command returns a stream that delivers the result.

using TodoListViewModel viewModel = new(InMemoryTodoStore.CreateSeeded());

IReadOnlyList<TodoItem> rows = await viewModel.Load.Execute();

Console.WriteLine(rows.Count);
Console.WriteLine(viewModel.RemainingCount);
4
3

A reader who stops here can already create and run a command. The rest of this page covers the other ways to build one, how a view binds to a command, and how a command reports its state.

Create commands

Five static factory methods on ReactiveCommand cover every way to run logic. Create takes a plain method. CreateFromObservable takes logic that is already a stream. CreateFromTask takes a Task-returning method. CreateRunInBackground runs logic off the calling thread. CreateCombined runs several commands as one.

Every one of them takes an optional IObservable<bool> for canExecute and an optional ISequencer that picks where the command delivers its result. A sequencer decides when and where work runs; see Scheduling. Left out, a command uses RxSchedulers.MainThreadScheduler, the UI thread in an app.

You can also let a source generator write the command property. ReactiveUI brings ReactiveUI.SourceGenerators, so there is nothing extra to install. Mark a method with [ReactiveCommand], and the generator writes a property that calls the matching factory method for you. Commands with [ReactiveCommand] lists the method shapes it accepts and its options.

Create

Create(Action, ...) runs a plain method with no parameter and no result. Create<TParam>, Create<TResult> and Create<TParam, TResult> add a parameter, a result, or both. Each of the four shapes has four overloads: the delegate alone, with a canExecute observable, with an output ISequencer, or with both.

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

using ReactiveCommand<RxVoid, RxVoid> open = ReactiveCommand.Create(() => log.Add("Desk opened"));
using ReactiveCommand<RxVoid, RxVoid> lendBook = ReactiveCommand.Create(() => log.Add("Book lent"), desk.WhenAnyValue(d => d.IsOpen));

// The last argument is the output ISequencer: it picks where the command delivers its result, IsExecuting
// and ThrownExceptions. Left out, a command uses RxSchedulers.MainThreadScheduler, the UI thread in an app.
using ReactiveCommand<RxVoid, RxVoid> tidyShelf = ReactiveCommand.Create(() => log.Add("Shelf tidied"), Sequencer.Immediate);
using ReactiveCommand<RxVoid, RxVoid> waiveFee = ReactiveCommand.Create(() => log.Add("Late fee waived"), desk.WhenAnyValue(d => d.IsOpen), Sequencer.Immediate);

_ = await open.Execute();
_ = await lendBook.Execute();
_ = await tidyShelf.Execute();
_ = await waiveFee.Execute();

desk.IsOpen = false;

foreach (string entry in log)
{
    Console.WriteLine(entry);
}

Console.WriteLine(await lendBook.CanExecute.FirstAsync());
Desk opened
Book lent
Shelf tidied
Late fee waived
False
DelegateParameterResultOverloads
ActionnonenoneCreate(Action), Create(Action, IObservable<bool>), Create(Action, ISequencer), Create(Action, IObservable<bool>, ISequencer)
Action<TParam>TParamnoneCreate<TParam>(Action<TParam>), Create<TParam>(Action<TParam>, IObservable<bool>), Create<TParam>(Action<TParam>, ISequencer), Create<TParam>(Action<TParam>, IObservable<bool>, ISequencer)
Func<TResult>noneTResultCreate<TResult>(Func<TResult>), Create<TResult>(Func<TResult>, IObservable<bool>), Create<TResult>(Func<TResult>, ISequencer), Create<TResult>(Func<TResult>, IObservable<bool>, ISequencer)
Func<TParam, TResult>TParamTResultCreate<TParam, TResult>(Func<TParam, TResult>), Create<TParam, TResult>(Func<TParam, TResult>, IObservable<bool>), Create<TParam, TResult>(Func<TParam, TResult>, ISequencer), Create<TParam, TResult>(Func<TParam, TResult>, IObservable<bool>, ISequencer)

CreateFromObservable

CreateFromObservable wraps logic that already returns an IObservable<TResult>, so a command can run a stream directly instead of a plain value. The command below borrows a book by id, turning the synchronous lookup into a single-value stream with Signal.Emit.

LibraryDesk desk = new();

using ReactiveCommand<int, Book> borrow = ReactiveCommand.CreateFromObservable<int, Book>(bookId => Signal.Emit(desk.Borrow(bookId)));
using ReactiveCommand<int, Book> borrowWhileOpen = ReactiveCommand.CreateFromObservable<int, Book>(bookId => Signal.Emit(desk.Borrow(bookId)), desk.WhenAnyValue(d => d.IsOpen));
using ReactiveCommand<int, Book> borrowImmediate = ReactiveCommand.CreateFromObservable<int, Book>(bookId => Signal.Emit(desk.Borrow(bookId)), Sequencer.Immediate);
using ReactiveCommand<int, Book> borrowGuarded = ReactiveCommand.CreateFromObservable<int, Book>(
    bookId => Signal.Emit(desk.Borrow(bookId)),
    desk.WhenAnyValue(d => d.IsOpen),
    Sequencer.Immediate);

Book first = await borrow.Execute(1);
Book second = await borrowWhileOpen.Execute(2);
Book third = await borrowImmediate.Execute(3);
Book fourth = await borrowGuarded.Execute(4);

Console.WriteLine(first.Title);
Console.WriteLine(second.Title);
Console.WriteLine(third.Title);
Console.WriteLine(fourth.Title);
Console.WriteLine(desk.LoanCount);
Clean Code
The Pragmatic Programmer
Design Patterns
Refactoring
4
DelegateParameterOverloads
Func<IObservable<TResult>>noneCreateFromObservable<TResult>(Func<IObservable<TResult>>), CreateFromObservable<TResult>(Func<IObservable<TResult>>, IObservable<bool>), CreateFromObservable<TResult>(Func<IObservable<TResult>>, ISequencer), CreateFromObservable<TResult>(Func<IObservable<TResult>>, IObservable<bool>, ISequencer)
Func<TParam, IObservable<TResult>>TParamCreateFromObservable<TParam, TResult>(Func<TParam, IObservable<TResult>>), CreateFromObservable<TParam, TResult>(Func<TParam, IObservable<TResult>>, IObservable<bool>), CreateFromObservable<TParam, TResult>(Func<TParam, IObservable<TResult>>, ISequencer), CreateFromObservable<TParam, TResult>(Func<TParam, IObservable<TResult>>, IObservable<bool>, ISequencer)

CreateFromTask

CreateFromTask wraps a method that returns a Task, so the method can use async/await. Eight delegate shapes cover every combination of a parameter, a result and a CancellationToken; each shape has the same four overloads as Create. Passing a CancellationToken lets the command cancel the task: disposing an execution cancels the token it was given, which Cancelling commands covers in full. The example below reads the number of books a sync returns, then cancels one mid-flight and shows IsExecuting drop back to false.

LibraryDesk desk = new();

using ReactiveCommand<RxVoid, int> sync = ReactiveCommand.CreateFromTask(desk.SyncWithCentralCatalogueAsync);
using ReactiveCommand<RxVoid, int> syncWhileOpen = ReactiveCommand.CreateFromTask(desk.SyncWithCentralCatalogueAsync, desk.WhenAnyValue(d => d.IsOpen));
using ReactiveCommand<RxVoid, int> syncImmediate = ReactiveCommand.CreateFromTask(desk.SyncWithCentralCatalogueAsync, Sequencer.Immediate);
using ReactiveCommand<RxVoid, int> syncGuarded = ReactiveCommand.CreateFromTask(desk.SyncWithCentralCatalogueAsync, desk.WhenAnyValue(d => d.IsOpen), Sequencer.Immediate);

Console.WriteLine(await sync.Execute());
Console.WriteLine(await syncWhileOpen.Execute());
Console.WriteLine(await syncImmediate.Execute());
Console.WriteLine(await syncGuarded.Execute());

Task<bool> stopped = sync.IsExecuting.Where(static executing => !executing).Skip(1).FirstAsync().ToTask();
IDisposable execution = sync.Execute().Subscribe(static _ => { }, static _ => { });
execution.Dispose();
_ = await stopped;

Console.WriteLine("Sync cancelled");
4
4
4
4
Sync cancelled

IsExecuting is an IObservable<bool> that reports whether the command is running. It, like the command's result and ThrownExceptions, is delivered on the output sequencer. Every Create* factory delivers it the same way.

DelegateParameterResultCancellableOverloads (each with the same four canExecute/ISequencer combinations as Create)
Func<Task>nonenonenoCreateFromTask(Func<Task>, ...)
Func<CancellationToken, Task>nonenoneyesCreateFromTask(Func<CancellationToken, Task>, ...)
Func<Task<TResult>>noneTResultnoCreateFromTask<TResult>(Func<Task<TResult>>, ...)
Func<CancellationToken, Task<TResult>>noneTResultyesCreateFromTask<TResult>(Func<CancellationToken, Task<TResult>>, ...)
Func<TParam, Task>TParamnonenoCreateFromTask<TParam>(Func<TParam, Task>, ...)
Func<TParam, CancellationToken, Task>TParamnoneyesCreateFromTask<TParam>(Func<TParam, CancellationToken, Task>, ...)
Func<TParam, Task<TResult>>TParamTResultnoCreateFromTask<TParam, TResult>(Func<TParam, Task<TResult>>, ...)
Func<TParam, CancellationToken, Task<TResult>>TParamTResultyesCreateFromTask<TParam, TResult>(Func<TParam, CancellationToken, Task<TResult>>, ...)

A simpler no-parameter, result-returning example shows the plain four-overload shape on its own:

LibraryDesk desk = new();

_ = desk.Borrow(1);
_ = desk.Borrow(2);
using ReactiveCommand<RxVoid, int> returnAll = ReactiveCommand.CreateFromTask(desk.ReturnAllBooksWithCountAsync);
Console.WriteLine(await returnAll.Execute());
2

CreateRunInBackground

Create and CreateFromTask run their logic wherever the caller is; the caller decides whether that is off the UI thread. CreateRunInBackground instead moves the execution logic itself onto a background ISequencer, so a synchronous method that takes a while does not block the UI thread. It takes the same four delegate shapes as Create (Action, Action<TParam>, Func<TResult>, Func<TParam, TResult>), each with five overloads: the delegate alone, with canExecute, with canExecute and a background sequencer, with a background sequencer and an output sequencer, or with all three.

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

using ReactiveCommand<RxVoid, RxVoid> logOpened = ReactiveCommand.CreateRunInBackground(() => log.Add("Desk opened"));
using ReactiveCommand<RxVoid, RxVoid> logWhileOpen = ReactiveCommand.CreateRunInBackground(() => log.Add("Book lent"), desk.WhenAnyValue(d => d.IsOpen));

// The third argument is the background ISequencer, where the execute action runs; omitted, it defaults to
// RxSchedulers.TaskpoolScheduler. The fourth is the output ISequencer, where the result, IsExecuting and
// ThrownExceptions are delivered; omitted, it defaults to RxSchedulers.MainThreadScheduler.
using ReactiveCommand<RxVoid, RxVoid> logBackground = ReactiveCommand.CreateRunInBackground(
    () => log.Add("Shelf tidied"),
    desk.WhenAnyValue(d => d.IsOpen),
    Sequencer.Immediate);
using ReactiveCommand<RxVoid, RxVoid> logBackgroundAndOutput = ReactiveCommand.CreateRunInBackground(
    () => log.Add("Late fee waived"),
    Sequencer.Immediate,
    Sequencer.Immediate);
using ReactiveCommand<RxVoid, RxVoid> logAll = ReactiveCommand.CreateRunInBackground(
    () => log.Add("Catalogue reindexed"),
    desk.WhenAnyValue(d => d.IsOpen),
    Sequencer.Immediate,
    Sequencer.Immediate);

_ = await logOpened.Execute();
_ = await logWhileOpen.Execute();
_ = await logBackground.Execute();
_ = await logBackgroundAndOutput.Execute();
_ = await logAll.Execute();

foreach (string entry in log)
{
    Console.WriteLine(entry);
}
Desk opened
Book lent
Shelf tidied
Late fee waived
Catalogue reindexed
DelegateParameterResultOverloads
ActionnonenoneCreateRunInBackground(Action), CreateRunInBackground(Action, IObservable<bool>), CreateRunInBackground(Action, IObservable<bool>, ISequencer), CreateRunInBackground(Action, ISequencer, ISequencer), CreateRunInBackground(Action, IObservable<bool>, ISequencer, ISequencer)
Action<TParam>TParamnoneCreateRunInBackground<TParam>(Action<TParam>), CreateRunInBackground<TParam>(Action<TParam>, IObservable<bool>), CreateRunInBackground<TParam>(Action<TParam>, IObservable<bool>, ISequencer), CreateRunInBackground<TParam>(Action<TParam>, ISequencer, ISequencer), CreateRunInBackground<TParam>(Action<TParam>, IObservable<bool>, ISequencer, ISequencer)
Func<TResult>noneTResultCreateRunInBackground<TResult>(Func<TResult>), CreateRunInBackground<TResult>(Func<TResult>, IObservable<bool>), CreateRunInBackground<TResult>(Func<TResult>, IObservable<bool>, ISequencer), CreateRunInBackground<TResult>(Func<TResult>, ISequencer, ISequencer), CreateRunInBackground<TResult>(Func<TResult>, IObservable<bool>, ISequencer, ISequencer)
Func<TParam, TResult>TParamTResultCreateRunInBackground<TParam, TResult>(Func<TParam, TResult>), CreateRunInBackground<TParam, TResult>(Func<TParam, TResult>, IObservable<bool>), CreateRunInBackground<TParam, TResult>(Func<TParam, TResult>, IObservable<bool>, ISequencer), CreateRunInBackground<TParam, TResult>(Func<TParam, TResult>, ISequencer, ISequencer), CreateRunInBackground<TParam, TResult>(Func<TParam, TResult>, IObservable<bool>, ISequencer, ISequencer)

CreateCombined

CreateCombined runs a group of commands together as one, useful when several independent commands should also be triggerable as a batch: clearing several caches at once, or refreshing several lists together. The combined command collects every child command's result into a list. Writing your own combined command type, and the CombinedReactiveCommand constructors behind this factory, are covered in Writing your own command type.

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

using ReactiveCommand<RxVoid, int> takeBackLoans = ReactiveCommand.Create(desk.ReturnAll);
using ReactiveCommand<RxVoid, int> countShelf = ReactiveCommand.Create(() => desk.Search(string.Empty).Count);
using CombinedReactiveCommand<RxVoid, int> endOfDay = ReactiveCommand.CreateCombined([takeBackLoans, countShelf]);
IList<int> firstRun = await endOfDay.Execute();

using ReactiveCommand<RxVoid, int> countShelfWhileOpen = ReactiveCommand.Create(() => desk.Search(string.Empty).Count);
using CombinedReactiveCommand<RxVoid, int> endOfDayWhileOpen = ReactiveCommand.CreateCombined([countShelfWhileOpen], desk.WhenAnyValue(d => d.IsOpen));
IList<int> secondRun = await endOfDayWhileOpen.Execute();

// The ISequencer argument picks where the combined list is delivered; omitted, it defaults to RxSchedulers.MainThreadScheduler.
using ReactiveCommand<RxVoid, int> countShelfImmediate = ReactiveCommand.Create(() => desk.Search(string.Empty).Count);
using CombinedReactiveCommand<RxVoid, int> endOfDayImmediate = ReactiveCommand.CreateCombined([countShelfImmediate], Sequencer.Immediate);
IList<int> thirdRun = await endOfDayImmediate.Execute();

using ReactiveCommand<RxVoid, int> countShelfGuarded = ReactiveCommand.Create(() => desk.Search(string.Empty).Count);
using CombinedReactiveCommand<RxVoid, int> endOfDayGuarded = ReactiveCommand.CreateCombined([countShelfGuarded], desk.WhenAnyValue(d => d.IsOpen), Sequencer.Immediate);
IList<int> fourthRun = await endOfDayGuarded.Execute();

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

Every child command passed to CreateCombined must be the same ReactiveCommandBase<TParam, TResult> type. The combined command can run only when every child command can run, and an extra canExecute observable narrows that further.

Overload
CreateCombined<TParam, TResult>(IEnumerable<ReactiveCommandBase<TParam, TResult>>)
CreateCombined<TParam, TResult>(IEnumerable<ReactiveCommandBase<TParam, TResult>>, IObservable<bool>)
CreateCombined<TParam, TResult>(IEnumerable<ReactiveCommandBase<TParam, TResult>>, ISequencer)
CreateCombined<TParam, TResult>(IEnumerable<ReactiveCommandBase<TParam, TResult>>, IObservable<bool>, ISequencer)

A view model that just wants to reload several lists together can combine them without any of this ceremony:

using TodoListViewModel home = new(InMemoryTodoStore.CreateSeeded());
InMemoryTodoStore workStore = new();
_ = await workStore.AddAsync("Send the quarterly report", CancellationToken.None);
using TodoListViewModel work = new(workStore);

using CombinedReactiveCommand<RxVoid, IReadOnlyList<TodoItem>>? refreshAll = ReactiveCommand.CreateCombined([home.Load, work.Load]);
IList<IReadOnlyList<TodoItem>> results = await refreshAll.Execute();

Console.WriteLine(results.Count);
Console.WriteLine(home.Items.Count);
Console.WriteLine(work.Items[0].Title);
2
4
Send the quarterly report

Decide whether a command can run

CanExecute is an IObservable<bool> on every command. Give a factory method an IObservable<bool> and the command can run only while the latest value from that stream is true; a command with no canExecute observable can always run. Below, the add command follows the title box: it can run once a title is typed, and not while the box is blank.

using TodoListViewModel viewModel = new(InMemoryTodoStore.CreateSeeded());
using IDisposable subscription = viewModel.Add.CanExecute.Subscribe(Console.WriteLine);

viewModel.NewTitle = ElectricianTitle;
viewModel.NewTitle = "   ";
False
True
False

CanExecute also turns false while the command is already running, so a second click cannot start it twice. A canExecute observable is not marshalled to the output sequencer, so build it from a stream that already ticks on the thread you want, such as WhenAnyValue.

Handle errors

A command never fails as a stream: it never calls OnError. An exception thrown inside its execution logic goes to ThrownExceptions, an IObservable<Exception> on every command, delivered on the output sequencer. It also propagates to whatever awaits or subscribes to Execute(), so a view model can catch it locally and still let ThrownExceptions update the rest of the screen.

using TodoListViewModel viewModel = new(InMemoryTodoStore.CreateSeeded());
_ = await viewModel.Load.Execute();
viewModel.NewTitle = "Buy groceries";

try
{
    _ = await viewModel.Add.Execute();
}
catch (TodoStoreException)
{
    // The awaiting caller sees the error too; the screen shows it through ErrorMessage.
}

Console.WriteLine(viewModel.ErrorMessage);
Console.WriteLine(viewModel.Items.Count);
'Buy groceries' is already on the list.
4

If nothing subscribes to ThrownExceptions, an exception from a command's execution logic brings the application down. Subscribe to it, even just to log the error. Use the default exception handler to change what happens to an exception nobody handles.

Invoke commands

Call Execute() to run a command. It returns a stream that delivers the result, so await it or Subscribe() to it; the stream is cold, so nothing runs until something does one of those. InvokeCommand runs a command each time another stream ticks, useful for triggering a command from something other than a click, such as a timer or another command's result.

using TodoListViewModel viewModel = new(InMemoryTodoStore.CreateSeeded());
Task<IReadOnlyList<TodoItem>> loaded = viewModel.Load.FirstAsync().ToTask();

using IDisposable subscription = Signal.Emit(RxVoid.Default).InvokeCommand(viewModel.Load);
_ = await loaded;

Console.WriteLine(viewModel.Items.Count);
4

InvokeCommand checks CanExecute before each run, so it does nothing while the command cannot run. Dispose the subscription it returns when you no longer want the source stream driving the command.

Bind a command to a view

BindCommand is a ReactiveUI.Binding method that connects a view model's command to a control on the view: it runs the command when the control's default event fires, such as a button's Click, and disables the control while the command cannot run. Read Bindings for how binding works in depth.

using TodoListViewModel viewModel = new(InMemoryTodoStore.CreateSeeded());
_ = await viewModel.Load.Execute();
using TodoListView view = new() { ViewModel = viewModel };

using IDisposable binding = view.BindCommand(viewModel, x => x.Add, v => v.AddButton);

view.AddButton.PerformClick();
Console.WriteLine(viewModel.Items.Count);

viewModel.NewTitle = "Book electrician";
Task<TodoItem> added = viewModel.Add.FirstAsync().ToTask();
view.AddButton.PerformClick();
TodoItem item = await added;

Console.WriteLine(item.Title);
Console.WriteLine(viewModel.Items.Count);
4
Book electrician
5

Clicking while the title is blank does nothing, because Add.CanExecute is false. Pass a fourth argument, an IObservable<TParam>, to hand the command a parameter each time it runs. Below, completing a to-do item passes the item currently selected in the list.

using TodoListViewModel viewModel = new(InMemoryTodoStore.CreateSeeded());
_ = await viewModel.Load.Execute();
using TodoListView view = new() { ViewModel = viewModel };
view.ItemList.Items = viewModel.Items;

using IDisposable binding = view.BindCommand(
    viewModel,
    x => x.Complete,
    v => v.CompleteButton,
    view.WhenAnyValue(v => v.ItemList.SelectedItem).WhereNotNull());

view.ItemList.SelectedItem = viewModel.Items[0];
Task<TodoItem> completed = viewModel.Complete.FirstAsync().ToTask();
view.CompleteButton.PerformClick();
TodoItem item = await completed;

Console.WriteLine($"{item.Title}: {item.IsDone}");
Console.WriteLine(viewModel.RemainingCount);
Buy groceries: True
2

A command's life cycle

The diagram below follows one execution of a command from the moment it may run to the moment it stops running.

%%{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"}}}%%
stateDiagram-v2
    classDef vm fill:#E3F2E8,stroke:#7FA88C,color:#12301C
    classDef warn fill:#FDE7E4,stroke:#C98A82,color:#410E0B
    [*] --> CanRun
    CanRun --> Running: Execute()
    Running --> Result: logic completes
    Running --> Failed: logic throws
    Result --> CanRun
    Failed --> CanRun
    class CanRun,Running,Result vm
    class Failed warn

CanExecute is true in the first state, and IsExecuting is true only in the Running state. A result reports through the command's own stream; a failure reports through ThrownExceptions as well as through whatever awaits Execute(). Either way, the command returns to a state where it may run again, unless a canExecute observable says otherwise.

Where results are delivered

Every Create* factory takes an optional ISequencer for its output. It picks where the command delivers its result, IsExecuting and ThrownExceptions; left out, that is RxSchedulers.MainThreadScheduler. The execution logic itself is not moved to that sequencer: for Create, CreateFromTask and CreateFromObservable, it runs wherever the caller invoked Execute from. Only CreateRunInBackground also takes a background ISequencer, which moves the execution logic itself off the caller's thread.

%%{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
    classDef vm fill:#E3F2E8,stroke:#7FA88C,color:#12301C
    classDef neutral fill:#F1F3F8,stroke:#A7AEBB,color:#1B1F27
    Caller(["Caller thread"]):::neutral -- "CreateRunInBackground moves logic here" --> Background(["Background sequencer"]):::neutral
    Background -- "result, IsExecuting, ThrownExceptions" --> Output(["Output sequencer"]):::vm
    Caller -- "Create / CreateFromTask / CreateFromObservable run logic here" --> Output

Passing canExecute as the last argument is a common mistake: it is always the argument before any ISequencer, so overloads that take both list canExecute first. See Scheduling for more on sequencers, including Sequencer.Immediate, used throughout this page so the examples run synchronously.

At a glance

Each ReactiveUI package that ships a Reactive sibling (ReactiveUI.Reactive) builds ReactiveCommand and the rest of this page's types from the same source, for apps that use System.Reactive.

MemberWhat it does
ReactiveCommand.CreateBuilds a command from a plain method: Action, Action<TParam>, Func<TResult> or Func<TParam, TResult>.
ReactiveCommand.CreateFromObservableBuilds a command from a method that returns IObservable<TResult>.
ReactiveCommand.CreateFromTaskBuilds a command from a method that returns Task or Task<TResult>, optionally cancellable.
ReactiveCommand.CreateRunInBackgroundBuilds a command whose execution logic runs on a background ISequencer.
ReactiveCommand.CreateCombinedBuilds a CombinedReactiveCommand that runs several commands together.
Execute()Runs the command and returns a stream of its result.
CanExecuteAn IObservable<bool> reporting whether the command may run.
IsExecutingAn IObservable<bool> reporting whether the command is currently running.
ThrownExceptionsAn IObservable<Exception> of every error the execution logic throws.
InvokeCommandRuns a command each time a stream ticks, respecting CanExecute.
BindCommandConnects a view control's event to a command, disabling the control while it cannot run.

Production implementation: ReactiveCommand.cs.