Skip to content

feat: interface-based observation plugin system with ReactiveUI-aligned affinities#14

Merged
glennawatson merged 4 commits intomainfrom
feature/affinity-backup
Mar 9, 2026
Merged

feat: interface-based observation plugin system with ReactiveUI-aligned affinities#14
glennawatson merged 4 commits intomainfrom
feature/affinity-backup

Conversation

@glennawatson
Copy link
Contributor

Summary

  • Replaces 7 static observation generators 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) — meaning the source generator now mirrors how ReactiveUI itself selects observation strategies at runtime, but resolved entirely at compile time
  • Platform-specific code generation replaces generic INPC-only output: WPF uses DependencyPropertyDescriptor.AddValueChanged, WinUI uses RegisterPropertyChangedCallback, WinForms uses direct {Property}Changed event subscription, KVO uses NSObject.AddObserver with compile-time resolved key paths
  • New EventObservable<T> runtime type provides platform-agnostic event-based observation using pure BCL types (Action<EventHandler> add/remove pattern), used by WPF and WinForms plugins
  • Pipeline A simplified from 7 separate filter calls + Consolidate() to a single ObservationPluginRegistry.GetBestPlugin() dispatch
  • User-extensible architecture — because the source generator uses the same affinity values as ReactiveUI's runtime, user-registered ICreatesObservableForProperty implementations with higher affinity will naturally override the generated observation at runtime

Test plan

  • 7,236 tests passing across all TFMs (net8.0, net9.0, net10.0)
  • 100% line coverage, 100% branch coverage
  • Build succeeds with zero warnings (-warnaserror)
  • Snapshot tests updated and verified for all platform types
  • Platform test stubs include full API surface (WinUI DP registration, KVO NSObject stubs, WinForms events)

glennawatson and others added 2 commits March 9, 2026 11:42
…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
Copy link

codecov bot commented Mar 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (f6d518b) to head (be14d5e).
⚠️ Report is 1 commits behind head on main.

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 IObservationPlugin abstraction with concrete platform plugins and a central ObservationPluginRegistry to 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.

…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
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@glennawatson glennawatson merged commit ac1f5a1 into main Mar 9, 2026
6 checks passed
@glennawatson glennawatson deleted the feature/affinity-backup branch March 9, 2026 02:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants