ReactiveUI.Primitives.Async
7.5.0
Prefix Reserved
dotnet add package ReactiveUI.Primitives.Async --version 7.5.0
NuGet\Install-Package ReactiveUI.Primitives.Async -Version 7.5.0
<PackageReference Include="ReactiveUI.Primitives.Async" Version="7.5.0" />
<PackageVersion Include="ReactiveUI.Primitives.Async" Version="7.5.0" />
<PackageReference Include="ReactiveUI.Primitives.Async" />
paket add ReactiveUI.Primitives.Async --version 7.5.0
#r "nuget: ReactiveUI.Primitives.Async, 7.5.0"
#:package ReactiveUI.Primitives.Async@7.5.0
#addin nuget:?package=ReactiveUI.Primitives.Async&version=7.5.0
#tool nuget:?package=ReactiveUI.Primitives.Async&version=7.5.0
<br>
<a href="https://www.nuget.org/packages/ReactiveUI.Primitives">
<img src="https://img.shields.io/nuget/dt/ReactiveUI.Primitives.svg">
</a>
<a href="https://reactiveui.net/slack">
<img src="https://img.shields.io/badge/chat-slack-blue.svg">
</a>
<img alt="ReactiveUI.Primitives" width="160" height="160" src="https://github.com/reactiveui/styleguide/blob/master/logo_primitives/logo.png?raw=true">
ReactiveUI.Primitives
ReactiveUI.Primitives is a small, fast library for reactive programming in .NET. Reactive programming means working with values that arrive over time, such as button clicks, timer ticks, or network replies, rather than values you already hold.
If you know LINQ, you already know the shape. LINQ queries a collection you already hold and pulls values out of an
IEnumerable<T>. Reactive programming queries values that arrive over time: an IObservable<T> pushes each value to
you as it happens. The operators carry over, so Select, Where, and Aggregate keep their meaning here. This library
also gives them the names Map, Keep, and Fold.
It gives you that model without a runtime dependency on System.Reactive, R3, or R3Async. Those are the established reactive libraries for .NET, and this package stands in for them in the common cases.
It builds on two interfaces that .NET already ships. IObservable<T> is a source you subscribe to. IObserver<T> is
the subscriber that receives each value. The library renames a few common concepts for clarity. It also favours code
paths that allocate little memory and run under ahead-of-time (AOT) compilation. AOT compiles the app to native code
before it runs, so the app cannot generate new code while running.
Goals and design posture
ReactiveUI.Primitives aims to:
- Cover the Rx model over
IObservable<T>: creating streams, subscribing, holding state, scheduling work, and composing operators. A stream is a sequence of values delivered over time. - Rename a few concepts where a clearer name helps. A
Signal<T>is a source you can both push values into and subscribe to (Rx calls this aSubject<T>).Maptransforms each value (RxSelect);Keepfilters values (RxWhere);Sparkturns each notification into a value you can inspect. - Stay AOT-friendly. The production package uses no runtime reflection, no generated code, no expression compilation, and no hidden dependency on System.Reactive, R3, or R3Async.
- Allocate as little as possible on hot paths. For example,
Signal<T>subscribes a single delegate directly, and the common return, empty, and never sources reuse one shared instance. - Run in production across modern .NET and .NET Framework, with separate integration packages for Windows UI and other
platforms. A target framework (TFM) is the .NET version and platform a build targets, such as
net8.0. - Support migration. The
.Reactivepackage variants match System.Reactive's public surface, and source-generator bridges connect to R3 or R3Async when your project already uses them.
Why not System.Reactive or R3?
System.Reactive is the original Rx library for .NET, and the reason IObservable<T> exists. It is mature and widely
used. Its weak point is performance: a typical operator chain allocates several objects per operator and per value, and
that grows under heavy load.
R3 is a newer library aimed at that weak point. It is fast. It reaches that speed partly by replacing IObservable<T>
with its own Observable<T> type. That swap means existing code, and the wider ecosystem built on IObservable<T>,
does not carry over without adaptation.
We wanted the speed without the break, so we kept IObservable<T>, the interface .NET already ships and most C# code
already knows. Our benchmarks pointed at the cause: the interface was not the bottleneck. The cost lived in how the
operators were implemented, not in the abstraction. So we kept the familiar contract and rebuilt the operators as
low-allocation sinks (see Why the operators are built this way).
This keeps the change small for anyone already on IObservable<T>. You keep the contract and the mental model, and you
gain the lower allocation profile. When you do need full System.Reactive or R3 behaviour, the .Reactive package
variants and the R3/R3Async source-generator bridges cover those boundaries.
Where we could not stay on the standard types
Keeping IObservable<T> and IObserver<T> was easy, because both ship in .NET itself. Two related types do not, so we
had to make a call.
The first is the scheduler. A scheduler decides when and on which thread work runs. .NET has no scheduler type of its
own. The standard one, IScheduler, lives in System.Reactive, so using it would pull System.Reactive back in as a
runtime dependency. That is the dependency we set out to avoid. So the lean library defines its own small scheduling
contract, ISequencer.
The second is Unit. Unit is the type that means "a value carrying no information", used for streams that report that
something happened but carry no data. .NET has no such type either, and the common Unit also lives in System.Reactive.
So the lean library defines its own, RxVoid.
These two types are the only places the lean surface departs from the System.Reactive shape. The .Reactive package
variants close the gap: they recompile the same source with ISequencer mapped to IScheduler and RxVoid mapped to
System.Reactive.Unit, so code that already speaks System.Reactive sees the types it expects.
Disposal groups are a third seam, and one the shared types cannot close on their own: MultipleDisposable ships in the
dependency-free ReactiveUI.Disposables package, so it cannot name CompositeDisposable. ReactiveUI.Primitives.Reactive
adds ContainerDisposable for that - a MultipleDisposable that converts implicitly to a CompositeDisposable it owns
and disposes. Hand one to DisposeWith, to a library that takes a CompositeDisposable, or to your own helper, and it
just works; anything registered through the composite is disposed with the container.
Table of contents
- Install
- Agent Skills
- Target frameworks and dependencies
- Core model
- Creation factories
- Operators
- ReactiveUI.Primitives.Async
- Extension helpers
- Stateful signals and subject-like types
- Sequencers
- Threading, disposal, and error semantics
- Source-generator bridge behavior
- Migration guides
- Benchmarks and performance posture
- Repository layout
Install
All packages are published on NuGet.org. Install the base package:
dotnet add package ReactiveUI.Primitives
The library is split into a layered set of packages, so you can pull only the surface that matches your integration point. Every package below is produced by a packable project in the current solution and ships at the same version. Target frameworks vary by package; the exact matrices are documented under Target frameworks and dependencies.
| Package | NuGet | Use when |
|---|---|---|
| ReactiveUI.Disposables | You only need the disposable primitives such as Disposable, MultipleDisposable, Slot, or Pocket. |
|
| ReactiveUI.Primitives.Core | The type-agnostic core shared by the lean and System.Reactive-flavoured leaves (usually a transitive dependency). | |
| ReactiveUI.Primitives | The default lean signal/operator/sequencer package, including the migrated ReactiveUI.Extensions helpers. |
|
| ReactiveUI.Primitives.Reactive | The Primitives and extension-helper APIs compiled against System.Reactive Unit and IScheduler. |
|
| ReactiveUI.Primitives.Async.Core | The type-agnostic async core shared by the async leaves. | |
| ReactiveUI.Primitives.Async | Native IObservableAsync<T> / IObserverAsync<T> signals. |
|
| ReactiveUI.Primitives.ObservableEvents | Optional analyzer package that exposes .NET events as provider-native IObservable<T> properties. |
|
| ReactiveUI.Primitives.R3Bridge.Generator | Optional analyzer package that generates R3 and R3Async bridge adapters. | |
| ReactiveUI.Primitives.Async.Reactive | Async Primitives compiled against System.Reactive Unit and IScheduler. |
|
| ReactiveUI.Primitives.Wpf | WPF dispatcher sequencer integration. | |
| ReactiveUI.Primitives.Wpf.Reactive | WPF dispatcher scheduler integration for System.Reactive-first projects. | |
| ReactiveUI.Primitives.WinForms | Windows Forms control sequencer integration. | |
| ReactiveUI.Primitives.WinForms.Reactive | Windows Forms control scheduler integration for System.Reactive-first projects. | |
| ReactiveUI.Primitives.WinUI | WinUI dispatcher-queue sequencer integration. | |
| ReactiveUI.Primitives.WinUI.Reactive | WinUI dispatcher-queue scheduler integration for System.Reactive-first projects. | |
| ReactiveUI.Primitives.Blazor | Blazor renderer sequencer integration. | |
| ReactiveUI.Primitives.Blazor.Reactive | Blazor renderer scheduler integration for System.Reactive-first projects. | |
| ReactiveUI.Primitives.Avalonia | Avalonia UI-thread sequencer integration. | |
| ReactiveUI.Primitives.Avalonia.Reactive | Avalonia UI-thread scheduler integration for System.Reactive-first projects. | |
| ReactiveUI.Primitives.Maui | MAUI dispatcher sequencer integration. | |
| ReactiveUI.Primitives.Maui.Reactive | MAUI dispatcher scheduler integration for System.Reactive-first projects. |
How the packages layer
The base and async families use type-agnostic .Core projects, with a lean leaf binding the shared
RxVoid/ISequencer source to lightweight implementations and a .Reactive leaf recompiling it against
System.Reactive's Unit/IScheduler. Type-agnostic extension-helper sources are compiled into
ReactiveUI.Primitives.Core, while the lean and System.Reactive helper surfaces ship from ReactiveUI.Primitives and
ReactiveUI.Primitives.Reactive. The src/ReactiveUI.Primitives.Extensions.Core directory is source only; it is not a
project or NuGet package. The platform packages also come in lean and .Reactive leaves. (Arrows point from a package
to what it depends on.)
graph TD
SR["System.Reactive"]
Disp["ReactiveUI.Disposables"]
Core["ReactiveUI.Primitives.Core"]
Prim["ReactiveUI.Primitives<br/>(lean)"]
Rx["ReactiveUI.Primitives.Reactive"]
AsyncCore["...Async.Core"]
Async["...Async (lean)"]
AsyncRx["...Async.Reactive"]
Plat["Wpf / WinForms / WinUI / Blazor<br/>Avalonia / Maui"]
PlatRx["Wpf.Reactive / WinForms.Reactive / WinUI.Reactive<br/>Blazor.Reactive / Avalonia.Reactive / Maui.Reactive"]
Core --> Disp
Prim --> Core
Prim --> Disp
Rx --> Core
Rx --> SR
AsyncCore --> Core
Async --> Prim
Async --> AsyncCore
AsyncRx --> Rx
AsyncRx --> AsyncCore
Plat --> Prim
PlatRx --> Rx
ReactiveUI.Primitives.Extensions and ReactiveUI.Primitives.Extensions.Reactive are no longer separate projects or
NuGet packages. Their implementations now ship from ReactiveUI.Primitives and ReactiveUI.Primitives.Reactive,
respectively. No API code was removed: the former lean Extensions package already depended on
ReactiveUI.Primitives, and the former Reactive Extensions package already depended on
ReactiveUI.Primitives.Reactive. Replace only the package reference; the existing
ReactiveUI.Primitives.Extensions* namespaces remain unchanged.
Then import the namespaces you need:
using ReactiveUI.Primitives;
using ReactiveUI.Primitives.Async;
using ReactiveUI.Primitives.Concurrency;
using ReactiveUI.Primitives.Disposables;
using ReactiveUI.Primitives.Extensions;
using ReactiveUI.Primitives.Extensions.Reactive;
using ReactiveUI.Primitives.Async.Signals;
using ReactiveUI.Primitives.Async.Reactive;
using ReactiveUI.Primitives.Reactive;
using ReactiveUI.Primitives.Signals;
The package metadata is configured to include this README in the NuGet package via PackageReadmeFile=README.md. The
base package also packs Skill.md at the package root and a Codex-ready copy at
.agents/skills/reactiveui-primitives/SKILL.md.
R3 and R3Async bridge generation lives in the standalone ReactiveUI.Primitives.R3Bridge.Generator analyzer package:
dotnet add package ReactiveUI.Primitives.R3Bridge.Generator
That generator does not add runtime R3 or R3Async dependencies to ReactiveUI.Primitives. It emits bridge code only when
the consuming compilation already references the relevant external library symbols. System.Reactive interop is provided
by the .Reactive package variants rather than by generated System.Reactive bridge methods.
Agent Skills
The base ReactiveUI.Primitives NuGet package includes Skill.md at the package root and a Codex-ready copy at
.agents/skills/reactiveui-primitives/SKILL.md. It is an agent-oriented guide for choosing the correct
ReactiveUI.Primitives package, using Async, extension helpers, UI sequencers, bridge source generators, and migration from
System.Reactive package variants, R3, or R3Async while assuming the libraries are consumed from NuGet packages.
After package restore, locate the file in the local NuGet package cache:
$version = "<version>"
$skill = "$env:USERPROFILE\.nuget\packages\reactiveui.primitives\$version\.agents\skills\reactiveui-primitives\SKILL.md"
On macOS or Linux:
version="<version>"
skill="$HOME/.nuget/packages/reactiveui.primitives/$version/.agents/skills/reactiveui-primitives/SKILL.md"
Install or link the packaged SKILL.md into the instruction location supported by the agent.