Skip to content

Members ReactiveUI.SourceGenerators writes

Run the complete page example.

ReactiveUI.SourceGenerators is another source generator. You mark a field [Reactive], a method [ReactiveCommand], or a class [IReactiveObject], and it writes the property, the command or the notification members for you. Since ReactiveUI.Binding 8.3.0, WhenAnyValue, WhenAny, Bind, OneWayBind, BindCommand and ToProperty all work on a member ReactiveUI.SourceGenerators writes. Earlier versions could not see such a member, and the call fell back to the runtime stub.

ReactiveUI.Binding cannot read ReactiveUI.SourceGenerators's generated code directly; the two run as separate generators in the same build. Instead, the binding generator reads the same attributes and follows the same rules ReactiveUI.SourceGenerators uses to decide what it writes, so it knows the shape of the member without seeing the code for it.

Observe a property written from a field

[Reactive] on a private field named _displayName writes a public property named DisplayName. The example view model below also writes a command, SaveCommand, from a method marked [ReactiveCommand].

public partial class ProfileViewModel : ReactiveObject
{
    [Reactive]
    private string _displayName = "Ada";

    public int Saves { get; private set; }

    [ReactiveCommand]
    private void Save() => Saves++;
}

WhenAnyValue reads DisplayName like any other property, even though your source never declares it: the property exists only in the code ReactiveUI.SourceGenerators writes.

ProfileViewModel profile = new ProfileViewModel();

using IDisposable subscription = profile.WhenAnyValue(x => x.DisplayName).Subscribe(Console.WriteLine);

profile.DisplayName = "Grace";
Ada
Grace

Observe a class marked IReactiveObject

[IReactiveObject] goes on a class that cannot derive from ReactiveObject, for example one that already derives from something else. ReactiveUI.SourceGenerators makes it implement IReactiveObject and raises its notifications for it. A [Reactive] field on that class still writes a property the same way.

[IReactiveObject]
public partial class ProfileCard
{
    [Reactive]
    private string _title = "Engineer";
}
ProfileCard card = new ProfileCard();

using IDisposable subscription = card.WhenAnyValue(x => x.Title).Subscribe(Console.WriteLine);

card.Title = "Lead engineer";
Engineer
Lead engineer

Bind a property written from a field

Bind connects DisplayName to a view property in both directions, the same way it connects a hand-written property.

ProfileViewModel profile = new ProfileViewModel();
ProfileView view = new ProfileView { ViewModel = profile };

using IDisposable binding = view.Bind(profile, x => x.DisplayName, v => v.NameText);

Console.WriteLine(view.NameText);

view.NameText = "Linus";

Console.WriteLine(profile.DisplayName);
Ada
Linus

Bind a button to a generated command

BindCommand connects SaveCommand, written from the Save method, to a button's click event.

ProfileViewModel profile = new ProfileViewModel();
ProfileView view = new ProfileView { ViewModel = profile };

using IDisposable binding = view.BindCommand(profile, x => x.SaveCommand, v => v.Save);

view.Save.Press();

Console.WriteLine(profile.Saves);
1

Observe from code another generator writes

The reverse gap exists too: code another source generator writes cannot call WhenAnyValue, because ReactiveUI.Binding never sees that call either. ObservedProperty, added in ReactiveUI.Binding 8.4.0, gives such code the same observation without a generated binding. It ships in both ReactiveUI.Binding and ReactiveUI.Binding.Reactive. Hand-written code keeps calling WhenAnyValue; ObservedProperty is for a generator's own output.

ObservedProperty.Create observes one property, two properties, or two properties through a selector. Each property is passed twice: as a lambda that names it, the way a generated binding reads a property, and as a delegate that reads it. Then continues a path one property further, and Switch follows a property that holds an observable, the way WhenAnyObservable does.

Generated code wantsIt calls
WhenAnyValue(x => x.Name)ObservedProperty.Create(source, x => x.Name, x => x.Name)
WhenAnyValue(x => x.Name, x => x.Age)ObservedProperty.Create(source, ...Name..., ...Age...), with an optional selector
WhenAnyValue(x => x.Home.City)ObservedProperty.Create(source, ...Home...).Then(h => h.City, h => h.City)
WhenAnyObservable(x => x.Messages)ObservedProperty.Create(source, ...Messages...).Switch()

The example below observes DisplayName, the same property Observe a property written from a field binds, but the way a generator's own output would: with ObservedProperty.Create instead of WhenAnyValue.

public static void ObserveFromGeneratedCode()
{
    ProfileViewModel profile = new ProfileViewModel();

    using IDisposable subscription = ObservedProperty
        .Create(profile, static x => x.DisplayName, static x => x.DisplayName)
        .Subscribe(Console.WriteLine);

    profile.DisplayName = "Grace";

    // Output:
    // Ada
    // Grace
}
Ada
Grace

When a member is still out of reach

A view member another source generator writes, such as a field a UI framework's XAML compiler adds to a partial view class, is not one ReactiveUI.SourceGenerators writes, so ReactiveUI.Binding still has no generated binding for it. A plain call on such a member reports RXUIBIND021 at build time and throws when it runs. Call the Unsafe twin instead: Unsafe twins and the runtime fallback shows BindUnsafe and BindCommandUnsafe on a view built this way.

Run the examples

The documentation examples live in folders named for their page. From the repository's src folder, run the example for this page:

dotnet run --project examples/Documentation/Pages/sourcegenerators/sourcegenerators.csproj -c Release