feat: interface-based observation plugin system with ReactiveUI-aligned affinities#14
Conversation
…ed selection
Replace 7 static observation generator classes with an interface-based plugin
system where plugins compete by affinity to generate platform-specific,
reflection-free observation code. Affinity values now match ReactiveUI's exact
runtime values (KVO=15, ReactiveObject=10, WinForms=8, WinUI=6, INPC=5,
Android=5, WPF=4).
Each plugin generates code that mirrors the corresponding ReactiveUI runtime
implementation but without reflection:
- WPF: EventObservable with DependencyPropertyDescriptor.AddValueChanged
- WinUI: inline __WinUIDPObservable with RegisterPropertyChangedCallback
- WinForms: EventObservable with direct {Property}Changed event subscription
- KVO: inline __KVOObservable with NSObject.AddObserver and compile-time
resolved key paths (lowercase first char, boolean "is" prefix)
- INPC/ReactiveObject: PropertyObservable/PropertyChangingObservable
- Android: ReturnObservable fallback (no INPC on Android View)
Pipeline A simplified from 7 separate filter calls + Consolidate() to a single
plugin-based step via ObservationPluginRegistry.GetBestPlugin().
New runtime type EventObservable<T> provides platform-agnostic event-based
observation using pure BCL types (Action<EventHandler> add/remove pattern).
100% line and branch coverage maintained across the full solution (7236 tests).
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #14 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 118 121 +3
Lines 1949 2014 +65
Branches 364 375 +11
=========================================
+ Hits 1949 2014 +65 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR refactors the ReactiveUI.Binding source generator’s property observation pipeline from multiple static, per-platform generators into an interface-based observation plugin system, and introduces new platform-specific observation outputs (WinUI DP callbacks, WPF DP descriptor hooks, WinForms {Property}Changed events, Apple KVO) that align with ReactiveUI’s runtime affinity model.
Changes:
- Added an
IObservationPluginabstraction with concrete platform plugins and a centralObservationPluginRegistryto select the best strategy by affinity. - Introduced
EventObservable<T>(runtime) to support platform-agnostic event-based observation (used by WPF/WinForms strategies). - Updated generator logic and snapshot tests to reflect new platform-specific observation code and simplified dispatch.
Reviewed changes
Copilot reviewed 51 out of 53 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tests/ReactiveUI.Binding.Tests/Observables/EventObservableTests.cs | Adds unit tests for the new EventObservable<T> runtime type. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WhenChangedGeneratorTests.cs | Updates platform stubs (WinForms/WinUI/KVO) used by generator tests. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_ReactiveObject#WhenChangingDispatch.g.verified.cs | Snapshot update for WhenChanging dispatch output formatting. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC_CFP#WhenChangingDispatch.g.verified.cs | Snapshot update for WhenChanging dispatch output formatting. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.SP_INPC#WhenChangingDispatch.g.verified.cs | Snapshot update for WhenChanging dispatch output formatting. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.ReactiveObject_Changing_Property#WhenChangingDispatch.g.verified.cs | Snapshot update for WhenChanging dispatch output formatting. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCnG.INPChangingOnly_Property#WhenChangingDispatch.g.verified.cs | Snapshot update for WhenChanging dispatch output formatting. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WpfDependencyObject_Property#WhenChangedDispatch.g.verified.cs | Snapshot update for WPF WhenChanged output formatting. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WpfDependencyObject_Property#GeneratedBinderRegistration.g.verified.cs | Snapshot update reflecting plugin-based kind detection comments. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinUIDependencyObject_Property#WhenChangedDispatch.g.verified.cs | Snapshot update switching WinUI DP observation to generated __WinUIDPObservable. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinUIDependencyObject_Property#GeneratedBinderRegistration.g.verified.cs | Snapshot update reflecting plugin-based kind detection comments. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinFormsComponent_Property#WhenChangedDispatch.g.verified.cs | Snapshot update switching WinForms observation to EventObservable<T> on {Property}Changed. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.WinFormsComponent_Property#GeneratedBinderRegistration.g.verified.cs | Snapshot update reflecting plugin-based kind detection comments. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_ReactiveObject#WhenChangedDispatch.g.verified.cs | Snapshot update for WhenChanged dispatch output formatting. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC_CFP#WhenChangedDispatch.g.verified.cs | Snapshot update for WhenChanged dispatch output formatting. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.SP_INPC#WhenChangedDispatch.g.verified.cs | Snapshot update for WhenChanged dispatch output formatting. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.NullableProperty#WhenChangedDispatch.g.verified.cs | Snapshot update for nullable property observation output formatting. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MultipleViewModels#WhenChangedDispatch.g.verified.cs | Snapshot update for multiple view-model dispatch formatting. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.MI_SameViewModel#WhenChangedDispatch.g.verified.cs | Snapshot update for multiple invocations formatting. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.KVO_NSObject_Property#WhenChangedDispatch.g.verified.cs | Snapshot update switching Apple observation to generated __KVOObservable. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.KVO_NSObject_Property#GeneratedBinderRegistration.g.verified.cs | Snapshot update reflecting plugin-based kind detection comments. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.IntProperty#WhenChangedDispatch.g.verified.cs | Snapshot update for value-type observation output formatting. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.AndroidView_Property#WhenChangedDispatch.g.verified.cs | Snapshot update for Android view observation output formatting. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WCG.AndroidView_Property#GeneratedBinderRegistration.g.verified.cs | Snapshot update reflecting plugin-based kind detection comments. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC_CFP#WhenAnyValueDispatch.g.verified.cs | Snapshot update for WhenAnyValue formatting. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.SP_INPC#WhenAnyValueDispatch.g.verified.cs | Snapshot update for WhenAnyValue formatting. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WAVG.NullableProperties#WhenAnyValueDispatch.g.verified.cs | Snapshot update for nullable WhenAnyValue formatting. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/Plugins/ObservationPluginTests.cs | Adds targeted unit tests for plugin emit methods not covered by snapshots. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/CodeGeneration/ObservationCodeGeneratorHelperTests.cs | Updates/extends generator helper tests for new fallback behavior. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BOG.ReactiveObject_Source#GeneratedBinderRegistration.g.verified.cs | Snapshot update for kind ordering/printing changes. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BIG.DeepPropertyPath#BindInteractionDispatch.g.verified.cs | Snapshot indentation/formatting changes for deep interaction binding code. |
| src/tests/ReactiveUI.Binding.SourceGenerators.Tests/BCG.DeepCommandPath#BindCommandDispatch.g.verified.cs | Snapshot indentation/formatting changes for deep command binding code. |
| src/ReactiveUI.Binding/Observables/EventObservable.cs | Adds new fused event-based observable implementation used by generated code. |
| src/ReactiveUI.Binding.SourceGenerators/Plugins/ObservationPluginRegistry.cs | Adds plugin registry + best-match dispatch by affinity/kind. |
| src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WpfObservationPlugin.cs | Adds WPF DP plugin emitting EventObservable<T> with DependencyPropertyDescriptor. |
| src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WinUIObservationPlugin.cs | Adds WinUI DP plugin emitting __WinUIDPObservable<T> helper class and usage. |
| src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/WinFormsObservationPlugin.cs | Adds WinForms plugin emitting EventObservable<T> for {Property}Changed. |
| src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/ReactiveObjectObservationPlugin.cs | Adds ReactiveObject plugin emitting PropertyObservable / PropertyChangingObservable. |
| src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/KVOObservationPlugin.cs | Adds Apple KVO plugin emitting __KVOObservable<T> + __KVOObserver helper classes. |
| src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/INPCObservationPlugin.cs | Adds INPC plugin emitting PropertyObservable / PropertyChangingObservable. |
| src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/AndroidObservationPlugin.cs | Adds Android plugin (currently ReturnObservable fallback). |
| src/ReactiveUI.Binding.SourceGenerators/Plugins/IObservationPlugin.cs | Introduces the observation plugin interface contract for codegen. |
| src/ReactiveUI.Binding.SourceGenerators/Plugins/ICommandBindingPlugin.cs | Introduces a command binding plugin interface (foundation for extensibility). |
| src/ReactiveUI.Binding.SourceGenerators/Generators/RegistrationGenerator.cs | Simplifies registration pipeline by removing old consolidation method. |
| src/ReactiveUI.Binding.SourceGenerators/BindingGenerator.cs | Switches from 7 per-kind filters to single plugin-based observable type selection. |
| src/ReactiveUI.Binding.SourceGenerators/Generators/Observation/WpfBindingGenerator.cs | Removes legacy static WPF observation generator filter. |
| src/ReactiveUI.Binding.SourceGenerators/Generators/Observation/WinUIBindingGenerator.cs | Removes legacy static WinUI observation generator filter. |
| src/ReactiveUI.Binding.SourceGenerators/Generators/Observation/WinFormsBindingGenerator.cs | Removes legacy static WinForms observation generator filter. |
| src/ReactiveUI.Binding.SourceGenerators/Generators/Observation/ReactiveObjectBindingGenerator.cs | Removes legacy static ReactiveObject observation generator filter. |
| src/ReactiveUI.Binding.SourceGenerators/Generators/Observation/KVOBindingGenerator.cs | Removes legacy static KVO observation generator filter. |
| src/ReactiveUI.Binding.SourceGenerators/Generators/Observation/INPCBindingGenerator.cs | Removes legacy static INPC observation generator filter. |
| src/ReactiveUI.Binding.SourceGenerators/Generators/Observation/AndroidBindingGenerator.cs | Removes legacy static Android observation generator filter. |
| src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/ObservationCodeGenerator.cs | Updates observation codegen to use plugin dispatch + emits plugin helper classes when needed. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/tests/ReactiveUI.Binding.SourceGenerators.Tests/WhenChangedGeneratorTests.cs
Outdated
Show resolved
Hide resolved
src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/ObservationCodeGenerator.cs
Outdated
Show resolved
Hide resolved
src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/ObservationCodeGenerator.cs
Outdated
Show resolved
Hide resolved
src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/ObservationCodeGenerator.cs
Outdated
Show resolved
Hide resolved
src/ReactiveUI.Binding.SourceGenerators/CodeGeneration/ObservationCodeGenerator.cs
Show resolved
Hide resolved
…ride - Move command binding plugins from Generators/CommandBinding/ to Plugins/CommandBinding/ mirroring the observation plugin folder structure - Create CommandBindingPluginRegistry for affinity-based plugin selection - Convert CommandPropertyBindingPlugin, EventEnabledBindingPlugin, and DefaultEventBindingPlugin from static classes to ICommandBindingPlugin implementations with Affinity properties (5, 4, 3) - Add CommandBindingAffinityChecker runtime class allowing user-registered ICreatesCommandBinding plugins to override generated bindings when they have higher affinity (mirrors ObservationAffinityChecker pattern) - Add ObservationAffinityChecker for property observation user override - Replace EmitCustomBinderFallback with EmitCommandAffinityCheck in BindCommandCodeGenerator for affinity-aware runtime fallback - Add RuntimeObservationFallback multi-property WhenChanging/WhenAnyValue overloads (2 and 3 properties) - Sort usedPluginKinds for deterministic helper class emission order - Add comprehensive tests: CommandBindingAffinityCheckerTests, ObservationAffinityCheckerTests, CommandBindingPluginTests - 7365 tests pass, 100% line/branch/method coverage
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 99 out of 100 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/ReactiveUI.Binding.SourceGenerators/Plugins/Observation/KVOObservationPlugin.cs
Show resolved
Hide resolved
…y starting with "Is"
Summary
DependencyPropertyDescriptor.AddValueChanged, WinUI usesRegisterPropertyChangedCallback, WinForms uses direct{Property}Changedevent subscription, KVO usesNSObject.AddObserverwith compile-time resolved key pathsEventObservable<T>runtime type provides platform-agnostic event-based observation using pure BCL types (Action<EventHandler>add/remove pattern), used by WPF and WinForms pluginsConsolidate()to a singleObservationPluginRegistry.GetBestPlugin()dispatchICreatesObservableForPropertyimplementations with higher affinity will naturally override the generated observation at runtimeTest plan
-warnaserror)