ReactiveGenerator is a modern C# source generator that automates property change notification implementation, supporting both standard INotifyPropertyChanged
and ReactiveUI patterns. It provides a robust, type-safe solution for generating reactive properties with comprehensive design-time support.
- Automated
INotifyPropertyChanged
implementation - Support for ReactiveUI's
ReactiveObject
pattern - Efficient, cached
PropertyChangedEventArgs
- Thread-safe weak event handling
- Automated generation of
ObservableAsPropertyHelper
properties - Simplifies ReactiveUI's observable-to-property pattern
- Reduces boilerplate code for computed properties
- Enhances readability and maintainability
- Class-level reactivity with
[Reactive]
attribute - Property-level granular control with
[Reactive]
and[ObservableAsProperty]
- Selective opt-out using
[IgnoreReactive]
- Support for custom property implementations
- Full nullable reference type support
- Inheritance-aware property generation
- Configurable backing field generation
- Comprehensive design-time support
- Automatic
WhenAnyValue
observable generation
dotnet add package ReactiveGenerator
-
.NET 9.0+
-
C# 13.0+
-
Visual Studio 2022 or compatible IDE
-
Project configuration:
<LangVersion>preview</LangVersion>
[Reactive]
public partial class Person
{
public partial string FirstName { get; set; }
public partial string LastName { get; set; }
}
public partial class Person
{
[Reactive]
public partial string FirstName { get; set; }
[Reactive]
public partial string LastName { get; set; }
}
public partial class DashboardViewModel : ReactiveObject
{
public DashboardViewModel()
{
this.WhenAnyValue(x => x.DataStream)
.Select(data => ProcessData(data))
.ToProperty(this, x => x.ProcessedData);
}
[ObservableAsProperty]
public partial string ProcessedData { get; }
[Reactive]
public partial IObservable<string> DataStream { get; set; }
}
Seamlessly integrate with ReactiveUI to harness its powerful reactive programming features.
public partial class ViewModel : ReactiveObject
{
[Reactive]
public partial string SearchText { get; set; }
public ViewModel()
{
this.WhenAnyValue(x => x.SearchText)
.Throttle(TimeSpan.FromMilliseconds(500))
.Subscribe(text => ExecuteSearch(text));
}
private void ExecuteSearch(string query)
{
// Search logic here
}
}
Using the generator's automatic WhenAny
methods:
public partial class ViewModel : ReactiveObject
{
[Reactive]
public partial string SearchText { get; set; }
public ViewModel()
{
this.WhenAnySearchText()
.Throttle(TimeSpan.FromMilliseconds(500))
.Subscribe(text => ExecuteSearch(text));
}
}
Opt out of class-level reactivity:
[Reactive]
public partial class Shape
{
public partial string Name { get; set; } // Reactive
[IgnoreReactive]
public partial string Tag { get; set; } // Non-reactive
}
Simplify the creation of computed properties using ObservableAsPropertyHelper
:
public partial class StatisticsViewModel : ReactiveObject
{
public StatisticsViewModel()
{
this.WhenAnyValue(x => x.Values)
.Select(values => values.Average())
.ToProperty(this, x => x.AverageValue);
}
[ObservableAsProperty]
public partial double AverageValue { get; }
[Reactive]
public partial IList<double> Values { get; set; }
}
Customize property implementations while still leveraging the generator:
[Reactive]
public partial class Component
{
[IgnoreReactive]
public partial string Id { get; set; }
}
public partial class Component
{
private string _id = Guid.NewGuid().ToString();
public partial string Id
{
get => _id;
private set => _id = value;
}
}
Leverage the power of ReactiveUI with less boilerplate:
public partial class WeatherViewModel : ReactiveObject
{
public WeatherViewModel()
{
this.WhenAnyValue(x => x.City)
.Throttle(TimeSpan.FromMilliseconds(500))
.SelectMany(city => FetchWeatherAsync(city))
.ToProperty(this, x => x.CurrentWeather);
}
[ObservableAsProperty]
public partial WeatherData CurrentWeather { get; }
[Reactive]
public partial string City { get; set; }
private Task<WeatherData> FetchWeatherAsync(string city)
{
// Async API call to fetch weather data
}
}
<PropertyGroup>
<!-- Use explicit backing fields instead of field keyword -->
<UseBackingFields>true</UseBackingFields>
</PropertyGroup>
ReactiveGenerator produces several key types:
PropertyObserver<TSource, TProperty>
: Handles property observationWeakEventManager<TDelegate>
: Manages thread-safe weak events- Property-specific extension methods (e.g.,
WhenAnyPropertyName
) ObservableAsPropertyHelper<T>
backing fields and properties
-
Property Generation:
- Properties without implementations receive reactive implementations
- Existing implementations are preserved
- Custom implementations take precedence
-
Declaration Requirements:
- Classes containing reactive properties must be
partial
- Reactive properties must be declared as
partial
[Reactive]
and[ObservableAsProperty]
can be applied at class or property level
- Classes containing reactive properties must be
// ❌ Incorrect
[Reactive]
public class Example
{
public string Property { get; set; }
}
// ✅ Correct
[Reactive]
public partial class Example
{
public partial string Property { get; set; }
}
// ❌ Incorrect
public class ViewModel : ReactiveObject
{
[ObservableAsProperty]
public string ComputedValue { get; }
}
// ✅ Correct
public partial class ViewModel : ReactiveObject
{
[ObservableAsProperty]
public partial string ComputedValue { get; }
}
- Properties must be declared as
partial
- Classes must be declared as
partial
- Custom implementations require
[IgnoreReactive]
attribute
We welcome contributions! Please follow these steps:
- Fork the repository
- Create a feature branch
- Submit a Pull Request
- For major changes, open an issue first
ReactiveGenerator is licensed under the MIT license. See LICENSE file for details.
For questions or feedback, please open an issue or reach out to the maintainer.