Skip to content

Commit

Permalink
Add ReactiveUI Source Generators Article (#876)
Browse files Browse the repository at this point in the history
Update Boilerplate code to include partial property based Source Generators
  • Loading branch information
ChrisPulman authored Jan 29, 2025
1 parent 50513a2 commit 8b4f955
Show file tree
Hide file tree
Showing 4 changed files with 294 additions and 13 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2022 .NET Foundation and Contributors
Copyright (c) ReactiveUI 2022 - 2025

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
Expand Down
227 changes: 227 additions & 0 deletions reactiveui/articles/2025-01-28-article-on-source-generators.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
---
NoTitle: true
IsBlog: true
Title: ReactiveUI Source Generators
Tags: Article
Author: Chris Pulman
Published: 2025-01-28
---

# Leveraging `ReactiveUI.SourceGenerators` with C# 12 and Visual Studio 17.8.0

Author: Chris Pulman
Published: January 28, 2025

---

With the release of C# 12 and Visual Studio 17.8.0, writing reactive applications has become more efficient thanks to `ReactiveUI.SourceGenerators`. This library streamlines the development process by reducing boilerplate code when working with properties, commands, and reactive bindings in ReactiveUI. By using `[Reactive]`, `[ObservableAsProperty]`, and `[ReactiveCommand]` attributes, you can write clean and maintainable ViewModel code.

In this article, we'll dive into the details of these attributes, showcase examples, and discuss why `ReactiveUI.SourceGenerators` is a significant improvement over previous approaches like `ReactiveUI.Fody`.

---

## Introduction to `ReactiveUI.SourceGenerators`

`ReactiveUI.SourceGenerators` simplifies the development of reactive applications by generating common code patterns at compile-time. This removes the need to manually implement features such as property change notifications or reactive command wiring.

While previous solutions like `ReactiveUI.Fody` used IL weaving to achieve similar goals, source generators offer a more modern and robust approach. Here’s why:

### Why `ReactiveUI.SourceGenerators` Is Better Than `ReactiveUI.Fody`:
1. **Compile-Time Safety**:
- Source generators operate during the compilation process, allowing you to see generated code directly. This provides better transparency and immediate feedback when things go wrong.
- With Fody, the weaving occurs post-compilation, making debugging and troubleshooting more complex.

2. **No External Tooling**:
- Source generators are natively supported by the C# compiler. Fody requires additional tooling and configuration to function, which can complicate build pipelines.

3. **IDE Integration**:
- Generated code from source generators is accessible in modern IDEs like Visual Studio 17.8.0. You can inspect the generated code, improving both understanding and maintainability.
- Fody-generated code is invisible, leading to a "black-box" experience.

4. **Future-Proof**:
- Source generators are the recommended approach for modern C# development and will continue to receive support and improvements. Fody, while useful, is considered more of a legacy solution for projects requiring IL weaving.

---

## Prerequisites

Before exploring the examples, ensure your environment meets the following requirements:

1. **Visual Studio 17.8.0** or later.
2. **.NET 7** or higher.
3. Install the required NuGet packages:
- `ReactiveUI`
- `ReactiveUI.SourceGenerators`

Install these packages using the NuGet Package Manager or the command line:

```bash
dotnet add package ReactiveUI
dotnet add package ReactiveUI.SourceGenerators
```

## Using the `[Reactive]` Attribute
The `[Reactive]` attribute enables automatic property change notifications for private fields in your ReactiveObject-derived classes.
It eliminates the need to manually define backing fields and call RaisePropertyChanged.

### Example
```csharp
using ReactiveUI;
using ReactiveUI.SourceGenerators;

public partial class MainViewModel : ReactiveObject
{
[Reactive]
private string _firstName;

[Reactive]
private string _lastName;

public string FullName => $"{FirstName} {LastName}";
}
```

### Explanation

The `[Reactive]` attribute generates properties for the annotated private fields and wires them to notify changes via `INotifyPropertyChanged`.
In the example above, `_firstName` and `_lastName` are private fields, but they behave like reactive properties, notifying bindings or subscribers when their values change.

## Using the `[ObservableAsProperty]` Attribute

The `[ObservableAsProperty]` attribute simplifies the creation of read-only properties backed by observables.
This attribute eliminates the need to manually use ObservableAsPropertyHelper.

### Example
```csharp
using ReactiveUI;
using ReactiveUI.SourceGenerators;

public partial class MainViewModel : ReactiveObject
{
[ObservableAsProperty]
private string _status;

[Reactive]
private string _firstName;

[Reactive]
private string _lastName;

public MainViewModel()
{
var statusObservable = this.WhenAnyValue(x => x.FirstName, x => x.LastName,
(firstName, lastName) => $"{firstName} {lastName}");

statusObservable.ToProperty(this, nameof(StatusProperty));
}
}
```

### Explanation

The `[ObservableAsProperty]` attribute converts the observable statusObservable into a read-only property.
The generated property _status automatically updates whenever the observable emits a new value.
This attribute is ideal for computed properties or projections that rely on reactive updates, as it reduces boilerplate and keeps your code declarative.

The `[ObservableAsProperty]` attribute is a powerful tool for creating reactive properties that automatically update based on observable sources.
By leveraging this attribute, you can simplify your ViewModel code and improve the readability and maintainability of your reactive applications.
By decorating a `IObservable<T>` property or method with the `[ObservableAsProperty]` attribute, you can automatically update the property whenever the source observable emits a new value.
To use the `[ObservableAsProperty]` attribute in this way, you need to provide the source observable and the property name to bind to the call `InitializeOAPH();` method.
This method is generated by the source generator and initializes the property with the latest value from the source observable.

## Using the `[ReactiveCommand]` Attribute
The `[ReactiveCommand]` attribute simplifies the creation of ReactiveCommand instances.
These commands encapsulate business logic and can handle both synchronous and asynchronous operations.

### Example
```csharp
using ReactiveUI;
using ReactiveUI.SourceGenerators;
using System;
using System.Reactive;

public partial class MainViewModel : ReactiveObject
{
[ReactiveCommand]
private void Save()
{
Console.WriteLine("Save executed!");
}

[ReactiveCommand]
private IObservable<Unit> Load()
{
Console.WriteLine("Loading data...");
return Observable.Return(Unit.Default);
}
}
```

### Explanation

- Save Command: The Save method is automatically wrapped in a ReactiveCommand that can be bound to UI elements.
- Load Command: This example demonstrates how to create a command for asynchronous operations.

The generator handles all the plumbing for creating ReactiveCommand.Create or ReactiveCommand.CreateFromTask, saving significant time and effort.

### Benefits of ReactiveUI.SourceGenerators

Using ReactiveUI.SourceGenerators over traditional manual coding or Fody-based solutions provides several advantages:

- Clarity and Transparency: Generated code is visible and debuggable in IDEs, ensuring developers always understand how their application behaves.
- Reduced Boilerplate: Attributes like `[Reactive]`, `[ObservableAsProperty]`, and `[ReactiveCommand]` drastically reduce repetitive code.
- Type Safety: The source generator operates at compile-time, ensuring type safety and better integration with modern C# features.
- Improved Debugging: Since the generated code is part of the compilation pipeline, you can step into it with your debugger, unlike Fody, where changes are applied after compilation.

### Full Example: A Complete ViewModel

Below is a comprehensive example that demonstrates all three attributes in action:

```csharp
using ReactiveUI;
using ReactiveUI.SourceGenerators;
using System;
using System.Reactive;
using System.Reactive.Linq;

public partial class MainViewModel : ReactiveObject
{
[Reactive]
private string _firstName;

[Reactive]
private string _lastName;

[ObservableAsProperty]
private string _fullName;

[ReactiveCommand]
private void Save() => Console.WriteLine("Saved!");

[ReactiveCommand]
private IObservable<Unit> Load()
{
Console.WriteLine("Loading...");
return Observable.Return(Unit.Default);
}

public MainViewModel()
{
var fullNameObservable = this.WhenAnyValue(
x => x.FirstName, x => x.LastName,
(firstName, lastName) => $"{firstName} {lastName}");

fullNameObservable.ToProperty(this, nameof(FullNameProperty));
}
}
```

### Conclusion

ReactiveUI.SourceGenerators is a game-changer for developers building reactive applications. By leveraging one of the `[Reactive]` `[ObservableAsProperty]` `[ReactiveCommand]` attributes, you can write concise, readable, and maintainable code while eliminating boilerplate.

This modern approach offers compile-time safety, seamless IDE integration, and future-proof tooling, making it a superior alternative to ReactiveUI.Fody.

Upgrade your projects to use ReactiveUI.SourceGenerators today and experience the benefits of a clean and efficient reactive development workflow.

For more details, visit the [ReactiveUI.SourceGenerators](https://github.com/reactiveui/ReactiveUI.SourceGenerators) GitHub repository.
2 changes: 2 additions & 0 deletions reactiveui/articles/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,5 @@
href: 2020-07-12-article-blazor-compelling-example.md
- name: In Praise of Elevated Values
href: 2020-07-16-article-on-elevated-values.md
- name: ReactiveUI Source Generators
href: 2025-01-28-article-on-source-generators.md
76 changes: 64 additions & 12 deletions reactiveui/docs/handbook/view-models/boilerplate-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,25 @@ _firstName = firstNameObservable

With [ReactiveUI.SourceGenerators](https://www.nuget.org/packages/ReactiveUI.SourceGenerators/).

- **Minimum Requirements**:
- **C# Version**: 12.0
- **Visual Studio Version**: 17.8.0
- **ReactiveUI Version**: 19.5.31+

These Source Generators were designed to work in full with ReactiveUI V19.5.31 and newer supporting all features, currently:
- [Reactive]
- [Reactive(SetModifier = AccessModifier.Protected)]
- [ObservableAsProperty]
- [ObservableAsProperty(PropertyName = "ReadOnlyPropertyName")]
- [ObservableAsProperty(ReadOnly = false)]
- [ObservableAsProperty(UseProtected = true)]
- [ReactiveCommand]
- [ReactiveCommand(CanExecute = nameof(IObservableBoolName))] with CanExecute
- [ReactiveCommand][property: AttribueToAddToCommand] with Attribute passthrough
- [IViewFor(nameof(ViewModelName))]
- [RoutedControlHost("YourNameSpace.CustomControl")] for WinForms
- [ViewModelControlHost("YourNameSpace.CustomControl")] for WinForms
- `[Reactive]` With field and access modifiers, partial property support (C# 13 Visual Studio Version 17.12.0)
- `[ObservableAsProperty]` With field, method, Observable property and partial property support (C# 13 Visual Studio Version 17.12.0)
- `[ObservableAsProperty(ReadOnly = false)]` Removes readonly keyword from the generated helper field
- `[ObservableAsProperty(PropertyName = "ReadOnlyPropertyName")]`
- `[ObservableAsProperty(InitialValue = "Default Value")]` Only valid for partial properties using (C# 13 Visual Studio Version 17.12.0)
- `[ReactiveCommand]`
- `[ReactiveCommand(CanExecute = nameof(IObservableBoolName))]` with CanExecute
- `[ReactiveCommand(OutputScheduler = "RxApp.MainThreadScheduler")]` using a ReactiveUI Scheduler
- `[ReactiveCommand(OutputScheduler = nameof(_isheduler))]` using a Scheduler defined in the class
- `[ReactiveCommand][property: AttributeToAddToCommand]` with Attribute passthrough
- `[IViewFor(nameof(ViewModelName))]`
- `[RoutedControlHost("YourNameSpace.CustomControl")]`
- `[ViewModelControlHost("YourNameSpace.CustomControl")]`

Versions older than V19.5.31 to this:
- All functions fully supported, except for `[ReactiveCommand]` all supported except Cancellation Token asnyc methods.
Expand Down Expand Up @@ -142,6 +148,24 @@ public partial class MyReactiveClass : ReactiveObject
}
```

### Usage Reactive property from partial property

Partial properties are supported in C# 13 and Visual Studio 17.12.0 and later.
Both the getter and setter must be empty, and the `[Reactive]` attribute must be placed on the property.
Override and Virtual properties are supported.
Set Access Modifier is also supported on partial properties.

```csharp
using ReactiveUI.SourceGenerators;

public partial class MyReactiveClass : ReactiveObject
{
[Reactive]
public partial string MyProperty { get; set; }
}
```


## Usage ObservableAsPropertyHelper `[ObservableAsProperty]`

ObservableAsPropertyHelper is used to create a read-only property from an IObservable. The generated code will create a backing field and a property that returns the value of the backing field. The backing field is initialized with the value of the IObservable when the class is instantiated.
Expand Down Expand Up @@ -268,6 +292,34 @@ public partial class MyReactiveClass : ReactiveObject
}
```

### Usage ObservableAsPropertyHelper with partial Property and a default value

Partial properties are supported in C# 13 and Visual Studio 17.12.0 and later.
The getter must be empty and no setter defined (ReadOnly), and the `[ObservableAsProperty]` attribute must be placed on the property.
Override and Virtual properties are supported.
The readonly Modifier is also supported to set the Helper field not to be readonly.
An InitialValue can be set to provide a default value for the property. This is a string value that will be passed through to the defined property until the Observable is initialized.
The Partial property can be defined with different access modifiers other than public, such as protected and virtual.

```csharp
using ReactiveUI.SourceGenerators;

public partial class MyReactiveClass : ReactiveObject
{
public MyReactiveClass()
{
// The value of MyProperty will be "Default Value" until the Observable is initialized
_myPrpertyHelper = MyPropertyObservable()
.ToProperty(this, nameof(MyProperty));
}

[ObservableAsProperty(InitialValue = "Default Value")]
public string MyProperty { get; }

public IObservable<string> MyPropertyObservable() => Observable.Return("Test Value");
}
```

## Usage ReactiveCommand `[ReactiveCommand]`

Note: `InitializeCommands();` has been removed from the latest version of the Source Generators. This is now handled by the Source Generator.
Expand Down

0 comments on commit 8b4f955

Please sign in to comment.