feat: re-platform ReactiveUI onto ReactiveUI.Primitives — leaner, faster, System.Reactive optional#4382
Merged
Merged
Conversation
56f99b4 to
d748997
Compare
Replaces System.Reactive with ReactiveUI.Primitives (5.4.0) across the core and every platform, behind a lean/.Reactive seam so both surfaces ship from one shared source with an identical public API. - Default packages (ReactiveUI, ReactiveUI.Wpf, …) run on ReactiveUI.Primitives with no System.Reactive dependency, exposing RxVoid/ISequencer/Signal; the .Reactive packages expose System.Reactive Unit/IScheduler interop types over the same Primitives engine and custom fused sinks/schedulers. - Most schedulers now live in ReactiveUI.Primitives and back both distributions; in-repo custom schedulers removed in favour of the Primitives apple/android reactive sequencers, resolved via seam namespace imports (no per-file #if). - DynamicData integration (change-set routing/collection/auto-persist mixins) moved into a separate ReactiveUI.Routing package, so core no longer depends on DynamicData; core RoutingState/IScreen/RoutedViewHost stay in the main package. - Re-enable PublicAPI tracking (RS0016/RS0017/RS0037) and regenerate Shipped baselines for all TFMs, including the android Resource designer entries. - Fixes: WinForms event handler unwires synchronously; interaction runner no longer disposes a live inline-scheduled handler; WaitForDispatcherScheduler lean generic Schedule overloads; routing test app-builder initialization; WPF markup-compile _wpftmp temp project output type; example/benchmark and MAUI sample build/doc fixes. - README documents the two distributions, the type differences, the routing package split, and the performance gains. Verified: full slnx builds 0/0 on Linux and Windows; core, routing, AOT, builder, splat, testing, blazor suites green on Linux; WPF (330) and WinForms (2011) suites green on real Windows, both distributions.
2f3dacd to
cdc71db
Compare
… non-Windows run) - MauiDispatcherSequencer schedules delays via the dispatcher's native DispatchDelayed in Primitives 5.4.0, not a created timer. Update the Maui test + MockDispatcher to assert the DispatchDelayed path (the mock no longer throws from DispatchDelayed); covers ReactiveUI.Maui.Tests and the ReactiveUI.Builder.Maui.Tests project that references it. - The WPF test assemblies have no cross-platform tests left (XamlApiApprovalTests were replaced by PublicAPI baselines), so on non-Windows they ran zero tests and MTP failed the run. Add a single cross-platform smoke test (compiled into both Wpf.Tests and Wpf.Tests.Reactive via the unconditional API/ include) so the non-Windows run is valid; the real WPF tests still run on Windows.
ChrisPulman
reviewed
Jun 18, 2026
| [ExcludeFromCodeCoverage] | ||
| [AttributeUsage(AttributeTargets.All)] | ||
| internal sealed class PreserveAttribute : Attribute | ||
| public sealed class PreserveAttribute : Attribute |
ChrisPulman
reviewed
Jun 18, 2026
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="ReactiveUI.Primitives" ExcludeAssets="analyzers" /> |
Member
There was a problem hiding this comment.
This is correct, but I spotted some places where the analysers were not turned off.
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #4382 +/- ##
==========================================
- Coverage 89.93% 89.69% -0.25%
==========================================
Files 366 341 -25
Lines 15653 14948 -705
Branches 1613 1502 -111
==========================================
- Hits 14078 13408 -670
+ Misses 1219 1208 -11
+ Partials 356 332 -24 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
ChrisPulman
approved these changes
Jun 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


ReactiveUI now ships in two interchangeable distributions with an identical public API. Both are built on
the same ReactiveUI.Primitives internals and the same high-performance custom schedulers/sinks — the only
difference is which reactive interop types appear in the public API:
ReactiveUI,ReactiveUI.Wpf,ReactiveUI.WinForms,ReactiveUI.WinUI,ReactiveUI.Maui,ReactiveUI.Blazor,ReactiveUI.AndroidX, …RxVoid,ISequencer,Signal<T>ReactiveUI.Reactive,ReactiveUI.Wpf.Reactive,ReactiveUI.WinForms.Reactive,ReactiveUI.WinUI.Reactive,ReactiveUI.Maui.Reactive,ReactiveUI.Blazor.Reactive, …Unit,ISchedulerWhenAnyValue,ReactiveCommand,ToProperty/OAPH, bindings, routing and interactions are the same in both — youpick a distribution, you don't rewrite code. The
.Reactivefamily is not "old ReactiveUI": it runs on the exactsame Primitives engine and custom schedulers as the default; it simply surfaces
System.Reactive.Unit/IScheduler(and
Subject<T>) in its public API so it composes with code that already uses System.Reactive.If you upgrade the default
ReactiveUIpackages, the public reactive types change:System.Reactive.Concurrency.IScheduler→ReactiveUI.Primitives.Concurrency.ISequencer(e.g.RxApp.MainThreadScheduler/TaskpoolScheduler, also exposed viaRxSchedulers).System.Reactive.Unit→ReactiveUI.Primitives.RxVoid(e.g.ReactiveCommand<Unit, Unit>→ReactiveCommand<RxVoid, RxVoid>).Subject<T>/BehaviorSubject<T>/ReplaySubject<T>→Signal<T>/BehaviorSignal<T>/ReplaySignal<T>.System.Reactive.Linq), add a directSystem.Reactivereference or use the.Reactivepackages.DynamicData is no longer a dependency of core. The DynamicData-backed routing and collection helpers (the
change-set /
ToObservableChangeSet-style routing and auto-persist mixins) moved into a newReactiveUI.Routingpackage (
ReactiveUI.Routing.Reactivefor the System.Reactive flavor). CoreRoutingState/IScreen/RoutedViewHoststay in the main package; if you used the DynamicData routing/collection extensions, add
ReactiveUI.Routing.To take the upgrade with zero source changes, reference the matching
ReactiveUI*.Reactivepackages — they keepIScheduler,UnitandSubject<T>.Why this change?
System.Reactive is a large, general-purpose dependency, but ReactiveUI only uses a small, well-defined slice of it —
schedulers, a handful of subjects, and a few operators behind
WhenAnyValue/ToProperty/ReactiveCommand. Carryingthe whole library meant every ReactiveUI app inherited its size, its transitive footprint, and its allocation/
throughput characteristics on the hottest MVVM paths.
ReactiveUI.Primitives is a purpose-built, allocation-conscious reactive core with fused sinks tailored to exactly
those paths, plus our own scheduler implementations written largely for speed. As part of this, most of the
schedulers now live in ReactiveUI.Primitives and are shared by both distributions. Moving onto it lets us:
trimming/AOT story.
.Reactivedistribution keeps the System.Reactive interop types, so this is anopt-in change of public types, not a forced rewrite.
Performance
Representative micro-benchmarks (BenchmarkDotNet, net10.0), last fully-System.Reactive release 23.2.28 vs the new
default (Primitives) distribution. Lower is better; allocation is per operation.
WhenAnyValue— subscribeWhenAnyValue— emit (10k changes)ToProperty/ OAPHNet effect: roughly 3–4× faster on subscribe and emit, and 5–13× less allocation (e.g.
WhenAnyValue.Emitarity 1 drops from ~6.8 MB to ~0.5 MB — a ~13× reduction;
ToProperty.Createfrom ~7.3 µs to ~1.0 µs). Because thefast schedulers/sinks live in Primitives and back both distributions, the
.Reactivefamily improves over 23.2.28 toowhile keeping the System.Reactive interop types.
What kind of change does this PR introduce?
Feature — a re-platforming of ReactiveUI onto ReactiveUI.Primitives, delivered as two interchangeable distributions.
What is the new behavior?
ReactiveUI,ReactiveUI.Wpf, …) exposes the ReactiveUI.Primitives interop types andcarries no System.Reactive dependency;
.Reactivefamily (ReactiveUI.Reactive,ReactiveUI.Wpf.Reactive, …) exposes the System.Reactiveinterop types (
Unit/IScheduler) over the same Primitives engine.ReactiveUI.Routingpackage, so core no longer depends on DynamicData.
PublicAPI.Shipped.txt) are tracked again for every target framework so the surface of bothdistributions is locked and reviewable.
What is the current behavior?
ReactiveUI is a single distribution built directly on System.Reactive and DynamicData, so every consumer takes those
transitive dependencies and the System.Reactive types (
IScheduler,Unit,Subject<T>) appear throughout the publicsurface. There is no System.Reactive-free option.
What might this PR break?
See Breaking Changes above. In short: upgrading the default
ReactiveUI*packages swaps the public reactive types(
IScheduler→ISequencer,Unit→RxVoid,Subject→Signal) and drops the transitive System.Reactive/DynamicDatadependencies; the DynamicData routing/collection helpers move to
ReactiveUI.Routing. Consumers who want no sourcechanges can move to the
ReactiveUI*.Reactivepackages, which retainUnit/IScheduler/Subject<T>.Checklist
mainbranchAdditional information
Verification: the full solution builds clean on Linux and Windows; the core, routing, AOT, builder, Splat, testing and
Blazor suites are green on Linux, and the WPF (330) and WinForms (2011) suites are green on real Windows for both
distributions.