ReactiveUI Documentation

repository·main·Indexed 25 days ago

https://github.com/reactiveui/reactiveui

A composable, cross-platform MVVM framework for .NET inspired by functional reactive programming. It provides platform-specific packages for WPF, WinUI, MAUI, Windows Forms, AndroidX, Blazor, Uno Platform, and Avalonia, offering both a high-performance default distribution and a System.Reactive interop distribution.

Tokens
5.7K
Snippets
7
Records
29
Agent score
94%

What's inside ReactiveUI

  1. Migrate from RxApp to RxSchedulers

    main

    To migrate existing code to use RxSchedulers and remove attribute requirements:

    1. Replace RxApp.MainThreadScheduler with RxSchedulers.MainThreadScheduler.
    2. Replace RxApp.TaskpoolScheduler with RxSchedulers.TaskpoolScheduler.
    3. Remove RequiresUnreferencedCode and RequiresDynamicCode attributes if they were only needed for scheduler access.
    4. Use ReactiveProperty<T>.Create() factory methods instead of constructors.
    5. Test that unit tests still work (you may need to manually set test schedulers if you relied on automatic detection).
  2. Install ReactiveUI NuGet packages

    main

    ReactiveUI provides platform-specific packages to ensure proper performance and integration. You must install the core package along with the package corresponding to your target platform.

    Core and Utility Packages:

    • .NET Standard: ReactiveUI
    • Any: ReactiveUI.SourceGenerators (for boilerplate reduction)
    • Any: ReactiveUI.Validation (for user input validation)
    • Any: ReactiveUI.Extensions
    • Unit Testing: ReactiveUI.Testing

    Platform-Specific Packages:

    • WPF: ReactiveUI.WPF
    • WinUI: ReactiveUI.WinUI
    • MAUI: ReactiveUI.Maui
    • Windows Forms: ReactiveUI.WinForms
    • AndroidX: ReactiveUI.AndroidX
    • Blazor: ReactiveUI.Blazor
    • Uno Platform: ReactiveUI.Uno or ReactiveUI.Uno.WinUI
    • Avalonia: ReactiveUI.Avalonia
  3. Use RxSchedulers to avoid RequiresUnreferencedCode attributes

    main

    When writing library code, ViewModels, or Repositories, using RxApp.MainThreadScheduler or RxApp.TaskpoolScheduler triggers the RequiresUnreferencedCode attribute because it initializes the Splat dependency injection system.

    To avoid forcing consumers to add these attributes, use the RxSchedulers static class instead. RxSchedulers provides access to the same scheduler functionality without triggering the reflection-based initialization.

    // Old way - requires RequiresUnreferencedCode attribute
    [RequiresUnreferencedCode("Uses RxApp which may require unreferenced code")]
    public IObservable<string> GetDataOld()
    {
        return Observable.Return("data")
            .ObserveOn(RxApp.MainThreadScheduler);
    }
    
    // New way - no attributes required
    public IObservable<string> GetDataNew()
    {
        return Observable.Return("data")
            .ObserveOn(RxSchedulers.MainThreadScheduler);
    }
  4. Regenerate PublicAPI baseline files

    main

    The generate-publicapi tool regenerates the PublicAPI baseline files used by Microsoft.CodeAnalysis.PublicApiAnalyzers (diagnostics RS0016, RS0017, RS0037). These files track the public surface of each shipped library per target framework (TFM) in:

    src/<Project>/PublicAPI/<tfm>/PublicAPI.Shipped.txt src/<Project>/PublicAPI/<tfm>/PublicAPI.Unshipped.txt

    You must run this tool whenever you add, remove, or change public API, or add a new target framework to a tracked library.

    Process:

    1. Resets baseline files to #nullable enable.
    2. Runs dotnet format analyzers to populate PublicAPI.Unshipped.txt with the current surface.
    3. Folds the surface into PublicAPI.Shipped.txt (sorted and deduped) and resets PublicAPI.Unshipped.txt to the bare header.

    Note: Always review the diff in PublicAPI.Unshipped.txt before committing, as it represents your public API changes.

    ### Linux / macOS
    ```bash
    tools/generate-publicapi.sh                 # all tracked libraries, all buildable TFMs
    tools/generate-publicapi.sh Async           # only projects whose path contains 'Async'
    tools/generate-publicapi.sh ReactiveUI.Wpf

    Windows (PowerShell)

    ./tools/generate-publicapi.ps1                  # all tracked libraries
    ./tools/generate-publicapi.ps1 -Filter Async    # path filter
  5. Implement reactive properties with `RaiseAndSetIfChanged`

    main

    While the C# field keyword is preferred for standard properties with backing logic, ReactiveUI's reactive properties must continue to use the canonical RaiseAndSetIfChanged pattern with an explicit backing field. This is because RaiseAndSetIfChanged is a ref-passing API.

    Example pattern:

    private string _name;
    public string Name
    {
        get => _name;
        set => this.RaiseAndSetIfChanged(ref _name, value);
    }
  6. Use optimized pattern matching and flow control

    main

    To maintain high performance and clean code, follow these C# patterns:

    • Flatten the happy path: Use guard clauses and early return or continue to avoid nested if/else blocks.
    • Prefer switch expressions: Use switch expressions over if/else chains where possible (e.g., property patterns like { HasValue: true }).
    • Use list patterns for collections: Use is [] for empty collections, is [_, ..] for non-empty, and is [var single] to bind a single-element collection.
    • Use is / is not: Prefer these for null and type checks over == or !=.
    • Avoid while (true): Express termination conditions in the loop header, such as while (!cancellationToken.IsCancellationRequested).
  7. Choose a ReactiveUI distribution: Primitives vs System.Reactive

    main

    ReactiveUI offers two interchangeable distributions with an identical public API. Both use the same high-performance engine and schedulers. The choice depends on whether you need compatibility with existing System.Reactive code.

    1. Default Distribution (Lighter/Faster)

    Use this for new projects to benefit from a smaller dependency footprint, better trimming/AOT support, and significantly higher performance (3–4× faster on WhenAnyValue/ToProperty and 5–13× less allocation).

    • Packages: ReactiveUI, ReactiveUI.Wpf, ReactiveUI.Maui, etc.
    • Public Types: ReactiveUI.PrimitivesRxVoid, ISequencer, Signal<T>

    2. System.Reactive Interop Distribution

    Use this for drop-in compatibility with existing codebases that rely on System.Reactive types.

    • Packages: ReactiveUI.Reactive, ReactiveUI.Wpf.Reactive, ReactiveUI.Maui.Reactive, etc.
    • Public Types: System.ReactiveUnit, IScheduler, Subject<T>

    Note on Routing: If you use DynamicData change-set routing, collection, or auto-persist helpers, you must also add the ReactiveUI.Routing package (or ReactiveUI.Routing.Reactive for the System.Reactive flavor).

  8. Follow async best practices

    main

    For async paths (e.g., ReactiveCommand.CreateFromTask, interaction handlers), adhere to these standards:

    • No sync-over-async: Never use .GetAwaiter().GetResult(), .Result, or .Wait().
    • Use ConfigureAwait(false): Apply this to every library await in production code.
    • Propagate Cancellation: Always pass CancellationToken through the pipeline. Use CancellationTokenSource.CreateLinkedTokenSource once at subscribe time rather than per emission.
    • Prefer ValueTask for zero-alloc paths: Use ValueTask when implementations frequently complete synchronously. Use Task for I/O-dominant or cold paths. Remember the consume-once rule for ValueTask (do not await the same instance twice).
    • Return completed tasks directly: For synchronous implementations, return ValueTask.CompletedTask or Task.CompletedTask to avoid state machine allocations.
  9. Handle strings efficiently in hot paths

    main

    While string is a first-class type in ReactiveUI, follow these rules to avoid unnecessary allocations:

    • Lazy string building: Do not use string interpolation or string.Format inside per-emission paths for values that might only be used on failure. Build the message lazily on the throw path.
    • Use nameof(...): Always use nameof(...) instead of string literals for member references.
    • Ordinal comparisons: Use StringComparer.Ordinal or StringComparison.Ordinal for identifier, type-name, and property-name comparisons.
    • Use Spans for parsing: Use ReadOnlySpan<char> and range expressions (e.g., path[..i]) instead of Substring when slicing member paths.
  10. Migrate from Xamarin to .NET MAUI

    main

    As of May 2024, ReactiveUI has removed support for legacy Xamarin platforms. Users should migrate to .NET MAUI using the following mappings:

    • Xamarin.Forms $\rightarrow$ MAUI (using ReactiveUI.Maui)
    • Xamarin.Android $\rightarrow$ MAUI Android or AndroidX (using ReactiveUI.AndroidX for native Android)
    • Xamarin.iOS/Mac $\rightarrow$ MAUI iOS/Mac Catalyst
  11. Follow ReactiveUI API shape and design standards

    main

    When designing or extending APIs, follow these structural rules to ensure compatibility and performance:

    • Avoid default parameter values: Do not use default values in new public APIs, as they are baked into the caller's IL. Instead, provide explicit overloads that delegate to the most-specific version.
    • Use concrete collection types: For new production APIs, prefer IReadOnlyList<T>, T[], Dictionary<K,V>, or HashSet<T> over IEnumerable<T>. Use IEnumerable<T> only when a streaming yield is genuinely required.
    • Seal non-inheritable classes: Mark classes as sealed by default unless they are specifically designed for derivation (e.g., ReactiveObject).
    • Prefer static methods: If a method does not access instance state (this), mark it as static to assist with devirtualization and reduce hidden allocations.
    • Use readonly record struct for data: For small (≤ 4–5 fields) immutable value-shaped data, use readonly record struct to reduce GC pressure.