Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
1f67010
Add reactive compatibility and update tests
ChrisPulman Jun 17, 2026
c96163a
Add Reactive project and update tests/benchmarks
ChrisPulman Jun 18, 2026
e4f7135
Add MSBuild task to generate Reactive sources
ChrisPulman Jun 18, 2026
4868422
Migrate Subjects to ReactiveUI Signals
ChrisPulman Jun 19, 2026
41b032a
Migrate to ReactiveUI.Primitives signals
ChrisPulman Jun 19, 2026
1b069eb
Refactor ReactiveUI primitives and use Lock
ChrisPulman Jun 20, 2026
0116269
Remove unused ReactiveUI.Primitives usings
ChrisPulman Jun 20, 2026
2b47398
Remove ReactiveCompatibility layer
ChrisPulman Jun 20, 2026
929eb53
Use ReactiveUI.Primitives.Optional across project
ChrisPulman Jun 20, 2026
17ead0f
Use Optional<T> and add null guards
ChrisPulman Jun 20, 2026
5bad5f0
Guard SourceList and MergeMany against disposal
ChrisPulman Jun 20, 2026
7178ed7
Add script to rewrite reactive sources
ChrisPulman Jun 20, 2026
3f4df68
Migrate DynamicData to local ReactiveUI.Primitives refs
ChrisPulman Jun 20, 2026
08672cb
Merge branch 'main' into MigrateToPrimitives
ChrisPulman Jun 20, 2026
18e7dbc
Enable net8 AOT analyzers; update ReactiveUI deps
ChrisPulman Jun 21, 2026
4ab89c6
Remove redundant usings, centralize imports
ChrisPulman Jun 21, 2026
2804c6c
Use TaskPoolScheduler alias in code and csproj
ChrisPulman Jun 21, 2026
49f72a7
Standardize null checks, add polyfills, update build props
ChrisPulman Jun 21, 2026
ddfebea
Use TaskPoolScheduler.Default in tests
ChrisPulman Jun 21, 2026
e4e3b34
Replace ThreadPoolSequencer with ThreadPoolScheduler
ChrisPulman Jun 21, 2026
0a58896
Replace Signal.FromAsync with Observable
ChrisPulman Jun 21, 2026
3ce3a77
Rename Sequencer alias to Scheduler and update usages
ChrisPulman Jun 21, 2026
187d03e
Use Observable.Interval/Return in test helper
ChrisPulman Jun 21, 2026
02b087d
Replace null checks with ThrowIfNull helper
ChrisPulman Jun 21, 2026
e3936cb
Merge branch 'main' into MigrateToPrimitives
ChrisPulman Jun 21, 2026
080d8bf
Add 11.0.x to CI matrix
ChrisPulman Jun 21, 2026
80e72ff
Massive refactor across core modules
ChrisPulman Jun 21, 2026
7bd80fb
Update reactive shims and replace obsolete operators
ChrisPulman Jun 21, 2026
b6c0416
Update DynamicData.Reactive.csproj
ChrisPulman Jun 21, 2026
709a626
Update ReactiveShimObservableExtensions.cs
ChrisPulman Jun 21, 2026
d0c6cb6
docs: resolve DynamicData XML documentation warnings
ChrisPulman Jun 21, 2026
ddabd8e
fix: stabilize merge many changeset tests
ChrisPulman Jun 21, 2026
ed2de8f
fix: resolve primitives SubscribeSafe binding
ChrisPulman Jun 21, 2026
eab488a
Remove ReactiveUI.Primitives.Extensions package refs
ChrisPulman Jun 21, 2026
a7ec313
Use Lock for _gate initialization
ChrisPulman Jun 21, 2026
1c2be9c
Unify gate parameter type to Lock
ChrisPulman Jun 21, 2026
fefd786
Fix DeliveryQueue deadlock and add repro test
ChrisPulman Jun 22, 2026
3259677
Add deadlock tests and refactor list delivery
ChrisPulman Jun 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .github/workflows/ci-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:
8.0.x
9.0.x
10.0.x
11.0.x
cache: true
cache-dependency-path: |
**/Directory.Packages.props
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -369,3 +369,5 @@ src/*.Tests/API/ApiApprovalTests.*.received.txt

# JetBrains
.idea/

.dotnet-home/
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
<PackageReference Include="xunit.runner.console" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageReference Include="FluentAssertions" Version="6.12.2" />
<PackageReference Include="Microsoft.Reactive.Testing" Version="6.1.0" />
</ItemGroup>

</Project>
48 changes: 33 additions & 15 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<Project>
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591;1701;1702;1705;VSX1000;CA1510</NoWarn>
<NoWarn>$(NoWarn);1701;1702;1705;VSX1000;CA1510</NoWarn>
<Platform>AnyCPU</Platform>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
Expand All @@ -22,14 +22,15 @@
<PackageIcon>logo.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<EnableNETAnalyzers>true</EnableNETAnalyzers>

<!-- Publish the repository URL in the built .nupkg (in the NuSpec <Repository> element) -->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<!-- Embed source files that are not tracked by the source control manager in the PDB -->
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<!-- Include PDB in the built .nupkg -->
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<WarningsAsErrors>CS8600;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8623;CS8624;CS8625;CS8626;CS8627;CS8628;CS8629;CS8630;CS8634;CS8766;CS8767</WarningsAsErrors>
<WarningsAsErrors>CS8600;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8623;CS8624;CS8625;CS8626;CS8627;CS8628;CS8629;CS8630;CS8634;CS8766;CS8767</WarningsAsErrors>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
Expand All @@ -38,9 +39,25 @@

<PropertyGroup Condition="$(IsTestProject) OR $(IsBenchmarkProject)">
<IsPackable>false</IsPackable>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<CodeAnalysisRuleSet></CodeAnalysisRuleSet>
</PropertyGroup>

<PropertyGroup>
<SolutionDir Condition="'$(SolutionDir)' == ''">$(MSBuildThisFileDirectory)</SolutionDir>
</PropertyGroup>

<!-- AOT/trim analyzers on net8.0+ (netfx doesn't support them). -->
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
<IsAotCompatible>true</IsAotCompatible>
<EnableAotAnalyzer>true</EnableAotAnalyzer>
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
</PropertyGroup>

<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net11.0'))">
<Features>$(Features);runtime-async=on</Features>
</PropertyGroup>

<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)..\images\logo.png" Pack="true" PackagePath="\"/>
<None Include="$(MSBuildThisFileDirectory)..\README.md" Pack="true" PackagePath="\"/>
Expand All @@ -49,22 +66,23 @@
<ItemGroup Condition="'$(IsTestProject)' != 'true'">
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
</ItemGroup>

<PropertyGroup>
<SolutionDir Condition="'$(SolutionDir)' == ''">$(MSBuildThisFileDirectory)</SolutionDir>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning" Version="3.9.50" PrivateAssets="all" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework) == 'netstandard2.0'">
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.10" />
<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning" Version="3.9.50" PrivateAssets="all" />
</ItemGroup>

<ItemGroup Condition="!$(IsTestProject) AND !$(IsBenchmarkProject)">
<PackageReference Include="stylecop.analyzers" Version="1.2.0-beta.556" PrivateAssets="all" />
<PackageReference Include="Roslynator.Analyzers" Version="4.15.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup Condition="!$(IsTestProject) AND !$(IsBenchmarkProject)">
<AdditionalFiles Include="$(MSBuildThisFileDirectory)stylecop.json" Link="stylecop.json" />
</ItemGroup>

<!-- Alias Lock to the dedicated System.Threading.Lock on .NET 9+ (faster EnterScope fast path),
and to a plain object elsewhere (the lock statement falls back to Monitor). -->
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net9.0'))">
<Using Include="System.Threading.Lock" Alias="Lock"/>
</ItemGroup>
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net9.0'))">
<Using Include="System.Object" Alias="Lock"/>
</ItemGroup>
</Project>
8 changes: 2 additions & 6 deletions src/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@
<DefineConstants>$(DefineConstants);P_LINQ;SUPPORTS_BINDINGLIST</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="$(TargetFramework.StartsWith('netstandard')) or $(TargetFramework.StartsWith('net6')) or $(TargetFramework.StartsWith('net7')) or $(TargetFramework.StartsWith('net8')) or $(TargetFramework.StartsWith('net9')) or $(TargetFramework.StartsWith('net10'))">
<DefineConstants>$(DefineConstants);NETSTANDARD;P_LINQ;SUPPORTS_BINDINGLIST;SUPPORTS_ASYNC_DISPOSABLE</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="$(TargetFramework.StartsWith('netstandard2.1')) or $(TargetFramework.StartsWith('netcoreapp3')) or $(TargetFramework.StartsWith('net5')) or $(TargetFramework.StartsWith('net6')) or $(TargetFramework.StartsWith('net7')) or $(TargetFramework.StartsWith('net8')) or $(TargetFramework.StartsWith('net9')) or $(TargetFramework.StartsWith('net10'))">
<DefineConstants>$(DefineConstants);SUPPORTS_DICTIONARY_MUTATION_DURING_ENUMERATION</DefineConstants>
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
<DefineConstants>$(DefineConstants);NETSTANDARD;P_LINQ;SUPPORTS_BINDINGLIST;SUPPORTS_ASYNC_DISPOSABLE;SUPPORTS_DICTIONARY_MUTATION_DURING_ENUMERATION</DefineConstants>
</PropertyGroup>
</Project>
10 changes: 0 additions & 10 deletions src/DynamicData.Benchmarks/Cache/DeliveryQueueBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,6 @@
// Roland Pheasant licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;

using BenchmarkDotNet.Attributes;

using DynamicData.Binding;

namespace DynamicData.Benchmarks.Cache;

/// <summary>
Expand Down
4 changes: 0 additions & 4 deletions src/DynamicData.Benchmarks/Cache/DisposeMany_Cache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@
// Roland Pheasant licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System;

using BenchmarkDotNet.Attributes;

namespace DynamicData.Benchmarks.Cache
{
[MemoryDiagnoser]
Expand Down
11 changes: 1 addition & 10 deletions src/DynamicData.Benchmarks/Cache/EditDiff.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;

using BenchmarkDotNet.Attributes;

using DynamicData.Kernel;

namespace DynamicData.Benchmarks.Cache;

[MemoryDiagnoser]
Expand Down Expand Up @@ -49,7 +40,7 @@ public void OptionalAddsAndRemoves(int maxItems)
.Range(0, MaxItems)
.Select(n => (n % 2) == 0
? new Person(n, "Name")
: Optional.None<Person>())
: Optional<Person>.None)
.ToObservable()
.EditDiff(p => p.Id)
.Subscribe();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
using System;
using System.Collections.Immutable;
using System.Linq;

using BenchmarkDotNet.Attributes;

using Bogus;
using Bogus;

namespace DynamicData.Benchmarks.Cache;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Reactive.Subjects;

using BenchmarkDotNet.Attributes;

using Bogus;

namespace DynamicData.Benchmarks.Cache;
Expand Down Expand Up @@ -113,7 +106,7 @@ public ExpireAfter_Cache_ForStream()
[Benchmark]
public void RandomizedEditsAndExpirations()
{
using var source = new Subject<IChangeSet<Item, int>>();
using var source = new Signal<IChangeSet<Item, int>>();

using var subscription = source
.ExpireAfter(
Expand Down
14 changes: 4 additions & 10 deletions src/DynamicData.Benchmarks/Cache/FilterImmutable.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
using System;
using System.Collections.Generic;
using System.Reactive.Subjects;

using BenchmarkDotNet.Attributes;

namespace DynamicData.Benchmarks.Cache;

[MemoryDiagnoser]
Expand Down Expand Up @@ -58,7 +52,7 @@ public FilterImmutable()
[Benchmark]
public void Adds()
{
using var source = new Subject<IChangeSet<Item, int>>();
using var source = new Signal<IChangeSet<Item, int>>();

using var subscription = source
.FilterImmutable(static item => item.IsIncluded)
Expand All @@ -73,7 +67,7 @@ public void Adds()
[Benchmark]
public void AddsAndReplacements()
{
using var source = new Subject<IChangeSet<Item, int>>();
using var source = new Signal<IChangeSet<Item, int>>();

using var subscription = source
.FilterImmutable(static item => item.IsIncluded)
Expand All @@ -91,7 +85,7 @@ public void AddsAndReplacements()
[Benchmark]
public void AddsAndRemoves()
{
using var source = new Subject<IChangeSet<Item, int>>();
using var source = new Signal<IChangeSet<Item, int>>();

using var subscription = source
.FilterImmutable(static item => item.IsIncluded)
Expand All @@ -109,7 +103,7 @@ public void AddsAndRemoves()
[Benchmark]
public void AddsReplacementsAndRemoves()
{
using var source = new Subject<IChangeSet<Item, int>>();
using var source = new Signal<IChangeSet<Item, int>>();

using var subscription = source
.FilterImmutable(static item => item.IsIncluded)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Reactive.Subjects;

using BenchmarkDotNet.Attributes;

using Bogus;

namespace DynamicData.Benchmarks.Cache;
Expand Down Expand Up @@ -99,7 +92,6 @@ public Filter_Cache_WithPredicateState()
}
_changeSets = changeSets.MoveToImmutable();


var predicateStates = ImmutableArray.CreateBuilder<int>(initialCapacity: 5_000);
while (predicateStates.Count < predicateStates.Capacity)
predicateStates.Add(randomizer.Int());
Expand All @@ -109,8 +101,8 @@ public Filter_Cache_WithPredicateState()
[Benchmark(Baseline = true)]
public void RandomizedEditsAndStateChanges()
{
using var source = new Subject<IChangeSet<Item, int>>();
using var predicateState = new Subject<int>();
using var source = new Signal<IChangeSet<Item, int>>();
using var predicateState = new Signal<int>();

using var subscription = source
.Filter(
Expand Down
40 changes: 11 additions & 29 deletions src/DynamicData.Benchmarks/Cache/SortAndBindChange.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using BenchmarkDotNet.Attributes;
using DynamicData.Binding;

namespace DynamicData.Benchmarks.Cache;

[MemoryDiagnoser]
Expand All @@ -19,11 +11,10 @@ private record Item(string Name, int Id, int Ranking);
.Ascending(i => i.Ranking)
.ThenByAscending(i => i.Name);


Subject<IChangeSet<Item, int>> _newSubject = new();
Subject<IChangeSet<Item, int>> _newSubjectOptimised = new();
Subject<IChangeSet<Item, int>> _oldSubject = new();
Subject<IChangeSet<Item, int>> _oldSubjectOptimised = new();
Signal<IChangeSet<Item, int>> _newSubject = new();
Signal<IChangeSet<Item, int>> _newSubjectOptimised = new();
Signal<IChangeSet<Item, int>> _oldSubject = new();
Signal<IChangeSet<Item, int>> _oldSubjectOptimised = new();

private IDisposable? _cleanUp;

Expand All @@ -32,22 +23,18 @@ private record Item(string Name, int Id, int Ranking);
private ReadOnlyObservableCollection<Item>? _oldList;
private ReadOnlyObservableCollection<Item>? _oldListOptimised;



[Params(10, 100, 1_000, 10_000, 50_000)]
public int Count { get; set; }


[GlobalSetup]
public void SetUp()
{
_oldSubject = new Subject<IChangeSet<Item, int>>();
_oldSubjectOptimised = new Subject<IChangeSet<Item, int>>();
_newSubject = new Subject<IChangeSet<Item, int>>();
_newSubjectOptimised = new Subject<IChangeSet<Item, int>>();

_oldSubject = new Signal<IChangeSet<Item, int>>();
_oldSubjectOptimised = new Signal<IChangeSet<Item, int>>();
_newSubject = new Signal<IChangeSet<Item, int>>();
_newSubjectOptimised = new Signal<IChangeSet<Item, int>>();

_cleanUp = new CompositeDisposable
_cleanUp = new CompositeDisposable
(
_newSubject.SortAndBind(out var newList, _comparer).Subscribe(),
_newSubjectOptimised.SortAndBind(out var optimisedList, _comparer, new SortAndBindOptions
Expand All @@ -65,8 +52,6 @@ public void SetUp()
_oldList = oldList;
_oldListOptimised = oldOptimisedList;



var changeSet = new ChangeSet<Item, int>(Count);
foreach (var i in Enumerable.Range(1, Count))
{
Expand All @@ -84,7 +69,6 @@ public void SetUp()
[Benchmark(Baseline = true)]
public void Old() => RunTest(_oldSubject, _oldList!);


[Benchmark]
public void OldOptimized() => RunTest(_oldSubjectOptimised, _oldListOptimised!);

Expand All @@ -94,8 +78,7 @@ public void SetUp()
[Benchmark]
public void NewOptimized() => RunTest(_newSubjectOptimised, _newListOptimised!);


void RunTest(Subject<IChangeSet<Item, int>> subject, ReadOnlyObservableCollection<Item> list)
void RunTest(Signal<IChangeSet<Item, int>> subject, ReadOnlyObservableCollection<Item> list)
{
var original = list[Count / 2];
var updated = original with { Ranking = _random.Next(1, 1000) };
Expand All @@ -106,6 +89,5 @@ void RunTest(Subject<IChangeSet<Item, int>> subject, ReadOnlyObservableCollectio
});
}


public void Dispose() => _cleanUp?.Dispose();
}
}
Loading
Loading