Migrate DynamicData to ReactiveUI.Primitives#1116
Conversation
Introduce reactive compatibility helpers and update the codebase to support newer Reactive APIs and runtime changes. Added ReactiveCompatibility and ReactiveTestCompatibility files, removed legacy Rxx support, and applied widespread updates across core, cache, list, binding, experimental, tests, and benchmarks to adjust API usage and test fixtures. Project/props files were also updated to align with the new build/runtime expectations.
Add a new DynamicData.Reactive project (reactive-preprocessed.xml) and update the codebase to integrate reactive support. This change includes coordinated updates across benchmarks, test fixtures, cache internals, binding code, utilities, and many ObservableCache/ObservableCacheEx implementations to align with API/behavior changes and the new reactive module.
Introduce a new DynamicDataReactiveSource item and a RoslynCodeTaskFactory task (RewriteDynamicDataReactiveNamespaces) to rewrite namespaces/usings from DynamicData to DynamicData.Reactive and emit generated sources into the intermediate output during build. Update the DynamicData.Reactive.csproj to use DynamicData.Reactive.* Using entries, replace direct Compile includes with DynamicDataReactiveSource (and adjust removal of AsyncDisposeMany), and add a GenerateDynamicDataReactiveSources target that wires generated files into Compile. Also add an API approval verified file for DynamicData.Reactive (.NET 10) and update the tests project (ApiApprovalTests and DynamicData.Tests.csproj) accordingly.
Replace usages of System.Reactive subjects (Subject, BehaviorSubject, ISubject) with ReactiveUI.Primitives signals (Signal, StateSignal, ISignal) across benchmarks, tests and library code. Update DynamicData.Reactive.csproj to reference ReactiveUI.Primitives assemblies and switch from the previous Roslyn code-generation task to a direct Compile Include for DynamicData sources (with a conditional Compile Remove for AsyncDisposeMany). Add a Directory.Build.props Using alias that maps Lock to System.Threading.Lock on .NET 9+ and to System.Object for earlier targets. Also remove the obsolete Binding/Observable.cs file and adjust related files to the new Signal types.
Replace Rx Subjects/BehaviorSubject/ReplaySubject and related primitives with ReactiveUI.Primitives equivalents (Signal, StateSignal, ReplaySignal) across cache, list, binding, and tests. Convert Observable helpers to Signal variants (Observable.Defer -> Signal.Lazy, Observable.FromAsync -> Signal.FromAsync), replace Do->Tap and Select->Map/Keep in several pipelines, and swap composite disposable types where appropriate (OnceDisposable/MultipleDisposable). Also introduce Kernel.Optional in place of Optional in internal cache types, add notnull constraints to SynchronizeSafe, tighten suppression attributes, and update various synchronization gates to use Lock. These changes aim to use the new primitives and avoid deadlocks / improve delivery semantics in multi-source operators.
Clean up ReactiveUI primitives usage across the repo: remove redundant ReactiveUI.Primitives.Signals usings from benchmark/list files, add ReactiveUI.Primitives and disposable aliases to project files, and replace MultipleDisposable references with the CompositeDisposable alias. Switch TransformMany's synchronization lock from a plain object to a Lock instance. Also simplify a CreateTask call (remove explicit generic), adjust doc comments to reference Signal<T>.OnNext, and apply minor csproj formatting/using updates to expose required ReactiveUI primitives and disposable aliases.
Clean up: remove unused ReactiveUI.Primitives using directives across several files and simplify a CombineLatest call by dropping explicit named parameters. Affected files: GroupOn.cs, TreeBuilder.cs, TrueFor.cs (removed named args for CombineLatest), Watcher.cs, and DynamicCombiner.cs. This reduces compiler warnings and tidies up the code.
Delete the large ReactiveCompatibility compatibility layer and related Optional type, and update internals to reflect the removal. Multiple kernel and list implementation files (OptionExtensions, OptionObservableExtensions, InternalEx, Change/ChangeAwareList/Transform/Transformer/UnifiedChange/ItemChange/ListEx/ObservableListEx and group/transform internals) plus net45 platform shims (PFilter/PTransform) and tests were modified to adapt to the change. This removes the old Rx compatibility surface in favor of the updated ReactiveUI/primitive APIs and simplifies the codebase.
Replace usages of DynamicData.Kernel.Optional with ReactiveUI.Primitives.Optional throughout the codebase and update related API surface (API approval files). Make several small API shape changes (make some structs readonly, expose init-only properties, and adjust ctor parameter names). Switch DynamicData.Reactive from package refs to local ReactiveUI.Primitives project references and add an MSBuild target to rewrite/generate Reactive sources into the intermediate output (with adjusted excludes and conditional removal of AsyncDisposeMany). Also update benchmark usings and a few tests/fixtures to match the updated Optional and generated-source changes.
Replace usages of Kernel.Optional<T> with Optional<T> across cache and internal components (joins, transforms, lookups, observers, and node/cache implementations). Add source.ThrowArgumentNullExceptionIfNull checks in SortAndBind and StartWithEmpty extension overloads to validate inputs. Remove obsolete Internal/ObservableEx.cs. These changes standardize the optional type and improve argument validation.
Add disposal guards and race-condition handling across core list operations and tests. In SourceList: introduce _isDisposed flag, short-circuit subscription/count observables when disposed, throw ObjectDisposedException for edits after disposal, and wrap Preview to check disposal before subscribing. In MergeMany: extract SubscribeChild to centralize subscription logic, handle ObjectDisposedException from the observable selector, and ensure the subscription counter is finalized. Tests updated to wait for expected results (WaitForResultContentsAsync/ResultContentsMatch) and to avoid collection-modification issues by copying item collections to arrays before disposing items.
Add a PowerShell script (RewriteDynamicDataReactiveSources.ps1) that generates Reactive-specific source files under GeneratedReactiveSources by transforming namespaces/usings and mapping Signal/Optional/scope/disposable/operators to ReactiveUI.Primitives/Observable equivalents. Update DynamicData.csproj to reference ReactiveUI.Primitives items and aliases and exclude Internal\ObservableEx.cs and Internal\ReactiveCompatibility.cs from compilation. Replace .Synchronize(...) with .SynchronizeObject(...) in several internal files to match the ReactiveUI.Primitives API.
|
So I'm assuming this is just meant to be a proof-of-concept, yes? I was expecting to have a conversation somewhere about the architecture first, but this works too. For starters, I get the feeling we may not all be on the same page. Is this effort separate from @glennawatson 's effort to add new functionality to Looking this PR over, the intention seems to be that we publish 2 distinct versions of the library, This was one of the ideas in my head as well, but it does have a downside of being potentially confusing for consumers. I think most consumers are used to the pattern of Regarding all the type aliasing, if I'm reading things correctly, this is just an effort to let the codebase not have to switch over to to all the Fundamentally, though, I don't think I have any objections to the core concept. Also, it looks like you accidentally checked in some junk? There seems to be an entire |
|
I believe Chris is using a Roslyn build tool to rename the namespaces which I'm not as big fan of to allow the two to coexist. |
|
I'm a big fan of csproj based usings. It's becoming more the industry standard than the .cs approach. Some neat tricks around Lock compatibility is available also for making .net 9 locks transparent to the code base without #if statements everywhere. |
|
Yeah, he actually has that Me, I genuinely hate implicit |
Add AOT/trim analyzers for net8+ in Directory.Build.props (IsAotCompatible, EnableAotAnalyzer, EnableTrimAnalyzer). Remove per-project IsAotCompatible settings and replace local ReactiveUI.Primitives project references with PackageReference entries (v5.5.0) in DynamicData and DynamicData.Reactive. Add .dotnet-home/ to .gitignore and delete several .dotnet-home cached SDK/workload/vulnerability files.
Clean up: removed many redundant/unused using directives across benchmark and test source files. Common benchmark imports were centralized into DynamicData.Benchmarks.csproj via <Using> entries (e.g. System.Collections.ObjectModel, System.Collections.Immutable, BenchmarkDotNet.* and DynamicData.Binding) to reduce duplication and compiler warnings. Also removed unused test usings (FluentAssertions, Xunit, etc.) where they were not required.
Replace references to TaskPoolSequencer with TaskPoolScheduler across tests and project files for consistent aliasing. Updated DynamicData.Reactive.csproj, DynamicData.Tests.csproj and DynamicData.csproj to include the TaskPoolScheduler Using alias, and updated usages in WhenPropertyChangedRaceFixture and multiple ToObservableChangeSetFixture integration tests to call TaskPoolScheduler.Default.
Replace ad-hoc ThrowArgumentNullExceptionIfNull calls with ArgumentExceptionHelper.ThrowIfNull across the codebase to centralize null-checking. Add a set of polyfill files and Task/attribute helpers to support older/newer target frameworks, and adjust DynamicData.Reactive.csproj to alias exception types for net4 vs newer frameworks. Update Directory.Build.props: set SolutionDir default, enable runtime-async for newer runtimes, reorganize analyzer/StyleCop configuration and restore the Lock alias ItemGroups. Remove ExceptionMixins.cs and tweak tests (e.g. use Signal instead of Subject in FilterImmutableFixture). These changes prepare the project for broader framework compatibility and unify argument validation behavior.
Replace TaskPoolSequencer.Default with TaskPoolScheduler.Default in multiple test fixtures to run asynchronous stress operations on the TaskPool scheduler. Updated test files under src/DynamicData.Tests (Cache and List fixtures) to align with the scheduler API/name change and ensure consistent scheduling behavior during tests.
Update test code and utilities to use ThreadPoolScheduler.Instance in place of ThreadPoolSequencer.Instance. Adjust DynamicData.Tests.csproj to import ThreadPoolSequencer as ThreadPoolScheduler and update NewThreadScheduler and various ExpireAfter/ToObservableChangeSet/InnerJoin tests to delegate to the new scheduler alias. This aligns the tests with the updated scheduler API and keeps compatibility in the reactive test helpers.
Update FromAsync tests to use Observable.FromAsync instead of Signal.FromAsync in Cache and List fixtures (src/DynamicData.Tests/Cache/FromAsyncFixture.cs, src/DynamicData.Tests/List/FromAsyncFixture.cs). Remove automatic Signal->Observable replacement rules for Signal.Lazy, Signal.After, Signal.FromAsync and Signal.Create from RewriteDynamicDataReactiveSources.ps1 to avoid applying those transformations during automated rewrites.
Replace the old Sequencer alias with Scheduler across project files and code. Updated csproj Using entries (DynamicData.Reactive, DynamicData, DynamicData.Tests) and all code references to use Scheduler.Default / Scheduler.Immediate / Scheduler.Normalize instead of Sequencer.*. Changes touch test fixtures, utilities, and internal ToObservableChangeSet implementations to align naming and fix alias/usage mismatches that would cause compilation errors.
Replace custom Signal helpers with standard Rx operators in TransformOnObservableFixture.CreateChildObs. The test now uses Observable.Interval(...) and Observable.Return(...) to emit periodic formatted values for a fixed count and then the expected final value before completion, removing the dependency on Signal.Every/Signal.Emit.
Replace inline ArgumentNullException checks with ArgumentExceptionHelper.ThrowIfNull across multiple files for consistent null validation. Also made small related refactors: assign validated parameters after the helper call, switch AsyncDisposeMany gate from object to Lock, replace Map/Keep calls with Select/Where in GroupOn, and tidy constructors in MergeManyItems and ChangeAwareCache. Files changed include EnumerableEx, IObservableListEx, ChangeAwareCache, AbstractFilter, AnonymousObservableCache, AsyncDisposeMany, GroupOn, LockFreeObservableCache, MergeManyItems, ObservableCacheEx (two files) and AnonymousObservableList.
Include 11.0.x in the CI job matrix by adding it to the versions list in .github/workflows/ci-build.yml. This expands the tested runtime to cover 11.0.x; cache and dependency-path settings are unchanged.
Remove preprocessed reactive source files and apply widespread updates across Aggregation, Alias, Binding, Cache, List, Kernel, Internal, Experimental and related areas. Many implementations, interfaces and tests were adjusted for consistency, cleanup and maintainability; includes deletion of DynamicData.Reactive preprocessed files and broad code updates across the library.
Add a temporary ReactiveShimObservableExtensions shim to provide SubscribeSafe overloads when using the REACTIVE_SHIM symbol, and update usages to match Reactive Extensions API changes. Replaced deprecated SynchronizeObject calls with Synchronize, and swapped Tap usages for Do. Also fix a TaskPolyfill token registration cast (remove null-forgiving operator). Changes touch ReactiveShimObservableExtensions.cs, ExpireAfter.ForSource.cs, ExpireAfter.ForStream.cs, TreeBuilder.cs, DisposeMany.cs, DynamicCombiner.cs, and TaskPolyfillExtensions.cs to restore compatibility with updated RX primitives and tidy minor issues.
Remove CS1584, CS1658, and CS1591 documentation-warning suppressions from the shared build configuration and benchmark project settings. Enable internal and private XML documentation coverage in StyleCop, add missing XML documentation across DynamicData and the DynamicData.Reactive shim source, and fix invalid cref references that produced XML doc warnings. Keep GenerateDocumentationFile disabled for test and benchmark projects so documentation validation only applies to production packages. Verification: dotnet build src\DynamicData.sln -c Debug --no-restore /nr:false /m:1 /p:UseSharedCompilation=false; dotnet test src\DynamicData.Tests\DynamicData.Tests.csproj -c Debug --no-build /nr:false /m:1 /p:UseSharedCompilation=false.
Rewrite list-source MergeManyChangeSets internals to serialize parent and child processing through CacheParentSubscription using stable internal parent keys. Remove child items before child subscription disposal for cache-source merge operators so stale child notifications cannot leave published items behind. Place timing-sensitive concurrency fixtures in the existing non-parallel integration collection to avoid suite-load induced timeout failures while keeping the tests enabled. Verified with Release solution tests and pack.
Use the ReactiveUI.Primitives 5.6.0 package SubscribeSafe overloads directly through project-level PrimitivesLinqExtensions aliases for DynamicData and DynamicData.Reactive. Remove the temporary ReactiveShimObservableExtensions implementation from DynamicData.Reactive and update callback-based SubscribeSafe call sites to static Primitives LinqExtensions calls so the Rx SubscribeSafe overload is not selected. Guard the DynamicData.Tests reactive output copy target when no target output directory exists, and run the property-changed race fixture in the integration collection to avoid suite-parallel timing failures. Verification: dotnet build src\\DynamicData.sln -c Release --no-restore /nr:false /m:1 /p:UseSharedCompilation=false; dotnet test src\\DynamicData.sln -c Release --no-build --logger console;verbosity=minimal /nr:false /m:1 /p:UseSharedCompilation=false
Delete ReactiveUI.Primitives.Extensions package references from DynamicData and DynamicData.Reactive project files. The two <PackageReference> entries (ReactiveUI.Primitives.Extensions and ReactiveUI.Primitives.Extensions.Reactive) were removed from their respective .csproj ItemGroup sections because the extensions are no longer referenced by these projects.
Remove conditional compilation and always initialize the _gate field with Lock instead of an object fallback. This simplifies the constructors in DeliveryQueue<T> and SharedDeliveryQueue and eliminates NET9_0_OR_GREATER #if branches.
Remove NET9_0_OR_GREATER conditional overloads and use the Lock type for gate parameters in DeliveryQueue and SharedDeliveryQueue. This eliminates object-based constructors and simplifies the code by unifying the gate type across these delivery queue implementations.
Add a regression test (CrossCacheDeadlockRepro) that reproduces a cross-cache deadlock during concurrent PopulateInto updates. Rework DeliveryQueue to avoid deadlocks by introducing a dedicated queue lock and active-access tracking: _queueGate, _activeAccessCount, EnterAccess/ExitAccessAndDeliver and DeliverIfNeeded/TryTakeNotification/StopDelivery. Adjust delivery start/stop logic to honor in-flight caller-owned access, ensure proper termination handling, and expose HasPendingNotifications. Update ScopedAccess and ReadOnlyScopedAccess to use the new access semantics. Include conditional APIs for NET9_0_OR_GREATER to use Lock where available.
What kind of change does this PR introduce?
What is the new behavior?
DynamicDatanow references theReactiveUI.PrimitivesandReactiveUI.Primitives.Extensionspackages instead of System.Reactive.What is the current behavior?
DynamicDataconsumed the packagedReactiveUI.Primitivesartifacts from NuGet.Checklist
Additional information
MigrateToPrimitivesbranch work.Testing