diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 1b588dd07..38b1c189a 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -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 diff --git a/.gitignore b/.gitignore index cc0cf38bc..d4768f880 100644 --- a/.gitignore +++ b/.gitignore @@ -369,3 +369,5 @@ src/*.Tests/API/ApiApprovalTests.*.received.txt # JetBrains .idea/ + +.dotnet-home/ \ No newline at end of file diff --git a/NonProduction/DynamicData.Profile/DynamicData.Profile.csproj b/NonProduction/DynamicData.Profile/DynamicData.Profile.csproj index 504b3165c..8e7cb5536 100644 --- a/NonProduction/DynamicData.Profile/DynamicData.Profile.csproj +++ b/NonProduction/DynamicData.Profile/DynamicData.Profile.csproj @@ -14,7 +14,6 @@ - diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 7d002a3bb..f6818a629 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,7 +3,7 @@ true - $(NoWarn);1591;1701;1702;1705;VSX1000;CA1510 + $(NoWarn);1701;1702;1705;VSX1000;CA1510 AnyCPU enable latest @@ -22,14 +22,15 @@ logo.png README.md true - + - true + true true $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb - 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 + 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 + enable @@ -38,9 +39,25 @@ false + false + + $(MSBuildThisFileDirectory) + + + + + true + true + true + + + + $(Features);runtime-async=on + + @@ -49,22 +66,23 @@ - - - $(MSBuildThisFileDirectory) - - - - - - + + + - - + + + + + + + + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 347ab888d..f831230b0 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -7,11 +7,7 @@ $(DefineConstants);P_LINQ;SUPPORTS_BINDINGLIST - - $(DefineConstants);NETSTANDARD;P_LINQ;SUPPORTS_BINDINGLIST;SUPPORTS_ASYNC_DISPOSABLE - - - - $(DefineConstants);SUPPORTS_DICTIONARY_MUTATION_DURING_ENUMERATION + + $(DefineConstants);NETSTANDARD;P_LINQ;SUPPORTS_BINDINGLIST;SUPPORTS_ASYNC_DISPOSABLE;SUPPORTS_DICTIONARY_MUTATION_DURING_ENUMERATION diff --git a/src/DynamicData.Benchmarks/Cache/DeliveryQueueBenchmarks.cs b/src/DynamicData.Benchmarks/Cache/DeliveryQueueBenchmarks.cs index f5e397006..35795043a 100644 --- a/src/DynamicData.Benchmarks/Cache/DeliveryQueueBenchmarks.cs +++ b/src/DynamicData.Benchmarks/Cache/DeliveryQueueBenchmarks.cs @@ -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; /// diff --git a/src/DynamicData.Benchmarks/Cache/DisposeMany_Cache.cs b/src/DynamicData.Benchmarks/Cache/DisposeMany_Cache.cs index 1e4f83e65..fc1b34c15 100644 --- a/src/DynamicData.Benchmarks/Cache/DisposeMany_Cache.cs +++ b/src/DynamicData.Benchmarks/Cache/DisposeMany_Cache.cs @@ -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] diff --git a/src/DynamicData.Benchmarks/Cache/EditDiff.cs b/src/DynamicData.Benchmarks/Cache/EditDiff.cs index a6b4fd53d..a3f408df8 100644 --- a/src/DynamicData.Benchmarks/Cache/EditDiff.cs +++ b/src/DynamicData.Benchmarks/Cache/EditDiff.cs @@ -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] @@ -49,7 +40,7 @@ public void OptionalAddsAndRemoves(int maxItems) .Range(0, MaxItems) .Select(n => (n % 2) == 0 ? new Person(n, "Name") - : Optional.None()) + : Optional.None) .ToObservable() .EditDiff(p => p.Id) .Subscribe(); diff --git a/src/DynamicData.Benchmarks/Cache/ExpireAfter_Cache_ForSource.cs b/src/DynamicData.Benchmarks/Cache/ExpireAfter_Cache_ForSource.cs index 3c437f89f..ff3e3699f 100644 --- a/src/DynamicData.Benchmarks/Cache/ExpireAfter_Cache_ForSource.cs +++ b/src/DynamicData.Benchmarks/Cache/ExpireAfter_Cache_ForSource.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Immutable; -using System.Linq; - -using BenchmarkDotNet.Attributes; - -using Bogus; +using Bogus; namespace DynamicData.Benchmarks.Cache; diff --git a/src/DynamicData.Benchmarks/Cache/ExpireAfter_Cache_ForStream.cs b/src/DynamicData.Benchmarks/Cache/ExpireAfter_Cache_ForStream.cs index a13cefbf0..f7703de96 100644 --- a/src/DynamicData.Benchmarks/Cache/ExpireAfter_Cache_ForStream.cs +++ b/src/DynamicData.Benchmarks/Cache/ExpireAfter_Cache_ForStream.cs @@ -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; @@ -113,7 +106,7 @@ public ExpireAfter_Cache_ForStream() [Benchmark] public void RandomizedEditsAndExpirations() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .ExpireAfter( diff --git a/src/DynamicData.Benchmarks/Cache/FilterImmutable.cs b/src/DynamicData.Benchmarks/Cache/FilterImmutable.cs index 6bde5da17..1faf82f25 100644 --- a/src/DynamicData.Benchmarks/Cache/FilterImmutable.cs +++ b/src/DynamicData.Benchmarks/Cache/FilterImmutable.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Reactive.Subjects; - -using BenchmarkDotNet.Attributes; - namespace DynamicData.Benchmarks.Cache; [MemoryDiagnoser] @@ -58,7 +52,7 @@ public FilterImmutable() [Benchmark] public void Adds() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .FilterImmutable(static item => item.IsIncluded) @@ -73,7 +67,7 @@ public void Adds() [Benchmark] public void AddsAndReplacements() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .FilterImmutable(static item => item.IsIncluded) @@ -91,7 +85,7 @@ public void AddsAndReplacements() [Benchmark] public void AddsAndRemoves() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .FilterImmutable(static item => item.IsIncluded) @@ -109,7 +103,7 @@ public void AddsAndRemoves() [Benchmark] public void AddsReplacementsAndRemoves() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .FilterImmutable(static item => item.IsIncluded) diff --git a/src/DynamicData.Benchmarks/Cache/Filter_Cache_WithPredicateState.cs b/src/DynamicData.Benchmarks/Cache/Filter_Cache_WithPredicateState.cs index 0404b7c54..e5bc6797e 100644 --- a/src/DynamicData.Benchmarks/Cache/Filter_Cache_WithPredicateState.cs +++ b/src/DynamicData.Benchmarks/Cache/Filter_Cache_WithPredicateState.cs @@ -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; @@ -99,7 +92,6 @@ public Filter_Cache_WithPredicateState() } _changeSets = changeSets.MoveToImmutable(); - var predicateStates = ImmutableArray.CreateBuilder(initialCapacity: 5_000); while (predicateStates.Count < predicateStates.Capacity) predicateStates.Add(randomizer.Int()); @@ -109,8 +101,8 @@ public Filter_Cache_WithPredicateState() [Benchmark(Baseline = true)] public void RandomizedEditsAndStateChanges() { - using var source = new Subject>(); - using var predicateState = new Subject(); + using var source = new Signal>(); + using var predicateState = new Signal(); using var subscription = source .Filter( diff --git a/src/DynamicData.Benchmarks/Cache/SortAndBindChange.cs b/src/DynamicData.Benchmarks/Cache/SortAndBindChange.cs index 484e544e1..cbf4340bb 100644 --- a/src/DynamicData.Benchmarks/Cache/SortAndBindChange.cs +++ b/src/DynamicData.Benchmarks/Cache/SortAndBindChange.cs @@ -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] @@ -19,11 +11,10 @@ private record Item(string Name, int Id, int Ranking); .Ascending(i => i.Ranking) .ThenByAscending(i => i.Name); - - Subject> _newSubject = new(); - Subject> _newSubjectOptimised = new(); - Subject> _oldSubject = new(); - Subject> _oldSubjectOptimised = new(); + Signal> _newSubject = new(); + Signal> _newSubjectOptimised = new(); + Signal> _oldSubject = new(); + Signal> _oldSubjectOptimised = new(); private IDisposable? _cleanUp; @@ -32,22 +23,18 @@ private record Item(string Name, int Id, int Ranking); private ReadOnlyObservableCollection? _oldList; private ReadOnlyObservableCollection? _oldListOptimised; - - [Params(10, 100, 1_000, 10_000, 50_000)] public int Count { get; set; } - [GlobalSetup] public void SetUp() { - _oldSubject = new Subject>(); - _oldSubjectOptimised = new Subject>(); - _newSubject = new Subject>(); - _newSubjectOptimised = new Subject>(); - + _oldSubject = new Signal>(); + _oldSubjectOptimised = new Signal>(); + _newSubject = new Signal>(); + _newSubjectOptimised = new Signal>(); - _cleanUp = new CompositeDisposable + _cleanUp = new CompositeDisposable ( _newSubject.SortAndBind(out var newList, _comparer).Subscribe(), _newSubjectOptimised.SortAndBind(out var optimisedList, _comparer, new SortAndBindOptions @@ -65,8 +52,6 @@ public void SetUp() _oldList = oldList; _oldListOptimised = oldOptimisedList; - - var changeSet = new ChangeSet(Count); foreach (var i in Enumerable.Range(1, Count)) { @@ -84,7 +69,6 @@ public void SetUp() [Benchmark(Baseline = true)] public void Old() => RunTest(_oldSubject, _oldList!); - [Benchmark] public void OldOptimized() => RunTest(_oldSubjectOptimised, _oldListOptimised!); @@ -94,8 +78,7 @@ public void SetUp() [Benchmark] public void NewOptimized() => RunTest(_newSubjectOptimised, _newListOptimised!); - - void RunTest(Subject> subject, ReadOnlyObservableCollection list) + void RunTest(Signal> subject, ReadOnlyObservableCollection list) { var original = list[Count / 2]; var updated = original with { Ranking = _random.Next(1, 1000) }; @@ -106,6 +89,5 @@ void RunTest(Subject> subject, ReadOnlyObservableCollectio }); } - public void Dispose() => _cleanUp?.Dispose(); -} \ No newline at end of file +} diff --git a/src/DynamicData.Benchmarks/Cache/SortAndBindInitial.cs b/src/DynamicData.Benchmarks/Cache/SortAndBindInitial.cs index a27b082bd..8d13605c0 100644 --- a/src/DynamicData.Benchmarks/Cache/SortAndBindInitial.cs +++ b/src/DynamicData.Benchmarks/Cache/SortAndBindInitial.cs @@ -1,10 +1,3 @@ -using BenchmarkDotNet.Attributes; -using System; -using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Subjects; -using DynamicData.Binding; - namespace DynamicData.Benchmarks.Cache; [MemoryDiagnoser] @@ -17,27 +10,24 @@ private record Item(string Name, int Id, int Ranking); private readonly SortExpressionComparer _comparer = SortExpressionComparer.Ascending(i => i.Ranking).ThenByAscending(i => i.Name); - - Subject> _newSubject = new(); - Subject> _newSubjectOptimised = new(); - Subject> _oldSubject = new(); - Subject> _oldSubjectOptimised = new(); + Signal> _newSubject = new(); + Signal> _newSubjectOptimised = new(); + Signal> _oldSubject = new(); + Signal> _oldSubjectOptimised = new(); private IDisposable? _cleanUp; private ChangeSet? _changeSet; - [Params(10, 100, 1_000, 10_000, 50_000)] public int Count { get; set; } - [GlobalSetup] public void SetUp() { - _oldSubject = new Subject>(); - _oldSubjectOptimised = new Subject>(); - _newSubject = new Subject>(); - _newSubjectOptimised = new Subject>(); + _oldSubject = new Signal>(); + _oldSubjectOptimised = new Signal>(); + _newSubject = new Signal>(); + _newSubjectOptimised = new Signal>(); var changeSet = new ChangeSet(Count); foreach (var i in Enumerable.Range(1, Count)) @@ -63,7 +53,6 @@ public void SetUp() ); } - [Benchmark(Baseline = true)] public void Old() => _oldSubject.OnNext(_changeSet!); @@ -76,6 +65,5 @@ public void SetUp() [Benchmark] public void NewOptimized() => _newSubjectOptimised.OnNext(_changeSet!); - public void Dispose() => _cleanUp?.Dispose(); -} \ No newline at end of file +} diff --git a/src/DynamicData.Benchmarks/Cache/SourceCache.cs b/src/DynamicData.Benchmarks/Cache/SourceCache.cs index f2157221d..567f88803 100644 --- a/src/DynamicData.Benchmarks/Cache/SourceCache.cs +++ b/src/DynamicData.Benchmarks/Cache/SourceCache.cs @@ -2,11 +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.Linq; - -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Jobs; - namespace DynamicData.Benchmarks.Cache { public class BenchmarkItem diff --git a/src/DynamicData.Benchmarks/Cache/StatelessFiltering.cs b/src/DynamicData.Benchmarks/Cache/StatelessFiltering.cs index b4a806afb..34161c08f 100644 --- a/src/DynamicData.Benchmarks/Cache/StatelessFiltering.cs +++ b/src/DynamicData.Benchmarks/Cache/StatelessFiltering.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Reactive.Subjects; - -using BenchmarkDotNet.Attributes; - namespace DynamicData.Benchmarks.Cache; [MemoryDiagnoser] @@ -53,7 +47,7 @@ public StatelessFiltering() [Benchmark(Baseline = true)] public void Filter() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .Filter(static item => item.IsIncluded) @@ -67,7 +61,7 @@ public void Filter() [Benchmark] public void FilterImmutable() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .FilterImmutable(static item => item.IsIncluded) diff --git a/src/DynamicData.Benchmarks/Cache/StatelessTransforming.cs b/src/DynamicData.Benchmarks/Cache/StatelessTransforming.cs index 9328dd956..a0179332e 100644 --- a/src/DynamicData.Benchmarks/Cache/StatelessTransforming.cs +++ b/src/DynamicData.Benchmarks/Cache/StatelessTransforming.cs @@ -1,10 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Subjects; - -using BenchmarkDotNet.Attributes; - namespace DynamicData.Benchmarks.Cache; [MemoryDiagnoser] @@ -54,7 +47,7 @@ public StatelessTransforming() [Benchmark(Baseline = true)] public void Transform() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .Transform(static item => item.Name) @@ -68,7 +61,7 @@ public void Transform() [Benchmark] public void TransformImmutable() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .TransformImmutable(static item => item.Name) diff --git a/src/DynamicData.Benchmarks/Cache/ToObservableChangeSet_Cache.cs b/src/DynamicData.Benchmarks/Cache/ToObservableChangeSet_Cache.cs index eca407d03..d90164cf7 100644 --- a/src/DynamicData.Benchmarks/Cache/ToObservableChangeSet_Cache.cs +++ b/src/DynamicData.Benchmarks/Cache/ToObservableChangeSet_Cache.cs @@ -1,11 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Subjects; - -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Columns; - namespace DynamicData.Benchmarks.Cache; [MemoryDiagnoser] @@ -51,7 +43,7 @@ static ToObservableChangeSet_Cache() [Arguments(1_000, 1_000)] public void AddsUpdatesAndFinalization(int itemCount, int sizeLimit) { - using var source = new Subject(); + using var source = new Signal(); using var subscription = source .ToObservableChangeSet( diff --git a/src/DynamicData.Benchmarks/Cache/TransformImmutable.cs b/src/DynamicData.Benchmarks/Cache/TransformImmutable.cs index 3bbd28d8f..d6d6a29b7 100644 --- a/src/DynamicData.Benchmarks/Cache/TransformImmutable.cs +++ b/src/DynamicData.Benchmarks/Cache/TransformImmutable.cs @@ -1,10 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Subjects; - -using BenchmarkDotNet.Attributes; - namespace DynamicData.Benchmarks.Cache; [MemoryDiagnoser] @@ -59,7 +52,7 @@ public TransformImmutable() [Benchmark] public void Adds() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .TransformImmutable(static item => item.Name) @@ -74,7 +67,7 @@ public void Adds() [Benchmark] public void AddsAndReplacements() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .TransformImmutable(static item => item.Name) @@ -92,7 +85,7 @@ public void AddsAndReplacements() [Benchmark] public void AddsAndRemoves() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .TransformImmutable(static item => item.Name) @@ -110,7 +103,7 @@ public void AddsAndRemoves() [Benchmark] public void AddsReplacementsAndRemoves() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .TransformImmutable(static item => item.Name) diff --git a/src/DynamicData.Benchmarks/Cache/TransformMany.cs b/src/DynamicData.Benchmarks/Cache/TransformMany.cs index 5b91eaa41..5de8d8524 100644 --- a/src/DynamicData.Benchmarks/Cache/TransformMany.cs +++ b/src/DynamicData.Benchmarks/Cache/TransformMany.cs @@ -1,10 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; - -using BenchmarkDotNet.Attributes; - namespace DynamicData.Benchmarks.Cache; [MemoryDiagnoser] diff --git a/src/DynamicData.Benchmarks/DynamicData.Benchmarks.csproj b/src/DynamicData.Benchmarks/DynamicData.Benchmarks.csproj index 451ac4697..9a3ad1e6a 100644 --- a/src/DynamicData.Benchmarks/DynamicData.Benchmarks.csproj +++ b/src/DynamicData.Benchmarks/DynamicData.Benchmarks.csproj @@ -1,28 +1,46 @@ - + - - Exe - net9.0 - AnyCPU - false - ;1591;1701;1702;1705;CA1822;CA1001 - + + Exe + net10.0 + AnyCPU + false + ;1701;1702;1705;CA1822;CA1001 + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/DynamicData.Benchmarks/List/DisposeMany_List.cs b/src/DynamicData.Benchmarks/List/DisposeMany_List.cs index 53cbe5d5e..100d8dbf6 100644 --- a/src/DynamicData.Benchmarks/List/DisposeMany_List.cs +++ b/src/DynamicData.Benchmarks/List/DisposeMany_List.cs @@ -1,12 +1,7 @@ -// Copyright (c) 2011-2019 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2019 Roland Pheasant. All rights reserved. // 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.Reactive.Disposables; - -using BenchmarkDotNet.Attributes; - namespace DynamicData.Benchmarks.List { [MemoryDiagnoser] diff --git a/src/DynamicData.Benchmarks/List/ExpireAfter_List.cs b/src/DynamicData.Benchmarks/List/ExpireAfter_List.cs index ec9a39316..1a68f00d4 100644 --- a/src/DynamicData.Benchmarks/List/ExpireAfter_List.cs +++ b/src/DynamicData.Benchmarks/List/ExpireAfter_List.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Immutable; -using System.Linq; - -using BenchmarkDotNet.Attributes; - -using Bogus; +using Bogus; namespace DynamicData.Benchmarks.List; diff --git a/src/DynamicData.Benchmarks/List/Filter_List_Static_RandomizedBoundedEdits.cs b/src/DynamicData.Benchmarks/List/Filter_List_Static_RandomizedBoundedEdits.cs index 6e4ab39ee..d20b7bb30 100644 --- a/src/DynamicData.Benchmarks/List/Filter_List_Static_RandomizedBoundedEdits.cs +++ b/src/DynamicData.Benchmarks/List/Filter_List_Static_RandomizedBoundedEdits.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Reactive.Subjects; using System.Reflection; -using BenchmarkDotNet.Attributes; using Bogus; @@ -44,7 +38,7 @@ public Filter_List_Static_RandomizedBoundedEdits() [Benchmark(Baseline = true)] public void CurrentImplementation() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .Filter(Item.FilterByIsIncluded) diff --git a/src/DynamicData.Benchmarks/List/Filter_List_Static_RandomizedUnboundedEdits.cs b/src/DynamicData.Benchmarks/List/Filter_List_Static_RandomizedUnboundedEdits.cs index e9065ef17..8691e2d22 100644 --- a/src/DynamicData.Benchmarks/List/Filter_List_Static_RandomizedUnboundedEdits.cs +++ b/src/DynamicData.Benchmarks/List/Filter_List_Static_RandomizedUnboundedEdits.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Reactive.Subjects; using System.Reflection; -using BenchmarkDotNet.Attributes; using Bogus; @@ -42,7 +36,7 @@ public Filter_List_Static_RandomizedUnboundedEdits() [Benchmark(Baseline = true)] public void CurrentImplementation() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .Filter(Item.FilterByIsIncluded) diff --git a/src/DynamicData.Benchmarks/List/Filter_List_WithPredicateState.cs b/src/DynamicData.Benchmarks/List/Filter_List_WithPredicateState.cs index 3707767be..846e20006 100644 --- a/src/DynamicData.Benchmarks/List/Filter_List_WithPredicateState.cs +++ b/src/DynamicData.Benchmarks/List/Filter_List_WithPredicateState.cs @@ -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.List; @@ -34,8 +27,8 @@ public Filter_List_WithPredicateState() [Benchmark(Baseline = true)] public void RandomizedEditsAndStateChanges() { - using var source = new Subject>(); - using var predicateState = new Subject(); + using var source = new Signal>(); + using var predicateState = new Signal(); using var subscription = source .Filter( diff --git a/src/DynamicData.Benchmarks/List/GroupAdd.cs b/src/DynamicData.Benchmarks/List/GroupAdd.cs index 92572496e..0037925eb 100644 --- a/src/DynamicData.Benchmarks/List/GroupAdd.cs +++ b/src/DynamicData.Benchmarks/List/GroupAdd.cs @@ -2,12 +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 BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Jobs; - namespace DynamicData.Benchmarks.List { [SimpleJob(RuntimeMoniker.NetCoreApp31)] diff --git a/src/DynamicData.Benchmarks/List/GroupRemove.cs b/src/DynamicData.Benchmarks/List/GroupRemove.cs index d4ba95e05..dee140d07 100644 --- a/src/DynamicData.Benchmarks/List/GroupRemove.cs +++ b/src/DynamicData.Benchmarks/List/GroupRemove.cs @@ -2,12 +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 BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Jobs; - namespace DynamicData.Benchmarks.List { [SimpleJob(RuntimeMoniker.NetCoreApp31)] diff --git a/src/DynamicData.Benchmarks/List/SourceList.cs b/src/DynamicData.Benchmarks/List/SourceList.cs index 6556d6ecf..1c29970e5 100644 --- a/src/DynamicData.Benchmarks/List/SourceList.cs +++ b/src/DynamicData.Benchmarks/List/SourceList.cs @@ -2,11 +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.Linq; - -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Jobs; - namespace DynamicData.Benchmarks.List { [SimpleJob(RuntimeMoniker.NetCoreApp31)] diff --git a/src/DynamicData.Benchmarks/List/ToObservableChangeSet_List.cs b/src/DynamicData.Benchmarks/List/ToObservableChangeSet_List.cs index 0908c20f9..799ed91d4 100644 --- a/src/DynamicData.Benchmarks/List/ToObservableChangeSet_List.cs +++ b/src/DynamicData.Benchmarks/List/ToObservableChangeSet_List.cs @@ -1,9 +1,3 @@ -using System; -using System.Reactive.Subjects; - -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Columns; - namespace DynamicData.Benchmarks.List; [MemoryDiagnoser] @@ -30,7 +24,7 @@ public class ToObservableChangeSet_List [Arguments(1_000, 1_000)] public void AddsUpdatesAndFinalization(int itemCount, int sizeLimit) { - using var source = new Subject(); + using var source = new Signal(); using var subscription = source .ToObservableChangeSet(limitSizeTo: sizeLimit) diff --git a/src/DynamicData.Benchmarks/Program.cs b/src/DynamicData.Benchmarks/Program.cs index 5bf5d888f..225fd0ee2 100644 --- a/src/DynamicData.Benchmarks/Program.cs +++ b/src/DynamicData.Benchmarks/Program.cs @@ -2,7 +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.IO; using System.Runtime.CompilerServices; using BenchmarkDotNet.Configs; diff --git a/src/DynamicData.Reactive/DynamicData.Reactive.csproj b/src/DynamicData.Reactive/DynamicData.Reactive.csproj new file mode 100644 index 000000000..c13d44062 --- /dev/null +++ b/src/DynamicData.Reactive/DynamicData.Reactive.csproj @@ -0,0 +1,64 @@ + + + + Dynamic Data + + Bring the power of Rx to collections using Dynamic Data. + Dynamic Data is a comprehensive caching and data manipulation solution which introduces domain centric observable collections. + Linq extensions enable dynamic filtering, sorting, grouping, transforms, binding, pagination, data virtualisation, expiration, disposal management plus more. + + net462;net472;net48;net481;net8.0;net9.0;net10.0;net11.0 + true + $(DefineConstants);REACTIVE_SHIM + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ObservableCacheEx.cs + + + + + + + + + + + + + + + + + + diff --git a/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataReactiveTests.DotNet10_0.verified.txt b/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataReactiveTests.DotNet10_0.verified.txt new file mode 100644 index 000000000..e3d1b2e30 --- /dev/null +++ b/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataReactiveTests.DotNet10_0.verified.txt @@ -0,0 +1,3061 @@ +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DynamicData.Benchmarks")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DynamicData.Profile")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DynamicData.ReactiveUI")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DynamicData.Tests")] +[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v10.0", FrameworkDisplayName=".NET 10.0")] +namespace DynamicData.Reactive.Aggregation +{ + public readonly struct AggregateItem : System.IEquatable> + { + public AggregateItem(DynamicData.Reactive.Aggregation.AggregateType Type, TObject Item) { } + public TObject Item { get; init; } + public DynamicData.Reactive.Aggregation.AggregateType Type { get; init; } + public override int GetHashCode() { } + } + public enum AggregateType + { + Add = 0, + Remove = 1, + } + public static class AggregationEx + { + public static System.IObservable> ForAggregation(this System.IObservable> source) + where TObject : notnull { } + public static System.IObservable> ForAggregation(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable InvalidateWhen(this System.IObservable source, System.IObservable invalidate) { } + public static System.IObservable InvalidateWhen(this System.IObservable source, System.IObservable invalidate) { } + } + public static class AvgEx + { + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, [System.Runtime.CompilerServices.DecimalConstant(0, 0, 0u, 0u, 0u)] decimal emptyValue) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, [System.Runtime.CompilerServices.DecimalConstant(0, 0, 0u, 0u, 0u)] decimal emptyValue) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, double emptyValue = 0) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, double emptyValue = 0) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, float emptyValue = 0) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, float emptyValue = 0) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, int emptyValue = 0) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, int emptyValue = 0) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, long emptyValue = 0) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, long emptyValue = 0) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, [System.Runtime.CompilerServices.DecimalConstant(0, 0, 0u, 0u, 0u)] decimal emptyValue) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, [System.Runtime.CompilerServices.DecimalConstant(0, 0, 0u, 0u, 0u)] decimal emptyValue) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, double emptyValue = 0) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, double emptyValue = 0) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, float emptyValue = 0) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, float emptyValue = 0) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, int emptyValue = 0) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, int emptyValue = 0) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, long emptyValue = 0) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, long emptyValue = 0) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, [System.Runtime.CompilerServices.DecimalConstant(0, 0, 0u, 0u, 0u)] decimal emptyValue) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, [System.Runtime.CompilerServices.DecimalConstant(0, 0, 0u, 0u, 0u)] decimal emptyValue) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, double emptyValue = 0) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, double emptyValue = 0) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, float emptyValue = 0) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, float emptyValue = 0) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, int emptyValue = 0) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, int emptyValue = 0) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, long emptyValue = 0) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, long emptyValue = 0) + where TObject : notnull + where TKey : notnull { } + } + public static class CountEx + { + public static System.IObservable Count(this System.IObservable> source) { } + public static System.IObservable Count(this System.IObservable> source) + where TObject : notnull { } + public static System.IObservable Count(this System.IObservable> source) + where TObject : notnull { } + public static System.IObservable Count(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable IsEmpty(this System.IObservable> source) + where TObject : notnull { } + public static System.IObservable IsEmpty(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable IsNotEmpty(this System.IObservable> source) + where TObject : notnull { } + public static System.IObservable IsNotEmpty(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + } + public interface IAggregateChangeSet : System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable { } + public static class MaxEx + { + public static System.IObservable Maximum(this System.IObservable> source, System.Func valueSelector, TResult emptyValue = default) + where TObject : notnull + where TResult : struct, System.IComparable { } + public static System.IObservable Maximum(this System.IObservable> source, System.Func valueSelector, TResult emptyValue = default) + where TObject : notnull + where TKey : notnull + where TResult : struct, System.IComparable { } + public static System.IObservable Minimum(this System.IObservable> source, System.Func valueSelector, TResult emptyValue = default) + where TObject : notnull + where TResult : struct, System.IComparable { } + public static System.IObservable Minimum(this System.IObservable> source, System.Func valueSelector, TResult emptyValue = default) + where TObject : notnull + where TKey : notnull + where TResult : struct, System.IComparable { } + } + public static class StdDevEx + { + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, [System.Runtime.CompilerServices.DecimalConstant(0, 0, 0u, 0u, 0u)] decimal fallbackValue) { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, double fallbackValue = 0) { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, float fallbackValue = 0) { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, int fallbackValue = 0) { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, long fallbackValue = 0) { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, decimal fallbackValue) + where T : notnull { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, double fallbackValue) + where T : notnull { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, float fallbackValue = 0) + where T : notnull { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, int fallbackValue) + where T : notnull { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, long fallbackValue) + where T : notnull { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, decimal fallbackValue) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, double fallbackValue) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, float fallbackValue = 0) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, int fallbackValue) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, long fallbackValue) + where TObject : notnull + where TKey : notnull { } + } + public static class SumEx + { + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + } +} +namespace DynamicData.Reactive.Alias +{ + public static class ObservableCacheAlias + { + public static System.IObservable> Select(this System.IObservable> source, System.Func transformFactory, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Select(this System.IObservable> source, System.Func transformFactory, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Select(this System.IObservable> source, System.Func transformFactory, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Select(this System.IObservable> source, System.Func transformFactory, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> SelectMany(this System.IObservable> source, System.Func> manySelector, System.Func keySelector) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> SelectSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> SelectSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> SelectSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> SelectSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable, TKey>> SelectTree(this System.IObservable> source, System.Func pivotOn) + where TObject : class + where TKey : notnull { } + public static System.IObservable> Where(this System.IObservable> source, System.Func filter) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Where(this System.IObservable> source, System.IObservable> predicateChanged) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Where(this System.IObservable> source, System.IObservable> predicateChanged, System.IObservable reapplyFilter) + where TObject : notnull + where TKey : notnull { } + } + public static class ObservableListAlias + { + public static System.IObservable> Select(this System.IObservable> source, System.Func transformFactory) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> SelectMany(this System.IObservable> source, System.Func> manySelector) + where TDestination : notnull + where TSource : notnull { } + public static System.IObservable> Where(this System.IObservable> source, System.Func predicate) + where T : notnull { } + public static System.IObservable> Where(this System.IObservable> source, System.IObservable> predicate) + where T : notnull { } + } +} +namespace DynamicData.Reactive.Binding +{ + public abstract class AbstractNotifyPropertyChanged : System.ComponentModel.INotifyPropertyChanged + { + protected AbstractNotifyPropertyChanged() { } + public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged; + protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) { } + protected virtual void SetAndRaise(ref T backingField, T newValue, [System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) { } + protected virtual void SetAndRaise(ref T backingField, T newValue, System.Collections.Generic.IEqualityComparer? comparer, [System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) { } + } + public class BindingListAdaptor<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] T> : DynamicData.Reactive.IChangeSetAdaptor + where T : notnull + { + public BindingListAdaptor(System.ComponentModel.BindingList list, int refreshThreshold = 25) { } + public void Adapt(DynamicData.Reactive.IChangeSet changes) { } + } + public class BindingListAdaptor<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey> : DynamicData.Reactive.IChangeSetAdaptor + where TObject : notnull + where TKey : notnull + { + public BindingListAdaptor(System.ComponentModel.BindingList list, int refreshThreshold = 25) { } + public void Adapt(DynamicData.Reactive.IChangeSet changes) { } + } + public static class BindingListEx + { + public static System.IObservable> ObserveCollectionChanges(this System.ComponentModel.IBindingList source) { } + public static System.IObservable> ToObservableChangeSet<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] T>(this System.ComponentModel.BindingList source) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this TCollection source) + where TCollection : System.ComponentModel.IBindingList, System.Collections.Generic.IEnumerable + where T : notnull { } + public static System.IObservable> ToObservableChangeSet<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.ComponentModel.BindingList source, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + } + public struct BindingOptions : System.IEquatable + { + public const bool DefaultResetOnFirstTimeLoad = true; + public const int DefaultResetThreshold = 25; + public const bool DefaultUseReplaceForUpdates = true; + public BindingOptions(int ResetThreshold, bool UseReplaceForUpdates = true, bool ResetOnFirstTimeLoad = true) { } + public bool ResetOnFirstTimeLoad { get; set; } + public int ResetThreshold { get; set; } + public bool UseReplaceForUpdates { get; set; } + public static DynamicData.Reactive.Binding.BindingOptions NeverFireReset(bool useReplaceForUpdates = true) { } + } + public interface IEvaluateAware + { + void Evaluate(); + } + public interface IIndexAware + { + int Index { get; set; } + } + public interface INotifyCollectionChangedSuspender + { + System.IDisposable SuspendCount(); + System.IDisposable SuspendNotifications(); + } + public interface IObservableCollectionAdaptor + where TObject : notnull + where TKey : notnull + { + void Adapt(DynamicData.Reactive.IChangeSet changes, DynamicData.Reactive.Binding.IObservableCollection collection); + } + public interface IObservableCollection : DynamicData.Reactive.Binding.INotifyCollectionChangedSuspender, System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IList, System.Collections.IEnumerable, System.Collections.Specialized.INotifyCollectionChanged, System.ComponentModel.INotifyPropertyChanged + { + void Load(System.Collections.Generic.IEnumerable items); + void Move(int oldIndex, int newIndex); + } + public static class IObservableListEx + { + public static System.IObservable> BindToObservableList(this System.IObservable> source, out DynamicData.Reactive.IObservableList observableList) + where TObject : notnull { } + public static System.IObservable> BindToObservableList(this System.IObservable> source, out DynamicData.Reactive.IObservableList observableList) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> BindToObservableList(this System.IObservable> source, out DynamicData.Reactive.IObservableList observableList) + where TObject : notnull + where TKey : notnull { } + } + public interface ISortedObservableCollectionAdaptor + where TObject : notnull + where TKey : notnull + { + void Adapt(DynamicData.Reactive.ISortedChangeSet changes, DynamicData.Reactive.Binding.IObservableCollection collection); + } + public static class NotifyPropertyChangedEx + { + public static System.IObservable WhenAnyPropertyChanged(this TObject source, params string[] propertiesToMonitor) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable WhenChanged(this TObject source, System.Linq.Expressions.Expression> p1, System.Func resultSelector, System.Func? p1Fallback = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable WhenChanged(this TObject source, System.Linq.Expressions.Expression> p1, System.Linq.Expressions.Expression> p2, System.Func resultSelector, System.Func? p1Fallback = null, System.Func? p2Fallback = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable WhenChanged(this TObject source, System.Linq.Expressions.Expression> p1, System.Linq.Expressions.Expression> p2, System.Linq.Expressions.Expression> p3, System.Func resultSelector, System.Func? p1Fallback = null, System.Func? p2Fallback = null, System.Func? p3Fallback = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable WhenChanged(this TObject source, System.Linq.Expressions.Expression> p1, System.Linq.Expressions.Expression> p2, System.Linq.Expressions.Expression> p3, System.Linq.Expressions.Expression> p4, System.Func resultSelector, System.Func? p1Fallback = null, System.Func? p2Fallback = null, System.Func? p3Fallback = null, System.Func? p4Fallback = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable WhenChanged(this TObject source, System.Linq.Expressions.Expression> p1, System.Linq.Expressions.Expression> p2, System.Linq.Expressions.Expression> p3, System.Linq.Expressions.Expression> p4, System.Linq.Expressions.Expression> p5, System.Func resultSelector, System.Func? p1Fallback = null, System.Func? p2Fallback = null, System.Func? p3Fallback = null, System.Func? p4Fallback = null, System.Func? p5Fallback = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable WhenChanged(this TObject source, System.Linq.Expressions.Expression> p1, System.Linq.Expressions.Expression> p2, System.Linq.Expressions.Expression> p3, System.Linq.Expressions.Expression> p4, System.Linq.Expressions.Expression> p5, System.Linq.Expressions.Expression> p6, System.Func resultSelector, System.Func? p1Fallback = null, System.Func? p2Fallback = null, System.Func? p3Fallback = null, System.Func? p4Fallback = null, System.Func? p5Fallback = null, System.Func? p6Fallback = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable> WhenPropertyChanged(this TObject source, System.Linq.Expressions.Expression> propertyAccessor, bool notifyOnInitialValue = true, System.Func? fallbackValue = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable WhenValueChanged(this TObject source, System.Linq.Expressions.Expression> propertyAccessor, bool notifyOnInitialValue = true, System.Func? fallbackValue = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + } + public class ObservableCollectionAdaptor : DynamicData.Reactive.IChangeSetAdaptor + where T : notnull + { + public ObservableCollectionAdaptor(DynamicData.Reactive.Binding.IObservableCollection collection) { } + public ObservableCollectionAdaptor(DynamicData.Reactive.Binding.IObservableCollection collection, DynamicData.Reactive.Binding.BindingOptions options) { } + public ObservableCollectionAdaptor(DynamicData.Reactive.Binding.IObservableCollection collection, int refreshThreshold, bool allowReplace = true, bool resetOnFirstTimeLoad = true) { } + public void Adapt(DynamicData.Reactive.IChangeSet changes) { } + } + public class ObservableCollectionAdaptor : DynamicData.Reactive.Binding.IObservableCollectionAdaptor + where TObject : notnull + where TKey : notnull + { + public ObservableCollectionAdaptor(DynamicData.Reactive.Binding.BindingOptions options) { } + public ObservableCollectionAdaptor(int refreshThreshold = 25, bool useReplaceForUpdates = true, bool resetOnFirstTimeLoad = true) { } + public void Adapt(DynamicData.Reactive.IChangeSet changes, DynamicData.Reactive.Binding.IObservableCollection collection) { } + } + public static class ObservableCollectionEx + { + public static System.IObservable> ObserveCollectionChanges(this System.Collections.Specialized.INotifyCollectionChanged source) { } + public static System.IObservable> ToObservableChangeSet(this System.Collections.ObjectModel.ObservableCollection source) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.Collections.ObjectModel.ReadOnlyObservableCollection source) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this TCollection source) + where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.Collections.ObjectModel.ObservableCollection source, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.Collections.ObjectModel.ReadOnlyObservableCollection source, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + } + public class ObservableCollectionExtended : System.Collections.ObjectModel.ObservableCollection, DynamicData.Reactive.Binding.INotifyCollectionChangedSuspender, DynamicData.Reactive.Binding.IObservableCollection, DynamicData.Reactive.IExtendedList, System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IList, System.Collections.IEnumerable, System.Collections.Specialized.INotifyCollectionChanged, System.ComponentModel.INotifyPropertyChanged + { + public ObservableCollectionExtended() { } + public ObservableCollectionExtended(System.Collections.Generic.IEnumerable collection) { } + public ObservableCollectionExtended(System.Collections.Generic.List list) { } + public void AddRange(System.Collections.Generic.IEnumerable collection) { } + public void InsertRange(System.Collections.Generic.IEnumerable collection, int index) { } + public void Load(System.Collections.Generic.IEnumerable items) { } + protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { } + protected override void OnPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e) { } + public void RemoveRange(int index, int count) { } + public System.IDisposable SuspendCount() { } + public System.IDisposable SuspendNotifications() { } + } + public sealed class PropertyValue : System.IEquatable> + { + public PropertyValue(TObject sender, TValue value) { } + public PropertyValue(TObject Sender, TValue? Value, bool UnobtainableValue) { } + public TObject Sender { get; init; } + public bool UnobtainableValue { get; init; } + public TValue Value { get; init; } + public override int GetHashCode() { } + public override string ToString() { } + } + public readonly struct SortAndBindOptions : System.IEquatable + { + public SortAndBindOptions() { } + public int InitialCapacity { get; init; } + public bool ResetOnFirstTimeLoad { get; init; } + public int ResetThreshold { get; init; } + public System.Reactive.Concurrency.IScheduler? Scheduler { get; init; } + public bool UseBinarySearch { get; init; } + public bool UseReplaceForUpdates { get; init; } + } + public enum SortDirection + { + Ascending = 0, + Descending = 1, + } + public class SortExpressionComparer : System.Collections.Generic.List>, System.Collections.Generic.IComparer + { + public SortExpressionComparer() { } + public int Compare(T? x, T? y) { } + public DynamicData.Reactive.Binding.SortExpressionComparer ThenByAscending(System.Func expression) { } + public DynamicData.Reactive.Binding.SortExpressionComparer ThenByDescending(System.Func expression) { } + public static DynamicData.Reactive.Binding.SortExpressionComparer Ascending(System.Func expression) { } + public static DynamicData.Reactive.Binding.SortExpressionComparer Descending(System.Func expression) { } + } + public class SortExpression + { + public SortExpression(System.Func expression, DynamicData.Reactive.Binding.SortDirection direction = 0) { } + public DynamicData.Reactive.Binding.SortDirection Direction { get; } + public System.Func Expression { get; } + } + public class SortedBindingListAdaptor<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey> : DynamicData.Reactive.ISortedChangeSetAdaptor + where TObject : notnull + where TKey : notnull + { + public SortedBindingListAdaptor(System.ComponentModel.BindingList list, int refreshThreshold = 25) { } + public void Adapt(DynamicData.Reactive.ISortedChangeSet changes) { } + } + public class SortedObservableCollectionAdaptor : DynamicData.Reactive.Binding.ISortedObservableCollectionAdaptor + where TObject : notnull + where TKey : notnull + { + public SortedObservableCollectionAdaptor() { } + public SortedObservableCollectionAdaptor(DynamicData.Reactive.Binding.BindingOptions options) { } + public SortedObservableCollectionAdaptor(int refreshThreshold, bool useReplaceForUpdates = true, bool resetOnFirstTimeLoad = true) { } + public void Adapt(DynamicData.Reactive.ISortedChangeSet changes, DynamicData.Reactive.Binding.IObservableCollection collection) { } + } +} +namespace DynamicData.Reactive.Cache.Internal +{ + public enum CombineOperator + { + And = 0, + Or = 1, + Xor = 2, + Except = 3, + } + [System.Serializable] + public class KeySelectorException : System.Exception + { + public KeySelectorException() { } + public KeySelectorException(string message) { } + public KeySelectorException(string message, System.Exception innerException) { } + } + [System.Diagnostics.DebuggerDisplay("LockFreeObservableCache<{typeof(TObject).Name}, {typeof(TKey).Name}> ({Count} Ite" + + "ms)")] + public sealed class LockFreeObservableCache : DynamicData.Reactive.IConnectableCache, DynamicData.Reactive.IObservableCache, System.IDisposable + where TObject : notnull + where TKey : notnull + { + public LockFreeObservableCache() { } + public LockFreeObservableCache(System.IObservable> source) { } + public int Count { get; } + public System.IObservable CountChanged { get; } + public System.Collections.Generic.IReadOnlyList Items { get; } + public System.Collections.Generic.IReadOnlyDictionary KeyValues { get; } + public System.Collections.Generic.IReadOnlyList Keys { get; } + public System.IObservable> Connect(System.Func? predicate = null, bool suppressEmptyChangeSets = true) { } + public void Dispose() { } + public void Edit(System.Action> editAction) { } + public ReactiveUI.Primitives.Optional Lookup(TKey key) { } + public System.IObservable> Preview(System.Func? predicate = null) { } + public System.IObservable> Watch(TKey key) { } + } +} +namespace DynamicData.Reactive +{ + public sealed class ChangeAwareCache : DynamicData.Reactive.ICache, DynamicData.Reactive.IQuery + where TObject : notnull + where TKey : notnull + { + public ChangeAwareCache() { } + public ChangeAwareCache(System.Collections.Generic.Dictionary data) { } + public ChangeAwareCache(int capacity) { } + public int Count { get; } + public System.Collections.Generic.IEnumerable Items { get; } + public System.Collections.Generic.IEnumerable> KeyValues { get; } + public System.Collections.Generic.IEnumerable Keys { get; } + public void Add(TObject item, TKey key) { } + public void AddOrUpdate(TObject item, TKey key) { } + public DynamicData.Reactive.ChangeSet CaptureChanges() { } + public void Clear() { } + public void Clone(DynamicData.Reactive.IChangeSet changes) { } + public ReactiveUI.Primitives.Optional Lookup(TKey key) { } + public void Refresh() { } + public void Refresh(System.Collections.Generic.IEnumerable keys) { } + public void Refresh(TKey key) { } + public void Remove(System.Collections.Generic.IEnumerable keys) { } + public void Remove(TKey key) { } + } + public class ChangeAwareList : DynamicData.Reactive.IExtendedList, System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IList, System.Collections.IEnumerable + where T : notnull + { + public ChangeAwareList(System.Collections.Generic.IEnumerable items) { } + public ChangeAwareList(int capacity = -1) { } + public ChangeAwareList(DynamicData.Reactive.ChangeAwareList list, bool copyChanges) { } + public int Capacity { get; set; } + public int Count { get; } + public bool IsReadOnly { get; } + public T this[int index] { get; set; } + public void Add(T item) { } + public void AddRange(System.Collections.Generic.IEnumerable collection) { } + public DynamicData.Reactive.IChangeSet CaptureChanges() { } + public virtual void Clear() { } + public virtual bool Contains(T item) { } + public void CopyTo(T[] array, int arrayIndex) { } + public System.Collections.Generic.IEnumerator GetEnumerator() { } + public int IndexOf(T item) { } + public int IndexOf(T item, System.Collections.Generic.IEqualityComparer equalityComparer) { } + public void Insert(int index, T item) { } + protected virtual void InsertItem(int index, T item) { } + public void InsertRange(System.Collections.Generic.IEnumerable collection, int index) { } + public virtual void Move(T item, int destination) { } + public virtual void Move(int original, int destination) { } + protected virtual void OnInsertItems(int startIndex, System.Collections.Generic.IEnumerable items) { } + protected virtual void OnRemoveItems(int startIndex, System.Collections.Generic.IEnumerable items) { } + protected virtual void OnSetItem(int index, T newItem, T oldItem) { } + public bool Refresh(T item) { } + public void Refresh(T item, int index) { } + public void RefreshAt(int index) { } + public bool Remove(T item) { } + public void RemoveAt(int index) { } + protected void RemoveItem(int index) { } + protected virtual void RemoveItem(int index, T item) { } + public void RemoveRange(int index, int count) { } + protected virtual void SetItem(int index, T item) { } + } + public enum ChangeReason + { + Add = 0, + Update = 1, + Remove = 2, + Refresh = 3, + Moved = 4, + } + public static class ChangeSetEx + { + public static System.Collections.Generic.IEnumerable> Flatten(this DynamicData.Reactive.IChangeSet source) + where T : notnull { } + public static DynamicData.Reactive.ChangeType GetChangeType(this DynamicData.Reactive.ListChangeReason source) { } + public static DynamicData.Reactive.IChangeSet Transform(this DynamicData.Reactive.IChangeSet source, System.Func transformer) + where TSource : notnull + where TDestination : notnull { } + public static System.Collections.Generic.IEnumerable> YieldWithoutIndex(this System.Collections.Generic.IEnumerable> source) + where T : notnull { } + } + public class ChangeSet : System.Collections.Generic.List>, DynamicData.Reactive.IChangeSet, DynamicData.Reactive.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where T : notnull + { + public static readonly DynamicData.Reactive.IChangeSet Empty; + public ChangeSet() { } + public ChangeSet(System.Collections.Generic.IEnumerable> items) { } + public ChangeSet(int capacity) { } + public int Adds { get; } + public int Moves { get; } + public int Refreshes { get; } + public int Removes { get; } + public int Replaced { get; } + public int TotalChanges { get; } + public override string ToString() { } + } + public class ChangeSet : System.Collections.Generic.List>, DynamicData.Reactive.IChangeSet, DynamicData.Reactive.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where TObject : notnull + where TKey : notnull + { + public static readonly DynamicData.Reactive.ChangeSet Empty; + public ChangeSet() { } + public ChangeSet(System.Collections.Generic.IEnumerable> collection) { } + public ChangeSet(int capacity) { } + public int Adds { get; } + public int Moves { get; } + public int Refreshes { get; } + public int Removes { get; } + public int Updates { get; } + public override string ToString() { } + } + public sealed class ChangeSet : DynamicData.Reactive.ChangeSet, DynamicData.Reactive.IChangeSet, DynamicData.Reactive.IChangeSet, DynamicData.Reactive.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where TObject : notnull + where TKey : notnull + { + public ChangeSet(TContext context) { } + public ChangeSet(System.Collections.Generic.IEnumerable> collection, TContext context) { } + public ChangeSet(int capacity, TContext context) { } + public TContext Context { get; } + } + public enum ChangeType + { + Item = 0, + Range = 1, + } + public sealed class Change : System.IEquatable> + where T : notnull + { + public Change(DynamicData.Reactive.ListChangeReason reason, System.Collections.Generic.IEnumerable items, int index = -1) { } + public Change(DynamicData.Reactive.ListChangeReason reason, T current, int index = -1) { } + public Change(T current, int currentIndex, int previousIndex) { } + public Change(DynamicData.Reactive.ListChangeReason reason, T current, in ReactiveUI.Primitives.Optional previous, int currentIndex = -1, int previousIndex = -1) { } + public DynamicData.Reactive.ItemChange Item { get; } + public DynamicData.Reactive.RangeChange Range { get; } + public DynamicData.Reactive.ListChangeReason Reason { get; } + public DynamicData.Reactive.ChangeType Type { get; } + public bool Equals(DynamicData.Reactive.Change? other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + public static bool operator !=(DynamicData.Reactive.Change left, DynamicData.Reactive.Change right) { } + public static bool operator ==(DynamicData.Reactive.Change left, DynamicData.Reactive.Change right) { } + } + public readonly struct Change : System.IEquatable> + where TObject : notnull + where TKey : notnull + { + public Change(DynamicData.Reactive.ChangeReason reason, TKey key, TObject current, int index = -1) { } + public Change(TKey key, TObject current, int currentIndex, int previousIndex) { } + public Change(DynamicData.Reactive.ChangeReason reason, TKey key, TObject current, in ReactiveUI.Primitives.Optional previous, int currentIndex = -1, int previousIndex = -1) { } + public TObject Current { get; } + public int CurrentIndex { get; } + public TKey Key { get; } + public ReactiveUI.Primitives.Optional Previous { get; } + public int PreviousIndex { get; } + public DynamicData.Reactive.ChangeReason Reason { get; } + public bool Equals(DynamicData.Reactive.Change other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + public static bool operator !=(in DynamicData.Reactive.Change left, in DynamicData.Reactive.Change right) { } + public static bool operator ==(in DynamicData.Reactive.Change left, in DynamicData.Reactive.Change right) { } + } + public static class DynamicDataOptions + { + public static DynamicData.Reactive.Binding.BindingOptions Binding { get; set; } + public static DynamicData.Reactive.Binding.SortAndBindOptions SortAndBind { get; set; } + } + public static class EnumerableEx + { + public static System.IObservable> AsObservableChangeSet(this System.Collections.Generic.IEnumerable source, bool completable = false) + where TObject : notnull { } + public static System.IObservable> AsObservableChangeSet(this System.Collections.Generic.IEnumerable source, System.Func keySelector, bool completable = false) + where TObject : notnull + where TKey : notnull { } + } + public interface ICacheUpdater : DynamicData.Reactive.IQuery + where TObject : notnull + where TKey : notnull + { + void AddOrUpdate(System.Collections.Generic.IEnumerable> keyValuePairs); + void AddOrUpdate(System.Collections.Generic.KeyValuePair item); + void AddOrUpdate(TObject item, TKey key); + void Clear(); + void Clone(DynamicData.Reactive.IChangeSet changes); + TKey GetKey(TObject item); + System.Collections.Generic.IEnumerable> GetKeyValues(System.Collections.Generic.IEnumerable items); + void Refresh(); + void Refresh(System.Collections.Generic.IEnumerable keys); + void Refresh(TKey key); + void Remove(System.Collections.Generic.IEnumerable> items); + void Remove(System.Collections.Generic.IEnumerable keys); + void Remove(System.Collections.Generic.KeyValuePair item); + void Remove(TKey key); + void RemoveKey(TKey key); + void RemoveKeys(System.Collections.Generic.IEnumerable key); + [System.Obsolete("Use Clone()")] + void Update(DynamicData.Reactive.IChangeSet changes); + } + public interface ICache : DynamicData.Reactive.IQuery + where TObject : notnull + where TKey : notnull + { + void AddOrUpdate(TObject item, TKey key); + void Clear(); + void Clone(DynamicData.Reactive.IChangeSet changes); + void Refresh(); + void Refresh(System.Collections.Generic.IEnumerable keys); + void Refresh(TKey key); + void Remove(System.Collections.Generic.IEnumerable keys); + void Remove(TKey key); + } + public interface IChangeSet + { + int Adds { get; } + int Capacity { get; set; } + int Count { get; } + int Moves { get; } + int Refreshes { get; } + int Removes { get; } + } + public interface IChangeSetAdaptor + where T : notnull + { + void Adapt(DynamicData.Reactive.IChangeSet changes); + } + public interface IChangeSetAdaptor + where TObject : notnull + where TKey : notnull + { + void Adapt(DynamicData.Reactive.IChangeSet changes); + } + public interface IChangeSet : DynamicData.Reactive.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where TObject : notnull + { + int Replaced { get; } + int TotalChanges { get; } + } + public interface IChangeSet : DynamicData.Reactive.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where TObject : notnull + where TKey : notnull + { + int Updates { get; } + } + public interface IChangeSet : DynamicData.Reactive.IChangeSet, DynamicData.Reactive.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where TObject : notnull + where TKey : notnull + { + TContext Context { get; } + } + public interface IConnectableCache + where TObject : notnull + where TKey : notnull + { + System.IObservable CountChanged { get; } + System.IObservable> Connect(System.Func? predicate = null, bool suppressEmptyChangeSets = true); + System.IObservable> Preview(System.Func? predicate = null); + System.IObservable> Watch(TKey key); + } + public interface IDistinctChangeSet : DynamicData.Reactive.IChangeSet, DynamicData.Reactive.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where T : notnull { } + public interface IExtendedList : System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IList, System.Collections.IEnumerable + { + void AddRange(System.Collections.Generic.IEnumerable collection); + void InsertRange(System.Collections.Generic.IEnumerable collection, int index); + void Move(int original, int destination); + void RemoveRange(int index, int count); + } + public interface IGroupChangeSet : DynamicData.Reactive.IChangeSet, DynamicData.Reactive.IChangeSet, TGroupKey>, System.Collections.Generic.IEnumerable, TGroupKey>>, System.Collections.IEnumerable + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public interface IGroup + where TObject : notnull + { + TGroup GroupKey { get; } + DynamicData.Reactive.IObservableList List { get; } + } + public interface IGroup : DynamicData.Reactive.IKey + where TObject : notnull + where TKey : notnull + { + DynamicData.Reactive.IObservableCache Cache { get; } + } + public interface IGrouping + where TObject : notnull + { + int Count { get; } + System.Collections.Generic.IEnumerable Items { get; } + TGroupKey Key { get; } + System.Collections.Generic.IEnumerable> KeyValues { get; } + System.Collections.Generic.IEnumerable Keys { get; } + ReactiveUI.Primitives.Optional Lookup(TKey key); + } + public interface IImmutableGroupChangeSet : DynamicData.Reactive.IChangeSet, DynamicData.Reactive.IChangeSet, TGroupKey>, System.Collections.Generic.IEnumerable, TGroupKey>>, System.Collections.IEnumerable + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public interface IIntermediateCache : DynamicData.Reactive.IConnectableCache, DynamicData.Reactive.IObservableCache, System.IDisposable + where TObject : notnull + where TKey : notnull + { + void Edit(System.Action> updateAction); + } + public interface IKeyValueCollection : System.Collections.Generic.IEnumerable>, System.Collections.Generic.IReadOnlyCollection>, System.Collections.Generic.IReadOnlyList>, System.Collections.IEnumerable + { + System.Collections.Generic.IComparer> Comparer { get; } + DynamicData.Reactive.SortOptimisations Optimisations { get; } + DynamicData.Reactive.SortReason SortReason { get; } + } + public interface IKeyValue : DynamicData.Reactive.IKey + { + TObject Value { get; } + } + public interface IKey + { + T Key { get; } + } + public interface IObservableCache : DynamicData.Reactive.IConnectableCache, System.IDisposable + where TObject : notnull + where TKey : notnull + { + int Count { get; } + System.Collections.Generic.IReadOnlyList Items { get; } + System.Collections.Generic.IReadOnlyDictionary KeyValues { get; } + System.Collections.Generic.IReadOnlyList Keys { get; } + ReactiveUI.Primitives.Optional Lookup(TKey key); + } + public interface IObservableList : System.IDisposable + where T : notnull + { + int Count { get; } + System.IObservable CountChanged { get; } + System.Collections.Generic.IReadOnlyList Items { get; } + System.IObservable> Connect(System.Func? predicate = null); + System.IObservable> Preview(System.Func? predicate = null); + } + public interface IPageChangeSet : DynamicData.Reactive.IChangeSet, DynamicData.Reactive.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where T : notnull + { + DynamicData.Reactive.Operators.IPageResponse Response { get; } + } + public interface IPageRequest + { + int Page { get; } + int Size { get; } + } + public interface IPagedChangeSet : DynamicData.Reactive.IChangeSet, DynamicData.Reactive.IChangeSet, DynamicData.Reactive.ISortedChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where TObject : notnull + where TKey : notnull + { + DynamicData.Reactive.Operators.IPageResponse Response { get; } + } + public interface IQuery + where TObject : notnull + { + int Count { get; } + System.Collections.Generic.IEnumerable Items { get; } + System.Collections.Generic.IEnumerable> KeyValues { get; } + System.Collections.Generic.IEnumerable Keys { get; } + ReactiveUI.Primitives.Optional Lookup(TKey key); + } + public interface ISortedChangeSetAdaptor + where TObject : notnull + where TKey : notnull + { + void Adapt(DynamicData.Reactive.ISortedChangeSet changes); + } + public interface ISortedChangeSet : DynamicData.Reactive.IChangeSet, DynamicData.Reactive.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where TObject : notnull + where TKey : notnull + { + DynamicData.Reactive.IKeyValueCollection SortedItems { get; } + } + public interface ISourceCache : DynamicData.Reactive.IConnectableCache, DynamicData.Reactive.IObservableCache, System.IDisposable + where TObject : notnull + where TKey : notnull + { + System.Func KeySelector { get; } + void Edit(System.Action> updateAction); + } + public interface ISourceList : DynamicData.Reactive.IObservableList, System.IDisposable + where T : notnull + { + void Edit(System.Action> updateAction); + } + public interface ISourceUpdater : DynamicData.Reactive.ICacheUpdater, DynamicData.Reactive.IQuery + where TObject : notnull + where TKey : notnull + { + void AddOrUpdate(System.Collections.Generic.IEnumerable items); + void AddOrUpdate(TObject item); + void AddOrUpdate(System.Collections.Generic.IEnumerable items, System.Collections.Generic.IEqualityComparer comparer); + void AddOrUpdate(TObject item, System.Collections.Generic.IEqualityComparer comparer); + void Load(System.Collections.Generic.IEnumerable items); + void Refresh(System.Collections.Generic.IEnumerable items); + void Refresh(TObject item); + void Remove(System.Collections.Generic.IEnumerable items); + void Remove(TObject item); + } + public interface IVirtualChangeSet : DynamicData.Reactive.IChangeSet, DynamicData.Reactive.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where T : notnull + { + DynamicData.Reactive.IVirtualResponse Response { get; } + } + public interface IVirtualChangeSet : DynamicData.Reactive.IChangeSet, DynamicData.Reactive.IChangeSet, DynamicData.Reactive.ISortedChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where TObject : notnull + where TKey : notnull + { + DynamicData.Reactive.IVirtualResponse Response { get; } + } + public interface IVirtualRequest + { + int Size { get; } + int StartIndex { get; } + } + public interface IVirtualResponse + { + int Size { get; } + int StartIndex { get; } + int TotalSize { get; } + } + public sealed class IndexedItem : System.IEquatable> + { + public IndexedItem(TObject value, TKey key, int index) { } + public int Index { get; } + public TKey Key { get; } + public TObject Value { get; } + public bool Equals(DynamicData.Reactive.IndexedItem? other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + } + [System.Diagnostics.DebuggerDisplay("IntermediateCache<{typeof(TObject).Name}, {typeof(TKey).Name}> ({Count} Items)")] + public sealed class IntermediateCache : DynamicData.Reactive.Binding.INotifyCollectionChangedSuspender, DynamicData.Reactive.IConnectableCache, DynamicData.Reactive.IIntermediateCache, DynamicData.Reactive.IObservableCache, System.IDisposable + where TObject : notnull + where TKey : notnull + { + public IntermediateCache() { } + public IntermediateCache(System.IObservable> source) { } + public int Count { get; } + public System.IObservable CountChanged { get; } + public System.Collections.Generic.IReadOnlyList Items { get; } + public System.Collections.Generic.IReadOnlyDictionary KeyValues { get; } + public System.Collections.Generic.IReadOnlyList Keys { get; } + public System.IObservable> Connect(System.Func? predicate = null, bool suppressEmptyChangeSets = true) { } + public void Dispose() { } + public void Edit(System.Action> updateAction) { } + public ReactiveUI.Primitives.Optional Lookup(TKey key) { } + public System.IObservable> Preview(System.Func? predicate = null) { } + public System.IDisposable SuspendCount() { } + public System.IDisposable SuspendNotifications() { } + public System.IObservable> Watch(TKey key) { } + } + public readonly struct ItemChange : System.IEquatable> + where T : notnull + { + public static readonly DynamicData.Reactive.ItemChange Empty; + public ItemChange(DynamicData.Reactive.ListChangeReason reason, T current, int currentIndex) { } + public ItemChange(DynamicData.Reactive.ListChangeReason reason, T current, in ReactiveUI.Primitives.Optional previous, int currentIndex = -1, int previousIndex = -1) { } + public T Current { get; } + public int CurrentIndex { get; } + public ReactiveUI.Primitives.Optional Previous { get; } + public int PreviousIndex { get; } + public DynamicData.Reactive.ListChangeReason Reason { get; } + public bool Equals(DynamicData.Reactive.ItemChange other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + public static bool operator !=(in DynamicData.Reactive.ItemChange left, in DynamicData.Reactive.ItemChange right) { } + public static bool operator ==(in DynamicData.Reactive.ItemChange left, in DynamicData.Reactive.ItemChange right) { } + } + public enum ListChangeReason + { + Add = 0, + AddRange = 1, + Replace = 2, + Remove = 3, + RemoveRange = 4, + Refresh = 5, + Moved = 6, + Clear = 7, + } + public static class ListEx + { + public static void Add(this System.Collections.Generic.IList source, System.Collections.Generic.IEnumerable items) { } + public static void AddOrInsertRange(this System.Collections.Generic.IList source, System.Collections.Generic.IEnumerable items, int index) { } + public static void AddRange(this System.Collections.Generic.IList source, System.Collections.Generic.IEnumerable items) { } + public static void AddRange(this System.Collections.Generic.IList source, System.Collections.Generic.IEnumerable items, int index) { } + public static int BinarySearch(this System.Collections.Generic.IList list, TItem value) { } + public static int BinarySearch(this System.Collections.Generic.IList list, TItem value, System.Collections.Generic.IComparer comparer) { } + public static int BinarySearch(this System.Collections.Generic.IList list, TSearch value, System.Func comparer) { } + public static void Clone(this System.Collections.Generic.IList source, DynamicData.Reactive.IChangeSet changes) + where T : notnull { } + public static void Clone(this System.Collections.Generic.IList source, DynamicData.Reactive.IChangeSet changes, System.Collections.Generic.IEqualityComparer? equalityComparer) + where T : notnull { } + public static void Clone(this System.Collections.Generic.IList source, System.Collections.Generic.IEnumerable> changes, System.Collections.Generic.IEqualityComparer? equalityComparer) + where T : notnull { } + public static int IndexOf(this System.Collections.Generic.IEnumerable source, T item) { } + public static int IndexOf(this System.Collections.Generic.IEnumerable source, T item, System.Collections.Generic.IEqualityComparer equalityComparer) { } + public static ReactiveUI.Primitives.Optional> IndexOfOptional(this System.Collections.Generic.IEnumerable source, T item, System.Collections.Generic.IEqualityComparer? equalityComparer = null) { } + public static void Remove(this System.Collections.Generic.IList source, System.Collections.Generic.IEnumerable items) { } + public static void RemoveMany(this System.Collections.Generic.IList source, System.Collections.Generic.IEnumerable itemsToRemove) { } + public static void Replace(this System.Collections.Generic.IList source, T original, T replaceWith) { } + public static void Replace(this System.Collections.Generic.IList source, T original, T replaceWith, System.Collections.Generic.IEqualityComparer comparer) { } + public static void ReplaceOrAdd(this System.Collections.Generic.IList source, T original, T replaceWith) { } + } + public enum ListFilterPolicy + { + ClearAndReplace = 0, + CalculateDiff = 1, + } + [System.Serializable] + public class MissingKeyException : System.Exception + { + public MissingKeyException() { } + public MissingKeyException(string message) { } + public MissingKeyException(string message, System.Exception innerException) { } + } + public class Node : System.IDisposable, System.IEquatable> + where TObject : class + where TKey : notnull + { + public Node(TObject item, TKey key) { } + public Node(TObject item, TKey key, in ReactiveUI.Primitives.Optional> parent) { } + public DynamicData.Reactive.IObservableCache, TKey> Children { get; } + public int Depth { get; } + public bool IsRoot { get; } + public TObject Item { get; } + public TKey Key { get; } + public ReactiveUI.Primitives.Optional> Parent { get; } + public void Dispose() { } + protected virtual void Dispose(bool isDisposing) { } + public bool Equals(DynamicData.Reactive.Node? other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + public static bool operator !=(DynamicData.Reactive.Node left, DynamicData.Reactive.Node right) { } + public static bool operator ==(DynamicData.Reactive.Node? left, DynamicData.Reactive.Node? right) { } + } + public static class ObservableCacheEx + { + public static System.IObservable> Adapt(this System.IObservable> source, DynamicData.Reactive.IChangeSetAdaptor adaptor) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Adapt(this System.IObservable> source, DynamicData.Reactive.ISortedChangeSetAdaptor adaptor) + where TObject : notnull + where TKey : notnull { } + public static void AddOrUpdate(this DynamicData.Reactive.ISourceCache source, System.Collections.Generic.IEnumerable items) + where TObject : notnull + where TKey : notnull { } + public static void AddOrUpdate(this DynamicData.Reactive.ISourceCache source, TObject item) + where TObject : notnull + where TKey : notnull { } + public static void AddOrUpdate(this DynamicData.Reactive.IIntermediateCache source, TObject item, TKey key) + where TObject : notnull + where TKey : notnull { } + public static void AddOrUpdate(this DynamicData.Reactive.ISourceCache source, System.Collections.Generic.IEnumerable items, System.Collections.Generic.IEqualityComparer equalityComparer) + where TObject : notnull + where TKey : notnull { } + public static void AddOrUpdate(this DynamicData.Reactive.ISourceCache source, TObject item, System.Collections.Generic.IEqualityComparer equalityComparer) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> And(this DynamicData.Reactive.IObservableList> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> And(this DynamicData.Reactive.IObservableList> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> And(this DynamicData.Reactive.IObservableList>> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> And(this System.Collections.Generic.ICollection>> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> And(this System.IObservable> source, params System.IObservable>[] others) + where TObject : notnull + where TKey : notnull { } + public static DynamicData.Reactive.IObservableCache AsObservableCache(this DynamicData.Reactive.IObservableCache source) + where TObject : notnull + where TKey : notnull { } + public static DynamicData.Reactive.IObservableCache AsObservableCache(this System.IObservable> source, bool applyLocking = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> AsyncDisposeMany(this System.IObservable> source, System.Action> disposalsCompletedAccessor) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> AutoRefresh(this System.IObservable> source, System.TimeSpan? changeSetBuffer = default, System.TimeSpan? propertyChangeThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : System.ComponentModel.INotifyPropertyChanged + where TKey : notnull { } + public static System.IObservable> AutoRefresh(this System.IObservable> source, System.Linq.Expressions.Expression> propertyAccessor, System.TimeSpan? changeSetBuffer = default, System.TimeSpan? propertyChangeThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : System.ComponentModel.INotifyPropertyChanged + where TKey : notnull { } + public static System.IObservable> AutoRefreshOnObservable(this System.IObservable> source, System.Func> reevaluator, System.TimeSpan? changeSetBuffer = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> AutoRefreshOnObservable(this System.IObservable> source, System.Func> reevaluator, System.TimeSpan? changeSetBuffer = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Batch(this System.IObservable> source, System.TimeSpan timeSpan, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, System.TimeSpan? timeOut = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState = false, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState = false, System.IObservable? timer = null, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState = false, System.TimeSpan? timeOut = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable>> source, System.Collections.Generic.IList targetList) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable>> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable>> source, System.Collections.Generic.IList targetList) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable>> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, DynamicData.Reactive.Binding.IObservableCollection destination) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, DynamicData.Reactive.Binding.IObservableCollection destination, DynamicData.Reactive.Binding.BindingOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, DynamicData.Reactive.Binding.IObservableCollection destination, DynamicData.Reactive.Binding.IObservableCollectionAdaptor updater) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, DynamicData.Reactive.Binding.IObservableCollection destination, int refreshThreshold = 25) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, DynamicData.Reactive.Binding.BindingOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, System.ComponentModel.BindingList bindingList, int resetThreshold = 25) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable>> source, System.Collections.Generic.IList targetList, DynamicData.Reactive.Binding.SortAndBindOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable>> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, DynamicData.Reactive.Binding.SortAndBindOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable>> source, System.Collections.Generic.IList targetList, DynamicData.Reactive.Binding.SortAndBindOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable>> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, DynamicData.Reactive.Binding.SortAndBindOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, DynamicData.Reactive.Binding.IObservableCollection destination, DynamicData.Reactive.Binding.BindingOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, DynamicData.Reactive.Binding.IObservableCollection destination, DynamicData.Reactive.Binding.ISortedObservableCollectionAdaptor updater) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, DynamicData.Reactive.Binding.BindingOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, System.ComponentModel.BindingList bindingList, int resetThreshold = 25) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, int resetThreshold = 25, bool useReplaceForUpdates = true, DynamicData.Reactive.Binding.IObservableCollectionAdaptor? adaptor = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, int resetThreshold = 25, bool useReplaceForUpdates = true, DynamicData.Reactive.Binding.ISortedObservableCollectionAdaptor? adaptor = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> BufferInitial(this System.IObservable> source, System.TimeSpan initialBuffer, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Cast(this System.IObservable> source, System.Func converter) + where TSource : notnull + where TKey : notnull + where TDestination : notnull { } + public static System.IObservable> ChangeKey(this System.IObservable> source, System.Func keySelector) + where TObject : notnull + where TSourceKey : notnull + where TDestinationKey : notnull { } + public static System.IObservable> ChangeKey(this System.IObservable> source, System.Func keySelector) + where TObject : notnull + where TSourceKey : notnull + where TDestinationKey : notnull { } + public static void Clear(this DynamicData.Reactive.Cache.Internal.LockFreeObservableCache source) + where TObject : notnull + where TKey : notnull { } + public static void Clear(this DynamicData.Reactive.IIntermediateCache source) + where TObject : notnull + where TKey : notnull { } + public static void Clear(this DynamicData.Reactive.ISourceCache source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Clone(this System.IObservable> source, System.Collections.Generic.ICollection target) + where TObject : notnull + where TKey : notnull { } + [System.Obsolete("This was an experiment that did not work. Use Transform instead")] + public static System.IObservable> Convert(this System.IObservable> source, System.Func conversionFactory) + where TObject : notnull + where TKey : notnull + where TDestination : notnull { } + public static System.IObservable> DeferUntilLoaded(this DynamicData.Reactive.IObservableCache source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> DeferUntilLoaded(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> DisposeMany(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> DistinctValues(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull + where TValue : notnull { } + public static void EditDiff(this DynamicData.Reactive.ISourceCache source, System.Collections.Generic.IEnumerable allItems, System.Collections.Generic.IEqualityComparer equalityComparer) + where TObject : notnull + where TKey : notnull { } + public static void EditDiff(this DynamicData.Reactive.ISourceCache source, System.Collections.Generic.IEnumerable allItems, System.Func areItemsEqual) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> EditDiff(this System.IObservable> source, System.Func keySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> EditDiff(this System.IObservable> source, System.Func keySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> EnsureUniqueKeys(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Except(this DynamicData.Reactive.IObservableList> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Except(this DynamicData.Reactive.IObservableList> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Except(this DynamicData.Reactive.IObservableList>> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Except(this System.Collections.Generic.ICollection>> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Except(this System.IObservable> source, params System.IObservable>[] others) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ExpireAfter(this System.IObservable> source, System.Func timeSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ExpireAfter(this System.IObservable> source, System.Func timeSelector, System.Reactive.Concurrency.IScheduler scheduler) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ExpireAfter(this System.IObservable> source, System.Func timeSelector, System.TimeSpan? pollingInterval) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> ExpireAfter(this DynamicData.Reactive.ISourceCache source, System.Func timeSelector, System.TimeSpan? pollingInterval = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ExpireAfter(this System.IObservable> source, System.Func timeSelector, System.TimeSpan? pollingInterval, System.Reactive.Concurrency.IScheduler scheduler) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Filter(this System.IObservable> source, System.Func filter, bool suppressEmptyChangeSets = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Filter(this System.IObservable> source, System.IObservable> predicateChanged, bool suppressEmptyChangeSets = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Filter(this System.IObservable> source, System.IObservable> predicateChanged, System.IObservable reapplyFilter, bool suppressEmptyChangeSets = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Filter(this System.IObservable> source, System.IObservable predicateState, System.Func predicate, bool suppressEmptyChangeSets = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> FilterImmutable(this System.IObservable> source, System.Func predicate, bool suppressEmptyChangeSets = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> FilterOnObservable(this System.IObservable> source, System.Func> filterFactory, System.TimeSpan? buffer = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> FilterOnObservable(this System.IObservable> source, System.Func> filterFactory, System.TimeSpan? buffer = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : notnull + where TKey : notnull { } + [System.Obsolete("This can cause unhandled exception issues so do not use")] + public static System.IObservable FinallySafe(this System.IObservable source, System.Action finallyAction) { } + public static System.IObservable> Flatten(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> FlattenBufferResult(this System.IObservable>> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ForEachChange(this System.IObservable> source, System.Action> action) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> FullJoin(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, ReactiveUI.Primitives.Optional, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> FullJoin(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, ReactiveUI.Primitives.Optional, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> FullJoinMany(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, DynamicData.Reactive.IGrouping, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> FullJoinMany(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, DynamicData.Reactive.IGrouping, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> Group(this System.IObservable> source, System.Func groupSelectorKey) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> Group(this System.IObservable> source, System.Func groupSelector, System.IObservable> resultGroupSource) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> Group(this System.IObservable> source, System.Func groupSelectorKey, System.IObservable regrouper) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> Group(this System.IObservable> source, System.IObservable> groupSelectorKeyObservable, System.IObservable? regrouper = null) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> Group(this System.IObservable> source, System.IObservable> groupSelectorKeyObservable, System.IObservable? regrouper = null) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> GroupOnObservable(this System.IObservable> source, System.Func> groupObservableSelector) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> GroupOnObservable(this System.IObservable> source, System.Func> groupObservableSelector) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> GroupOnProperty(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.TimeSpan? propertyChangedThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : System.ComponentModel.INotifyPropertyChanged + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> GroupOnPropertyWithImmutableState(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.TimeSpan? propertyChangedThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : System.ComponentModel.INotifyPropertyChanged + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> GroupWithImmutableState(this System.IObservable> source, System.Func groupSelectorKey, System.IObservable? regrouper = null) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> IgnoreSameReferenceUpdate(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> IgnoreUpdateWhen(this System.IObservable> source, System.Func ignoreFunction) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> IncludeUpdateWhen(this System.IObservable> source, System.Func includeFunction) + where TObject : notnull + where TKey : notnull { } + [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { + "leftKey", + "rightKey"})] + public static System.IObservable>> InnerJoin(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { + "leftKey", + "rightKey"})] + public static System.IObservable>> InnerJoin(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, [System.Runtime.CompilerServices.TupleElementNames(new string[] { + "leftKey", + "rightKey"})] System.Func, TLeft, TRight, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> InnerJoinMany(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> InnerJoinMany(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> InvokeEvaluate(this System.IObservable> source) + where TObject : DynamicData.Reactive.Binding.IEvaluateAware + where TKey : notnull { } + public static System.IObservable> LeftJoin(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> LeftJoin(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> LeftJoinMany(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> LeftJoinMany(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> LimitSizeTo(this System.IObservable> source, int size) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> LimitSizeTo(this DynamicData.Reactive.ISourceCache source, int sizeLimit, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable>> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable>> source, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable>> source, System.Collections.Generic.IEqualityComparer equalityComparer) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable>> source, System.Collections.Generic.IEqualityComparer equalityComparer, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, System.Collections.Generic.IComparer comparer, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, System.Collections.Generic.IEqualityComparer equalityComparer, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, System.Collections.Generic.IEqualityComparer equalityComparer, System.Collections.Generic.IComparer comparer, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, System.Collections.Generic.IComparer comparer, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, System.Collections.Generic.IEqualityComparer equalityComparer, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, System.Collections.Generic.IComparer comparer, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, System.Collections.Generic.IEqualityComparer equalityComparer, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, System.Collections.Generic.IEqualityComparer equalityComparer, System.Collections.Generic.IComparer comparer, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, System.Collections.Generic.IEqualityComparer equalityComparer, System.Collections.Generic.IComparer comparer, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable MergeMany(this System.IObservable> source, System.Func> observableSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable MergeMany(this System.IObservable> source, System.Func> observableSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull + where TKey : notnull + where TDestination : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull + where TKey : notnull + where TDestination : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer sourceComparer, System.Collections.Generic.IComparer childComparer) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer sourceComparer, System.Collections.Generic.IComparer childComparer) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer sourceComparer, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? childComparer = null) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer sourceComparer, bool resortOnSourceRefresh, System.Collections.Generic.IComparer childComparer) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer sourceComparer, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? childComparer = null) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer sourceComparer, bool resortOnSourceRefresh, System.Collections.Generic.IComparer childComparer) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer sourceComparer, bool resortOnSourceRefresh, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? childComparer = null) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer sourceComparer, bool resortOnSourceRefresh, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? childComparer = null) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyItems(this System.IObservable> source, System.Func> observableSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeManyItems(this System.IObservable> source, System.Func> observableSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable MonitorStatus(this System.IObservable source) { } + public static System.IObservable> NotEmpty(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> OfType(this System.IObservable> source, bool suppressEmptyChangeSets = true) + where TObject : notnull + where TKey : notnull + where TDestination : notnull { } + public static System.IObservable> OnItemAdded(this System.IObservable> source, System.Action addAction) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> OnItemAdded(this System.IObservable> source, System.Action addAction) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> OnItemRefreshed(this System.IObservable> source, System.Action refreshAction) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> OnItemRefreshed(this System.IObservable> source, System.Action refreshAction) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> OnItemRemoved(this System.IObservable> source, System.Action removeAction, bool invokeOnUnsubscribe = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> OnItemRemoved(this System.IObservable> source, System.Action removeAction, bool invokeOnUnsubscribe = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> OnItemUpdated(this System.IObservable> source, System.Action updateAction) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> OnItemUpdated(this System.IObservable> source, System.Action updateAction) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Or(this DynamicData.Reactive.IObservableList> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Or(this DynamicData.Reactive.IObservableList> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Or(this DynamicData.Reactive.IObservableList>> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Or(this System.Collections.Generic.ICollection>> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Or(this System.IObservable> source, params System.IObservable>[] others) + where TObject : notnull + where TKey : notnull { } + [System.Obsolete("Use SortAndPage as it\'s more efficient")] + public static System.IObservable> Page(this System.IObservable> source, System.IObservable pageRequests) + where TObject : notnull + where TKey : notnull { } + public static System.IDisposable PopulateFrom(this DynamicData.Reactive.ISourceCache source, System.IObservable> observable) + where TObject : notnull + where TKey : notnull { } + public static System.IDisposable PopulateFrom(this DynamicData.Reactive.ISourceCache source, System.IObservable observable) + where TObject : notnull + where TKey : notnull { } + public static System.IDisposable PopulateInto(this System.IObservable> source, DynamicData.Reactive.Cache.Internal.LockFreeObservableCache destination) + where TObject : notnull + where TKey : notnull { } + public static System.IDisposable PopulateInto(this System.IObservable> source, DynamicData.Reactive.IIntermediateCache destination) + where TObject : notnull + where TKey : notnull { } + public static System.IDisposable PopulateInto(this System.IObservable> source, DynamicData.Reactive.ISourceCache destination) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> QueryWhenChanged(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable QueryWhenChanged(this System.IObservable> source, System.Func, TDestination> resultSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> QueryWhenChanged(this System.IObservable> source, System.Func> itemChangedTrigger) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> RefCount(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static void Refresh(this DynamicData.Reactive.ISourceCache source) + where TObject : notnull + where TKey : notnull { } + public static void Refresh(this DynamicData.Reactive.ISourceCache source, System.Collections.Generic.IEnumerable items) + where TObject : notnull + where TKey : notnull { } + public static void Refresh(this DynamicData.Reactive.ISourceCache source, TObject item) + where TObject : notnull + where TKey : notnull { } + public static void Remove(this DynamicData.Reactive.IIntermediateCache source, System.Collections.Generic.IEnumerable keys) + where TObject : notnull + where TKey : notnull { } + public static void Remove(this DynamicData.Reactive.IIntermediateCache source, TKey key) + where TObject : notnull + where TKey : notnull { } + public static void Remove(this DynamicData.Reactive.ISourceCache source, System.Collections.Generic.IEnumerable keys) + where TObject : notnull + where TKey : notnull { } + public static void Remove(this DynamicData.Reactive.ISourceCache source, System.Collections.Generic.IEnumerable items) + where TObject : notnull + where TKey : notnull { } + public static void Remove(this DynamicData.Reactive.ISourceCache source, TKey key) + where TObject : notnull + where TKey : notnull { } + public static void Remove(this DynamicData.Reactive.ISourceCache source, TObject item) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> RemoveKey(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static void RemoveKey(this DynamicData.Reactive.ISourceCache source, TKey key) + where TObject : notnull + where TKey : notnull { } + public static void RemoveKeys(this DynamicData.Reactive.ISourceCache source, System.Collections.Generic.IEnumerable keys) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> RightJoin(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, TRight, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> RightJoin(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, TRight, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> RightJoinMany(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, DynamicData.Reactive.IGrouping, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> RightJoinMany(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, DynamicData.Reactive.IGrouping, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> SkipInitial(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + [System.Obsolete("Use SortAndBind as it\'s more efficient")] + public static System.IObservable> Sort(this System.IObservable> source, System.Collections.Generic.IComparer comparer, DynamicData.Reactive.SortOptimisations sortOptimisations = 0, int resetThreshold = 100) + where TObject : notnull + where TKey : notnull { } + [System.Obsolete("Use SortAndBind as it\'s more efficient")] + public static System.IObservable> Sort(this System.IObservable> source, System.IObservable> comparerObservable, DynamicData.Reactive.SortOptimisations sortOptimisations = 0, int resetThreshold = 100) + where TObject : notnull + where TKey : notnull { } + [System.Obsolete("Use SortAndBind as it\'s more efficient")] + public static System.IObservable> Sort(this System.IObservable> source, System.Collections.Generic.IComparer comparer, System.IObservable resorter, DynamicData.Reactive.SortOptimisations sortOptimisations = 0, int resetThreshold = 100) + where TObject : notnull + where TKey : notnull { } + [System.Obsolete("Use SortAndBind as it\'s more efficient")] + public static System.IObservable> Sort(this System.IObservable> source, System.IObservable> comparerObservable, System.IObservable resorter, DynamicData.Reactive.SortOptimisations sortOptimisations = 0, int resetThreshold = 100) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, System.Collections.Generic.IList targetList) + where TObject : notnull, System.IComparable + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection) + where TObject : notnull, System.IComparable + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, System.Collections.Generic.IList targetList, DynamicData.Reactive.Binding.SortAndBindOptions options) + where TObject : notnull, System.IComparable + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, System.Collections.Generic.IList targetList, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, System.Collections.Generic.IList targetList, System.IObservable> comparerChanged) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, DynamicData.Reactive.Binding.SortAndBindOptions options) + where TObject : notnull, System.IComparable + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, System.IObservable> comparerChanged) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, System.Collections.Generic.IList targetList, System.Collections.Generic.IComparer comparer, DynamicData.Reactive.Binding.SortAndBindOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, System.Collections.Generic.IList targetList, System.IObservable> comparerChanged, DynamicData.Reactive.Binding.SortAndBindOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, System.Collections.Generic.IComparer comparer, DynamicData.Reactive.Binding.SortAndBindOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, System.IObservable> comparerChanged, DynamicData.Reactive.Binding.SortAndBindOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> SortAndPage(this System.IObservable> source, System.Collections.Generic.IComparer comparer, System.IObservable pageRequests) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> SortAndPage(this System.IObservable> source, System.IObservable> comparerChanged, System.IObservable pageRequests) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> SortAndPage(this System.IObservable> source, System.Collections.Generic.IComparer comparer, System.IObservable pageRequests, DynamicData.Reactive.SortAndPageOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> SortAndPage(this System.IObservable> source, System.IObservable> comparerChanged, System.IObservable pageRequests, DynamicData.Reactive.SortAndPageOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> SortAndVirtualize(this System.IObservable> source, System.Collections.Generic.IComparer comparer, System.IObservable virtualRequests) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> SortAndVirtualize(this System.IObservable> source, System.IObservable> comparerChanged, System.IObservable virtualRequests) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> SortAndVirtualize(this System.IObservable> source, System.Collections.Generic.IComparer comparer, System.IObservable virtualRequests, DynamicData.Reactive.SortAndVirtualizeOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> SortAndVirtualize(this System.IObservable> source, System.IObservable> comparerChanged, System.IObservable virtualRequests, DynamicData.Reactive.SortAndVirtualizeOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SortBy(this System.IObservable> source, System.Func expression, DynamicData.Reactive.Binding.SortDirection sortOrder = 0, DynamicData.Reactive.SortOptimisations sortOptimisations = 0, int resetThreshold = 100) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> StartWithEmpty(this System.IObservable> source) { } + public static System.IObservable> StartWithEmpty(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> StartWithEmpty(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> StartWithEmpty(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> StartWithEmpty(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> StartWithEmpty(this System.IObservable> source) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> StartWithEmpty(this System.IObservable> source) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> StartWithItem(this System.IObservable> source, TObject item) + where TObject : DynamicData.Reactive.IKey + where TKey : notnull { } + public static System.IObservable> StartWithItem(this System.IObservable> source, TObject item, TKey key) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SubscribeMany(this System.IObservable> source, System.Func subscriptionFactory) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SubscribeMany(this System.IObservable> source, System.Func subscriptionFactory) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SuppressRefresh(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Switch(this System.IObservable> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Switch(this System.IObservable>> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ToCollection(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, System.Func keySelector, System.Func? expireAfter = null, int limitSizeTo = -1, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable source, System.Func keySelector, System.Func? expireAfter = null, int limitSizeTo = -1, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ToObservableOptional(this System.IObservable> source, TKey key, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ToObservableOptional(this System.IObservable> source, TKey key, bool initialOptionalWhenMissing, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ToSortedCollection(this System.IObservable> source, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ToSortedCollection(this System.IObservable> source, System.Func sort, DynamicData.Reactive.Binding.SortDirection sortOrder = 0) + where TObject : notnull + where TKey : notnull + where TSortKey : notnull { } + [System.Obsolete("Use Overload with comparer as it\'s more efficient")] + public static System.IObservable> Top(this System.IObservable> source, int size) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> Top(this System.IObservable> source, System.Collections.Generic.IComparer comparer, int size) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, bool transformOnRefresh) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, bool transformOnRefresh) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func, TKey, TDestination> transformFactory, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func, TKey, TDestination> transformFactory, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func, TKey, TDestination> transformFactory, bool transformOnRefresh) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func> transformFactory, DynamicData.Reactive.TransformAsyncOptions options) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func> transformFactory, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func> transformFactory, DynamicData.Reactive.TransformAsyncOptions options) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func> transformFactory, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func, TKey, System.Threading.Tasks.Task> transformFactory, DynamicData.Reactive.TransformAsyncOptions options) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func, TKey, System.Threading.Tasks.Task> transformFactory, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformImmutable(this System.IObservable> source, System.Func transformFactory) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformMany(this System.IObservable> source, System.Func> manySelector, System.Func keySelector) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformMany(this System.IObservable> source, System.Func> manySelector, System.Func keySelector) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformMany(this System.IObservable> source, System.Func> manySelector, System.Func keySelector) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformMany(this System.IObservable> source, System.Func> manySelector, System.Func keySelector) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformManyAsync(this System.IObservable> source, System.Func>> manySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformManyAsync(this System.IObservable> source, System.Func>> manySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformManyAsync(this System.IObservable> source, System.Func>> manySelector, System.Func keySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformManyAsync(this System.IObservable> source, System.Func>> manySelector, System.Func keySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformManyAsync(this System.IObservable> source, System.Func> manySelector, System.Func keySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull + where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } + public static System.IObservable> TransformManyAsync(this System.IObservable> source, System.Func> manySelector, System.Func keySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull + where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } + public static System.IObservable> TransformManySafeAsync(this System.IObservable> source, System.Func>> manySelector, System.Action> errorHandler, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformManySafeAsync(this System.IObservable> source, System.Func>> manySelector, System.Action> errorHandler, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformManySafeAsync(this System.IObservable> source, System.Func>> manySelector, System.Func keySelector, System.Action> errorHandler, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformManySafeAsync(this System.IObservable> source, System.Func>> manySelector, System.Func keySelector, System.Action> errorHandler, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformManySafeAsync(this System.IObservable> source, System.Func> manySelector, System.Func keySelector, System.Action> errorHandler, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull + where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } + public static System.IObservable> TransformManySafeAsync(this System.IObservable> source, System.Func> manySelector, System.Func keySelector, System.Action> errorHandler, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull + where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } + public static System.IObservable> TransformOnObservable(this System.IObservable> source, System.Func> transformFactory) + where TSource : notnull + where TKey : notnull + where TDestination : notnull { } + public static System.IObservable> TransformOnObservable(this System.IObservable> source, System.Func> transformFactory) + where TSource : notnull + where TKey : notnull + where TDestination : notnull { } + public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func, TKey, TDestination> transformFactory, System.Action> errorHandler, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func, TKey, TDestination> transformFactory, System.Action> errorHandler, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafeAsync(this System.IObservable> source, System.Func> transformFactory, System.Action> errorHandler, DynamicData.Reactive.TransformAsyncOptions options) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafeAsync(this System.IObservable> source, System.Func> transformFactory, System.Action> errorHandler, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafeAsync(this System.IObservable> source, System.Func> transformFactory, System.Action> errorHandler, DynamicData.Reactive.TransformAsyncOptions options) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafeAsync(this System.IObservable> source, System.Func> transformFactory, System.Action> errorHandler, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafeAsync(this System.IObservable> source, System.Func, TKey, System.Threading.Tasks.Task> transformFactory, System.Action> errorHandler, DynamicData.Reactive.TransformAsyncOptions options) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafeAsync(this System.IObservable> source, System.Func, TKey, System.Threading.Tasks.Task> transformFactory, System.Action> errorHandler, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable, TKey>> TransformToTree(this System.IObservable> source, System.Func pivotOn, System.IObservable, bool>>? predicateChanged = null) + where TObject : class + where TKey : notnull { } + public static System.IObservable> TransformWithInlineUpdate(this System.IObservable> source, System.Func transformFactory, System.Action updateAction) + where TDestination : class + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformWithInlineUpdate(this System.IObservable> source, System.Func transformFactory, System.Action updateAction, System.Action> errorHandler) + where TDestination : class + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformWithInlineUpdate(this System.IObservable> source, System.Func transformFactory, System.Action updateAction, bool transformOnRefresh) + where TDestination : class + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformWithInlineUpdate(this System.IObservable> source, System.Func transformFactory, System.Action updateAction, System.Action> errorHandler, bool transformOnRefresh) + where TDestination : class + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TreatMovesAsRemoveAdd(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable TrueForAll(this System.IObservable> source, System.Func> observableSelector, System.Func equalityCondition) + where TObject : notnull + where TKey : notnull + where TValue : notnull { } + public static System.IObservable TrueForAll(this System.IObservable> source, System.Func> observableSelector, System.Func equalityCondition) + where TObject : notnull + where TKey : notnull + where TValue : notnull { } + public static System.IObservable TrueForAny(this System.IObservable> source, System.Func> observableSelector, System.Func equalityCondition) + where TObject : notnull + where TKey : notnull + where TValue : notnull { } + public static System.IObservable TrueForAny(this System.IObservable> source, System.Func> observableSelector, System.Func equalityCondition) + where TObject : notnull + where TKey : notnull + where TValue : notnull { } + public static System.IObservable> UpdateIndex(this System.IObservable> source) + where TObject : DynamicData.Reactive.Binding.IIndexAware + where TKey : notnull { } + [System.Obsolete("Use SortAndVirtualize as it\'s more efficient")] + public static System.IObservable> Virtualise(this System.IObservable> source, System.IObservable virtualRequests) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Watch(this System.IObservable> source, TKey key) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable WatchValue(this DynamicData.Reactive.IObservableCache source, TKey key) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable WatchValue(this System.IObservable> source, TKey key) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable WhenAnyPropertyChanged(this System.IObservable> source, params string[] propertiesToMonitor) + where TObject : System.ComponentModel.INotifyPropertyChanged + where TKey : notnull { } + public static System.IObservable> WhenPropertyChanged(this System.IObservable> source, System.Linq.Expressions.Expression> propertyAccessor, bool notifyOnInitialValue = true) + where TObject : System.ComponentModel.INotifyPropertyChanged + where TKey : notnull { } + public static System.IObservable WhenValueChanged(this System.IObservable> source, System.Linq.Expressions.Expression> propertyAccessor, bool notifyOnInitialValue = true) + where TObject : System.ComponentModel.INotifyPropertyChanged + where TKey : notnull { } + public static System.IObservable> WhereReasonsAre(this System.IObservable> source, params DynamicData.Reactive.ChangeReason[] reasons) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> WhereReasonsAreNot(this System.IObservable> source, params DynamicData.Reactive.ChangeReason[] reasons) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Xor(this DynamicData.Reactive.IObservableList> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Xor(this DynamicData.Reactive.IObservableList> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Xor(this DynamicData.Reactive.IObservableList>> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Xor(this System.Collections.Generic.ICollection>> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Xor(this System.IObservable> source, params System.IObservable>[] others) + where TObject : notnull + where TKey : notnull { } + } + public static class ObservableChangeSet + { + public static System.IObservable> Create(System.Func, System.Action> subscribe) + where T : notnull { } + public static System.IObservable> Create(System.Func, System.IDisposable> subscribe) + where T : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.Tasks.Task> subscribe) + where T : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.Tasks.Task> subscribe) + where T : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.Tasks.Task> subscribe) + where T : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.CancellationToken, System.Threading.Tasks.Task> subscribe) + where T : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.CancellationToken, System.Threading.Tasks.Task> subscribe) + where T : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.CancellationToken, System.Threading.Tasks.Task> subscribe) + where T : notnull { } + public static System.IObservable> Create(System.Func, System.Action> subscribe, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Create(System.Func, System.IDisposable> subscribe, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.Tasks.Task> subscribe, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.Tasks.Task> subscribe, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.Tasks.Task> subscribe, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.CancellationToken, System.Threading.Tasks.Task> subscribe, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.CancellationToken, System.Threading.Tasks.Task> subscribe, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.CancellationToken, System.Threading.Tasks.Task> subscribe, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + } + public static class ObservableListEx + { + public static System.IObservable> Adapt(this System.IObservable> source, DynamicData.Reactive.IChangeSetAdaptor adaptor) + where T : notnull { } + public static System.IObservable> AddKey(this System.IObservable> source, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> And(this DynamicData.Reactive.IObservableList> sources) + where T : notnull { } + public static System.IObservable> And(this DynamicData.Reactive.IObservableList> sources) + where T : notnull { } + public static System.IObservable> And(this DynamicData.Reactive.IObservableList>> sources) + where T : notnull { } + public static System.IObservable> And(this System.Collections.Generic.ICollection>> sources) + where T : notnull { } + public static System.IObservable> And(this System.IObservable> source, params System.IObservable>[] others) + where T : notnull { } + public static DynamicData.Reactive.IObservableList AsObservableList(this DynamicData.Reactive.ISourceList source) + where T : notnull { } + public static DynamicData.Reactive.IObservableList AsObservableList(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> AutoRefresh(this System.IObservable> source, System.TimeSpan? changeSetBuffer = default, System.TimeSpan? propertyChangeThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable> AutoRefresh(this System.IObservable> source, System.Linq.Expressions.Expression> propertyAccessor, System.TimeSpan? changeSetBuffer = default, System.TimeSpan? propertyChangeThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable> AutoRefreshOnObservable(this System.IObservable> source, System.Func> reevaluator, System.TimeSpan? changeSetBuffer = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, DynamicData.Reactive.Binding.IObservableCollection targetCollection, DynamicData.Reactive.Binding.BindingOptions options) + where T : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, DynamicData.Reactive.Binding.IObservableCollection targetCollection, int resetThreshold = 25) + where T : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, DynamicData.Reactive.Binding.BindingOptions options) + where T : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, int resetThreshold = 25) + where T : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] T>(this System.IObservable> source, System.ComponentModel.BindingList bindingList, int resetThreshold = 25) + where T : notnull { } + public static System.IObservable> BufferIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, System.Reactive.Concurrency.IScheduler? scheduler = null) + where T : notnull { } + public static System.IObservable> BufferIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, System.TimeSpan? timeOut, System.Reactive.Concurrency.IScheduler? scheduler = null) + where T : notnull { } + public static System.IObservable> BufferIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState, System.Reactive.Concurrency.IScheduler? scheduler = null) + where T : notnull { } + public static System.IObservable> BufferIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState, System.TimeSpan? timeOut, System.Reactive.Concurrency.IScheduler? scheduler = null) + where T : notnull { } + public static System.IObservable> BufferInitial(this System.IObservable> source, System.TimeSpan initialBuffer, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : notnull { } + public static System.IObservable> Cast(this System.IObservable> source) + where TDestination : notnull { } + public static System.IObservable> Cast(this System.IObservable> source, System.Func conversionFactory) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> CastToObject(this System.IObservable> source) + where T : class { } + public static System.IObservable> Clone(this System.IObservable> source, System.Collections.Generic.IList target) + where T : notnull { } + [System.Obsolete("Prefer Cast as it is does the same thing but is semantically correct")] + public static System.IObservable> Convert(this System.IObservable> source, System.Func conversionFactory) + where TObject : notnull + where TDestination : notnull { } + public static System.IObservable> DeferUntilLoaded(this DynamicData.Reactive.IObservableList source) + where T : notnull { } + public static System.IObservable> DeferUntilLoaded(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> DisposeMany(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> DistinctValues(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TValue : notnull { } + public static System.IObservable> Except(this DynamicData.Reactive.IObservableList> sources) + where T : notnull { } + public static System.IObservable> Except(this DynamicData.Reactive.IObservableList> sources) + where T : notnull { } + public static System.IObservable> Except(this DynamicData.Reactive.IObservableList>> sources) + where T : notnull { } + public static System.IObservable> Except(this System.Collections.Generic.ICollection>> sources) + where T : notnull { } + public static System.IObservable> Except(this System.IObservable> source, params System.IObservable>[] others) + where T : notnull { } + public static System.IObservable> ExpireAfter(this DynamicData.Reactive.ISourceList source, System.Func timeSelector, System.TimeSpan? pollingInterval = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + where T : notnull { } + public static System.IObservable> Filter(this System.IObservable> source, System.Func predicate) + where T : notnull { } + public static System.IObservable> Filter(this System.IObservable> source, System.IObservable> predicate, DynamicData.Reactive.ListFilterPolicy filterPolicy = 1) + where T : notnull { } + public static System.IObservable> Filter(this System.IObservable> source, System.IObservable predicateState, System.Func predicate, DynamicData.Reactive.ListFilterPolicy filterPolicy = 1, bool suppressEmptyChangeSets = true) + where T : notnull { } + public static System.IObservable> FilterOnObservable(this System.IObservable> source, System.Func> objectFilterObservable, System.TimeSpan? propertyChangedThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : notnull { } + [System.Obsolete("Use AutoRefresh(), followed by Filter() instead")] + public static System.IObservable> FilterOnProperty(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.Func predicate, System.TimeSpan? propertyChangedThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable> FlattenBufferResult(this System.IObservable>> source) + where T : notnull { } + public static System.IObservable> ForEachChange(this System.IObservable> source, System.Action> action) + where TObject : notnull { } + public static System.IObservable> ForEachItemChange(this System.IObservable> source, System.Action> action) + where TObject : notnull { } + public static System.IObservable>> GroupOn(this System.IObservable> source, System.Func groupSelector, System.IObservable? regrouper = null) + where TObject : notnull + where TGroup : notnull { } + public static System.IObservable>> GroupOnProperty(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.TimeSpan? propertyChangedThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : System.ComponentModel.INotifyPropertyChanged + where TGroup : notnull { } + public static System.IObservable>> GroupOnPropertyWithImmutableState(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.TimeSpan? propertyChangedThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : System.ComponentModel.INotifyPropertyChanged + where TGroup : notnull { } + public static System.IObservable>> GroupWithImmutableState(this System.IObservable> source, System.Func groupSelectorKey, System.IObservable? regrouper = null) + where TObject : notnull + where TGroupKey : notnull { } + public static System.IObservable> LimitSizeTo(this DynamicData.Reactive.ISourceList source, int sizeLimit, System.Reactive.Concurrency.IScheduler? scheduler = null) + where T : notnull { } + public static System.IObservable> MergeChangeSets(this DynamicData.Reactive.IObservableList>> source, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable>>> source, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable>> source, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull { } + public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + where TObject : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + where TObject : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + where TObject : notnull { } + public static System.IObservable> MergeChangeSets(this DynamicData.Reactive.IObservableList>> source, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable>>> source, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this DynamicData.Reactive.IObservableList>> source, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable>>> source, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable MergeMany(this System.IObservable> source, System.Func> observableSelector) + where T : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull + where TDestination : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TObject : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> NotEmpty(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> OnItemAdded(this System.IObservable> source, System.Action addAction) + where T : notnull { } + public static System.IObservable> OnItemRefreshed(this System.IObservable> source, System.Action refreshAction) + where T : notnull { } + public static System.IObservable> OnItemRemoved(this System.IObservable> source, System.Action removeAction, bool invokeOnUnsubscribe = true) + where T : notnull { } + public static System.IObservable> Or(this DynamicData.Reactive.IObservableList> sources) + where T : notnull { } + public static System.IObservable> Or(this DynamicData.Reactive.IObservableList> sources) + where T : notnull { } + public static System.IObservable> Or(this DynamicData.Reactive.IObservableList>> sources) + where T : notnull { } + public static System.IObservable> Or(this System.Collections.Generic.ICollection>> sources) + where T : notnull { } + public static System.IObservable> Or(this System.IObservable> source, params System.IObservable>[] others) + where T : notnull { } + public static System.IObservable> Page(this System.IObservable> source, System.IObservable requests) + where T : notnull { } + public static System.IDisposable PopulateInto(this System.IObservable> source, DynamicData.Reactive.ISourceList destination) + where T : notnull { } + public static System.IObservable> QueryWhenChanged(this System.IObservable> source) + where T : notnull { } + public static System.IObservable QueryWhenChanged(this System.IObservable> source, System.Func, TDestination> resultSelector) + where TObject : notnull { } + public static System.IObservable> RefCount(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> RemoveIndex(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> Reverse(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> SkipInitial(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> Sort(this System.IObservable> source, System.IObservable> comparerChanged, DynamicData.Reactive.SortOptions options = 0, System.IObservable? resort = null, int resetThreshold = 50) + where T : notnull { } + public static System.IObservable> Sort(this System.IObservable> source, System.Collections.Generic.IComparer comparer, DynamicData.Reactive.SortOptions options = 0, System.IObservable? resort = null, System.IObservable>? comparerChanged = null, int resetThreshold = 50) + where T : notnull { } + public static System.IObservable> StartWithEmpty(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> SubscribeMany(this System.IObservable> source, System.Func subscriptionFactory) + where T : notnull { } + public static System.IObservable> SuppressRefresh(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> Switch(this System.IObservable> sources) + where T : notnull { } + public static System.IObservable> Switch(this System.IObservable>> sources) + where T : notnull { } + public static System.IObservable> ToCollection(this System.IObservable> source) + where TObject : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, System.Reactive.Concurrency.IScheduler? scheduler = null) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable source, System.Reactive.Concurrency.IScheduler? scheduler = null) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, System.Func expireAfter, System.Reactive.Concurrency.IScheduler? scheduler = null) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, int limitSizeTo, System.Reactive.Concurrency.IScheduler? scheduler = null) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable source, System.Func expireAfter, System.Reactive.Concurrency.IScheduler? scheduler = null) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable source, int limitSizeTo, System.Reactive.Concurrency.IScheduler? scheduler = null) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, System.Func? expireAfter, int limitSizeTo, System.Reactive.Concurrency.IScheduler? scheduler = null) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable source, System.Func? expireAfter, int limitSizeTo, System.Reactive.Concurrency.IScheduler? scheduler = null) + where T : notnull { } + public static System.IObservable> ToSortedCollection(this System.IObservable> source, System.Collections.Generic.IComparer comparer) + where TObject : notnull { } + public static System.IObservable> ToSortedCollection(this System.IObservable> source, System.Func sort, DynamicData.Reactive.Binding.SortDirection sortOrder = 0) + where TObject : notnull { } + public static System.IObservable> Top(this System.IObservable> source, int numberOfItems) + where T : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, bool transformOnRefresh = false) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func, TDestination> transformFactory, bool transformOnRefresh = false) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, bool transformOnRefresh = false) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func, int, TDestination> transformFactory, bool transformOnRefresh = false) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func> transformFactory, bool transformOnRefresh = false) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func, System.Threading.Tasks.Task> transformFactory, bool transformOnRefresh = false) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func> transformFactory, bool transformOnRefresh = false) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func, int, System.Threading.Tasks.Task> transformFactory, bool transformOnRefresh = false) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> TransformMany(this System.IObservable> source, System.Func> manySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TDestination : notnull + where TSource : notnull { } + public static System.IObservable> TransformMany(this System.IObservable> source, System.Func> manySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TDestination : notnull + where TSource : notnull { } + public static System.IObservable> TransformMany(this System.IObservable> source, System.Func> manySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TDestination : notnull + where TSource : notnull { } + public static System.IObservable> TransformMany(this System.IObservable> source, System.Func> manySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TDestination : notnull + where TSource : notnull { } + public static System.IObservable> Virtualise(this System.IObservable> source, System.IObservable requests) + where T : notnull { } + public static System.IObservable WhenAnyPropertyChanged(this System.IObservable> source, params string[] propertiesToMonitor) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable> WhenPropertyChanged(this System.IObservable> source, System.Linq.Expressions.Expression> propertyAccessor, bool notifyOnInitialValue = true) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable WhenValueChanged(this System.IObservable> source, System.Linq.Expressions.Expression> propertyAccessor, bool notifyOnInitialValue = true) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable> WhereReasonsAre(this System.IObservable> source, params DynamicData.Reactive.ListChangeReason[] reasons) + where T : notnull { } + public static System.IObservable> WhereReasonsAreNot(this System.IObservable> source, params DynamicData.Reactive.ListChangeReason[] reasons) + where T : notnull { } + public static System.IObservable> Xor(this DynamicData.Reactive.IObservableList> sources) + where T : notnull { } + public static System.IObservable> Xor(this DynamicData.Reactive.IObservableList> sources) + where T : notnull { } + public static System.IObservable> Xor(this DynamicData.Reactive.IObservableList>> sources) + where T : notnull { } + public static System.IObservable> Xor(this System.Collections.Generic.ICollection>> sources) + where T : notnull { } + public static System.IObservable> Xor(this System.IObservable> source, params System.IObservable>[] others) + where T : notnull { } + } + public class PageContext : System.IEquatable> + { + public PageContext(DynamicData.Reactive.Operators.IPageResponse Response, System.Collections.Generic.IComparer Comparer, DynamicData.Reactive.SortAndPageOptions Options) { } + public System.Collections.Generic.IComparer Comparer { get; init; } + public DynamicData.Reactive.SortAndPageOptions Options { get; init; } + public DynamicData.Reactive.Operators.IPageResponse Response { get; init; } + } + public sealed class PageRequest : DynamicData.Reactive.IPageRequest, System.IEquatable + { + public static readonly DynamicData.Reactive.IPageRequest Default; + public static readonly DynamicData.Reactive.IPageRequest Empty; + public PageRequest() { } + public PageRequest(int page, int size) { } + public int Page { get; } + public int Size { get; } + public static System.Collections.Generic.IEqualityComparer DefaultComparer { get; } + public bool Equals(DynamicData.Reactive.IPageRequest? other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + } + public sealed class RangeChange : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable + { + public RangeChange(System.Collections.Generic.IEnumerable items, int index = -1) { } + public int Count { get; } + public int Index { get; } + public static DynamicData.Reactive.RangeChange Empty { get; } + public void Add(T item) { } + public System.Collections.Generic.IEnumerator GetEnumerator() { } + public void Insert(int index, T item) { } + public void SetStartingIndex(int index) { } + public override string ToString() { } + } + public readonly struct SortAndPageOptions : System.IEquatable + { + public SortAndPageOptions() { } + public int InitialCapacity { get; init; } + public int ResetThreshold { get; init; } + public bool UseBinarySearch { get; init; } + } + public readonly struct SortAndVirtualizeOptions : System.IEquatable + { + public SortAndVirtualizeOptions() { } + public int InitialCapacity { get; init; } + public int ResetThreshold { get; init; } + public bool UseBinarySearch { get; init; } + } + [System.Serializable] + public class SortException : System.Exception + { + public SortException() { } + public SortException(string message) { } + public SortException(string message, System.Exception innerException) { } + } + [System.Flags] + public enum SortOptimisations + { + None = 0, + ComparesImmutableValuesOnly = 1, + IgnoreEvaluates = 2, + [System.Obsolete("This is no longer being used. Use one of the other options instead.")] + InsertAtEndThenSort = 3, + } + public enum SortOptions + { + None = 0, + UseBinarySearch = 1, + } + public enum SortReason + { + InitialLoad = 0, + ComparerChanged = 1, + DataChanged = 2, + Reorder = 3, + Reset = 4, + } + public static class SourceCacheEx + { + public static System.IObservable> Cast(this DynamicData.Reactive.IObservableCache source, System.Func converter) + where TSource : notnull + where TKey : notnull + where TDestination : notnull { } + } + [System.Diagnostics.DebuggerDisplay("SourceCache<{typeof(TObject).Name}, {typeof(TKey).Name}> ({Count} Items)")] + public sealed class SourceCache : DynamicData.Reactive.Binding.INotifyCollectionChangedSuspender, DynamicData.Reactive.IConnectableCache, DynamicData.Reactive.IObservableCache, DynamicData.Reactive.ISourceCache, System.IDisposable + where TObject : notnull + where TKey : notnull + { + public SourceCache(System.Func keySelector) { } + public int Count { get; } + public System.IObservable CountChanged { get; } + public System.Collections.Generic.IReadOnlyList Items { get; } + public System.Func KeySelector { get; } + public System.Collections.Generic.IReadOnlyDictionary KeyValues { get; } + public System.Collections.Generic.IReadOnlyList Keys { get; } + public System.IObservable> Connect(System.Func? predicate = null, bool suppressEmptyChangeSets = true) { } + public void Dispose() { } + public void Edit(System.Action> updateAction) { } + public ReactiveUI.Primitives.Optional Lookup(TKey key) { } + public System.IObservable> Preview(System.Func? predicate = null) { } + public System.IDisposable SuspendCount() { } + public System.IDisposable SuspendNotifications() { } + public System.IObservable> Watch(TKey key) { } + } + public static class SourceListEditConvenienceEx + { + public static void Add(this DynamicData.Reactive.ISourceList source, T item) + where T : notnull { } + public static void AddRange(this DynamicData.Reactive.ISourceList source, System.Collections.Generic.IEnumerable items) + where T : notnull { } + public static void Clear(this DynamicData.Reactive.ISourceList source) + where T : notnull { } + public static void EditDiff(this DynamicData.Reactive.ISourceList source, System.Collections.Generic.IEnumerable allItems, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where T : notnull { } + public static void Insert(this DynamicData.Reactive.ISourceList source, int index, T item) + where T : notnull { } + public static void InsertRange(this DynamicData.Reactive.ISourceList source, System.Collections.Generic.IEnumerable items, int index) + where T : notnull { } + public static void Move(this DynamicData.Reactive.ISourceList source, int original, int destination) + where T : notnull { } + public static bool Remove(this DynamicData.Reactive.ISourceList source, T item) + where T : notnull { } + public static void RemoveAt(this DynamicData.Reactive.ISourceList source, int index) + where T : notnull { } + public static void RemoveMany(this DynamicData.Reactive.ISourceList source, System.Collections.Generic.IEnumerable itemsToRemove) + where T : notnull { } + public static void RemoveRange(this DynamicData.Reactive.ISourceList source, int index, int count) + where T : notnull { } + public static void Replace(this DynamicData.Reactive.ISourceList source, T original, T destination) + where T : notnull { } + public static void ReplaceAt(this DynamicData.Reactive.ISourceList source, int index, T item) + where T : notnull { } + } + public static class SourceListEx + { + public static System.IObservable> Cast(this DynamicData.Reactive.ISourceList source, System.Func conversionFactory) + where TSource : notnull + where TDestination : notnull { } + } + [System.Diagnostics.DebuggerDisplay("SourceList<{typeof(T).Name}> ({Count} Items)")] + public sealed class SourceList : DynamicData.Reactive.IObservableList, DynamicData.Reactive.ISourceList, System.IDisposable + where T : notnull + { + public SourceList(System.IObservable>? source = null) { } + public int Count { get; } + public System.IObservable CountChanged { get; } + public System.Collections.Generic.IReadOnlyList Items { get; } + public System.IObservable> Connect(System.Func? predicate = null) { } + public void Dispose() { } + public void Edit(System.Action> updateAction) { } + public System.IObservable> Preview(System.Func? predicate = null) { } + } + public struct TransformAsyncOptions : System.IEquatable + { + public static readonly DynamicData.Reactive.TransformAsyncOptions Default; + public TransformAsyncOptions(int? MaximumConcurrency, bool TransformOnRefresh) { } + public int? MaximumConcurrency { get; set; } + public bool TransformOnRefresh { get; set; } + } + [System.Serializable] + public class UnspecifiedIndexException : System.Exception + { + public UnspecifiedIndexException() { } + public UnspecifiedIndexException(string message) { } + public UnspecifiedIndexException(string message, System.Exception innerException) { } + } + public class VirtualContext : System.IEquatable> + { + public VirtualContext(DynamicData.Reactive.IVirtualResponse Response, System.Collections.Generic.IComparer Comparer, DynamicData.Reactive.SortAndVirtualizeOptions Options) { } + public System.Collections.Generic.IComparer Comparer { get; init; } + public DynamicData.Reactive.SortAndVirtualizeOptions Options { get; init; } + public DynamicData.Reactive.IVirtualResponse Response { get; init; } + } + public class VirtualRequest : DynamicData.Reactive.IVirtualRequest, System.IEquatable + { + public static readonly DynamicData.Reactive.VirtualRequest Default; + public VirtualRequest() { } + public VirtualRequest(int startIndex, int size) { } + public int Size { get; } + public int StartIndex { get; } + public static System.Collections.Generic.IEqualityComparer StartIndexSizeComparer { get; } + public bool Equals(DynamicData.Reactive.IVirtualRequest? other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + } +} +namespace DynamicData.Reactive.Diagnostics +{ + public class ChangeStatistics : System.IEquatable + { + public ChangeStatistics() { } + public ChangeStatistics(int Index, int Adds, int Updates, int Removes, int Refreshes, int Moves, int Count) { } + public int Adds { get; init; } + public int Count { get; init; } + public int Index { get; init; } + public System.DateTime LastUpdated { get; } + public int Moves { get; init; } + public int Refreshes { get; init; } + public int Removes { get; init; } + public int Updates { get; init; } + public override int GetHashCode() { } + public override string ToString() { } + } + public class ChangeSummary + { + public static readonly DynamicData.Reactive.Diagnostics.ChangeSummary Empty; + public ChangeSummary(int index, DynamicData.Reactive.Diagnostics.ChangeStatistics latest, DynamicData.Reactive.Diagnostics.ChangeStatistics overall) { } + public DynamicData.Reactive.Diagnostics.ChangeStatistics Latest { get; } + public DynamicData.Reactive.Diagnostics.ChangeStatistics Overall { get; } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + } + public static class DiagnosticOperators + { + public static System.IObservable CollectUpdateStats(this System.IObservable> source) + where TSource : notnull { } + public static System.IObservable CollectUpdateStats(this System.IObservable> source) + where TSource : notnull + where TKey : notnull { } + } +} +namespace DynamicData.Reactive.Experimental +{ + public static class ExperimentalEx + { + public static DynamicData.Reactive.Experimental.IWatcher AsWatcher(this System.IObservable> source, System.Reactive.Concurrency.IScheduler? scheduler = null) + where TObject : notnull + where TKey : notnull { } + } + public interface IWatcher : System.IDisposable + where TObject : notnull + where TKey : notnull + { + System.IObservable> Watch(TKey key); + } +} +namespace DynamicData.Reactive.Kernel +{ + public enum ConnectionStatus + { + Pending = 0, + Loaded = 1, + Errored = 2, + Completed = 3, + } + public static class EnumerableEx + { + public static T[] AsArray(this System.Collections.Generic.IEnumerable source) { } + public static System.Collections.Generic.List AsList(this System.Collections.Generic.IEnumerable source) { } + public static System.Collections.Generic.IEnumerable Duplicates(this System.Collections.Generic.IEnumerable source, System.Func valueSelector) { } + public static System.Collections.Generic.IEnumerable> IndexOfMany(this System.Collections.Generic.IEnumerable source, System.Collections.Generic.IEnumerable itemsToFind) { } + public static System.Collections.Generic.IEnumerable IndexOfMany(this System.Collections.Generic.IEnumerable source, System.Collections.Generic.IEnumerable itemsToFind, System.Func resultSelector) { } + } + public sealed class Error : DynamicData.Reactive.IKeyValue, DynamicData.Reactive.IKey, System.IEquatable> + where TKey : notnull + { + public Error(System.Exception? exception, TObject value, TKey key) { } + public System.Exception? Exception { get; } + public TKey Key { get; } + public TObject Value { get; } + public bool Equals(DynamicData.Reactive.Kernel.Error? other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + public static bool operator !=(DynamicData.Reactive.Kernel.Error left, DynamicData.Reactive.Kernel.Error right) { } + public static bool operator ==(DynamicData.Reactive.Kernel.Error left, DynamicData.Reactive.Kernel.Error right) { } + } + public static class InternalEx + { + public static System.IObservable RetryWithBackOff(this System.IObservable source, System.Func backOffStrategy) + where TException : System.Exception { } + public static System.IDisposable ScheduleRecurringAction(this System.Reactive.Concurrency.IScheduler scheduler, System.Func interval, System.Action action) { } + public static System.IDisposable ScheduleRecurringAction(this System.Reactive.Concurrency.IScheduler scheduler, System.TimeSpan interval, System.Action action) { } + } + public readonly struct ItemWithIndex : System.IEquatable> + { + public ItemWithIndex(T Item, int Index) { } + public int Index { get; init; } + public T Item { get; init; } + public override int GetHashCode() { } + public override string ToString() { } + } + public readonly struct ItemWithValue : System.IEquatable> + { + public ItemWithValue(TObject Item, TValue Value) { } + public TObject Item { get; init; } + public TValue Value { get; init; } + public override int GetHashCode() { } + public override string ToString() { } + } + public sealed class OptionElse + { + public void Else(System.Action action) { } + } + public static class OptionExtensions + { + public static ReactiveUI.Primitives.Optional Convert(this in ReactiveUI.Primitives.Optional source, System.Func> converter) + where TSource : notnull + where TDestination : notnull { } + public static ReactiveUI.Primitives.Optional Convert(this in ReactiveUI.Primitives.Optional source, System.Func converter) + where TSource : notnull + where TDestination : notnull { } + public static TDestination? ConvertOr(this in ReactiveUI.Primitives.Optional source, System.Func converter, System.Func fallbackConverter) + where TSource : notnull { } + public static ReactiveUI.Primitives.Optional FirstOrOptional(this System.Collections.Generic.IEnumerable source, System.Func selector) + where T : notnull { } + public static DynamicData.Reactive.Kernel.OptionElse IfHasValue(this ReactiveUI.Primitives.Optional? source, System.Action action) + where T : notnull { } + public static DynamicData.Reactive.Kernel.OptionElse IfHasValue(this in ReactiveUI.Primitives.Optional source, System.Action action) + where T : notnull { } + public static ReactiveUI.Primitives.Optional Lookup(this System.Collections.Generic.IDictionary source, TKey key) + where TValue : notnull { } + public static ReactiveUI.Primitives.Optional OrElse(this in ReactiveUI.Primitives.Optional source, System.Func> fallbackOperation) + where T : notnull { } + public static bool RemoveIfContained(this System.Collections.Generic.IDictionary source, TKey key) { } + public static System.Collections.Generic.IEnumerable SelectValues(this System.Collections.Generic.IEnumerable> source) + where T : notnull { } + public static T ValueOr(this T? source, T defaultValue) + where T : struct { } + public static T ValueOr(this in ReactiveUI.Primitives.Optional source, System.Func valueSelector) + where T : notnull { } + public static T? ValueOrDefault(this in ReactiveUI.Primitives.Optional source) + where T : notnull { } + public static T ValueOrThrow(this in ReactiveUI.Primitives.Optional source, System.Func exceptionGenerator) + where T : notnull { } + } + public static class OptionObservableExtensions + { + public static System.IObservable> Convert(this System.IObservable> source, System.Func> converter) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> Convert(this System.IObservable> source, System.Func converter) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable ConvertOr(this System.IObservable> source, System.Func converter, System.Func fallbackConverter) + where TSource : notnull { } + public static System.IObservable> OnHasNoValue(this System.IObservable> source, System.Action action, System.Action? elseAction = null) + where T : notnull { } + public static System.IObservable> OnHasValue(this System.IObservable> source, System.Action action, System.Action? elseAction = null) + where T : notnull { } + public static System.IObservable> OrElse(this System.IObservable> source, System.Func> fallbackOperation) + where T : notnull { } + public static System.IObservable SelectValues(this System.IObservable> source) + where T : notnull { } + public static System.IObservable ValueOr(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable ValueOrDefault(this System.IObservable> source) + where T : notnull { } + public static System.IObservable ValueOrThrow(this System.IObservable> source, System.Func exceptionGenerator) + where T : notnull { } + } +} +namespace DynamicData.Reactive.List +{ + public interface IGrouping + { + int Count { get; } + System.Collections.Generic.IEnumerable Items { get; } + TGroupKey Key { get; } + } +} +namespace DynamicData.Reactive.Operators +{ + public interface IPageResponse + { + int Page { get; } + int PageSize { get; } + int Pages { get; } + int TotalSize { get; } + } +} +namespace DynamicData.Reactive.PLinq +{ + public static class ParallelOperators + { + public static System.IObservable> Filter(this System.IObservable> source, System.Func filter, DynamicData.Reactive.PLinq.ParallelisationOptions parallelisationOptions) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SubscribeMany(this System.IObservable> source, System.Func subscriptionFactory, DynamicData.Reactive.PLinq.ParallelisationOptions parallelisationOptions) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SubscribeMany(this System.IObservable> source, System.Func subscriptionFactory, DynamicData.Reactive.PLinq.ParallelisationOptions parallelisationOptions) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, DynamicData.Reactive.PLinq.ParallelisationOptions parallelisationOptions) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, DynamicData.Reactive.PLinq.ParallelisationOptions parallelisationOptions) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, DynamicData.Reactive.PLinq.ParallelisationOptions parallelisationOptions) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, DynamicData.Reactive.PLinq.ParallelisationOptions parallelisationOptions) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + } + public enum ParallelType + { + None = 0, + Parallelise = 1, + Ordered = 2, + } + public class ParallelisationOptions + { + public static readonly DynamicData.Reactive.PLinq.ParallelisationOptions Default; + public static readonly DynamicData.Reactive.PLinq.ParallelisationOptions None; + public ParallelisationOptions(DynamicData.Reactive.PLinq.ParallelType type = 0, int threshold = 0, int maxDegreeOfParallelisation = 0) { } + public int MaxDegreeOfParallelisation { get; } + public int Threshold { get; } + public DynamicData.Reactive.PLinq.ParallelType Type { get; } + } +} +namespace DynamicData.Reactive.Tests +{ + public class ChangeSetAggregator : System.IDisposable + where TObject : notnull + { + public ChangeSetAggregator(System.IObservable> source) { } + public DynamicData.Reactive.IObservableList Data { get; } + public System.Exception? Exception { get; set; } + public bool IsCompleted { get; } + public System.Collections.Generic.IList> Messages { get; } + public void Dispose() { } + protected virtual void Dispose(bool isDisposing) { } + } + public sealed class ChangeSetAggregator : System.IDisposable + where TObject : notnull + where TKey : notnull + { + public ChangeSetAggregator(System.IObservable> source) { } + public DynamicData.Reactive.IObservableCache Data { get; } + public System.Exception? Error { get; } + public bool IsCompleted { get; } + public System.Collections.Generic.IList> Messages { get; } + public DynamicData.Reactive.Diagnostics.ChangeSummary Summary { get; } + public void Dispose() { } + } + public sealed class ChangeSetAggregator : System.IDisposable + where TObject : notnull + where TKey : notnull + { + public ChangeSetAggregator(System.IObservable> source) { } + public DynamicData.Reactive.IObservableCache Data { get; } + public System.Exception? Error { get; } + public bool IsCompleted { get; } + public System.Collections.Generic.IList> Messages { get; } + public DynamicData.Reactive.Diagnostics.ChangeSummary Summary { get; } + public void Dispose() { } + } + public class DistinctChangeSetAggregator : System.IDisposable + where TValue : notnull + { + public DistinctChangeSetAggregator(System.IObservable> source) { } + public DynamicData.Reactive.IObservableCache Data { get; } + public System.Exception? Error { get; } + public System.Collections.Generic.IList> Messages { get; } + public DynamicData.Reactive.Diagnostics.ChangeSummary Summary { get; } + public void Dispose() { } + protected virtual void Dispose(bool isDisposing) { } + } + public class GroupChangeSetAggregator : System.IDisposable + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull + { + public GroupChangeSetAggregator(System.IObservable> source) { } + public DynamicData.Reactive.IObservableCache, TGroupKey> Data { get; } + public System.Exception? Error { get; } + public DynamicData.Reactive.IObservableCache, TGroupKey> Groups { get; } + public bool IsCompleted { get; } + public System.Collections.Generic.IReadOnlyList> Messages { get; } + public DynamicData.Reactive.Diagnostics.ChangeSummary Summary { get; } + public void Dispose() { } + protected virtual void Dispose(bool disposing) { } + } + public static class ListTextEx + { + public static DynamicData.Reactive.Tests.ChangeSetAggregator AsAggregator(this System.IObservable> source) + where T : notnull { } + } + public class PagedChangeSetAggregator : System.IDisposable + where TObject : notnull + where TKey : notnull + { + public PagedChangeSetAggregator(System.IObservable> source) { } + public DynamicData.Reactive.IObservableCache Data { get; } + public System.Exception? Error { get; } + public System.Collections.Generic.IList> Messages { get; } + public DynamicData.Reactive.Diagnostics.ChangeSummary Summary { get; } + public void Dispose() { } + protected virtual void Dispose(bool isDisposing) { } + } + public class SortedChangeSetAggregator : System.IDisposable + where TObject : notnull + where TKey : notnull + { + public SortedChangeSetAggregator(System.IObservable> source) { } + public DynamicData.Reactive.IObservableCache Data { get; } + public System.Exception? Error { get; } + public System.Collections.Generic.IList> Messages { get; } + public DynamicData.Reactive.Diagnostics.ChangeSummary Summary { get; } + public void Dispose() { } + protected virtual void Dispose(bool isDisposing) { } + } + public static class TestEx + { + public static DynamicData.Reactive.Tests.DistinctChangeSetAggregator AsAggregator(this System.IObservable> source) + where TValue : notnull { } + public static DynamicData.Reactive.Tests.ChangeSetAggregator AsAggregator(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static DynamicData.Reactive.Tests.PagedChangeSetAggregator AsAggregator(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static DynamicData.Reactive.Tests.SortedChangeSetAggregator AsAggregator(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static DynamicData.Reactive.Tests.VirtualChangeSetAggregator AsAggregator(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static DynamicData.Reactive.Tests.ChangeSetAggregator AsAggregator(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static DynamicData.Reactive.Tests.GroupChangeSetAggregator AsAggregator(this System.IObservable> source) + where TValue : notnull + where TKey : notnull + where TGroupKey : notnull { } + } + public class VirtualChangeSetAggregator : System.IDisposable + where TObject : notnull + where TKey : notnull + { + public VirtualChangeSetAggregator(System.IObservable> source) { } + public DynamicData.Reactive.IObservableCache Data { get; } + public System.Exception? Error { get; } + public System.Collections.Generic.IList> Messages { get; } + public DynamicData.Reactive.Diagnostics.ChangeSummary Summary { get; } + public void Dispose() { } + protected virtual void Dispose(bool isDisposing) { } + } +} \ No newline at end of file diff --git a/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataTests.DotNet10_0.verified.txt b/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataTests.DotNet10_0.verified.txt new file mode 100644 index 000000000..5a14a98c2 --- /dev/null +++ b/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataTests.DotNet10_0.verified.txt @@ -0,0 +1,3061 @@ +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DynamicData.Benchmarks")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DynamicData.Profile")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DynamicData.ReactiveUI")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DynamicData.Tests")] +[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v10.0", FrameworkDisplayName=".NET 10.0")] +namespace DynamicData.Aggregation +{ + public readonly struct AggregateItem : System.IEquatable> + { + public AggregateItem(DynamicData.Aggregation.AggregateType Type, TObject Item) { } + public TObject Item { get; init; } + public DynamicData.Aggregation.AggregateType Type { get; init; } + public override int GetHashCode() { } + } + public enum AggregateType + { + Add = 0, + Remove = 1, + } + public static class AggregationEx + { + public static System.IObservable> ForAggregation(this System.IObservable> source) + where TObject : notnull { } + public static System.IObservable> ForAggregation(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable InvalidateWhen(this System.IObservable source, System.IObservable invalidate) { } + public static System.IObservable InvalidateWhen(this System.IObservable source, System.IObservable invalidate) { } + } + public static class AvgEx + { + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, [System.Runtime.CompilerServices.DecimalConstant(0, 0, 0u, 0u, 0u)] decimal emptyValue) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, [System.Runtime.CompilerServices.DecimalConstant(0, 0, 0u, 0u, 0u)] decimal emptyValue) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, double emptyValue = 0) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, double emptyValue = 0) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, float emptyValue = 0) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, float emptyValue = 0) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, int emptyValue = 0) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, int emptyValue = 0) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, long emptyValue = 0) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, long emptyValue = 0) { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, [System.Runtime.CompilerServices.DecimalConstant(0, 0, 0u, 0u, 0u)] decimal emptyValue) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, [System.Runtime.CompilerServices.DecimalConstant(0, 0, 0u, 0u, 0u)] decimal emptyValue) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, double emptyValue = 0) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, double emptyValue = 0) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, float emptyValue = 0) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, float emptyValue = 0) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, int emptyValue = 0) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, int emptyValue = 0) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, long emptyValue = 0) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, long emptyValue = 0) + where T : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, [System.Runtime.CompilerServices.DecimalConstant(0, 0, 0u, 0u, 0u)] decimal emptyValue) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, [System.Runtime.CompilerServices.DecimalConstant(0, 0, 0u, 0u, 0u)] decimal emptyValue) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, double emptyValue = 0) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, double emptyValue = 0) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, float emptyValue = 0) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, float emptyValue = 0) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, int emptyValue = 0) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, int emptyValue = 0) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, long emptyValue = 0) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Avg(this System.IObservable> source, System.Func valueSelector, long emptyValue = 0) + where TObject : notnull + where TKey : notnull { } + } + public static class CountEx + { + public static System.IObservable Count(this System.IObservable> source) { } + public static System.IObservable Count(this System.IObservable> source) + where TObject : notnull { } + public static System.IObservable Count(this System.IObservable> source) + where TObject : notnull { } + public static System.IObservable Count(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable IsEmpty(this System.IObservable> source) + where TObject : notnull { } + public static System.IObservable IsEmpty(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable IsNotEmpty(this System.IObservable> source) + where TObject : notnull { } + public static System.IObservable IsNotEmpty(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + } + public interface IAggregateChangeSet : System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable { } + public static class MaxEx + { + public static System.IObservable Maximum(this System.IObservable> source, System.Func valueSelector, TResult emptyValue = default) + where TObject : notnull + where TResult : struct, System.IComparable { } + public static System.IObservable Maximum(this System.IObservable> source, System.Func valueSelector, TResult emptyValue = default) + where TObject : notnull + where TKey : notnull + where TResult : struct, System.IComparable { } + public static System.IObservable Minimum(this System.IObservable> source, System.Func valueSelector, TResult emptyValue = default) + where TObject : notnull + where TResult : struct, System.IComparable { } + public static System.IObservable Minimum(this System.IObservable> source, System.Func valueSelector, TResult emptyValue = default) + where TObject : notnull + where TKey : notnull + where TResult : struct, System.IComparable { } + } + public static class StdDevEx + { + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, [System.Runtime.CompilerServices.DecimalConstant(0, 0, 0u, 0u, 0u)] decimal fallbackValue) { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, double fallbackValue = 0) { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, float fallbackValue = 0) { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, int fallbackValue = 0) { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, long fallbackValue = 0) { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, decimal fallbackValue) + where T : notnull { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, double fallbackValue) + where T : notnull { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, float fallbackValue = 0) + where T : notnull { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, int fallbackValue) + where T : notnull { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, long fallbackValue) + where T : notnull { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, decimal fallbackValue) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, double fallbackValue) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, float fallbackValue = 0) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, int fallbackValue) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable StdDev(this System.IObservable> source, System.Func valueSelector, long fallbackValue) + where TObject : notnull + where TKey : notnull { } + } + public static class SumEx + { + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable Sum(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull { } + } +} +namespace DynamicData.Alias +{ + public static class ObservableCacheAlias + { + public static System.IObservable> Select(this System.IObservable> source, System.Func transformFactory, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Select(this System.IObservable> source, System.Func transformFactory, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Select(this System.IObservable> source, System.Func transformFactory, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Select(this System.IObservable> source, System.Func transformFactory, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> SelectMany(this System.IObservable> source, System.Func> manySelector, System.Func keySelector) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> SelectSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> SelectSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> SelectSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> SelectSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable, TKey>> SelectTree(this System.IObservable> source, System.Func pivotOn) + where TObject : class + where TKey : notnull { } + public static System.IObservable> Where(this System.IObservable> source, System.Func filter) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Where(this System.IObservable> source, System.IObservable> predicateChanged) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Where(this System.IObservable> source, System.IObservable> predicateChanged, System.IObservable reapplyFilter) + where TObject : notnull + where TKey : notnull { } + } + public static class ObservableListAlias + { + public static System.IObservable> Select(this System.IObservable> source, System.Func transformFactory) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> SelectMany(this System.IObservable> source, System.Func> manySelector) + where TDestination : notnull + where TSource : notnull { } + public static System.IObservable> Where(this System.IObservable> source, System.Func predicate) + where T : notnull { } + public static System.IObservable> Where(this System.IObservable> source, System.IObservable> predicate) + where T : notnull { } + } +} +namespace DynamicData.Binding +{ + public abstract class AbstractNotifyPropertyChanged : System.ComponentModel.INotifyPropertyChanged + { + protected AbstractNotifyPropertyChanged() { } + public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged; + protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) { } + protected virtual void SetAndRaise(ref T backingField, T newValue, [System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) { } + protected virtual void SetAndRaise(ref T backingField, T newValue, System.Collections.Generic.IEqualityComparer? comparer, [System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) { } + } + public class BindingListAdaptor<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] T> : DynamicData.IChangeSetAdaptor + where T : notnull + { + public BindingListAdaptor(System.ComponentModel.BindingList list, int refreshThreshold = 25) { } + public void Adapt(DynamicData.IChangeSet changes) { } + } + public class BindingListAdaptor<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey> : DynamicData.IChangeSetAdaptor + where TObject : notnull + where TKey : notnull + { + public BindingListAdaptor(System.ComponentModel.BindingList list, int refreshThreshold = 25) { } + public void Adapt(DynamicData.IChangeSet changes) { } + } + public static class BindingListEx + { + public static System.IObservable> ObserveCollectionChanges(this System.ComponentModel.IBindingList source) { } + public static System.IObservable> ToObservableChangeSet<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] T>(this System.ComponentModel.BindingList source) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this TCollection source) + where TCollection : System.ComponentModel.IBindingList, System.Collections.Generic.IEnumerable + where T : notnull { } + public static System.IObservable> ToObservableChangeSet<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.ComponentModel.BindingList source, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + } + public struct BindingOptions : System.IEquatable + { + public const bool DefaultResetOnFirstTimeLoad = true; + public const int DefaultResetThreshold = 25; + public const bool DefaultUseReplaceForUpdates = true; + public BindingOptions(int ResetThreshold, bool UseReplaceForUpdates = true, bool ResetOnFirstTimeLoad = true) { } + public bool ResetOnFirstTimeLoad { get; set; } + public int ResetThreshold { get; set; } + public bool UseReplaceForUpdates { get; set; } + public static DynamicData.Binding.BindingOptions NeverFireReset(bool useReplaceForUpdates = true) { } + } + public interface IEvaluateAware + { + void Evaluate(); + } + public interface IIndexAware + { + int Index { get; set; } + } + public interface INotifyCollectionChangedSuspender + { + System.IDisposable SuspendCount(); + System.IDisposable SuspendNotifications(); + } + public interface IObservableCollectionAdaptor + where TObject : notnull + where TKey : notnull + { + void Adapt(DynamicData.IChangeSet changes, DynamicData.Binding.IObservableCollection collection); + } + public interface IObservableCollection : DynamicData.Binding.INotifyCollectionChangedSuspender, System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IList, System.Collections.IEnumerable, System.Collections.Specialized.INotifyCollectionChanged, System.ComponentModel.INotifyPropertyChanged + { + void Load(System.Collections.Generic.IEnumerable items); + void Move(int oldIndex, int newIndex); + } + public static class IObservableListEx + { + public static System.IObservable> BindToObservableList(this System.IObservable> source, out DynamicData.IObservableList observableList) + where TObject : notnull { } + public static System.IObservable> BindToObservableList(this System.IObservable> source, out DynamicData.IObservableList observableList) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> BindToObservableList(this System.IObservable> source, out DynamicData.IObservableList observableList) + where TObject : notnull + where TKey : notnull { } + } + public interface ISortedObservableCollectionAdaptor + where TObject : notnull + where TKey : notnull + { + void Adapt(DynamicData.ISortedChangeSet changes, DynamicData.Binding.IObservableCollection collection); + } + public static class NotifyPropertyChangedEx + { + public static System.IObservable WhenAnyPropertyChanged(this TObject source, params string[] propertiesToMonitor) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable WhenChanged(this TObject source, System.Linq.Expressions.Expression> p1, System.Func resultSelector, System.Func? p1Fallback = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable WhenChanged(this TObject source, System.Linq.Expressions.Expression> p1, System.Linq.Expressions.Expression> p2, System.Func resultSelector, System.Func? p1Fallback = null, System.Func? p2Fallback = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable WhenChanged(this TObject source, System.Linq.Expressions.Expression> p1, System.Linq.Expressions.Expression> p2, System.Linq.Expressions.Expression> p3, System.Func resultSelector, System.Func? p1Fallback = null, System.Func? p2Fallback = null, System.Func? p3Fallback = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable WhenChanged(this TObject source, System.Linq.Expressions.Expression> p1, System.Linq.Expressions.Expression> p2, System.Linq.Expressions.Expression> p3, System.Linq.Expressions.Expression> p4, System.Func resultSelector, System.Func? p1Fallback = null, System.Func? p2Fallback = null, System.Func? p3Fallback = null, System.Func? p4Fallback = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable WhenChanged(this TObject source, System.Linq.Expressions.Expression> p1, System.Linq.Expressions.Expression> p2, System.Linq.Expressions.Expression> p3, System.Linq.Expressions.Expression> p4, System.Linq.Expressions.Expression> p5, System.Func resultSelector, System.Func? p1Fallback = null, System.Func? p2Fallback = null, System.Func? p3Fallback = null, System.Func? p4Fallback = null, System.Func? p5Fallback = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable WhenChanged(this TObject source, System.Linq.Expressions.Expression> p1, System.Linq.Expressions.Expression> p2, System.Linq.Expressions.Expression> p3, System.Linq.Expressions.Expression> p4, System.Linq.Expressions.Expression> p5, System.Linq.Expressions.Expression> p6, System.Func resultSelector, System.Func? p1Fallback = null, System.Func? p2Fallback = null, System.Func? p3Fallback = null, System.Func? p4Fallback = null, System.Func? p5Fallback = null, System.Func? p6Fallback = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable> WhenPropertyChanged(this TObject source, System.Linq.Expressions.Expression> propertyAccessor, bool notifyOnInitialValue = true, System.Func? fallbackValue = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable WhenValueChanged(this TObject source, System.Linq.Expressions.Expression> propertyAccessor, bool notifyOnInitialValue = true, System.Func? fallbackValue = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + } + public class ObservableCollectionAdaptor : DynamicData.IChangeSetAdaptor + where T : notnull + { + public ObservableCollectionAdaptor(DynamicData.Binding.IObservableCollection collection) { } + public ObservableCollectionAdaptor(DynamicData.Binding.IObservableCollection collection, DynamicData.Binding.BindingOptions options) { } + public ObservableCollectionAdaptor(DynamicData.Binding.IObservableCollection collection, int refreshThreshold, bool allowReplace = true, bool resetOnFirstTimeLoad = true) { } + public void Adapt(DynamicData.IChangeSet changes) { } + } + public class ObservableCollectionAdaptor : DynamicData.Binding.IObservableCollectionAdaptor + where TObject : notnull + where TKey : notnull + { + public ObservableCollectionAdaptor(DynamicData.Binding.BindingOptions options) { } + public ObservableCollectionAdaptor(int refreshThreshold = 25, bool useReplaceForUpdates = true, bool resetOnFirstTimeLoad = true) { } + public void Adapt(DynamicData.IChangeSet changes, DynamicData.Binding.IObservableCollection collection) { } + } + public static class ObservableCollectionEx + { + public static System.IObservable> ObserveCollectionChanges(this System.Collections.Specialized.INotifyCollectionChanged source) { } + public static System.IObservable> ToObservableChangeSet(this System.Collections.ObjectModel.ObservableCollection source) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.Collections.ObjectModel.ReadOnlyObservableCollection source) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this TCollection source) + where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.Collections.ObjectModel.ObservableCollection source, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.Collections.ObjectModel.ReadOnlyObservableCollection source, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + } + public class ObservableCollectionExtended : System.Collections.ObjectModel.ObservableCollection, DynamicData.Binding.INotifyCollectionChangedSuspender, DynamicData.Binding.IObservableCollection, DynamicData.IExtendedList, System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IList, System.Collections.IEnumerable, System.Collections.Specialized.INotifyCollectionChanged, System.ComponentModel.INotifyPropertyChanged + { + public ObservableCollectionExtended() { } + public ObservableCollectionExtended(System.Collections.Generic.IEnumerable collection) { } + public ObservableCollectionExtended(System.Collections.Generic.List list) { } + public void AddRange(System.Collections.Generic.IEnumerable collection) { } + public void InsertRange(System.Collections.Generic.IEnumerable collection, int index) { } + public void Load(System.Collections.Generic.IEnumerable items) { } + protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { } + protected override void OnPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e) { } + public void RemoveRange(int index, int count) { } + public System.IDisposable SuspendCount() { } + public System.IDisposable SuspendNotifications() { } + } + public sealed class PropertyValue : System.IEquatable> + { + public PropertyValue(TObject sender, TValue value) { } + public PropertyValue(TObject Sender, TValue? Value, bool UnobtainableValue) { } + public TObject Sender { get; init; } + public bool UnobtainableValue { get; init; } + public TValue Value { get; init; } + public override int GetHashCode() { } + public override string ToString() { } + } + public readonly struct SortAndBindOptions : System.IEquatable + { + public SortAndBindOptions() { } + public int InitialCapacity { get; init; } + public bool ResetOnFirstTimeLoad { get; init; } + public int ResetThreshold { get; init; } + public ReactiveUI.Primitives.Concurrency.ISequencer? Scheduler { get; init; } + public bool UseBinarySearch { get; init; } + public bool UseReplaceForUpdates { get; init; } + } + public enum SortDirection + { + Ascending = 0, + Descending = 1, + } + public class SortExpressionComparer : System.Collections.Generic.List>, System.Collections.Generic.IComparer + { + public SortExpressionComparer() { } + public int Compare(T? x, T? y) { } + public DynamicData.Binding.SortExpressionComparer ThenByAscending(System.Func expression) { } + public DynamicData.Binding.SortExpressionComparer ThenByDescending(System.Func expression) { } + public static DynamicData.Binding.SortExpressionComparer Ascending(System.Func expression) { } + public static DynamicData.Binding.SortExpressionComparer Descending(System.Func expression) { } + } + public class SortExpression + { + public SortExpression(System.Func expression, DynamicData.Binding.SortDirection direction = 0) { } + public DynamicData.Binding.SortDirection Direction { get; } + public System.Func Expression { get; } + } + public class SortedBindingListAdaptor<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey> : DynamicData.ISortedChangeSetAdaptor + where TObject : notnull + where TKey : notnull + { + public SortedBindingListAdaptor(System.ComponentModel.BindingList list, int refreshThreshold = 25) { } + public void Adapt(DynamicData.ISortedChangeSet changes) { } + } + public class SortedObservableCollectionAdaptor : DynamicData.Binding.ISortedObservableCollectionAdaptor + where TObject : notnull + where TKey : notnull + { + public SortedObservableCollectionAdaptor() { } + public SortedObservableCollectionAdaptor(DynamicData.Binding.BindingOptions options) { } + public SortedObservableCollectionAdaptor(int refreshThreshold, bool useReplaceForUpdates = true, bool resetOnFirstTimeLoad = true) { } + public void Adapt(DynamicData.ISortedChangeSet changes, DynamicData.Binding.IObservableCollection collection) { } + } +} +namespace DynamicData.Cache.Internal +{ + public enum CombineOperator + { + And = 0, + Or = 1, + Xor = 2, + Except = 3, + } + [System.Serializable] + public class KeySelectorException : System.Exception + { + public KeySelectorException() { } + public KeySelectorException(string message) { } + public KeySelectorException(string message, System.Exception innerException) { } + } + [System.Diagnostics.DebuggerDisplay("LockFreeObservableCache<{typeof(TObject).Name}, {typeof(TKey).Name}> ({Count} Ite" + + "ms)")] + public sealed class LockFreeObservableCache : DynamicData.IConnectableCache, DynamicData.IObservableCache, System.IDisposable + where TObject : notnull + where TKey : notnull + { + public LockFreeObservableCache() { } + public LockFreeObservableCache(System.IObservable> source) { } + public int Count { get; } + public System.IObservable CountChanged { get; } + public System.Collections.Generic.IReadOnlyList Items { get; } + public System.Collections.Generic.IReadOnlyDictionary KeyValues { get; } + public System.Collections.Generic.IReadOnlyList Keys { get; } + public System.IObservable> Connect(System.Func? predicate = null, bool suppressEmptyChangeSets = true) { } + public void Dispose() { } + public void Edit(System.Action> editAction) { } + public ReactiveUI.Primitives.Optional Lookup(TKey key) { } + public System.IObservable> Preview(System.Func? predicate = null) { } + public System.IObservable> Watch(TKey key) { } + } +} +namespace DynamicData +{ + public sealed class ChangeAwareCache : DynamicData.ICache, DynamicData.IQuery + where TObject : notnull + where TKey : notnull + { + public ChangeAwareCache() { } + public ChangeAwareCache(System.Collections.Generic.Dictionary data) { } + public ChangeAwareCache(int capacity) { } + public int Count { get; } + public System.Collections.Generic.IEnumerable Items { get; } + public System.Collections.Generic.IEnumerable> KeyValues { get; } + public System.Collections.Generic.IEnumerable Keys { get; } + public void Add(TObject item, TKey key) { } + public void AddOrUpdate(TObject item, TKey key) { } + public DynamicData.ChangeSet CaptureChanges() { } + public void Clear() { } + public void Clone(DynamicData.IChangeSet changes) { } + public ReactiveUI.Primitives.Optional Lookup(TKey key) { } + public void Refresh() { } + public void Refresh(System.Collections.Generic.IEnumerable keys) { } + public void Refresh(TKey key) { } + public void Remove(System.Collections.Generic.IEnumerable keys) { } + public void Remove(TKey key) { } + } + public class ChangeAwareList : DynamicData.IExtendedList, System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IList, System.Collections.IEnumerable + where T : notnull + { + public ChangeAwareList(System.Collections.Generic.IEnumerable items) { } + public ChangeAwareList(int capacity = -1) { } + public ChangeAwareList(DynamicData.ChangeAwareList list, bool copyChanges) { } + public int Capacity { get; set; } + public int Count { get; } + public bool IsReadOnly { get; } + public T this[int index] { get; set; } + public void Add(T item) { } + public void AddRange(System.Collections.Generic.IEnumerable collection) { } + public DynamicData.IChangeSet CaptureChanges() { } + public virtual void Clear() { } + public virtual bool Contains(T item) { } + public void CopyTo(T[] array, int arrayIndex) { } + public System.Collections.Generic.IEnumerator GetEnumerator() { } + public int IndexOf(T item) { } + public int IndexOf(T item, System.Collections.Generic.IEqualityComparer equalityComparer) { } + public void Insert(int index, T item) { } + protected virtual void InsertItem(int index, T item) { } + public void InsertRange(System.Collections.Generic.IEnumerable collection, int index) { } + public virtual void Move(T item, int destination) { } + public virtual void Move(int original, int destination) { } + protected virtual void OnInsertItems(int startIndex, System.Collections.Generic.IEnumerable items) { } + protected virtual void OnRemoveItems(int startIndex, System.Collections.Generic.IEnumerable items) { } + protected virtual void OnSetItem(int index, T newItem, T oldItem) { } + public bool Refresh(T item) { } + public void Refresh(T item, int index) { } + public void RefreshAt(int index) { } + public bool Remove(T item) { } + public void RemoveAt(int index) { } + protected void RemoveItem(int index) { } + protected virtual void RemoveItem(int index, T item) { } + public void RemoveRange(int index, int count) { } + protected virtual void SetItem(int index, T item) { } + } + public enum ChangeReason + { + Add = 0, + Update = 1, + Remove = 2, + Refresh = 3, + Moved = 4, + } + public static class ChangeSetEx + { + public static System.Collections.Generic.IEnumerable> Flatten(this DynamicData.IChangeSet source) + where T : notnull { } + public static DynamicData.ChangeType GetChangeType(this DynamicData.ListChangeReason source) { } + public static DynamicData.IChangeSet Transform(this DynamicData.IChangeSet source, System.Func transformer) + where TSource : notnull + where TDestination : notnull { } + public static System.Collections.Generic.IEnumerable> YieldWithoutIndex(this System.Collections.Generic.IEnumerable> source) + where T : notnull { } + } + public class ChangeSet : System.Collections.Generic.List>, DynamicData.IChangeSet, DynamicData.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where T : notnull + { + public static readonly DynamicData.IChangeSet Empty; + public ChangeSet() { } + public ChangeSet(System.Collections.Generic.IEnumerable> items) { } + public ChangeSet(int capacity) { } + public int Adds { get; } + public int Moves { get; } + public int Refreshes { get; } + public int Removes { get; } + public int Replaced { get; } + public int TotalChanges { get; } + public override string ToString() { } + } + public class ChangeSet : System.Collections.Generic.List>, DynamicData.IChangeSet, DynamicData.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where TObject : notnull + where TKey : notnull + { + public static readonly DynamicData.ChangeSet Empty; + public ChangeSet() { } + public ChangeSet(System.Collections.Generic.IEnumerable> collection) { } + public ChangeSet(int capacity) { } + public int Adds { get; } + public int Moves { get; } + public int Refreshes { get; } + public int Removes { get; } + public int Updates { get; } + public override string ToString() { } + } + public sealed class ChangeSet : DynamicData.ChangeSet, DynamicData.IChangeSet, DynamicData.IChangeSet, DynamicData.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where TObject : notnull + where TKey : notnull + { + public ChangeSet(TContext context) { } + public ChangeSet(System.Collections.Generic.IEnumerable> collection, TContext context) { } + public ChangeSet(int capacity, TContext context) { } + public TContext Context { get; } + } + public enum ChangeType + { + Item = 0, + Range = 1, + } + public sealed class Change : System.IEquatable> + where T : notnull + { + public Change(DynamicData.ListChangeReason reason, System.Collections.Generic.IEnumerable items, int index = -1) { } + public Change(DynamicData.ListChangeReason reason, T current, int index = -1) { } + public Change(T current, int currentIndex, int previousIndex) { } + public Change(DynamicData.ListChangeReason reason, T current, in ReactiveUI.Primitives.Optional previous, int currentIndex = -1, int previousIndex = -1) { } + public DynamicData.ItemChange Item { get; } + public DynamicData.RangeChange Range { get; } + public DynamicData.ListChangeReason Reason { get; } + public DynamicData.ChangeType Type { get; } + public bool Equals(DynamicData.Change? other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + public static bool operator !=(DynamicData.Change left, DynamicData.Change right) { } + public static bool operator ==(DynamicData.Change left, DynamicData.Change right) { } + } + public readonly struct Change : System.IEquatable> + where TObject : notnull + where TKey : notnull + { + public Change(DynamicData.ChangeReason reason, TKey key, TObject current, int index = -1) { } + public Change(TKey key, TObject current, int currentIndex, int previousIndex) { } + public Change(DynamicData.ChangeReason reason, TKey key, TObject current, in ReactiveUI.Primitives.Optional previous, int currentIndex = -1, int previousIndex = -1) { } + public TObject Current { get; } + public int CurrentIndex { get; } + public TKey Key { get; } + public ReactiveUI.Primitives.Optional Previous { get; } + public int PreviousIndex { get; } + public DynamicData.ChangeReason Reason { get; } + public bool Equals(DynamicData.Change other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + public static bool operator !=(in DynamicData.Change left, in DynamicData.Change right) { } + public static bool operator ==(in DynamicData.Change left, in DynamicData.Change right) { } + } + public static class DynamicDataOptions + { + public static DynamicData.Binding.BindingOptions Binding { get; set; } + public static DynamicData.Binding.SortAndBindOptions SortAndBind { get; set; } + } + public static class EnumerableEx + { + public static System.IObservable> AsObservableChangeSet(this System.Collections.Generic.IEnumerable source, bool completable = false) + where TObject : notnull { } + public static System.IObservable> AsObservableChangeSet(this System.Collections.Generic.IEnumerable source, System.Func keySelector, bool completable = false) + where TObject : notnull + where TKey : notnull { } + } + public interface ICacheUpdater : DynamicData.IQuery + where TObject : notnull + where TKey : notnull + { + void AddOrUpdate(System.Collections.Generic.IEnumerable> keyValuePairs); + void AddOrUpdate(System.Collections.Generic.KeyValuePair item); + void AddOrUpdate(TObject item, TKey key); + void Clear(); + void Clone(DynamicData.IChangeSet changes); + TKey GetKey(TObject item); + System.Collections.Generic.IEnumerable> GetKeyValues(System.Collections.Generic.IEnumerable items); + void Refresh(); + void Refresh(System.Collections.Generic.IEnumerable keys); + void Refresh(TKey key); + void Remove(System.Collections.Generic.IEnumerable> items); + void Remove(System.Collections.Generic.IEnumerable keys); + void Remove(System.Collections.Generic.KeyValuePair item); + void Remove(TKey key); + void RemoveKey(TKey key); + void RemoveKeys(System.Collections.Generic.IEnumerable key); + [System.Obsolete("Use Clone()")] + void Update(DynamicData.IChangeSet changes); + } + public interface ICache : DynamicData.IQuery + where TObject : notnull + where TKey : notnull + { + void AddOrUpdate(TObject item, TKey key); + void Clear(); + void Clone(DynamicData.IChangeSet changes); + void Refresh(); + void Refresh(System.Collections.Generic.IEnumerable keys); + void Refresh(TKey key); + void Remove(System.Collections.Generic.IEnumerable keys); + void Remove(TKey key); + } + public interface IChangeSet + { + int Adds { get; } + int Capacity { get; set; } + int Count { get; } + int Moves { get; } + int Refreshes { get; } + int Removes { get; } + } + public interface IChangeSetAdaptor + where T : notnull + { + void Adapt(DynamicData.IChangeSet changes); + } + public interface IChangeSetAdaptor + where TObject : notnull + where TKey : notnull + { + void Adapt(DynamicData.IChangeSet changes); + } + public interface IChangeSet : DynamicData.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where TObject : notnull + { + int Replaced { get; } + int TotalChanges { get; } + } + public interface IChangeSet : DynamicData.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where TObject : notnull + where TKey : notnull + { + int Updates { get; } + } + public interface IChangeSet : DynamicData.IChangeSet, DynamicData.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where TObject : notnull + where TKey : notnull + { + TContext Context { get; } + } + public interface IConnectableCache + where TObject : notnull + where TKey : notnull + { + System.IObservable CountChanged { get; } + System.IObservable> Connect(System.Func? predicate = null, bool suppressEmptyChangeSets = true); + System.IObservable> Preview(System.Func? predicate = null); + System.IObservable> Watch(TKey key); + } + public interface IDistinctChangeSet : DynamicData.IChangeSet, DynamicData.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where T : notnull { } + public interface IExtendedList : System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IList, System.Collections.IEnumerable + { + void AddRange(System.Collections.Generic.IEnumerable collection); + void InsertRange(System.Collections.Generic.IEnumerable collection, int index); + void Move(int original, int destination); + void RemoveRange(int index, int count); + } + public interface IGroupChangeSet : DynamicData.IChangeSet, DynamicData.IChangeSet, TGroupKey>, System.Collections.Generic.IEnumerable, TGroupKey>>, System.Collections.IEnumerable + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public interface IGroup + where TObject : notnull + { + TGroup GroupKey { get; } + DynamicData.IObservableList List { get; } + } + public interface IGroup : DynamicData.IKey + where TObject : notnull + where TKey : notnull + { + DynamicData.IObservableCache Cache { get; } + } + public interface IGrouping + where TObject : notnull + { + int Count { get; } + System.Collections.Generic.IEnumerable Items { get; } + TGroupKey Key { get; } + System.Collections.Generic.IEnumerable> KeyValues { get; } + System.Collections.Generic.IEnumerable Keys { get; } + ReactiveUI.Primitives.Optional Lookup(TKey key); + } + public interface IImmutableGroupChangeSet : DynamicData.IChangeSet, DynamicData.IChangeSet, TGroupKey>, System.Collections.Generic.IEnumerable, TGroupKey>>, System.Collections.IEnumerable + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public interface IIntermediateCache : DynamicData.IConnectableCache, DynamicData.IObservableCache, System.IDisposable + where TObject : notnull + where TKey : notnull + { + void Edit(System.Action> updateAction); + } + public interface IKeyValueCollection : System.Collections.Generic.IEnumerable>, System.Collections.Generic.IReadOnlyCollection>, System.Collections.Generic.IReadOnlyList>, System.Collections.IEnumerable + { + System.Collections.Generic.IComparer> Comparer { get; } + DynamicData.SortOptimisations Optimisations { get; } + DynamicData.SortReason SortReason { get; } + } + public interface IKeyValue : DynamicData.IKey + { + TObject Value { get; } + } + public interface IKey + { + T Key { get; } + } + public interface IObservableCache : DynamicData.IConnectableCache, System.IDisposable + where TObject : notnull + where TKey : notnull + { + int Count { get; } + System.Collections.Generic.IReadOnlyList Items { get; } + System.Collections.Generic.IReadOnlyDictionary KeyValues { get; } + System.Collections.Generic.IReadOnlyList Keys { get; } + ReactiveUI.Primitives.Optional Lookup(TKey key); + } + public interface IObservableList : System.IDisposable + where T : notnull + { + int Count { get; } + System.IObservable CountChanged { get; } + System.Collections.Generic.IReadOnlyList Items { get; } + System.IObservable> Connect(System.Func? predicate = null); + System.IObservable> Preview(System.Func? predicate = null); + } + public interface IPageChangeSet : DynamicData.IChangeSet, DynamicData.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where T : notnull + { + DynamicData.Operators.IPageResponse Response { get; } + } + public interface IPageRequest + { + int Page { get; } + int Size { get; } + } + public interface IPagedChangeSet : DynamicData.IChangeSet, DynamicData.IChangeSet, DynamicData.ISortedChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where TObject : notnull + where TKey : notnull + { + DynamicData.Operators.IPageResponse Response { get; } + } + public interface IQuery + where TObject : notnull + { + int Count { get; } + System.Collections.Generic.IEnumerable Items { get; } + System.Collections.Generic.IEnumerable> KeyValues { get; } + System.Collections.Generic.IEnumerable Keys { get; } + ReactiveUI.Primitives.Optional Lookup(TKey key); + } + public interface ISortedChangeSetAdaptor + where TObject : notnull + where TKey : notnull + { + void Adapt(DynamicData.ISortedChangeSet changes); + } + public interface ISortedChangeSet : DynamicData.IChangeSet, DynamicData.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where TObject : notnull + where TKey : notnull + { + DynamicData.IKeyValueCollection SortedItems { get; } + } + public interface ISourceCache : DynamicData.IConnectableCache, DynamicData.IObservableCache, System.IDisposable + where TObject : notnull + where TKey : notnull + { + System.Func KeySelector { get; } + void Edit(System.Action> updateAction); + } + public interface ISourceList : DynamicData.IObservableList, System.IDisposable + where T : notnull + { + void Edit(System.Action> updateAction); + } + public interface ISourceUpdater : DynamicData.ICacheUpdater, DynamicData.IQuery + where TObject : notnull + where TKey : notnull + { + void AddOrUpdate(System.Collections.Generic.IEnumerable items); + void AddOrUpdate(TObject item); + void AddOrUpdate(System.Collections.Generic.IEnumerable items, System.Collections.Generic.IEqualityComparer comparer); + void AddOrUpdate(TObject item, System.Collections.Generic.IEqualityComparer comparer); + void Load(System.Collections.Generic.IEnumerable items); + void Refresh(System.Collections.Generic.IEnumerable items); + void Refresh(TObject item); + void Remove(System.Collections.Generic.IEnumerable items); + void Remove(TObject item); + } + public interface IVirtualChangeSet : DynamicData.IChangeSet, DynamicData.IChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where T : notnull + { + DynamicData.IVirtualResponse Response { get; } + } + public interface IVirtualChangeSet : DynamicData.IChangeSet, DynamicData.IChangeSet, DynamicData.ISortedChangeSet, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + where TObject : notnull + where TKey : notnull + { + DynamicData.IVirtualResponse Response { get; } + } + public interface IVirtualRequest + { + int Size { get; } + int StartIndex { get; } + } + public interface IVirtualResponse + { + int Size { get; } + int StartIndex { get; } + int TotalSize { get; } + } + public sealed class IndexedItem : System.IEquatable> + { + public IndexedItem(TObject value, TKey key, int index) { } + public int Index { get; } + public TKey Key { get; } + public TObject Value { get; } + public bool Equals(DynamicData.IndexedItem? other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + } + [System.Diagnostics.DebuggerDisplay("IntermediateCache<{typeof(TObject).Name}, {typeof(TKey).Name}> ({Count} Items)")] + public sealed class IntermediateCache : DynamicData.Binding.INotifyCollectionChangedSuspender, DynamicData.IConnectableCache, DynamicData.IIntermediateCache, DynamicData.IObservableCache, System.IDisposable + where TObject : notnull + where TKey : notnull + { + public IntermediateCache() { } + public IntermediateCache(System.IObservable> source) { } + public int Count { get; } + public System.IObservable CountChanged { get; } + public System.Collections.Generic.IReadOnlyList Items { get; } + public System.Collections.Generic.IReadOnlyDictionary KeyValues { get; } + public System.Collections.Generic.IReadOnlyList Keys { get; } + public System.IObservable> Connect(System.Func? predicate = null, bool suppressEmptyChangeSets = true) { } + public void Dispose() { } + public void Edit(System.Action> updateAction) { } + public ReactiveUI.Primitives.Optional Lookup(TKey key) { } + public System.IObservable> Preview(System.Func? predicate = null) { } + public System.IDisposable SuspendCount() { } + public System.IDisposable SuspendNotifications() { } + public System.IObservable> Watch(TKey key) { } + } + public readonly struct ItemChange : System.IEquatable> + where T : notnull + { + public static readonly DynamicData.ItemChange Empty; + public ItemChange(DynamicData.ListChangeReason reason, T current, int currentIndex) { } + public ItemChange(DynamicData.ListChangeReason reason, T current, in ReactiveUI.Primitives.Optional previous, int currentIndex = -1, int previousIndex = -1) { } + public T Current { get; } + public int CurrentIndex { get; } + public ReactiveUI.Primitives.Optional Previous { get; } + public int PreviousIndex { get; } + public DynamicData.ListChangeReason Reason { get; } + public bool Equals(DynamicData.ItemChange other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + public static bool operator !=(in DynamicData.ItemChange left, in DynamicData.ItemChange right) { } + public static bool operator ==(in DynamicData.ItemChange left, in DynamicData.ItemChange right) { } + } + public enum ListChangeReason + { + Add = 0, + AddRange = 1, + Replace = 2, + Remove = 3, + RemoveRange = 4, + Refresh = 5, + Moved = 6, + Clear = 7, + } + public static class ListEx + { + public static void Add(this System.Collections.Generic.IList source, System.Collections.Generic.IEnumerable items) { } + public static void AddOrInsertRange(this System.Collections.Generic.IList source, System.Collections.Generic.IEnumerable items, int index) { } + public static void AddRange(this System.Collections.Generic.IList source, System.Collections.Generic.IEnumerable items) { } + public static void AddRange(this System.Collections.Generic.IList source, System.Collections.Generic.IEnumerable items, int index) { } + public static int BinarySearch(this System.Collections.Generic.IList list, TItem value) { } + public static int BinarySearch(this System.Collections.Generic.IList list, TItem value, System.Collections.Generic.IComparer comparer) { } + public static int BinarySearch(this System.Collections.Generic.IList list, TSearch value, System.Func comparer) { } + public static void Clone(this System.Collections.Generic.IList source, DynamicData.IChangeSet changes) + where T : notnull { } + public static void Clone(this System.Collections.Generic.IList source, DynamicData.IChangeSet changes, System.Collections.Generic.IEqualityComparer? equalityComparer) + where T : notnull { } + public static void Clone(this System.Collections.Generic.IList source, System.Collections.Generic.IEnumerable> changes, System.Collections.Generic.IEqualityComparer? equalityComparer) + where T : notnull { } + public static int IndexOf(this System.Collections.Generic.IEnumerable source, T item) { } + public static int IndexOf(this System.Collections.Generic.IEnumerable source, T item, System.Collections.Generic.IEqualityComparer equalityComparer) { } + public static ReactiveUI.Primitives.Optional> IndexOfOptional(this System.Collections.Generic.IEnumerable source, T item, System.Collections.Generic.IEqualityComparer? equalityComparer = null) { } + public static void Remove(this System.Collections.Generic.IList source, System.Collections.Generic.IEnumerable items) { } + public static void RemoveMany(this System.Collections.Generic.IList source, System.Collections.Generic.IEnumerable itemsToRemove) { } + public static void Replace(this System.Collections.Generic.IList source, T original, T replaceWith) { } + public static void Replace(this System.Collections.Generic.IList source, T original, T replaceWith, System.Collections.Generic.IEqualityComparer comparer) { } + public static void ReplaceOrAdd(this System.Collections.Generic.IList source, T original, T replaceWith) { } + } + public enum ListFilterPolicy + { + ClearAndReplace = 0, + CalculateDiff = 1, + } + [System.Serializable] + public class MissingKeyException : System.Exception + { + public MissingKeyException() { } + public MissingKeyException(string message) { } + public MissingKeyException(string message, System.Exception innerException) { } + } + public class Node : System.IDisposable, System.IEquatable> + where TObject : class + where TKey : notnull + { + public Node(TObject item, TKey key) { } + public Node(TObject item, TKey key, in ReactiveUI.Primitives.Optional> parent) { } + public DynamicData.IObservableCache, TKey> Children { get; } + public int Depth { get; } + public bool IsRoot { get; } + public TObject Item { get; } + public TKey Key { get; } + public ReactiveUI.Primitives.Optional> Parent { get; } + public void Dispose() { } + protected virtual void Dispose(bool isDisposing) { } + public bool Equals(DynamicData.Node? other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + public static bool operator !=(DynamicData.Node left, DynamicData.Node right) { } + public static bool operator ==(DynamicData.Node? left, DynamicData.Node? right) { } + } + public static class ObservableCacheEx + { + public static System.IObservable> Adapt(this System.IObservable> source, DynamicData.IChangeSetAdaptor adaptor) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Adapt(this System.IObservable> source, DynamicData.ISortedChangeSetAdaptor adaptor) + where TObject : notnull + where TKey : notnull { } + public static void AddOrUpdate(this DynamicData.ISourceCache source, System.Collections.Generic.IEnumerable items) + where TObject : notnull + where TKey : notnull { } + public static void AddOrUpdate(this DynamicData.ISourceCache source, TObject item) + where TObject : notnull + where TKey : notnull { } + public static void AddOrUpdate(this DynamicData.IIntermediateCache source, TObject item, TKey key) + where TObject : notnull + where TKey : notnull { } + public static void AddOrUpdate(this DynamicData.ISourceCache source, System.Collections.Generic.IEnumerable items, System.Collections.Generic.IEqualityComparer equalityComparer) + where TObject : notnull + where TKey : notnull { } + public static void AddOrUpdate(this DynamicData.ISourceCache source, TObject item, System.Collections.Generic.IEqualityComparer equalityComparer) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> And(this DynamicData.IObservableList> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> And(this DynamicData.IObservableList> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> And(this DynamicData.IObservableList>> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> And(this System.Collections.Generic.ICollection>> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> And(this System.IObservable> source, params System.IObservable>[] others) + where TObject : notnull + where TKey : notnull { } + public static DynamicData.IObservableCache AsObservableCache(this DynamicData.IObservableCache source) + where TObject : notnull + where TKey : notnull { } + public static DynamicData.IObservableCache AsObservableCache(this System.IObservable> source, bool applyLocking = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> AsyncDisposeMany(this System.IObservable> source, System.Action> disposalsCompletedAccessor) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> AutoRefresh(this System.IObservable> source, System.TimeSpan? changeSetBuffer = default, System.TimeSpan? propertyChangeThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : System.ComponentModel.INotifyPropertyChanged + where TKey : notnull { } + public static System.IObservable> AutoRefresh(this System.IObservable> source, System.Linq.Expressions.Expression> propertyAccessor, System.TimeSpan? changeSetBuffer = default, System.TimeSpan? propertyChangeThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : System.ComponentModel.INotifyPropertyChanged + where TKey : notnull { } + public static System.IObservable> AutoRefreshOnObservable(this System.IObservable> source, System.Func> reevaluator, System.TimeSpan? changeSetBuffer = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> AutoRefreshOnObservable(this System.IObservable> source, System.Func> reevaluator, System.TimeSpan? changeSetBuffer = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Batch(this System.IObservable> source, System.TimeSpan timeSpan, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, System.TimeSpan? timeOut = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState = false, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState = false, System.IObservable? timer = null, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState = false, System.TimeSpan? timeOut = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable>> source, System.Collections.Generic.IList targetList) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable>> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable>> source, System.Collections.Generic.IList targetList) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable>> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, DynamicData.Binding.IObservableCollection destination) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, DynamicData.Binding.IObservableCollection destination, DynamicData.Binding.BindingOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, DynamicData.Binding.IObservableCollection destination, DynamicData.Binding.IObservableCollectionAdaptor updater) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, DynamicData.Binding.IObservableCollection destination, int refreshThreshold = 25) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, DynamicData.Binding.BindingOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, System.ComponentModel.BindingList bindingList, int resetThreshold = 25) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable>> source, System.Collections.Generic.IList targetList, DynamicData.Binding.SortAndBindOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable>> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, DynamicData.Binding.SortAndBindOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable>> source, System.Collections.Generic.IList targetList, DynamicData.Binding.SortAndBindOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable>> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, DynamicData.Binding.SortAndBindOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, DynamicData.Binding.IObservableCollection destination, DynamicData.Binding.BindingOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, DynamicData.Binding.IObservableCollection destination, DynamicData.Binding.ISortedObservableCollectionAdaptor updater) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, DynamicData.Binding.BindingOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, System.ComponentModel.BindingList bindingList, int resetThreshold = 25) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, int resetThreshold = 25, bool useReplaceForUpdates = true, DynamicData.Binding.IObservableCollectionAdaptor? adaptor = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, int resetThreshold = 25, bool useReplaceForUpdates = true, DynamicData.Binding.ISortedObservableCollectionAdaptor? adaptor = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> BufferInitial(this System.IObservable> source, System.TimeSpan initialBuffer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Cast(this System.IObservable> source, System.Func converter) + where TSource : notnull + where TKey : notnull + where TDestination : notnull { } + public static System.IObservable> ChangeKey(this System.IObservable> source, System.Func keySelector) + where TObject : notnull + where TSourceKey : notnull + where TDestinationKey : notnull { } + public static System.IObservable> ChangeKey(this System.IObservable> source, System.Func keySelector) + where TObject : notnull + where TSourceKey : notnull + where TDestinationKey : notnull { } + public static void Clear(this DynamicData.Cache.Internal.LockFreeObservableCache source) + where TObject : notnull + where TKey : notnull { } + public static void Clear(this DynamicData.IIntermediateCache source) + where TObject : notnull + where TKey : notnull { } + public static void Clear(this DynamicData.ISourceCache source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Clone(this System.IObservable> source, System.Collections.Generic.ICollection target) + where TObject : notnull + where TKey : notnull { } + [System.Obsolete("This was an experiment that did not work. Use Transform instead")] + public static System.IObservable> Convert(this System.IObservable> source, System.Func conversionFactory) + where TObject : notnull + where TKey : notnull + where TDestination : notnull { } + public static System.IObservable> DeferUntilLoaded(this DynamicData.IObservableCache source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> DeferUntilLoaded(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> DisposeMany(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> DistinctValues(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TKey : notnull + where TValue : notnull { } + public static void EditDiff(this DynamicData.ISourceCache source, System.Collections.Generic.IEnumerable allItems, System.Collections.Generic.IEqualityComparer equalityComparer) + where TObject : notnull + where TKey : notnull { } + public static void EditDiff(this DynamicData.ISourceCache source, System.Collections.Generic.IEnumerable allItems, System.Func areItemsEqual) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> EditDiff(this System.IObservable> source, System.Func keySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> EditDiff(this System.IObservable> source, System.Func keySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> EnsureUniqueKeys(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Except(this DynamicData.IObservableList> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Except(this DynamicData.IObservableList> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Except(this DynamicData.IObservableList>> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Except(this System.Collections.Generic.ICollection>> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Except(this System.IObservable> source, params System.IObservable>[] others) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ExpireAfter(this System.IObservable> source, System.Func timeSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ExpireAfter(this System.IObservable> source, System.Func timeSelector, ReactiveUI.Primitives.Concurrency.ISequencer scheduler) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ExpireAfter(this System.IObservable> source, System.Func timeSelector, System.TimeSpan? pollingInterval) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> ExpireAfter(this DynamicData.ISourceCache source, System.Func timeSelector, System.TimeSpan? pollingInterval = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ExpireAfter(this System.IObservable> source, System.Func timeSelector, System.TimeSpan? pollingInterval, ReactiveUI.Primitives.Concurrency.ISequencer scheduler) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Filter(this System.IObservable> source, System.Func filter, bool suppressEmptyChangeSets = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Filter(this System.IObservable> source, System.IObservable> predicateChanged, bool suppressEmptyChangeSets = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Filter(this System.IObservable> source, System.IObservable> predicateChanged, System.IObservable reapplyFilter, bool suppressEmptyChangeSets = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Filter(this System.IObservable> source, System.IObservable predicateState, System.Func predicate, bool suppressEmptyChangeSets = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> FilterImmutable(this System.IObservable> source, System.Func predicate, bool suppressEmptyChangeSets = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> FilterOnObservable(this System.IObservable> source, System.Func> filterFactory, System.TimeSpan? buffer = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> FilterOnObservable(this System.IObservable> source, System.Func> filterFactory, System.TimeSpan? buffer = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : notnull + where TKey : notnull { } + [System.Obsolete("This can cause unhandled exception issues so do not use")] + public static System.IObservable FinallySafe(this System.IObservable source, System.Action finallyAction) { } + public static System.IObservable> Flatten(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> FlattenBufferResult(this System.IObservable>> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ForEachChange(this System.IObservable> source, System.Action> action) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> FullJoin(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, ReactiveUI.Primitives.Optional, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> FullJoin(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, ReactiveUI.Primitives.Optional, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> FullJoinMany(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, DynamicData.IGrouping, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> FullJoinMany(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, DynamicData.IGrouping, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> Group(this System.IObservable> source, System.Func groupSelectorKey) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> Group(this System.IObservable> source, System.Func groupSelector, System.IObservable> resultGroupSource) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> Group(this System.IObservable> source, System.Func groupSelectorKey, System.IObservable regrouper) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> Group(this System.IObservable> source, System.IObservable> groupSelectorKeyObservable, System.IObservable? regrouper = null) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> Group(this System.IObservable> source, System.IObservable> groupSelectorKeyObservable, System.IObservable? regrouper = null) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> GroupOnObservable(this System.IObservable> source, System.Func> groupObservableSelector) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> GroupOnObservable(this System.IObservable> source, System.Func> groupObservableSelector) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> GroupOnProperty(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.TimeSpan? propertyChangedThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : System.ComponentModel.INotifyPropertyChanged + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> GroupOnPropertyWithImmutableState(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.TimeSpan? propertyChangedThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : System.ComponentModel.INotifyPropertyChanged + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> GroupWithImmutableState(this System.IObservable> source, System.Func groupSelectorKey, System.IObservable? regrouper = null) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> IgnoreSameReferenceUpdate(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> IgnoreUpdateWhen(this System.IObservable> source, System.Func ignoreFunction) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> IncludeUpdateWhen(this System.IObservable> source, System.Func includeFunction) + where TObject : notnull + where TKey : notnull { } + [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { + "leftKey", + "rightKey"})] + public static System.IObservable>> InnerJoin(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { + "leftKey", + "rightKey"})] + public static System.IObservable>> InnerJoin(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, [System.Runtime.CompilerServices.TupleElementNames(new string[] { + "leftKey", + "rightKey"})] System.Func, TLeft, TRight, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> InnerJoinMany(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> InnerJoinMany(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> InvokeEvaluate(this System.IObservable> source) + where TObject : DynamicData.Binding.IEvaluateAware + where TKey : notnull { } + public static System.IObservable> LeftJoin(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> LeftJoin(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> LeftJoinMany(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> LeftJoinMany(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> LimitSizeTo(this System.IObservable> source, int size) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> LimitSizeTo(this DynamicData.ISourceCache source, int sizeLimit, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable>> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable>> source, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable>> source, System.Collections.Generic.IEqualityComparer equalityComparer) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable>> source, System.Collections.Generic.IEqualityComparer equalityComparer, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, System.Collections.Generic.IComparer comparer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, System.Collections.Generic.IEqualityComparer equalityComparer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, System.Collections.Generic.IEqualityComparer equalityComparer, System.Collections.Generic.IComparer comparer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, System.Collections.Generic.IComparer comparer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, System.Collections.Generic.IEqualityComparer equalityComparer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, System.Collections.Generic.IComparer comparer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, System.Collections.Generic.IEqualityComparer equalityComparer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, System.Collections.Generic.IEqualityComparer equalityComparer, System.Collections.Generic.IComparer comparer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, System.Collections.Generic.IEqualityComparer equalityComparer, System.Collections.Generic.IComparer comparer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable MergeMany(this System.IObservable> source, System.Func> observableSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable MergeMany(this System.IObservable> source, System.Func> observableSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull + where TKey : notnull + where TDestination : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull + where TKey : notnull + where TDestination : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer sourceComparer, System.Collections.Generic.IComparer childComparer) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer sourceComparer, System.Collections.Generic.IComparer childComparer) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer sourceComparer, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? childComparer = null) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer sourceComparer, bool resortOnSourceRefresh, System.Collections.Generic.IComparer childComparer) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer sourceComparer, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? childComparer = null) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer sourceComparer, bool resortOnSourceRefresh, System.Collections.Generic.IComparer childComparer) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer sourceComparer, bool resortOnSourceRefresh, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? childComparer = null) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer sourceComparer, bool resortOnSourceRefresh, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? childComparer = null) + where TObject : notnull + where TKey : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyItems(this System.IObservable> source, System.Func> observableSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeManyItems(this System.IObservable> source, System.Func> observableSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable MonitorStatus(this System.IObservable source) { } + public static System.IObservable> NotEmpty(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> OfType(this System.IObservable> source, bool suppressEmptyChangeSets = true) + where TObject : notnull + where TKey : notnull + where TDestination : notnull { } + public static System.IObservable> OnItemAdded(this System.IObservable> source, System.Action addAction) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> OnItemAdded(this System.IObservable> source, System.Action addAction) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> OnItemRefreshed(this System.IObservable> source, System.Action refreshAction) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> OnItemRefreshed(this System.IObservable> source, System.Action refreshAction) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> OnItemRemoved(this System.IObservable> source, System.Action removeAction, bool invokeOnUnsubscribe = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> OnItemRemoved(this System.IObservable> source, System.Action removeAction, bool invokeOnUnsubscribe = true) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> OnItemUpdated(this System.IObservable> source, System.Action updateAction) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> OnItemUpdated(this System.IObservable> source, System.Action updateAction) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Or(this DynamicData.IObservableList> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Or(this DynamicData.IObservableList> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Or(this DynamicData.IObservableList>> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Or(this System.Collections.Generic.ICollection>> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Or(this System.IObservable> source, params System.IObservable>[] others) + where TObject : notnull + where TKey : notnull { } + [System.Obsolete("Use SortAndPage as it\'s more efficient")] + public static System.IObservable> Page(this System.IObservable> source, System.IObservable pageRequests) + where TObject : notnull + where TKey : notnull { } + public static System.IDisposable PopulateFrom(this DynamicData.ISourceCache source, System.IObservable> observable) + where TObject : notnull + where TKey : notnull { } + public static System.IDisposable PopulateFrom(this DynamicData.ISourceCache source, System.IObservable observable) + where TObject : notnull + where TKey : notnull { } + public static System.IDisposable PopulateInto(this System.IObservable> source, DynamicData.Cache.Internal.LockFreeObservableCache destination) + where TObject : notnull + where TKey : notnull { } + public static System.IDisposable PopulateInto(this System.IObservable> source, DynamicData.IIntermediateCache destination) + where TObject : notnull + where TKey : notnull { } + public static System.IDisposable PopulateInto(this System.IObservable> source, DynamicData.ISourceCache destination) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> QueryWhenChanged(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable QueryWhenChanged(this System.IObservable> source, System.Func, TDestination> resultSelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> QueryWhenChanged(this System.IObservable> source, System.Func> itemChangedTrigger) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> RefCount(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static void Refresh(this DynamicData.ISourceCache source) + where TObject : notnull + where TKey : notnull { } + public static void Refresh(this DynamicData.ISourceCache source, System.Collections.Generic.IEnumerable items) + where TObject : notnull + where TKey : notnull { } + public static void Refresh(this DynamicData.ISourceCache source, TObject item) + where TObject : notnull + where TKey : notnull { } + public static void Remove(this DynamicData.IIntermediateCache source, System.Collections.Generic.IEnumerable keys) + where TObject : notnull + where TKey : notnull { } + public static void Remove(this DynamicData.IIntermediateCache source, TKey key) + where TObject : notnull + where TKey : notnull { } + public static void Remove(this DynamicData.ISourceCache source, System.Collections.Generic.IEnumerable keys) + where TObject : notnull + where TKey : notnull { } + public static void Remove(this DynamicData.ISourceCache source, System.Collections.Generic.IEnumerable items) + where TObject : notnull + where TKey : notnull { } + public static void Remove(this DynamicData.ISourceCache source, TKey key) + where TObject : notnull + where TKey : notnull { } + public static void Remove(this DynamicData.ISourceCache source, TObject item) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> RemoveKey(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static void RemoveKey(this DynamicData.ISourceCache source, TKey key) + where TObject : notnull + where TKey : notnull { } + public static void RemoveKeys(this DynamicData.ISourceCache source, System.Collections.Generic.IEnumerable keys) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> RightJoin(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, TRight, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> RightJoin(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, TRight, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> RightJoinMany(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, DynamicData.IGrouping, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> RightJoinMany(this System.IObservable> left, System.IObservable> right, System.Func rightKeySelector, System.Func, DynamicData.IGrouping, TDestination> resultSelector) + where TLeft : notnull + where TLeftKey : notnull + where TRight : notnull + where TRightKey : notnull + where TDestination : notnull { } + public static System.IObservable> SkipInitial(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + [System.Obsolete("Use SortAndBind as it\'s more efficient")] + public static System.IObservable> Sort(this System.IObservable> source, System.Collections.Generic.IComparer comparer, DynamicData.SortOptimisations sortOptimisations = 0, int resetThreshold = 100) + where TObject : notnull + where TKey : notnull { } + [System.Obsolete("Use SortAndBind as it\'s more efficient")] + public static System.IObservable> Sort(this System.IObservable> source, System.IObservable> comparerObservable, DynamicData.SortOptimisations sortOptimisations = 0, int resetThreshold = 100) + where TObject : notnull + where TKey : notnull { } + [System.Obsolete("Use SortAndBind as it\'s more efficient")] + public static System.IObservable> Sort(this System.IObservable> source, System.Collections.Generic.IComparer comparer, System.IObservable resorter, DynamicData.SortOptimisations sortOptimisations = 0, int resetThreshold = 100) + where TObject : notnull + where TKey : notnull { } + [System.Obsolete("Use SortAndBind as it\'s more efficient")] + public static System.IObservable> Sort(this System.IObservable> source, System.IObservable> comparerObservable, System.IObservable resorter, DynamicData.SortOptimisations sortOptimisations = 0, int resetThreshold = 100) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, System.Collections.Generic.IList targetList) + where TObject : notnull, System.IComparable + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection) + where TObject : notnull, System.IComparable + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, System.Collections.Generic.IList targetList, DynamicData.Binding.SortAndBindOptions options) + where TObject : notnull, System.IComparable + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, System.Collections.Generic.IList targetList, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, System.Collections.Generic.IList targetList, System.IObservable> comparerChanged) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, DynamicData.Binding.SortAndBindOptions options) + where TObject : notnull, System.IComparable + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, System.IObservable> comparerChanged) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, System.Collections.Generic.IList targetList, System.Collections.Generic.IComparer comparer, DynamicData.Binding.SortAndBindOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, System.Collections.Generic.IList targetList, System.IObservable> comparerChanged, DynamicData.Binding.SortAndBindOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, System.Collections.Generic.IComparer comparer, DynamicData.Binding.SortAndBindOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, System.IObservable> comparerChanged, DynamicData.Binding.SortAndBindOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> SortAndPage(this System.IObservable> source, System.Collections.Generic.IComparer comparer, System.IObservable pageRequests) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> SortAndPage(this System.IObservable> source, System.IObservable> comparerChanged, System.IObservable pageRequests) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> SortAndPage(this System.IObservable> source, System.Collections.Generic.IComparer comparer, System.IObservable pageRequests, DynamicData.SortAndPageOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> SortAndPage(this System.IObservable> source, System.IObservable> comparerChanged, System.IObservable pageRequests, DynamicData.SortAndPageOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> SortAndVirtualize(this System.IObservable> source, System.Collections.Generic.IComparer comparer, System.IObservable virtualRequests) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> SortAndVirtualize(this System.IObservable> source, System.IObservable> comparerChanged, System.IObservable virtualRequests) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> SortAndVirtualize(this System.IObservable> source, System.Collections.Generic.IComparer comparer, System.IObservable virtualRequests, DynamicData.SortAndVirtualizeOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> SortAndVirtualize(this System.IObservable> source, System.IObservable> comparerChanged, System.IObservable virtualRequests, DynamicData.SortAndVirtualizeOptions options) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SortBy(this System.IObservable> source, System.Func expression, DynamicData.Binding.SortDirection sortOrder = 0, DynamicData.SortOptimisations sortOptimisations = 0, int resetThreshold = 100) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> StartWithEmpty(this System.IObservable> source) { } + public static System.IObservable> StartWithEmpty(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> StartWithEmpty(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> StartWithEmpty(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> StartWithEmpty(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> StartWithEmpty(this System.IObservable> source) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> StartWithEmpty(this System.IObservable> source) + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull { } + public static System.IObservable> StartWithItem(this System.IObservable> source, TObject item) + where TObject : DynamicData.IKey + where TKey : notnull { } + public static System.IObservable> StartWithItem(this System.IObservable> source, TObject item, TKey key) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SubscribeMany(this System.IObservable> source, System.Func subscriptionFactory) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SubscribeMany(this System.IObservable> source, System.Func subscriptionFactory) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SuppressRefresh(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Switch(this System.IObservable> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Switch(this System.IObservable>> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ToCollection(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, System.Func keySelector, System.Func? expireAfter = null, int limitSizeTo = -1, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable source, System.Func keySelector, System.Func? expireAfter = null, int limitSizeTo = -1, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ToObservableOptional(this System.IObservable> source, TKey key, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ToObservableOptional(this System.IObservable> source, TKey key, bool initialOptionalWhenMissing, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ToSortedCollection(this System.IObservable> source, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> ToSortedCollection(this System.IObservable> source, System.Func sort, DynamicData.Binding.SortDirection sortOrder = 0) + where TObject : notnull + where TKey : notnull + where TSortKey : notnull { } + [System.Obsolete("Use Overload with comparer as it\'s more efficient")] + public static System.IObservable> Top(this System.IObservable> source, int size) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable>> Top(this System.IObservable> source, System.Collections.Generic.IComparer comparer, int size) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, bool transformOnRefresh) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, bool transformOnRefresh) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func, TKey, TDestination> transformFactory, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func, TKey, TDestination> transformFactory, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func, TKey, TDestination> transformFactory, bool transformOnRefresh) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func> transformFactory, DynamicData.TransformAsyncOptions options) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func> transformFactory, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func> transformFactory, DynamicData.TransformAsyncOptions options) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func> transformFactory, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func, TKey, System.Threading.Tasks.Task> transformFactory, DynamicData.TransformAsyncOptions options) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func, TKey, System.Threading.Tasks.Task> transformFactory, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformImmutable(this System.IObservable> source, System.Func transformFactory) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformMany(this System.IObservable> source, System.Func> manySelector, System.Func keySelector) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformMany(this System.IObservable> source, System.Func> manySelector, System.Func keySelector) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformMany(this System.IObservable> source, System.Func> manySelector, System.Func keySelector) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformMany(this System.IObservable> source, System.Func> manySelector, System.Func keySelector) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformManyAsync(this System.IObservable> source, System.Func>> manySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformManyAsync(this System.IObservable> source, System.Func>> manySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformManyAsync(this System.IObservable> source, System.Func>> manySelector, System.Func keySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformManyAsync(this System.IObservable> source, System.Func>> manySelector, System.Func keySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformManyAsync(this System.IObservable> source, System.Func> manySelector, System.Func keySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull + where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } + public static System.IObservable> TransformManyAsync(this System.IObservable> source, System.Func> manySelector, System.Func keySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull + where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } + public static System.IObservable> TransformManySafeAsync(this System.IObservable> source, System.Func>> manySelector, System.Action> errorHandler, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformManySafeAsync(this System.IObservable> source, System.Func>> manySelector, System.Action> errorHandler, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformManySafeAsync(this System.IObservable> source, System.Func>> manySelector, System.Func keySelector, System.Action> errorHandler, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformManySafeAsync(this System.IObservable> source, System.Func>> manySelector, System.Func keySelector, System.Action> errorHandler, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull { } + public static System.IObservable> TransformManySafeAsync(this System.IObservable> source, System.Func> manySelector, System.Func keySelector, System.Action> errorHandler, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull + where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } + public static System.IObservable> TransformManySafeAsync(this System.IObservable> source, System.Func> manySelector, System.Func keySelector, System.Action> errorHandler, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull + where TCollection : System.Collections.Specialized.INotifyCollectionChanged, System.Collections.Generic.IEnumerable { } + public static System.IObservable> TransformOnObservable(this System.IObservable> source, System.Func> transformFactory) + where TSource : notnull + where TKey : notnull + where TDestination : notnull { } + public static System.IObservable> TransformOnObservable(this System.IObservable> source, System.Func> transformFactory) + where TSource : notnull + where TKey : notnull + where TDestination : notnull { } + public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func, TKey, TDestination> transformFactory, System.Action> errorHandler, System.IObservable forceTransform) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func, TKey, TDestination> transformFactory, System.Action> errorHandler, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafeAsync(this System.IObservable> source, System.Func> transformFactory, System.Action> errorHandler, DynamicData.TransformAsyncOptions options) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafeAsync(this System.IObservable> source, System.Func> transformFactory, System.Action> errorHandler, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafeAsync(this System.IObservable> source, System.Func> transformFactory, System.Action> errorHandler, DynamicData.TransformAsyncOptions options) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafeAsync(this System.IObservable> source, System.Func> transformFactory, System.Action> errorHandler, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafeAsync(this System.IObservable> source, System.Func, TKey, System.Threading.Tasks.Task> transformFactory, System.Action> errorHandler, DynamicData.TransformAsyncOptions options) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafeAsync(this System.IObservable> source, System.Func, TKey, System.Threading.Tasks.Task> transformFactory, System.Action> errorHandler, System.IObservable>? forceTransform = null) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable, TKey>> TransformToTree(this System.IObservable> source, System.Func pivotOn, System.IObservable, bool>>? predicateChanged = null) + where TObject : class + where TKey : notnull { } + public static System.IObservable> TransformWithInlineUpdate(this System.IObservable> source, System.Func transformFactory, System.Action updateAction) + where TDestination : class + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformWithInlineUpdate(this System.IObservable> source, System.Func transformFactory, System.Action updateAction, System.Action> errorHandler) + where TDestination : class + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformWithInlineUpdate(this System.IObservable> source, System.Func transformFactory, System.Action updateAction, bool transformOnRefresh) + where TDestination : class + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformWithInlineUpdate(this System.IObservable> source, System.Func transformFactory, System.Action updateAction, System.Action> errorHandler, bool transformOnRefresh) + where TDestination : class + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TreatMovesAsRemoveAdd(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable TrueForAll(this System.IObservable> source, System.Func> observableSelector, System.Func equalityCondition) + where TObject : notnull + where TKey : notnull + where TValue : notnull { } + public static System.IObservable TrueForAll(this System.IObservable> source, System.Func> observableSelector, System.Func equalityCondition) + where TObject : notnull + where TKey : notnull + where TValue : notnull { } + public static System.IObservable TrueForAny(this System.IObservable> source, System.Func> observableSelector, System.Func equalityCondition) + where TObject : notnull + where TKey : notnull + where TValue : notnull { } + public static System.IObservable TrueForAny(this System.IObservable> source, System.Func> observableSelector, System.Func equalityCondition) + where TObject : notnull + where TKey : notnull + where TValue : notnull { } + public static System.IObservable> UpdateIndex(this System.IObservable> source) + where TObject : DynamicData.Binding.IIndexAware + where TKey : notnull { } + [System.Obsolete("Use SortAndVirtualize as it\'s more efficient")] + public static System.IObservable> Virtualise(this System.IObservable> source, System.IObservable virtualRequests) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Watch(this System.IObservable> source, TKey key) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable WatchValue(this DynamicData.IObservableCache source, TKey key) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable WatchValue(this System.IObservable> source, TKey key) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable WhenAnyPropertyChanged(this System.IObservable> source, params string[] propertiesToMonitor) + where TObject : System.ComponentModel.INotifyPropertyChanged + where TKey : notnull { } + public static System.IObservable> WhenPropertyChanged(this System.IObservable> source, System.Linq.Expressions.Expression> propertyAccessor, bool notifyOnInitialValue = true) + where TObject : System.ComponentModel.INotifyPropertyChanged + where TKey : notnull { } + public static System.IObservable WhenValueChanged(this System.IObservable> source, System.Linq.Expressions.Expression> propertyAccessor, bool notifyOnInitialValue = true) + where TObject : System.ComponentModel.INotifyPropertyChanged + where TKey : notnull { } + public static System.IObservable> WhereReasonsAre(this System.IObservable> source, params DynamicData.ChangeReason[] reasons) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> WhereReasonsAreNot(this System.IObservable> source, params DynamicData.ChangeReason[] reasons) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Xor(this DynamicData.IObservableList> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Xor(this DynamicData.IObservableList> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Xor(this DynamicData.IObservableList>> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Xor(this System.Collections.Generic.ICollection>> sources) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Xor(this System.IObservable> source, params System.IObservable>[] others) + where TObject : notnull + where TKey : notnull { } + } + public static class ObservableChangeSet + { + public static System.IObservable> Create(System.Func, System.Action> subscribe) + where T : notnull { } + public static System.IObservable> Create(System.Func, System.IDisposable> subscribe) + where T : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.Tasks.Task> subscribe) + where T : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.Tasks.Task> subscribe) + where T : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.Tasks.Task> subscribe) + where T : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.CancellationToken, System.Threading.Tasks.Task> subscribe) + where T : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.CancellationToken, System.Threading.Tasks.Task> subscribe) + where T : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.CancellationToken, System.Threading.Tasks.Task> subscribe) + where T : notnull { } + public static System.IObservable> Create(System.Func, System.Action> subscribe, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Create(System.Func, System.IDisposable> subscribe, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.Tasks.Task> subscribe, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.Tasks.Task> subscribe, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.Tasks.Task> subscribe, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.CancellationToken, System.Threading.Tasks.Task> subscribe, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.CancellationToken, System.Threading.Tasks.Task> subscribe, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Create(System.Func, System.Threading.CancellationToken, System.Threading.Tasks.Task> subscribe, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + } + public static class ObservableListEx + { + public static System.IObservable> Adapt(this System.IObservable> source, DynamicData.IChangeSetAdaptor adaptor) + where T : notnull { } + public static System.IObservable> AddKey(this System.IObservable> source, System.Func keySelector) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> And(this DynamicData.IObservableList> sources) + where T : notnull { } + public static System.IObservable> And(this DynamicData.IObservableList> sources) + where T : notnull { } + public static System.IObservable> And(this DynamicData.IObservableList>> sources) + where T : notnull { } + public static System.IObservable> And(this System.Collections.Generic.ICollection>> sources) + where T : notnull { } + public static System.IObservable> And(this System.IObservable> source, params System.IObservable>[] others) + where T : notnull { } + public static DynamicData.IObservableList AsObservableList(this DynamicData.ISourceList source) + where T : notnull { } + public static DynamicData.IObservableList AsObservableList(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> AutoRefresh(this System.IObservable> source, System.TimeSpan? changeSetBuffer = default, System.TimeSpan? propertyChangeThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable> AutoRefresh(this System.IObservable> source, System.Linq.Expressions.Expression> propertyAccessor, System.TimeSpan? changeSetBuffer = default, System.TimeSpan? propertyChangeThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable> AutoRefreshOnObservable(this System.IObservable> source, System.Func> reevaluator, System.TimeSpan? changeSetBuffer = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, DynamicData.Binding.IObservableCollection targetCollection, DynamicData.Binding.BindingOptions options) + where T : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, DynamicData.Binding.IObservableCollection targetCollection, int resetThreshold = 25) + where T : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, DynamicData.Binding.BindingOptions options) + where T : notnull { } + public static System.IObservable> Bind(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, int resetThreshold = 25) + where T : notnull { } + public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] T>(this System.IObservable> source, System.ComponentModel.BindingList bindingList, int resetThreshold = 25) + where T : notnull { } + public static System.IObservable> BufferIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where T : notnull { } + public static System.IObservable> BufferIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, System.TimeSpan? timeOut, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where T : notnull { } + public static System.IObservable> BufferIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where T : notnull { } + public static System.IObservable> BufferIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState, System.TimeSpan? timeOut, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where T : notnull { } + public static System.IObservable> BufferInitial(this System.IObservable> source, System.TimeSpan initialBuffer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : notnull { } + public static System.IObservable> Cast(this System.IObservable> source) + where TDestination : notnull { } + public static System.IObservable> Cast(this System.IObservable> source, System.Func conversionFactory) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> CastToObject(this System.IObservable> source) + where T : class { } + public static System.IObservable> Clone(this System.IObservable> source, System.Collections.Generic.IList target) + where T : notnull { } + [System.Obsolete("Prefer Cast as it is does the same thing but is semantically correct")] + public static System.IObservable> Convert(this System.IObservable> source, System.Func conversionFactory) + where TObject : notnull + where TDestination : notnull { } + public static System.IObservable> DeferUntilLoaded(this DynamicData.IObservableList source) + where T : notnull { } + public static System.IObservable> DeferUntilLoaded(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> DisposeMany(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> DistinctValues(this System.IObservable> source, System.Func valueSelector) + where TObject : notnull + where TValue : notnull { } + public static System.IObservable> Except(this DynamicData.IObservableList> sources) + where T : notnull { } + public static System.IObservable> Except(this DynamicData.IObservableList> sources) + where T : notnull { } + public static System.IObservable> Except(this DynamicData.IObservableList>> sources) + where T : notnull { } + public static System.IObservable> Except(this System.Collections.Generic.ICollection>> sources) + where T : notnull { } + public static System.IObservable> Except(this System.IObservable> source, params System.IObservable>[] others) + where T : notnull { } + public static System.IObservable> ExpireAfter(this DynamicData.ISourceList source, System.Func timeSelector, System.TimeSpan? pollingInterval = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where T : notnull { } + public static System.IObservable> Filter(this System.IObservable> source, System.Func predicate) + where T : notnull { } + public static System.IObservable> Filter(this System.IObservable> source, System.IObservable> predicate, DynamicData.ListFilterPolicy filterPolicy = 1) + where T : notnull { } + public static System.IObservable> Filter(this System.IObservable> source, System.IObservable predicateState, System.Func predicate, DynamicData.ListFilterPolicy filterPolicy = 1, bool suppressEmptyChangeSets = true) + where T : notnull { } + public static System.IObservable> FilterOnObservable(this System.IObservable> source, System.Func> objectFilterObservable, System.TimeSpan? propertyChangedThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : notnull { } + [System.Obsolete("Use AutoRefresh(), followed by Filter() instead")] + public static System.IObservable> FilterOnProperty(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.Func predicate, System.TimeSpan? propertyChangedThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable> FlattenBufferResult(this System.IObservable>> source) + where T : notnull { } + public static System.IObservable> ForEachChange(this System.IObservable> source, System.Action> action) + where TObject : notnull { } + public static System.IObservable> ForEachItemChange(this System.IObservable> source, System.Action> action) + where TObject : notnull { } + public static System.IObservable>> GroupOn(this System.IObservable> source, System.Func groupSelector, System.IObservable? regrouper = null) + where TObject : notnull + where TGroup : notnull { } + public static System.IObservable>> GroupOnProperty(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.TimeSpan? propertyChangedThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : System.ComponentModel.INotifyPropertyChanged + where TGroup : notnull { } + public static System.IObservable>> GroupOnPropertyWithImmutableState(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.TimeSpan? propertyChangedThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : System.ComponentModel.INotifyPropertyChanged + where TGroup : notnull { } + public static System.IObservable>> GroupWithImmutableState(this System.IObservable> source, System.Func groupSelectorKey, System.IObservable? regrouper = null) + where TObject : notnull + where TGroupKey : notnull { } + public static System.IObservable> LimitSizeTo(this DynamicData.ISourceList source, int sizeLimit, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where T : notnull { } + public static System.IObservable> MergeChangeSets(this DynamicData.IObservableList>> source, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable>>> source, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable>> source, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull { } + public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, System.Collections.Generic.IEqualityComparer? equalityComparer = null, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) + where TObject : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, System.Collections.Generic.IEqualityComparer? equalityComparer = null, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) + where TObject : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, System.Collections.Generic.IEqualityComparer? equalityComparer = null, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) + where TObject : notnull { } + public static System.IObservable> MergeChangeSets(this DynamicData.IObservableList>> source, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable>>> source, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this DynamicData.IObservableList>> source, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> MergeChangeSets(this System.IObservable>>> source, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable MergeMany(this System.IObservable> source, System.Func> observableSelector) + where T : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TObject : notnull + where TDestination : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IComparer comparer) + where TObject : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> MergeManyChangeSets(this System.IObservable> source, System.Func>> observableSelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Collections.Generic.IComparer? comparer = null) + where TObject : notnull + where TDestination : notnull + where TDestinationKey : notnull { } + public static System.IObservable> NotEmpty(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> OnItemAdded(this System.IObservable> source, System.Action addAction) + where T : notnull { } + public static System.IObservable> OnItemRefreshed(this System.IObservable> source, System.Action refreshAction) + where T : notnull { } + public static System.IObservable> OnItemRemoved(this System.IObservable> source, System.Action removeAction, bool invokeOnUnsubscribe = true) + where T : notnull { } + public static System.IObservable> Or(this DynamicData.IObservableList> sources) + where T : notnull { } + public static System.IObservable> Or(this DynamicData.IObservableList> sources) + where T : notnull { } + public static System.IObservable> Or(this DynamicData.IObservableList>> sources) + where T : notnull { } + public static System.IObservable> Or(this System.Collections.Generic.ICollection>> sources) + where T : notnull { } + public static System.IObservable> Or(this System.IObservable> source, params System.IObservable>[] others) + where T : notnull { } + public static System.IObservable> Page(this System.IObservable> source, System.IObservable requests) + where T : notnull { } + public static System.IDisposable PopulateInto(this System.IObservable> source, DynamicData.ISourceList destination) + where T : notnull { } + public static System.IObservable> QueryWhenChanged(this System.IObservable> source) + where T : notnull { } + public static System.IObservable QueryWhenChanged(this System.IObservable> source, System.Func, TDestination> resultSelector) + where TObject : notnull { } + public static System.IObservable> RefCount(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> RemoveIndex(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> Reverse(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> SkipInitial(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> Sort(this System.IObservable> source, System.IObservable> comparerChanged, DynamicData.SortOptions options = 0, System.IObservable? resort = null, int resetThreshold = 50) + where T : notnull { } + public static System.IObservable> Sort(this System.IObservable> source, System.Collections.Generic.IComparer comparer, DynamicData.SortOptions options = 0, System.IObservable? resort = null, System.IObservable>? comparerChanged = null, int resetThreshold = 50) + where T : notnull { } + public static System.IObservable> StartWithEmpty(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> SubscribeMany(this System.IObservable> source, System.Func subscriptionFactory) + where T : notnull { } + public static System.IObservable> SuppressRefresh(this System.IObservable> source) + where T : notnull { } + public static System.IObservable> Switch(this System.IObservable> sources) + where T : notnull { } + public static System.IObservable> Switch(this System.IObservable>> sources) + where T : notnull { } + public static System.IObservable> ToCollection(this System.IObservable> source) + where TObject : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable source, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, System.Func expireAfter, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, int limitSizeTo, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable source, System.Func expireAfter, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable source, int limitSizeTo, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, System.Func? expireAfter, int limitSizeTo, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where T : notnull { } + public static System.IObservable> ToObservableChangeSet(this System.IObservable source, System.Func? expireAfter, int limitSizeTo, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where T : notnull { } + public static System.IObservable> ToSortedCollection(this System.IObservable> source, System.Collections.Generic.IComparer comparer) + where TObject : notnull { } + public static System.IObservable> ToSortedCollection(this System.IObservable> source, System.Func sort, DynamicData.Binding.SortDirection sortOrder = 0) + where TObject : notnull { } + public static System.IObservable> Top(this System.IObservable> source, int numberOfItems) + where T : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, bool transformOnRefresh = false) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func, TDestination> transformFactory, bool transformOnRefresh = false) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, bool transformOnRefresh = false) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func, int, TDestination> transformFactory, bool transformOnRefresh = false) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func> transformFactory, bool transformOnRefresh = false) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func, System.Threading.Tasks.Task> transformFactory, bool transformOnRefresh = false) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func> transformFactory, bool transformOnRefresh = false) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> TransformAsync(this System.IObservable> source, System.Func, int, System.Threading.Tasks.Task> transformFactory, bool transformOnRefresh = false) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> TransformMany(this System.IObservable> source, System.Func> manySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TDestination : notnull + where TSource : notnull { } + public static System.IObservable> TransformMany(this System.IObservable> source, System.Func> manySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TDestination : notnull + where TSource : notnull { } + public static System.IObservable> TransformMany(this System.IObservable> source, System.Func> manySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TDestination : notnull + where TSource : notnull { } + public static System.IObservable> TransformMany(this System.IObservable> source, System.Func> manySelector, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where TDestination : notnull + where TSource : notnull { } + public static System.IObservable> Virtualise(this System.IObservable> source, System.IObservable requests) + where T : notnull { } + public static System.IObservable WhenAnyPropertyChanged(this System.IObservable> source, params string[] propertiesToMonitor) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable> WhenPropertyChanged(this System.IObservable> source, System.Linq.Expressions.Expression> propertyAccessor, bool notifyOnInitialValue = true) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable WhenValueChanged(this System.IObservable> source, System.Linq.Expressions.Expression> propertyAccessor, bool notifyOnInitialValue = true) + where TObject : System.ComponentModel.INotifyPropertyChanged { } + public static System.IObservable> WhereReasonsAre(this System.IObservable> source, params DynamicData.ListChangeReason[] reasons) + where T : notnull { } + public static System.IObservable> WhereReasonsAreNot(this System.IObservable> source, params DynamicData.ListChangeReason[] reasons) + where T : notnull { } + public static System.IObservable> Xor(this DynamicData.IObservableList> sources) + where T : notnull { } + public static System.IObservable> Xor(this DynamicData.IObservableList> sources) + where T : notnull { } + public static System.IObservable> Xor(this DynamicData.IObservableList>> sources) + where T : notnull { } + public static System.IObservable> Xor(this System.Collections.Generic.ICollection>> sources) + where T : notnull { } + public static System.IObservable> Xor(this System.IObservable> source, params System.IObservable>[] others) + where T : notnull { } + } + public class PageContext : System.IEquatable> + { + public PageContext(DynamicData.Operators.IPageResponse Response, System.Collections.Generic.IComparer Comparer, DynamicData.SortAndPageOptions Options) { } + public System.Collections.Generic.IComparer Comparer { get; init; } + public DynamicData.SortAndPageOptions Options { get; init; } + public DynamicData.Operators.IPageResponse Response { get; init; } + } + public sealed class PageRequest : DynamicData.IPageRequest, System.IEquatable + { + public static readonly DynamicData.IPageRequest Default; + public static readonly DynamicData.IPageRequest Empty; + public PageRequest() { } + public PageRequest(int page, int size) { } + public int Page { get; } + public int Size { get; } + public static System.Collections.Generic.IEqualityComparer DefaultComparer { get; } + public bool Equals(DynamicData.IPageRequest? other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + } + public sealed class RangeChange : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable + { + public RangeChange(System.Collections.Generic.IEnumerable items, int index = -1) { } + public int Count { get; } + public int Index { get; } + public static DynamicData.RangeChange Empty { get; } + public void Add(T item) { } + public System.Collections.Generic.IEnumerator GetEnumerator() { } + public void Insert(int index, T item) { } + public void SetStartingIndex(int index) { } + public override string ToString() { } + } + public readonly struct SortAndPageOptions : System.IEquatable + { + public SortAndPageOptions() { } + public int InitialCapacity { get; init; } + public int ResetThreshold { get; init; } + public bool UseBinarySearch { get; init; } + } + public readonly struct SortAndVirtualizeOptions : System.IEquatable + { + public SortAndVirtualizeOptions() { } + public int InitialCapacity { get; init; } + public int ResetThreshold { get; init; } + public bool UseBinarySearch { get; init; } + } + [System.Serializable] + public class SortException : System.Exception + { + public SortException() { } + public SortException(string message) { } + public SortException(string message, System.Exception innerException) { } + } + [System.Flags] + public enum SortOptimisations + { + None = 0, + ComparesImmutableValuesOnly = 1, + IgnoreEvaluates = 2, + [System.Obsolete("This is no longer being used. Use one of the other options instead.")] + InsertAtEndThenSort = 3, + } + public enum SortOptions + { + None = 0, + UseBinarySearch = 1, + } + public enum SortReason + { + InitialLoad = 0, + ComparerChanged = 1, + DataChanged = 2, + Reorder = 3, + Reset = 4, + } + public static class SourceCacheEx + { + public static System.IObservable> Cast(this DynamicData.IObservableCache source, System.Func converter) + where TSource : notnull + where TKey : notnull + where TDestination : notnull { } + } + [System.Diagnostics.DebuggerDisplay("SourceCache<{typeof(TObject).Name}, {typeof(TKey).Name}> ({Count} Items)")] + public sealed class SourceCache : DynamicData.Binding.INotifyCollectionChangedSuspender, DynamicData.IConnectableCache, DynamicData.IObservableCache, DynamicData.ISourceCache, System.IDisposable + where TObject : notnull + where TKey : notnull + { + public SourceCache(System.Func keySelector) { } + public int Count { get; } + public System.IObservable CountChanged { get; } + public System.Collections.Generic.IReadOnlyList Items { get; } + public System.Func KeySelector { get; } + public System.Collections.Generic.IReadOnlyDictionary KeyValues { get; } + public System.Collections.Generic.IReadOnlyList Keys { get; } + public System.IObservable> Connect(System.Func? predicate = null, bool suppressEmptyChangeSets = true) { } + public void Dispose() { } + public void Edit(System.Action> updateAction) { } + public ReactiveUI.Primitives.Optional Lookup(TKey key) { } + public System.IObservable> Preview(System.Func? predicate = null) { } + public System.IDisposable SuspendCount() { } + public System.IDisposable SuspendNotifications() { } + public System.IObservable> Watch(TKey key) { } + } + public static class SourceListEditConvenienceEx + { + public static void Add(this DynamicData.ISourceList source, T item) + where T : notnull { } + public static void AddRange(this DynamicData.ISourceList source, System.Collections.Generic.IEnumerable items) + where T : notnull { } + public static void Clear(this DynamicData.ISourceList source) + where T : notnull { } + public static void EditDiff(this DynamicData.ISourceList source, System.Collections.Generic.IEnumerable allItems, System.Collections.Generic.IEqualityComparer? equalityComparer = null) + where T : notnull { } + public static void Insert(this DynamicData.ISourceList source, int index, T item) + where T : notnull { } + public static void InsertRange(this DynamicData.ISourceList source, System.Collections.Generic.IEnumerable items, int index) + where T : notnull { } + public static void Move(this DynamicData.ISourceList source, int original, int destination) + where T : notnull { } + public static bool Remove(this DynamicData.ISourceList source, T item) + where T : notnull { } + public static void RemoveAt(this DynamicData.ISourceList source, int index) + where T : notnull { } + public static void RemoveMany(this DynamicData.ISourceList source, System.Collections.Generic.IEnumerable itemsToRemove) + where T : notnull { } + public static void RemoveRange(this DynamicData.ISourceList source, int index, int count) + where T : notnull { } + public static void Replace(this DynamicData.ISourceList source, T original, T destination) + where T : notnull { } + public static void ReplaceAt(this DynamicData.ISourceList source, int index, T item) + where T : notnull { } + } + public static class SourceListEx + { + public static System.IObservable> Cast(this DynamicData.ISourceList source, System.Func conversionFactory) + where TSource : notnull + where TDestination : notnull { } + } + [System.Diagnostics.DebuggerDisplay("SourceList<{typeof(T).Name}> ({Count} Items)")] + public sealed class SourceList : DynamicData.IObservableList, DynamicData.ISourceList, System.IDisposable + where T : notnull + { + public SourceList(System.IObservable>? source = null) { } + public int Count { get; } + public System.IObservable CountChanged { get; } + public System.Collections.Generic.IReadOnlyList Items { get; } + public System.IObservable> Connect(System.Func? predicate = null) { } + public void Dispose() { } + public void Edit(System.Action> updateAction) { } + public System.IObservable> Preview(System.Func? predicate = null) { } + } + public struct TransformAsyncOptions : System.IEquatable + { + public static readonly DynamicData.TransformAsyncOptions Default; + public TransformAsyncOptions(int? MaximumConcurrency, bool TransformOnRefresh) { } + public int? MaximumConcurrency { get; set; } + public bool TransformOnRefresh { get; set; } + } + [System.Serializable] + public class UnspecifiedIndexException : System.Exception + { + public UnspecifiedIndexException() { } + public UnspecifiedIndexException(string message) { } + public UnspecifiedIndexException(string message, System.Exception innerException) { } + } + public class VirtualContext : System.IEquatable> + { + public VirtualContext(DynamicData.IVirtualResponse Response, System.Collections.Generic.IComparer Comparer, DynamicData.SortAndVirtualizeOptions Options) { } + public System.Collections.Generic.IComparer Comparer { get; init; } + public DynamicData.SortAndVirtualizeOptions Options { get; init; } + public DynamicData.IVirtualResponse Response { get; init; } + } + public class VirtualRequest : DynamicData.IVirtualRequest, System.IEquatable + { + public static readonly DynamicData.VirtualRequest Default; + public VirtualRequest() { } + public VirtualRequest(int startIndex, int size) { } + public int Size { get; } + public int StartIndex { get; } + public static System.Collections.Generic.IEqualityComparer StartIndexSizeComparer { get; } + public bool Equals(DynamicData.IVirtualRequest? other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + } +} +namespace DynamicData.Diagnostics +{ + public class ChangeStatistics : System.IEquatable + { + public ChangeStatistics() { } + public ChangeStatistics(int Index, int Adds, int Updates, int Removes, int Refreshes, int Moves, int Count) { } + public int Adds { get; init; } + public int Count { get; init; } + public int Index { get; init; } + public System.DateTime LastUpdated { get; } + public int Moves { get; init; } + public int Refreshes { get; init; } + public int Removes { get; init; } + public int Updates { get; init; } + public override int GetHashCode() { } + public override string ToString() { } + } + public class ChangeSummary + { + public static readonly DynamicData.Diagnostics.ChangeSummary Empty; + public ChangeSummary(int index, DynamicData.Diagnostics.ChangeStatistics latest, DynamicData.Diagnostics.ChangeStatistics overall) { } + public DynamicData.Diagnostics.ChangeStatistics Latest { get; } + public DynamicData.Diagnostics.ChangeStatistics Overall { get; } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + } + public static class DiagnosticOperators + { + public static System.IObservable CollectUpdateStats(this System.IObservable> source) + where TSource : notnull { } + public static System.IObservable CollectUpdateStats(this System.IObservable> source) + where TSource : notnull + where TKey : notnull { } + } +} +namespace DynamicData.Experimental +{ + public static class ExperimentalEx + { + public static DynamicData.Experimental.IWatcher AsWatcher(this System.IObservable> source, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) + where TObject : notnull + where TKey : notnull { } + } + public interface IWatcher : System.IDisposable + where TObject : notnull + where TKey : notnull + { + System.IObservable> Watch(TKey key); + } +} +namespace DynamicData.Kernel +{ + public enum ConnectionStatus + { + Pending = 0, + Loaded = 1, + Errored = 2, + Completed = 3, + } + public static class EnumerableEx + { + public static T[] AsArray(this System.Collections.Generic.IEnumerable source) { } + public static System.Collections.Generic.List AsList(this System.Collections.Generic.IEnumerable source) { } + public static System.Collections.Generic.IEnumerable Duplicates(this System.Collections.Generic.IEnumerable source, System.Func valueSelector) { } + public static System.Collections.Generic.IEnumerable> IndexOfMany(this System.Collections.Generic.IEnumerable source, System.Collections.Generic.IEnumerable itemsToFind) { } + public static System.Collections.Generic.IEnumerable IndexOfMany(this System.Collections.Generic.IEnumerable source, System.Collections.Generic.IEnumerable itemsToFind, System.Func resultSelector) { } + } + public sealed class Error : DynamicData.IKeyValue, DynamicData.IKey, System.IEquatable> + where TKey : notnull + { + public Error(System.Exception? exception, TObject value, TKey key) { } + public System.Exception? Exception { get; } + public TKey Key { get; } + public TObject Value { get; } + public bool Equals(DynamicData.Kernel.Error? other) { } + public override bool Equals(object? obj) { } + public override int GetHashCode() { } + public override string ToString() { } + public static bool operator !=(DynamicData.Kernel.Error left, DynamicData.Kernel.Error right) { } + public static bool operator ==(DynamicData.Kernel.Error left, DynamicData.Kernel.Error right) { } + } + public static class InternalEx + { + public static System.IObservable RetryWithBackOff(this System.IObservable source, System.Func backOffStrategy) + where TException : System.Exception { } + public static System.IDisposable ScheduleRecurringAction(this ReactiveUI.Primitives.Concurrency.ISequencer scheduler, System.Func interval, System.Action action) { } + public static System.IDisposable ScheduleRecurringAction(this ReactiveUI.Primitives.Concurrency.ISequencer scheduler, System.TimeSpan interval, System.Action action) { } + } + public readonly struct ItemWithIndex : System.IEquatable> + { + public ItemWithIndex(T Item, int Index) { } + public int Index { get; init; } + public T Item { get; init; } + public override int GetHashCode() { } + public override string ToString() { } + } + public readonly struct ItemWithValue : System.IEquatable> + { + public ItemWithValue(TObject Item, TValue Value) { } + public TObject Item { get; init; } + public TValue Value { get; init; } + public override int GetHashCode() { } + public override string ToString() { } + } + public sealed class OptionElse + { + public void Else(System.Action action) { } + } + public static class OptionExtensions + { + public static ReactiveUI.Primitives.Optional Convert(this in ReactiveUI.Primitives.Optional source, System.Func> converter) + where TSource : notnull + where TDestination : notnull { } + public static ReactiveUI.Primitives.Optional Convert(this in ReactiveUI.Primitives.Optional source, System.Func converter) + where TSource : notnull + where TDestination : notnull { } + public static TDestination? ConvertOr(this in ReactiveUI.Primitives.Optional source, System.Func converter, System.Func fallbackConverter) + where TSource : notnull { } + public static ReactiveUI.Primitives.Optional FirstOrOptional(this System.Collections.Generic.IEnumerable source, System.Func selector) + where T : notnull { } + public static DynamicData.Kernel.OptionElse IfHasValue(this ReactiveUI.Primitives.Optional? source, System.Action action) + where T : notnull { } + public static DynamicData.Kernel.OptionElse IfHasValue(this in ReactiveUI.Primitives.Optional source, System.Action action) + where T : notnull { } + public static ReactiveUI.Primitives.Optional Lookup(this System.Collections.Generic.IDictionary source, TKey key) + where TValue : notnull { } + public static ReactiveUI.Primitives.Optional OrElse(this in ReactiveUI.Primitives.Optional source, System.Func> fallbackOperation) + where T : notnull { } + public static bool RemoveIfContained(this System.Collections.Generic.IDictionary source, TKey key) { } + public static System.Collections.Generic.IEnumerable SelectValues(this System.Collections.Generic.IEnumerable> source) + where T : notnull { } + public static T ValueOr(this T? source, T defaultValue) + where T : struct { } + public static T ValueOr(this in ReactiveUI.Primitives.Optional source, System.Func valueSelector) + where T : notnull { } + public static T? ValueOrDefault(this in ReactiveUI.Primitives.Optional source) + where T : notnull { } + public static T ValueOrThrow(this in ReactiveUI.Primitives.Optional source, System.Func exceptionGenerator) + where T : notnull { } + } + public static class OptionObservableExtensions + { + public static System.IObservable> Convert(this System.IObservable> source, System.Func> converter) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable> Convert(this System.IObservable> source, System.Func converter) + where TSource : notnull + where TDestination : notnull { } + public static System.IObservable ConvertOr(this System.IObservable> source, System.Func converter, System.Func fallbackConverter) + where TSource : notnull { } + public static System.IObservable> OnHasNoValue(this System.IObservable> source, System.Action action, System.Action? elseAction = null) + where T : notnull { } + public static System.IObservable> OnHasValue(this System.IObservable> source, System.Action action, System.Action? elseAction = null) + where T : notnull { } + public static System.IObservable> OrElse(this System.IObservable> source, System.Func> fallbackOperation) + where T : notnull { } + public static System.IObservable SelectValues(this System.IObservable> source) + where T : notnull { } + public static System.IObservable ValueOr(this System.IObservable> source, System.Func valueSelector) + where T : notnull { } + public static System.IObservable ValueOrDefault(this System.IObservable> source) + where T : notnull { } + public static System.IObservable ValueOrThrow(this System.IObservable> source, System.Func exceptionGenerator) + where T : notnull { } + } +} +namespace DynamicData.List +{ + public interface IGrouping + { + int Count { get; } + System.Collections.Generic.IEnumerable Items { get; } + TGroupKey Key { get; } + } +} +namespace DynamicData.Operators +{ + public interface IPageResponse + { + int Page { get; } + int PageSize { get; } + int Pages { get; } + int TotalSize { get; } + } +} +namespace DynamicData.PLinq +{ + public static class ParallelOperators + { + public static System.IObservable> Filter(this System.IObservable> source, System.Func filter, DynamicData.PLinq.ParallelisationOptions parallelisationOptions) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SubscribeMany(this System.IObservable> source, System.Func subscriptionFactory, DynamicData.PLinq.ParallelisationOptions parallelisationOptions) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> SubscribeMany(this System.IObservable> source, System.Func subscriptionFactory, DynamicData.PLinq.ParallelisationOptions parallelisationOptions) + where TObject : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, DynamicData.PLinq.ParallelisationOptions parallelisationOptions) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, DynamicData.PLinq.ParallelisationOptions parallelisationOptions) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, DynamicData.PLinq.ParallelisationOptions parallelisationOptions) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, DynamicData.PLinq.ParallelisationOptions parallelisationOptions) + where TDestination : notnull + where TSource : notnull + where TKey : notnull { } + } + public enum ParallelType + { + None = 0, + Parallelise = 1, + Ordered = 2, + } + public class ParallelisationOptions + { + public static readonly DynamicData.PLinq.ParallelisationOptions Default; + public static readonly DynamicData.PLinq.ParallelisationOptions None; + public ParallelisationOptions(DynamicData.PLinq.ParallelType type = 0, int threshold = 0, int maxDegreeOfParallelisation = 0) { } + public int MaxDegreeOfParallelisation { get; } + public int Threshold { get; } + public DynamicData.PLinq.ParallelType Type { get; } + } +} +namespace DynamicData.Tests +{ + public class ChangeSetAggregator : System.IDisposable + where TObject : notnull + { + public ChangeSetAggregator(System.IObservable> source) { } + public DynamicData.IObservableList Data { get; } + public System.Exception? Exception { get; set; } + public bool IsCompleted { get; } + public System.Collections.Generic.IList> Messages { get; } + public void Dispose() { } + protected virtual void Dispose(bool isDisposing) { } + } + public sealed class ChangeSetAggregator : System.IDisposable + where TObject : notnull + where TKey : notnull + { + public ChangeSetAggregator(System.IObservable> source) { } + public DynamicData.IObservableCache Data { get; } + public System.Exception? Error { get; } + public bool IsCompleted { get; } + public System.Collections.Generic.IList> Messages { get; } + public DynamicData.Diagnostics.ChangeSummary Summary { get; } + public void Dispose() { } + } + public sealed class ChangeSetAggregator : System.IDisposable + where TObject : notnull + where TKey : notnull + { + public ChangeSetAggregator(System.IObservable> source) { } + public DynamicData.IObservableCache Data { get; } + public System.Exception? Error { get; } + public bool IsCompleted { get; } + public System.Collections.Generic.IList> Messages { get; } + public DynamicData.Diagnostics.ChangeSummary Summary { get; } + public void Dispose() { } + } + public class DistinctChangeSetAggregator : System.IDisposable + where TValue : notnull + { + public DistinctChangeSetAggregator(System.IObservable> source) { } + public DynamicData.IObservableCache Data { get; } + public System.Exception? Error { get; } + public System.Collections.Generic.IList> Messages { get; } + public DynamicData.Diagnostics.ChangeSummary Summary { get; } + public void Dispose() { } + protected virtual void Dispose(bool isDisposing) { } + } + public class GroupChangeSetAggregator : System.IDisposable + where TObject : notnull + where TKey : notnull + where TGroupKey : notnull + { + public GroupChangeSetAggregator(System.IObservable> source) { } + public DynamicData.IObservableCache, TGroupKey> Data { get; } + public System.Exception? Error { get; } + public DynamicData.IObservableCache, TGroupKey> Groups { get; } + public bool IsCompleted { get; } + public System.Collections.Generic.IReadOnlyList> Messages { get; } + public DynamicData.Diagnostics.ChangeSummary Summary { get; } + public void Dispose() { } + protected virtual void Dispose(bool disposing) { } + } + public static class ListTextEx + { + public static DynamicData.Tests.ChangeSetAggregator AsAggregator(this System.IObservable> source) + where T : notnull { } + } + public class PagedChangeSetAggregator : System.IDisposable + where TObject : notnull + where TKey : notnull + { + public PagedChangeSetAggregator(System.IObservable> source) { } + public DynamicData.IObservableCache Data { get; } + public System.Exception? Error { get; } + public System.Collections.Generic.IList> Messages { get; } + public DynamicData.Diagnostics.ChangeSummary Summary { get; } + public void Dispose() { } + protected virtual void Dispose(bool isDisposing) { } + } + public class SortedChangeSetAggregator : System.IDisposable + where TObject : notnull + where TKey : notnull + { + public SortedChangeSetAggregator(System.IObservable> source) { } + public DynamicData.IObservableCache Data { get; } + public System.Exception? Error { get; } + public System.Collections.Generic.IList> Messages { get; } + public DynamicData.Diagnostics.ChangeSummary Summary { get; } + public void Dispose() { } + protected virtual void Dispose(bool isDisposing) { } + } + public static class TestEx + { + public static DynamicData.Tests.DistinctChangeSetAggregator AsAggregator(this System.IObservable> source) + where TValue : notnull { } + public static DynamicData.Tests.ChangeSetAggregator AsAggregator(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static DynamicData.Tests.PagedChangeSetAggregator AsAggregator(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static DynamicData.Tests.SortedChangeSetAggregator AsAggregator(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static DynamicData.Tests.VirtualChangeSetAggregator AsAggregator(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static DynamicData.Tests.ChangeSetAggregator AsAggregator(this System.IObservable> source) + where TObject : notnull + where TKey : notnull { } + public static DynamicData.Tests.GroupChangeSetAggregator AsAggregator(this System.IObservable> source) + where TValue : notnull + where TKey : notnull + where TGroupKey : notnull { } + } + public class VirtualChangeSetAggregator : System.IDisposable + where TObject : notnull + where TKey : notnull + { + public VirtualChangeSetAggregator(System.IObservable> source) { } + public DynamicData.IObservableCache Data { get; } + public System.Exception? Error { get; } + public System.Collections.Generic.IList> Messages { get; } + public DynamicData.Diagnostics.ChangeSummary Summary { get; } + public void Dispose() { } + protected virtual void Dispose(bool isDisposing) { } + } +} \ No newline at end of file diff --git a/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataTests.DotNet9_0.verified.txt b/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataTests.DotNet9_0.verified.txt index ff1f94916..b58a51b16 100644 --- a/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataTests.DotNet9_0.verified.txt +++ b/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataTests.DotNet9_0.verified.txt @@ -1,7 +1,7 @@ -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DynamicData.Profile")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DynamicData.Benchmarks")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DynamicData.Profile")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DynamicData.ReactiveUI")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DynamicData.Tests")] -[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v9.0", FrameworkDisplayName=".NET 9.0")] namespace DynamicData.Aggregation { public readonly struct AggregateItem : System.IEquatable> @@ -27,7 +27,7 @@ namespace DynamicData.Aggregation public static System.IObservable> ForAggregation(this System.IObservable> source) where TObject : notnull where TKey : notnull { } - public static System.IObservable InvalidateWhen(this System.IObservable source, System.IObservable invalidate) { } + public static System.IObservable InvalidateWhen(this System.IObservable source, System.IObservable invalidate) { } public static System.IObservable InvalidateWhen(this System.IObservable source, System.IObservable invalidate) { } } public static class AvgEx @@ -233,7 +233,7 @@ namespace DynamicData.Alias { public static class ObservableCacheAlias { - public static System.IObservable> Select(this System.IObservable> source, System.Func transformFactory, System.IObservable forceTransform) + public static System.IObservable> Select(this System.IObservable> source, System.Func transformFactory, System.IObservable forceTransform) where TDestination : notnull where TSource : notnull where TKey : notnull { } @@ -241,7 +241,7 @@ namespace DynamicData.Alias where TDestination : notnull where TSource : notnull where TKey : notnull { } - public static System.IObservable> Select(this System.IObservable> source, System.Func transformFactory, System.IObservable forceTransform) + public static System.IObservable> Select(this System.IObservable> source, System.Func transformFactory, System.IObservable forceTransform) where TDestination : notnull where TSource : notnull where TKey : notnull { } @@ -254,7 +254,7 @@ namespace DynamicData.Alias where TDestinationKey : notnull where TSource : notnull where TSourceKey : notnull { } - public static System.IObservable> SelectSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable forceTransform) + public static System.IObservable> SelectSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable forceTransform) where TDestination : notnull where TSource : notnull where TKey : notnull { } @@ -262,7 +262,7 @@ namespace DynamicData.Alias where TDestination : notnull where TSource : notnull where TKey : notnull { } - public static System.IObservable> SelectSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable forceTransform) + public static System.IObservable> SelectSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable forceTransform) where TDestination : notnull where TSource : notnull where TKey : notnull { } @@ -279,7 +279,7 @@ namespace DynamicData.Alias public static System.IObservable> Where(this System.IObservable> source, System.IObservable> predicateChanged) where TObject : notnull where TKey : notnull { } - public static System.IObservable> Where(this System.IObservable> source, System.IObservable> predicateChanged, System.IObservable reapplyFilter) + public static System.IObservable> Where(this System.IObservable> source, System.IObservable> predicateChanged, System.IObservable reapplyFilter) where TObject : notnull where TKey : notnull { } } @@ -322,7 +322,7 @@ namespace DynamicData.Binding } public static class BindingListEx { - public static System.IObservable> ObserveCollectionChanges(this System.ComponentModel.IBindingList source) { } + public static System.IObservable> ObserveCollectionChanges(this System.ComponentModel.IBindingList source) { } public static System.IObservable> ToObservableChangeSet<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] T>(this System.ComponentModel.BindingList source) where T : notnull { } public static System.IObservable> ToObservableChangeSet(this TCollection source) @@ -423,7 +423,7 @@ namespace DynamicData.Binding } public static class ObservableCollectionEx { - public static System.IObservable> ObserveCollectionChanges(this System.Collections.Specialized.INotifyCollectionChanged source) { } + public static System.IObservable> ObserveCollectionChanges(this System.Collections.Specialized.INotifyCollectionChanged source) { } public static System.IObservable> ToObservableChangeSet(this System.Collections.ObjectModel.ObservableCollection source) where T : notnull { } public static System.IObservable> ToObservableChangeSet(this System.Collections.ObjectModel.ReadOnlyObservableCollection source) @@ -470,7 +470,7 @@ namespace DynamicData.Binding public int InitialCapacity { get; init; } public bool ResetOnFirstTimeLoad { get; init; } public int ResetThreshold { get; init; } - public System.Reactive.Concurrency.IScheduler? Scheduler { get; init; } + public ReactiveUI.Primitives.Concurrency.ISequencer? Scheduler { get; init; } public bool UseBinarySearch { get; init; } public bool UseReplaceForUpdates { get; init; } } @@ -1132,37 +1132,37 @@ namespace DynamicData public static DynamicData.IObservableCache AsObservableCache(this System.IObservable> source, bool applyLocking = true) where TObject : notnull where TKey : notnull { } - public static System.IObservable> AsyncDisposeMany(this System.IObservable> source, System.Action> disposalsCompletedAccessor) + public static System.IObservable> AsyncDisposeMany(this System.IObservable> source, System.Action> disposalsCompletedAccessor) where TObject : notnull where TKey : notnull { } - public static System.IObservable> AutoRefresh(this System.IObservable> source, System.TimeSpan? changeSetBuffer = default, System.TimeSpan? propertyChangeThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> AutoRefresh(this System.IObservable> source, System.TimeSpan? changeSetBuffer = default, System.TimeSpan? propertyChangeThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : System.ComponentModel.INotifyPropertyChanged where TKey : notnull { } - public static System.IObservable> AutoRefresh(this System.IObservable> source, System.Linq.Expressions.Expression> propertyAccessor, System.TimeSpan? changeSetBuffer = default, System.TimeSpan? propertyChangeThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> AutoRefresh(this System.IObservable> source, System.Linq.Expressions.Expression> propertyAccessor, System.TimeSpan? changeSetBuffer = default, System.TimeSpan? propertyChangeThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : System.ComponentModel.INotifyPropertyChanged where TKey : notnull { } - public static System.IObservable> AutoRefreshOnObservable(this System.IObservable> source, System.Func> reevaluator, System.TimeSpan? changeSetBuffer = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> AutoRefreshOnObservable(this System.IObservable> source, System.Func> reevaluator, System.TimeSpan? changeSetBuffer = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : notnull where TKey : notnull { } - public static System.IObservable> AutoRefreshOnObservable(this System.IObservable> source, System.Func> reevaluator, System.TimeSpan? changeSetBuffer = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> AutoRefreshOnObservable(this System.IObservable> source, System.Func> reevaluator, System.TimeSpan? changeSetBuffer = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : notnull where TKey : notnull { } - public static System.IObservable> Batch(this System.IObservable> source, System.TimeSpan timeSpan, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> Batch(this System.IObservable> source, System.TimeSpan timeSpan, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : notnull where TKey : notnull { } - public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : notnull where TKey : notnull { } - public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, System.TimeSpan? timeOut = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, System.TimeSpan? timeOut = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : notnull where TKey : notnull { } - public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState = false, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState = false, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : notnull where TKey : notnull { } - public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState = false, System.IObservable? timer = null, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState = false, System.IObservable? timer = null, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : notnull where TKey : notnull { } - public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState = false, System.TimeSpan? timeOut = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> BatchIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState = false, System.TimeSpan? timeOut = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : notnull where TKey : notnull { } public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable>> source, System.Collections.Generic.IList targetList) @@ -1225,7 +1225,7 @@ namespace DynamicData public static System.IObservable> Bind(this System.IObservable> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection readOnlyObservableCollection, int resetThreshold = 25, bool useReplaceForUpdates = true, DynamicData.Binding.ISortedObservableCollectionAdaptor? adaptor = null) where TObject : notnull where TKey : notnull { } - public static System.IObservable> BufferInitial(this System.IObservable> source, System.TimeSpan initialBuffer, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> BufferInitial(this System.IObservable> source, System.TimeSpan initialBuffer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : notnull where TKey : notnull { } public static System.IObservable> Cast(this System.IObservable> source, System.Func converter) @@ -1303,16 +1303,16 @@ namespace DynamicData public static System.IObservable> ExpireAfter(this System.IObservable> source, System.Func timeSelector) where TObject : notnull where TKey : notnull { } - public static System.IObservable> ExpireAfter(this System.IObservable> source, System.Func timeSelector, System.Reactive.Concurrency.IScheduler scheduler) + public static System.IObservable> ExpireAfter(this System.IObservable> source, System.Func timeSelector, ReactiveUI.Primitives.Concurrency.ISequencer scheduler) where TObject : notnull where TKey : notnull { } public static System.IObservable> ExpireAfter(this System.IObservable> source, System.Func timeSelector, System.TimeSpan? pollingInterval) where TObject : notnull where TKey : notnull { } - public static System.IObservable>> ExpireAfter(this DynamicData.ISourceCache source, System.Func timeSelector, System.TimeSpan? pollingInterval = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable>> ExpireAfter(this DynamicData.ISourceCache source, System.Func timeSelector, System.TimeSpan? pollingInterval = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : notnull where TKey : notnull { } - public static System.IObservable> ExpireAfter(this System.IObservable> source, System.Func timeSelector, System.TimeSpan? pollingInterval, System.Reactive.Concurrency.IScheduler scheduler) + public static System.IObservable> ExpireAfter(this System.IObservable> source, System.Func timeSelector, System.TimeSpan? pollingInterval, ReactiveUI.Primitives.Concurrency.ISequencer scheduler) where TObject : notnull where TKey : notnull { } public static System.IObservable> Filter(this System.IObservable> source, System.Func filter, bool suppressEmptyChangeSets = true) @@ -1321,7 +1321,7 @@ namespace DynamicData public static System.IObservable> Filter(this System.IObservable> source, System.IObservable> predicateChanged, bool suppressEmptyChangeSets = true) where TObject : notnull where TKey : notnull { } - public static System.IObservable> Filter(this System.IObservable> source, System.IObservable> predicateChanged, System.IObservable reapplyFilter, bool suppressEmptyChangeSets = true) + public static System.IObservable> Filter(this System.IObservable> source, System.IObservable> predicateChanged, System.IObservable reapplyFilter, bool suppressEmptyChangeSets = true) where TObject : notnull where TKey : notnull { } public static System.IObservable> Filter(this System.IObservable> source, System.IObservable predicateState, System.Func predicate, bool suppressEmptyChangeSets = true) @@ -1330,10 +1330,10 @@ namespace DynamicData public static System.IObservable> FilterImmutable(this System.IObservable> source, System.Func predicate, bool suppressEmptyChangeSets = true) where TObject : notnull where TKey : notnull { } - public static System.IObservable> FilterOnObservable(this System.IObservable> source, System.Func> filterFactory, System.TimeSpan? buffer = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> FilterOnObservable(this System.IObservable> source, System.Func> filterFactory, System.TimeSpan? buffer = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : notnull where TKey : notnull { } - public static System.IObservable> FilterOnObservable(this System.IObservable> source, System.Func> filterFactory, System.TimeSpan? buffer = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> FilterOnObservable(this System.IObservable> source, System.Func> filterFactory, System.TimeSpan? buffer = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : notnull where TKey : notnull { } [System.Obsolete("This can cause unhandled exception issues so do not use")] @@ -1379,15 +1379,15 @@ namespace DynamicData where TObject : notnull where TKey : notnull where TGroupKey : notnull { } - public static System.IObservable> Group(this System.IObservable> source, System.Func groupSelectorKey, System.IObservable regrouper) + public static System.IObservable> Group(this System.IObservable> source, System.Func groupSelectorKey, System.IObservable regrouper) where TObject : notnull where TKey : notnull where TGroupKey : notnull { } - public static System.IObservable> Group(this System.IObservable> source, System.IObservable> groupSelectorKeyObservable, System.IObservable? regrouper = null) + public static System.IObservable> Group(this System.IObservable> source, System.IObservable> groupSelectorKeyObservable, System.IObservable? regrouper = null) where TObject : notnull where TKey : notnull where TGroupKey : notnull { } - public static System.IObservable> Group(this System.IObservable> source, System.IObservable> groupSelectorKeyObservable, System.IObservable? regrouper = null) + public static System.IObservable> Group(this System.IObservable> source, System.IObservable> groupSelectorKeyObservable, System.IObservable? regrouper = null) where TObject : notnull where TKey : notnull where TGroupKey : notnull { } @@ -1399,15 +1399,15 @@ namespace DynamicData where TObject : notnull where TKey : notnull where TGroupKey : notnull { } - public static System.IObservable> GroupOnProperty(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.TimeSpan? propertyChangedThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> GroupOnProperty(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.TimeSpan? propertyChangedThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : System.ComponentModel.INotifyPropertyChanged where TKey : notnull where TGroupKey : notnull { } - public static System.IObservable> GroupOnPropertyWithImmutableState(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.TimeSpan? propertyChangedThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> GroupOnPropertyWithImmutableState(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.TimeSpan? propertyChangedThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : System.ComponentModel.INotifyPropertyChanged where TKey : notnull where TGroupKey : notnull { } - public static System.IObservable> GroupWithImmutableState(this System.IObservable> source, System.Func groupSelectorKey, System.IObservable? regrouper = null) + public static System.IObservable> GroupWithImmutableState(this System.IObservable> source, System.Func groupSelectorKey, System.IObservable? regrouper = null) where TObject : notnull where TKey : notnull where TGroupKey : notnull { } @@ -1482,7 +1482,7 @@ namespace DynamicData public static System.IObservable> LimitSizeTo(this System.IObservable> source, int size) where TObject : notnull where TKey : notnull { } - public static System.IObservable>> LimitSizeTo(this DynamicData.ISourceCache source, int sizeLimit, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable>> LimitSizeTo(this DynamicData.ISourceCache source, int sizeLimit, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : notnull where TKey : notnull { } public static System.IObservable> MergeChangeSets(this System.IObservable>> source) @@ -1494,43 +1494,43 @@ namespace DynamicData public static System.IObservable> MergeChangeSets(this System.IObservable>> source, System.Collections.Generic.IEqualityComparer equalityComparer) where TObject : notnull where TKey : notnull { } - public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) where TObject : notnull where TKey : notnull { } public static System.IObservable> MergeChangeSets(this System.IObservable>> source, System.Collections.Generic.IEqualityComparer equalityComparer, System.Collections.Generic.IComparer comparer) where TObject : notnull where TKey : notnull { } - public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, System.Collections.Generic.IComparer comparer, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, System.Collections.Generic.IComparer comparer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) where TObject : notnull where TKey : notnull { } - public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, System.Collections.Generic.IEqualityComparer equalityComparer, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, System.Collections.Generic.IEqualityComparer equalityComparer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) where TObject : notnull where TKey : notnull { } - public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) where TObject : notnull where TKey : notnull { } - public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) where TObject : notnull where TKey : notnull { } - public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, System.Collections.Generic.IEqualityComparer equalityComparer, System.Collections.Generic.IComparer comparer, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, System.Collections.Generic.IEqualityComparer equalityComparer, System.Collections.Generic.IComparer comparer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) where TObject : notnull where TKey : notnull { } - public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, System.Collections.Generic.IComparer comparer, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, System.Collections.Generic.IComparer comparer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) where TObject : notnull where TKey : notnull { } - public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, System.Collections.Generic.IEqualityComparer equalityComparer, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, System.Collections.Generic.IEqualityComparer equalityComparer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) where TObject : notnull where TKey : notnull { } - public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, System.Collections.Generic.IComparer comparer, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, System.Collections.Generic.IComparer comparer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) where TObject : notnull where TKey : notnull { } - public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, System.Collections.Generic.IEqualityComparer equalityComparer, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, System.Collections.Generic.IEqualityComparer equalityComparer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) where TObject : notnull where TKey : notnull { } - public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, System.Collections.Generic.IEqualityComparer equalityComparer, System.Collections.Generic.IComparer comparer, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, System.Collections.Generic.IEqualityComparer equalityComparer, System.Collections.Generic.IComparer comparer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) where TObject : notnull where TKey : notnull { } - public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, System.Collections.Generic.IEqualityComparer equalityComparer, System.Collections.Generic.IComparer comparer, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, System.Collections.Generic.IEqualityComparer equalityComparer, System.Collections.Generic.IComparer comparer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) where TObject : notnull where TKey : notnull { } public static System.IObservable MergeMany(this System.IObservable> source, System.Func> observableSelector) @@ -1763,11 +1763,11 @@ namespace DynamicData where TObject : notnull where TKey : notnull { } [System.Obsolete("Use SortAndBind as it\'s more efficient")] - public static System.IObservable> Sort(this System.IObservable> source, System.Collections.Generic.IComparer comparer, System.IObservable resorter, DynamicData.SortOptimisations sortOptimisations = 0, int resetThreshold = 100) + public static System.IObservable> Sort(this System.IObservable> source, System.Collections.Generic.IComparer comparer, System.IObservable resorter, DynamicData.SortOptimisations sortOptimisations = 0, int resetThreshold = 100) where TObject : notnull where TKey : notnull { } [System.Obsolete("Use SortAndBind as it\'s more efficient")] - public static System.IObservable> Sort(this System.IObservable> source, System.IObservable> comparerObservable, System.IObservable resorter, DynamicData.SortOptimisations sortOptimisations = 0, int resetThreshold = 100) + public static System.IObservable> Sort(this System.IObservable> source, System.IObservable> comparerObservable, System.IObservable resorter, DynamicData.SortOptimisations sortOptimisations = 0, int resetThreshold = 100) where TObject : notnull where TKey : notnull { } public static System.IObservable> SortAndBind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TObject, TKey>(this System.IObservable> source, System.Collections.Generic.IList targetList) @@ -1878,10 +1878,10 @@ namespace DynamicData public static System.IObservable> ToCollection(this System.IObservable> source) where TObject : notnull where TKey : notnull { } - public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, System.Func keySelector, System.Func? expireAfter = null, int limitSizeTo = -1, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, System.Func keySelector, System.Func? expireAfter = null, int limitSizeTo = -1, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : notnull where TKey : notnull { } - public static System.IObservable> ToObservableChangeSet(this System.IObservable source, System.Func keySelector, System.Func? expireAfter = null, int limitSizeTo = -1, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> ToObservableChangeSet(this System.IObservable source, System.Func keySelector, System.Func? expireAfter = null, int limitSizeTo = -1, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : notnull where TKey : notnull { } public static System.IObservable> ToObservableOptional(this System.IObservable> source, TKey key, System.Collections.Generic.IEqualityComparer? equalityComparer = null) @@ -1904,7 +1904,7 @@ namespace DynamicData public static System.IObservable>> Top(this System.IObservable> source, System.Collections.Generic.IComparer comparer, int size) where TObject : notnull where TKey : notnull { } - public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, System.IObservable forceTransform) + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, System.IObservable forceTransform) where TDestination : notnull where TSource : notnull where TKey : notnull { } @@ -1916,7 +1916,7 @@ namespace DynamicData where TDestination : notnull where TSource : notnull where TKey : notnull { } - public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, System.IObservable forceTransform) + public static System.IObservable> Transform(this System.IObservable> source, System.Func transformFactory, System.IObservable forceTransform) where TDestination : notnull where TSource : notnull where TKey : notnull { } @@ -1928,7 +1928,7 @@ namespace DynamicData where TDestination : notnull where TSource : notnull where TKey : notnull { } - public static System.IObservable> Transform(this System.IObservable> source, System.Func, TKey, TDestination> transformFactory, System.IObservable forceTransform) + public static System.IObservable> Transform(this System.IObservable> source, System.Func, TKey, TDestination> transformFactory, System.IObservable forceTransform) where TDestination : notnull where TSource : notnull where TKey : notnull { } @@ -2060,7 +2060,7 @@ namespace DynamicData where TSource : notnull where TKey : notnull where TDestination : notnull { } - public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable forceTransform) + public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable forceTransform) where TDestination : notnull where TSource : notnull where TKey : notnull { } @@ -2068,7 +2068,7 @@ namespace DynamicData where TDestination : notnull where TSource : notnull where TKey : notnull { } - public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable forceTransform) + public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func transformFactory, System.Action> errorHandler, System.IObservable forceTransform) where TDestination : notnull where TSource : notnull where TKey : notnull { } @@ -2076,7 +2076,7 @@ namespace DynamicData where TDestination : notnull where TSource : notnull where TKey : notnull { } - public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func, TKey, TDestination> transformFactory, System.Action> errorHandler, System.IObservable forceTransform) + public static System.IObservable> TransformSafe(this System.IObservable> source, System.Func, TKey, TDestination> transformFactory, System.Action> errorHandler, System.IObservable forceTransform) where TDestination : notnull where TSource : notnull where TKey : notnull { } @@ -2257,11 +2257,11 @@ namespace DynamicData where T : notnull { } public static DynamicData.IObservableList AsObservableList(this System.IObservable> source) where T : notnull { } - public static System.IObservable> AutoRefresh(this System.IObservable> source, System.TimeSpan? changeSetBuffer = default, System.TimeSpan? propertyChangeThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> AutoRefresh(this System.IObservable> source, System.TimeSpan? changeSetBuffer = default, System.TimeSpan? propertyChangeThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : System.ComponentModel.INotifyPropertyChanged { } - public static System.IObservable> AutoRefresh(this System.IObservable> source, System.Linq.Expressions.Expression> propertyAccessor, System.TimeSpan? changeSetBuffer = default, System.TimeSpan? propertyChangeThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> AutoRefresh(this System.IObservable> source, System.Linq.Expressions.Expression> propertyAccessor, System.TimeSpan? changeSetBuffer = default, System.TimeSpan? propertyChangeThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : System.ComponentModel.INotifyPropertyChanged { } - public static System.IObservable> AutoRefreshOnObservable(this System.IObservable> source, System.Func> reevaluator, System.TimeSpan? changeSetBuffer = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> AutoRefreshOnObservable(this System.IObservable> source, System.Func> reevaluator, System.TimeSpan? changeSetBuffer = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : notnull { } public static System.IObservable> Bind(this System.IObservable> source, DynamicData.Binding.IObservableCollection targetCollection, DynamicData.Binding.BindingOptions options) where T : notnull { } @@ -2273,15 +2273,15 @@ namespace DynamicData where T : notnull { } public static System.IObservable> Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] T>(this System.IObservable> source, System.ComponentModel.BindingList bindingList, int resetThreshold = 25) where T : notnull { } - public static System.IObservable> BufferIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> BufferIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where T : notnull { } - public static System.IObservable> BufferIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, System.TimeSpan? timeOut, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> BufferIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, System.TimeSpan? timeOut, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where T : notnull { } - public static System.IObservable> BufferIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> BufferIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where T : notnull { } - public static System.IObservable> BufferIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState, System.TimeSpan? timeOut, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> BufferIf(this System.IObservable> source, System.IObservable pauseIfTrueSelector, bool initialPauseState, System.TimeSpan? timeOut, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where T : notnull { } - public static System.IObservable> BufferInitial(this System.IObservable> source, System.TimeSpan initialBuffer, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> BufferInitial(this System.IObservable> source, System.TimeSpan initialBuffer, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : notnull { } public static System.IObservable> Cast(this System.IObservable> source) where TDestination : notnull { } @@ -2315,7 +2315,7 @@ namespace DynamicData where T : notnull { } public static System.IObservable> Except(this System.IObservable> source, params System.IObservable>[] others) where T : notnull { } - public static System.IObservable> ExpireAfter(this DynamicData.ISourceList source, System.Func timeSelector, System.TimeSpan? pollingInterval = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> ExpireAfter(this DynamicData.ISourceList source, System.Func timeSelector, System.TimeSpan? pollingInterval = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where T : notnull { } public static System.IObservable> Filter(this System.IObservable> source, System.Func predicate) where T : notnull { } @@ -2323,10 +2323,10 @@ namespace DynamicData where T : notnull { } public static System.IObservable> Filter(this System.IObservable> source, System.IObservable predicateState, System.Func predicate, DynamicData.ListFilterPolicy filterPolicy = 1, bool suppressEmptyChangeSets = true) where T : notnull { } - public static System.IObservable> FilterOnObservable(this System.IObservable> source, System.Func> objectFilterObservable, System.TimeSpan? propertyChangedThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> FilterOnObservable(this System.IObservable> source, System.Func> objectFilterObservable, System.TimeSpan? propertyChangedThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : notnull { } [System.Obsolete("Use AutoRefresh(), followed by Filter() instead")] - public static System.IObservable> FilterOnProperty(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.Func predicate, System.TimeSpan? propertyChangedThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> FilterOnProperty(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.Func predicate, System.TimeSpan? propertyChangedThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : System.ComponentModel.INotifyPropertyChanged { } public static System.IObservable> FlattenBufferResult(this System.IObservable>> source) where T : notnull { } @@ -2334,19 +2334,19 @@ namespace DynamicData where TObject : notnull { } public static System.IObservable> ForEachItemChange(this System.IObservable> source, System.Action> action) where TObject : notnull { } - public static System.IObservable>> GroupOn(this System.IObservable> source, System.Func groupSelector, System.IObservable? regrouper = null) + public static System.IObservable>> GroupOn(this System.IObservable> source, System.Func groupSelector, System.IObservable? regrouper = null) where TObject : notnull where TGroup : notnull { } - public static System.IObservable>> GroupOnProperty(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.TimeSpan? propertyChangedThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable>> GroupOnProperty(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.TimeSpan? propertyChangedThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : System.ComponentModel.INotifyPropertyChanged where TGroup : notnull { } - public static System.IObservable>> GroupOnPropertyWithImmutableState(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.TimeSpan? propertyChangedThrottle = default, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable>> GroupOnPropertyWithImmutableState(this System.IObservable> source, System.Linq.Expressions.Expression> propertySelector, System.TimeSpan? propertyChangedThrottle = default, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : System.ComponentModel.INotifyPropertyChanged where TGroup : notnull { } - public static System.IObservable>> GroupWithImmutableState(this System.IObservable> source, System.Func groupSelectorKey, System.IObservable? regrouper = null) + public static System.IObservable>> GroupWithImmutableState(this System.IObservable> source, System.Func groupSelectorKey, System.IObservable? regrouper = null) where TObject : notnull where TGroupKey : notnull { } - public static System.IObservable> LimitSizeTo(this DynamicData.ISourceList source, int sizeLimit, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> LimitSizeTo(this DynamicData.ISourceList source, int sizeLimit, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where T : notnull { } public static System.IObservable> MergeChangeSets(this DynamicData.IObservableList>> source, System.Collections.Generic.IEqualityComparer? equalityComparer = null) where TObject : notnull { } @@ -2354,11 +2354,11 @@ namespace DynamicData where TObject : notnull { } public static System.IObservable> MergeChangeSets(this System.IObservable>> source, System.Collections.Generic.IEqualityComparer? equalityComparer = null) where TObject : notnull { } - public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + public static System.IObservable> MergeChangeSets(this System.Collections.Generic.IEnumerable>> source, System.Collections.Generic.IEqualityComparer? equalityComparer = null, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) where TObject : notnull { } - public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.Collections.Generic.IEnumerable>> others, System.Collections.Generic.IEqualityComparer? equalityComparer = null, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) where TObject : notnull { } - public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, System.Collections.Generic.IEqualityComparer? equalityComparer = null, System.Reactive.Concurrency.IScheduler? scheduler = null, bool completable = true) + public static System.IObservable> MergeChangeSets(this System.IObservable> source, System.IObservable> other, System.Collections.Generic.IEqualityComparer? equalityComparer = null, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null, bool completable = true) where TObject : notnull { } public static System.IObservable> MergeChangeSets(this DynamicData.IObservableList>> source, System.Collections.Generic.IComparer comparer) where TObject : notnull @@ -2419,9 +2419,9 @@ namespace DynamicData where T : notnull { } public static System.IObservable> SkipInitial(this System.IObservable> source) where T : notnull { } - public static System.IObservable> Sort(this System.IObservable> source, System.IObservable> comparerChanged, DynamicData.SortOptions options = 0, System.IObservable? resort = null, int resetThreshold = 50) + public static System.IObservable> Sort(this System.IObservable> source, System.IObservable> comparerChanged, DynamicData.SortOptions options = 0, System.IObservable? resort = null, int resetThreshold = 50) where T : notnull { } - public static System.IObservable> Sort(this System.IObservable> source, System.Collections.Generic.IComparer comparer, DynamicData.SortOptions options = 0, System.IObservable? resort = null, System.IObservable>? comparerChanged = null, int resetThreshold = 50) + public static System.IObservable> Sort(this System.IObservable> source, System.Collections.Generic.IComparer comparer, DynamicData.SortOptions options = 0, System.IObservable? resort = null, System.IObservable>? comparerChanged = null, int resetThreshold = 50) where T : notnull { } public static System.IObservable> StartWithEmpty(this System.IObservable> source) where T : notnull { } @@ -2435,21 +2435,21 @@ namespace DynamicData where T : notnull { } public static System.IObservable> ToCollection(this System.IObservable> source) where TObject : notnull { } - public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where T : notnull { } - public static System.IObservable> ToObservableChangeSet(this System.IObservable source, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> ToObservableChangeSet(this System.IObservable source, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where T : notnull { } - public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, System.Func expireAfter, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, System.Func expireAfter, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where T : notnull { } - public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, int limitSizeTo, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, int limitSizeTo, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where T : notnull { } - public static System.IObservable> ToObservableChangeSet(this System.IObservable source, System.Func expireAfter, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> ToObservableChangeSet(this System.IObservable source, System.Func expireAfter, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where T : notnull { } - public static System.IObservable> ToObservableChangeSet(this System.IObservable source, int limitSizeTo, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> ToObservableChangeSet(this System.IObservable source, int limitSizeTo, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where T : notnull { } - public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, System.Func? expireAfter, int limitSizeTo, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> ToObservableChangeSet(this System.IObservable> source, System.Func? expireAfter, int limitSizeTo, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where T : notnull { } - public static System.IObservable> ToObservableChangeSet(this System.IObservable source, System.Func? expireAfter, int limitSizeTo, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static System.IObservable> ToObservableChangeSet(this System.IObservable source, System.Func? expireAfter, int limitSizeTo, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where T : notnull { } public static System.IObservable> ToSortedCollection(this System.IObservable> source, System.Collections.Generic.IComparer comparer) where TObject : notnull { } @@ -2747,7 +2747,7 @@ namespace DynamicData.Experimental { public static class ExperimentalEx { - public static DynamicData.Experimental.IWatcher AsWatcher(this System.IObservable> source, System.Reactive.Concurrency.IScheduler? scheduler = null) + public static DynamicData.Experimental.IWatcher AsWatcher(this System.IObservable> source, ReactiveUI.Primitives.Concurrency.ISequencer? scheduler = null) where TObject : notnull where TKey : notnull { } } @@ -2793,8 +2793,8 @@ namespace DynamicData.Kernel { public static System.IObservable RetryWithBackOff(this System.IObservable source, System.Func backOffStrategy) where TException : System.Exception { } - public static System.IDisposable ScheduleRecurringAction(this System.Reactive.Concurrency.IScheduler scheduler, System.Func interval, System.Action action) { } - public static System.IDisposable ScheduleRecurringAction(this System.Reactive.Concurrency.IScheduler scheduler, System.TimeSpan interval, System.Action action) { } + public static System.IDisposable ScheduleRecurringAction(this ReactiveUI.Primitives.Concurrency.ISequencer scheduler, System.Func interval, System.Action action) { } + public static System.IDisposable ScheduleRecurringAction(this ReactiveUI.Primitives.Concurrency.ISequencer scheduler, System.TimeSpan interval, System.Action action) { } } public readonly struct ItemWithIndex : System.IEquatable> { @@ -3100,4 +3100,4 @@ namespace DynamicData.Tests public void Dispose() { } protected virtual void Dispose(bool isDisposing) { } } -} +} \ No newline at end of file diff --git a/src/DynamicData.Tests/API/ApiApprovalTests.cs b/src/DynamicData.Tests/API/ApiApprovalTests.cs index 4799b73eb..3c6adfdf8 100644 --- a/src/DynamicData.Tests/API/ApiApprovalTests.cs +++ b/src/DynamicData.Tests/API/ApiApprovalTests.cs @@ -1,6 +1,4 @@ using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; -using Xunit; namespace DynamicData.APITests { @@ -15,5 +13,17 @@ public class ApiApprovalTests /// [Fact] public Task DynamicDataTests() => typeof(VirtualRequest).Assembly.CheckApproval(["DynamicData"]); + + /// + /// Tests to make sure the API of DynamicData.Reactive project is approved. + /// + [Fact] + public Task DynamicDataReactiveTests() + { + var reactiveAssemblyPath = Path.Combine(AppContext.BaseDirectory, "DynamicData.Reactive.dll"); + var reactiveAssembly = System.Reflection.Assembly.LoadFrom(reactiveAssemblyPath); + + return reactiveAssembly.CheckApproval(["DynamicData.Reactive"]); + } } } diff --git a/src/DynamicData.Tests/API/ApiExtensions.cs b/src/DynamicData.Tests/API/ApiExtensions.cs index cd465eb86..749aef265 100644 --- a/src/DynamicData.Tests/API/ApiExtensions.cs +++ b/src/DynamicData.Tests/API/ApiExtensions.cs @@ -1,10 +1,7 @@ -using System; -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; -using System.Threading.Tasks; using PublicApiGenerator; -using VerifyXunit; namespace DynamicData.APITests; diff --git a/src/DynamicData.Tests/AggregationTests/AggregationFixture.cs b/src/DynamicData.Tests/AggregationTests/AggregationFixture.cs index 4755cddbc..0ca8f138d 100644 --- a/src/DynamicData.Tests/AggregationTests/AggregationFixture.cs +++ b/src/DynamicData.Tests/AggregationTests/AggregationFixture.cs @@ -1,14 +1,7 @@ -using System; -using System.Reactive.Linq; - using DynamicData.Aggregation; using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.AggregationTests; public class AggregationFixture : IDisposable diff --git a/src/DynamicData.Tests/AggregationTests/AverageFixture.cs b/src/DynamicData.Tests/AggregationTests/AverageFixture.cs index 400461a92..c9a13db95 100644 --- a/src/DynamicData.Tests/AggregationTests/AverageFixture.cs +++ b/src/DynamicData.Tests/AggregationTests/AverageFixture.cs @@ -1,12 +1,6 @@ -using System; - using DynamicData.Aggregation; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.AggregationTests; public class AverageFixture : IDisposable diff --git a/src/DynamicData.Tests/AggregationTests/MaxFixture.cs b/src/DynamicData.Tests/AggregationTests/MaxFixture.cs index 89f274619..64035e685 100644 --- a/src/DynamicData.Tests/AggregationTests/MaxFixture.cs +++ b/src/DynamicData.Tests/AggregationTests/MaxFixture.cs @@ -1,12 +1,6 @@ -using System; - using DynamicData.Aggregation; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.AggregationTests; public class MaxFixture : IDisposable diff --git a/src/DynamicData.Tests/AggregationTests/MinFixture.cs b/src/DynamicData.Tests/AggregationTests/MinFixture.cs index 168babdbd..a8e82d842 100644 --- a/src/DynamicData.Tests/AggregationTests/MinFixture.cs +++ b/src/DynamicData.Tests/AggregationTests/MinFixture.cs @@ -1,12 +1,6 @@ -using System; - using DynamicData.Aggregation; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.AggregationTests; public class MinFixture : IDisposable diff --git a/src/DynamicData.Tests/AggregationTests/SumFixture.ForCache.cs b/src/DynamicData.Tests/AggregationTests/SumFixture.ForCache.cs index 078a8f1b0..35907e32f 100644 --- a/src/DynamicData.Tests/AggregationTests/SumFixture.ForCache.cs +++ b/src/DynamicData.Tests/AggregationTests/SumFixture.ForCache.cs @@ -1,12 +1,5 @@ -using System; - using DynamicData.Aggregation; using DynamicData.Tests.Domain; -using DynamicData.Tests.Utilities; - -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.AggregationTests; diff --git a/src/DynamicData.Tests/AggregationTests/SumFixture.ForList.cs b/src/DynamicData.Tests/AggregationTests/SumFixture.ForList.cs index 9e9108602..2b2d27f14 100644 --- a/src/DynamicData.Tests/AggregationTests/SumFixture.ForList.cs +++ b/src/DynamicData.Tests/AggregationTests/SumFixture.ForList.cs @@ -1,12 +1,4 @@ -using System; -using System.Linq; - using DynamicData.Aggregation; -using DynamicData.Tests.Utilities; - -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.AggregationTests; diff --git a/src/DynamicData.Tests/AutoRefreshFilter.cs b/src/DynamicData.Tests/AutoRefreshFilter.cs index 94226edb9..9734cc07b 100644 --- a/src/DynamicData.Tests/AutoRefreshFilter.cs +++ b/src/DynamicData.Tests/AutoRefreshFilter.cs @@ -1,13 +1,4 @@ -using System; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Subjects; using DynamicData.Binding; -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests; @@ -32,7 +23,6 @@ public void Bind_Transform_and_FilterOnObservable() ); } - [Fact] public void Test() { @@ -69,7 +59,7 @@ public void AutoRefreshWithObservablePredicate1() var items = new SourceList(); items.Add(item1); - var filterSubject = new BehaviorSubject>(_ => false); + var filterSubject = new StateSignal>(_ => false); var obsListDerived = items .Connect() @@ -113,7 +103,7 @@ public void AutoRefreshWithObservablePredicate2() var items = new ObservableCollection(); items.Add(item1); - var filterSubject = new BehaviorSubject>(_ => false); + var filterSubject = new StateSignal>(_ => false); var obsListDerived = items .ToObservableChangeSet() diff --git a/src/DynamicData.Tests/Binding/AvaloniaDictionaryFixture.cs b/src/DynamicData.Tests/Binding/AvaloniaDictionaryFixture.cs index 478fa6807..43904f417 100644 --- a/src/DynamicData.Tests/Binding/AvaloniaDictionaryFixture.cs +++ b/src/DynamicData.Tests/Binding/AvaloniaDictionaryFixture.cs @@ -1,15 +1,7 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.ComponentModel; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using DynamicData.Binding; +using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; -using Xunit; namespace DynamicData.Tests.Binding; @@ -51,7 +43,6 @@ public void Replace() _results.Data.Items[0].Should().Be(person2); } - [Fact] public void Remove() { @@ -64,7 +55,6 @@ public void Remove() } } - public interface IAvaloniaDictionary : IDictionary, IAvaloniaReadOnlyDictionary, @@ -73,7 +63,6 @@ public interface IAvaloniaDictionary { } - public interface IAvaloniaReadOnlyDictionary : IReadOnlyDictionary, INotifyCollectionChanged, @@ -82,7 +71,6 @@ public interface IAvaloniaReadOnlyDictionary { } - /* Copied from Avalionia because an issue was raised due to compatibility issues with ToObservableChangeSet(). @@ -202,7 +190,6 @@ public void Clear() PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(CommonPropertyNames.IndexerName)); - if (CollectionChanged != null) { var e = new NotifyCollectionChangedEventArgs( @@ -294,7 +281,6 @@ private void NotifyAdd(TKey key, TValue value) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"{CommonPropertyNames.IndexerName}[{key}]")); - if (CollectionChanged != null) { var e = new NotifyCollectionChangedEventArgs( diff --git a/src/DynamicData.Tests/Binding/BindingLIstBindListFixture.cs b/src/DynamicData.Tests/Binding/BindingLIstBindListFixture.cs index 750aaf1ae..b0bb334d1 100644 --- a/src/DynamicData.Tests/Binding/BindingLIstBindListFixture.cs +++ b/src/DynamicData.Tests/Binding/BindingLIstBindListFixture.cs @@ -1,15 +1,7 @@ #if SUPPORTS_BINDINGLIST -using System; -using System.ComponentModel; -using System.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Binding { public class BindingLIstBindListFixture : IDisposable @@ -61,8 +53,6 @@ public void Clear() _collection.Count.Should().Be(0, "Should be 100 items in the collection"); } - - [Fact] public void Refresh() { @@ -83,7 +73,6 @@ public void Refresh() args.NewIndex.Should().Be(10); } - [Fact] public void RemoveSourceRemovesFromTheDestination() { diff --git a/src/DynamicData.Tests/Binding/BindingListBindCacheFixture.cs b/src/DynamicData.Tests/Binding/BindingListBindCacheFixture.cs index f76ff68bf..c51a13565 100644 --- a/src/DynamicData.Tests/Binding/BindingListBindCacheFixture.cs +++ b/src/DynamicData.Tests/Binding/BindingListBindCacheFixture.cs @@ -1,15 +1,7 @@ -#if SUPPORTS_BINDINGLIST - -using System; -using System.ComponentModel; -using System.Linq; +#if SUPPORTS_BINDINGLIST using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Binding { public class BindingListCacheFixture : IDisposable @@ -57,7 +49,6 @@ public void BatchRemove() _collection.Count.Should().Be(0, "Should be 100 items in the collection"); } - [Fact] public void RemoveSourceRemovesFromTheDestination() { diff --git a/src/DynamicData.Tests/Binding/BindingListBindCacheSortedFixture.cs b/src/DynamicData.Tests/Binding/BindingListBindCacheSortedFixture.cs index a706c4aea..ee56e96d5 100644 --- a/src/DynamicData.Tests/Binding/BindingListBindCacheSortedFixture.cs +++ b/src/DynamicData.Tests/Binding/BindingListBindCacheSortedFixture.cs @@ -1,17 +1,8 @@ #if SUPPORTS_BINDINGLIST -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; - using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Binding { public class BindingListBindCacheSortedFixture : IDisposable @@ -70,7 +61,6 @@ public void CollectionIsInSortOrder() sorted.Should().BeEquivalentTo(_collection.ToList()); } - [Fact] public void LargeUpdateInvokesAReset() { @@ -109,7 +99,6 @@ public void Refresh() _collection[args.NewIndex].Should().Be(people[10]); } - [Fact] public void RemoveSourceRemovesFromTheDestination() { @@ -197,7 +186,6 @@ public void UpdateToSourceUpdatesTheDestination() _collection.First().Should().Be(personUpdated, "Should be updated person"); } - public void Dispose() { _binder.Dispose(); @@ -205,6 +193,5 @@ public void Dispose() } } - } #endif diff --git a/src/DynamicData.Tests/Binding/BindingListToChangeSetFixture.cs b/src/DynamicData.Tests/Binding/BindingListToChangeSetFixture.cs index b9818d8bc..c50271424 100644 --- a/src/DynamicData.Tests/Binding/BindingListToChangeSetFixture.cs +++ b/src/DynamicData.Tests/Binding/BindingListToChangeSetFixture.cs @@ -1,13 +1,5 @@ -using System; -using System.ComponentModel; -using System.Linq; - using DynamicData.Binding; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Binding; public class BindingListToChangeSetFixture : IDisposable @@ -80,7 +72,6 @@ public void RefreshCausesReplace() sourceCacheResults.Messages.First().Adds.Should().Be(1); sourceCacheResults.Messages.Last().Refreshes.Should().Be(1); - /* List receives add and replace instead of refresh (and as of 23/02/2023 it receives a refresh too!) */ @@ -130,7 +121,6 @@ public void ResetFiresClearsAndAdds() resetNotification.Adds.Should().Be(10); } - [Fact] public void InsertInto() { diff --git a/src/DynamicData.Tests/Binding/DeeplyNestedNotifyPropertyChangedFixture.cs b/src/DynamicData.Tests/Binding/DeeplyNestedNotifyPropertyChangedFixture.cs index e233c5c6a..1cf0ff9e0 100644 --- a/src/DynamicData.Tests/Binding/DeeplyNestedNotifyPropertyChangedFixture.cs +++ b/src/DynamicData.Tests/Binding/DeeplyNestedNotifyPropertyChangedFixture.cs @@ -1,15 +1,7 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Reactive.Linq; using DynamicData.Binding; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Binding; public class DeeplyNestedNotifyPropertyChangedFixture diff --git a/src/DynamicData.Tests/Binding/IObservableListBindCacheFixture.cs b/src/DynamicData.Tests/Binding/IObservableListBindCacheFixture.cs index 2c01a256d..41f40cd4d 100644 --- a/src/DynamicData.Tests/Binding/IObservableListBindCacheFixture.cs +++ b/src/DynamicData.Tests/Binding/IObservableListBindCacheFixture.cs @@ -1,13 +1,6 @@ -using System; -using System.Linq; - -using DynamicData.Binding; +using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Binding; public class IObservableListBindCacheFixture : IDisposable diff --git a/src/DynamicData.Tests/Binding/IObservableListBindCacheSortedFixture.cs b/src/DynamicData.Tests/Binding/IObservableListBindCacheSortedFixture.cs index 35aa4eb1f..b371bf934 100644 --- a/src/DynamicData.Tests/Binding/IObservableListBindCacheSortedFixture.cs +++ b/src/DynamicData.Tests/Binding/IObservableListBindCacheSortedFixture.cs @@ -1,15 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Subjects; - using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Binding; public class IObservableListBindCacheSortedFixture : IDisposable @@ -18,7 +9,7 @@ public class IObservableListBindCacheSortedFixture : IDisposable private static readonly IComparer _comparerNameDesc = SortExpressionComparer.Descending(p => p.Name); - private readonly BehaviorSubject> _comparer = new(_comparerAgeAscThanNameAsc); + private readonly StateSignal> _comparer = new(_comparerAgeAscThanNameAsc); private readonly RandomPersonGenerator _generator = new(); diff --git a/src/DynamicData.Tests/Binding/IObservableListBindListFixture.cs b/src/DynamicData.Tests/Binding/IObservableListBindListFixture.cs index 9dceabfb2..3b1749158 100644 --- a/src/DynamicData.Tests/Binding/IObservableListBindListFixture.cs +++ b/src/DynamicData.Tests/Binding/IObservableListBindListFixture.cs @@ -1,15 +1,6 @@ -using System; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Linq; -using System.Reactive.Linq; using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Binding; public class IObservableListBindListFixture : IDisposable @@ -43,7 +34,6 @@ public void ResetThresholdsForBinding_ObservableCollection() var test3 = Test(new BindingOptions(105, ResetOnFirstTimeLoad: false)); var test4 = Test(BindingOptions.NeverFireReset()); - test1.action.Should().Be(NotifyCollectionChangedAction.Reset); test2.action.Should().Be(NotifyCollectionChangedAction.Reset); test3.action.Should().Be(NotifyCollectionChangedAction.Add); @@ -65,7 +55,6 @@ public void ResetThresholdsForBinding_ObservableCollection() result = events; }); - var binder = options == null ? _source.Connect().Bind(list).Subscribe() : _source.Connect().Bind(list, options.Value).Subscribe(); @@ -82,14 +71,12 @@ public void ResetThresholdsForBinding_ReadonlyObservableCollection() { var people = _generator.Take(100).ToArray(); - // check whether reset is fired with different params var test1 = Test(); var test2 = Test(new BindingOptions(95)); var test3 = Test(new BindingOptions(105, ResetOnFirstTimeLoad: false)); var test4 = Test(BindingOptions.NeverFireReset()); - test1.action.Should().Be(NotifyCollectionChangedAction.Reset); test2.action.Should().Be(NotifyCollectionChangedAction.Reset); test3.action.Should().Be(NotifyCollectionChangedAction.Add); @@ -122,8 +109,6 @@ public void ResetThresholdsForBinding_ReadonlyObservableCollection() } } - - [Fact] public void AddRange() { diff --git a/src/DynamicData.Tests/Binding/NotifyPropertyChangedExFixture.cs b/src/DynamicData.Tests/Binding/NotifyPropertyChangedExFixture.cs index 2de2402f2..6f84694c9 100644 --- a/src/DynamicData.Tests/Binding/NotifyPropertyChangedExFixture.cs +++ b/src/DynamicData.Tests/Binding/NotifyPropertyChangedExFixture.cs @@ -1,16 +1,7 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; using DynamicData.Binding; using DynamicData.Tests.Domain; -using DynamicData.Tests.Utilities; - -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.Binding; diff --git a/src/DynamicData.Tests/Binding/ObservableCollectionBindCacheFixture.cs b/src/DynamicData.Tests/Binding/ObservableCollectionBindCacheFixture.cs index 6d68b12c0..fe66bba0c 100644 --- a/src/DynamicData.Tests/Binding/ObservableCollectionBindCacheFixture.cs +++ b/src/DynamicData.Tests/Binding/ObservableCollectionBindCacheFixture.cs @@ -1,16 +1,6 @@ -using System; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Linq; -using System.Reactive.Linq; - using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Binding; public class ObservableCollectionBindCacheFixture : IDisposable @@ -34,15 +24,12 @@ public void ResetThresholdsForBinding_ObservableCollection() { var people = _generator.Take(100).ToArray(); - - // check whether reset is fired with different params var test1 = Test(); var test2 = Test(new BindingOptions(95)); var test3 = Test(new BindingOptions(105, ResetOnFirstTimeLoad: false)); var test4 = Test(BindingOptions.NeverFireReset()); - test1.action.Should().Be(NotifyCollectionChangedAction.Reset); test2.action.Should().Be(NotifyCollectionChangedAction.Reset); test3.action.Should().Be(NotifyCollectionChangedAction.Add); @@ -64,7 +51,6 @@ public void ResetThresholdsForBinding_ObservableCollection() result = events; }); - var binder = options == null ? _source.Connect().Bind(list).Subscribe() : _source.Connect().Bind(list, options.Value).Subscribe(); @@ -81,14 +67,12 @@ public void ResetThresholdsForBinding_ReadonlyObservableCollection() { var people = _generator.Take(100).ToArray(); - // check whether reset is fired with different params var test1 = Test(); var test2 = Test(new BindingOptions(95)); var test3 = Test(new BindingOptions(105, ResetOnFirstTimeLoad: false)); var test4 = Test(BindingOptions.NeverFireReset()); - test1.action.Should().Be(NotifyCollectionChangedAction.Reset); test2.action.Should().Be(NotifyCollectionChangedAction.Reset); test3.action.Should().Be(NotifyCollectionChangedAction.Add); @@ -121,7 +105,6 @@ public void ResetThresholdsForBinding_ReadonlyObservableCollection() } } - [Fact] public void AddToSourceAddsToDestination() { @@ -173,7 +156,6 @@ public void UpdateToSourceSendsReplaceOnDestination() RunTest(true); RunTest(false); - void RunTest(bool useReplace) { var collection = new ObservableCollectionExtended(); @@ -181,7 +163,6 @@ void RunTest(bool useReplace) using var source = new SourceCache(p => p.Name); using var binder = source.Connect().Bind(collection, new ObservableCollectionAdaptor(useReplaceForUpdates: useReplace)).Subscribe(); - NotifyCollectionChangedAction action = default; source.AddOrUpdate(new Person("Adult1", 50)); diff --git a/src/DynamicData.Tests/Binding/ObservableCollectionBindCacheSortedFixture.cs b/src/DynamicData.Tests/Binding/ObservableCollectionBindCacheSortedFixture.cs index c5df8ce24..63581b13f 100644 --- a/src/DynamicData.Tests/Binding/ObservableCollectionBindCacheSortedFixture.cs +++ b/src/DynamicData.Tests/Binding/ObservableCollectionBindCacheSortedFixture.cs @@ -1,16 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Linq; -using System.Reactive.Linq; - using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; -using Xunit; - namespace DynamicData.Tests.Binding; public class ObservableCollectionBindCacheSortedFixture : IDisposable @@ -32,21 +22,17 @@ public ObservableCollectionBindCacheSortedFixture() _binder = _source.Connect().Sort(_comparer, resetThreshold: 25).Bind(_collection).Subscribe(); } - [Fact] public void ResetThresholdsForBinding_ObservableCollection() { var people = _generator.Take(100).ToArray(); - - // check whether reset is fired with different params var test1 = Test(); var test2 = Test(new BindingOptions(95)); var test3 = Test(new BindingOptions(105, ResetOnFirstTimeLoad: false)); var test4 = Test(BindingOptions.NeverFireReset()); - test1.action.Should().Be(NotifyCollectionChangedAction.Reset); test2.action.Should().Be(NotifyCollectionChangedAction.Reset); test3.action.Should().Be(NotifyCollectionChangedAction.Add); @@ -68,7 +54,6 @@ public void ResetThresholdsForBinding_ObservableCollection() result = events; }); - var binder = options == null ? _source.Connect().Sort(_comparer).Bind(list).Subscribe() : _source.Connect().Sort(_comparer).Bind(list, options.Value).Subscribe(); @@ -85,14 +70,12 @@ public void ResetThresholdsForBinding_ReadonlyObservableCollection() { var people = _generator.Take(100).ToArray(); - // check whether reset is fired with different params var test1 = Test(); var test2 = Test(new BindingOptions(95)); var test3 = Test(new BindingOptions(105, ResetOnFirstTimeLoad: false)); var test4 = Test(BindingOptions.NeverFireReset()); - test1.action.Should().Be(NotifyCollectionChangedAction.Reset); test2.action.Should().Be(NotifyCollectionChangedAction.Reset); test3.action.Should().Be(NotifyCollectionChangedAction.Add); @@ -292,7 +275,6 @@ public void UpdateToSourceSendsReplaceIfSortingIsNotAffected() RunTest(true); RunTest(false); - void RunTest(bool useReplace) { var collection = new ObservableCollectionExtended(); diff --git a/src/DynamicData.Tests/Binding/ObservableCollectionBindListFixture.cs b/src/DynamicData.Tests/Binding/ObservableCollectionBindListFixture.cs index 8e71766e3..9062dfcdb 100644 --- a/src/DynamicData.Tests/Binding/ObservableCollectionBindListFixture.cs +++ b/src/DynamicData.Tests/Binding/ObservableCollectionBindListFixture.cs @@ -1,13 +1,6 @@ -using System; -using System.Linq; - using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Binding; public class ObservableCollectionBindListFixture : IDisposable diff --git a/src/DynamicData.Tests/Binding/ObservableCollectionExtendedToChangeSetFixture.cs b/src/DynamicData.Tests/Binding/ObservableCollectionExtendedToChangeSetFixture.cs index a3d89a0d1..f94cf5527 100644 --- a/src/DynamicData.Tests/Binding/ObservableCollectionExtendedToChangeSetFixture.cs +++ b/src/DynamicData.Tests/Binding/ObservableCollectionExtendedToChangeSetFixture.cs @@ -1,13 +1,5 @@ -using System; -using System.Collections.ObjectModel; -using System.Linq; - using DynamicData.Binding; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Binding; public class ObservableCollectionExtendedToChangeSetFixture : IDisposable diff --git a/src/DynamicData.Tests/Binding/ObservableCollectionToChangeSetFixture.cs b/src/DynamicData.Tests/Binding/ObservableCollectionToChangeSetFixture.cs index da7252cef..701b0bec3 100644 --- a/src/DynamicData.Tests/Binding/ObservableCollectionToChangeSetFixture.cs +++ b/src/DynamicData.Tests/Binding/ObservableCollectionToChangeSetFixture.cs @@ -1,14 +1,5 @@ -using System; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Linq; - using DynamicData.Binding; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Binding; public class ObservableCollectionToChangeSetFixture : IDisposable diff --git a/src/DynamicData.Tests/Binding/ReadOnlyObservableCollectionToChangeSetFixture.cs b/src/DynamicData.Tests/Binding/ReadOnlyObservableCollectionToChangeSetFixture.cs index 13efc2a87..39c154dd3 100644 --- a/src/DynamicData.Tests/Binding/ReadOnlyObservableCollectionToChangeSetFixture.cs +++ b/src/DynamicData.Tests/Binding/ReadOnlyObservableCollectionToChangeSetFixture.cs @@ -1,14 +1,5 @@ -using System; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Linq; - using DynamicData.Binding; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Binding; public class ReadOnlyObservableCollectionToChangeSetFixture : IDisposable diff --git a/src/DynamicData.Tests/Binding/ReadonlyCollectionBindCacheFixture.cs b/src/DynamicData.Tests/Binding/ReadonlyCollectionBindCacheFixture.cs index 492448b73..d68514ab2 100644 --- a/src/DynamicData.Tests/Binding/ReadonlyCollectionBindCacheFixture.cs +++ b/src/DynamicData.Tests/Binding/ReadonlyCollectionBindCacheFixture.cs @@ -1,12 +1,5 @@ -using System; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Linq; -using System.Reactive.Linq; using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; -using Xunit; namespace DynamicData.Tests.Binding; @@ -74,7 +67,6 @@ public void UpdateToSourceSendsReplaceOnDestination() RunTest(true); RunTest(false); - void RunTest(bool useReplace) { using var source = new SourceCache(p => p.Name); diff --git a/src/DynamicData.Tests/Binding/WhenPropertyChangedBehaviorFixture.cs b/src/DynamicData.Tests/Binding/WhenPropertyChangedBehaviorFixture.cs index 48a60e35d..2a50aa139 100644 --- a/src/DynamicData.Tests/Binding/WhenPropertyChangedBehaviorFixture.cs +++ b/src/DynamicData.Tests/Binding/WhenPropertyChangedBehaviorFixture.cs @@ -2,16 +2,8 @@ // 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.Collections.Generic; -using System.ComponentModel; - using DynamicData.Binding; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Binding; /// diff --git a/src/DynamicData.Tests/Binding/WhenPropertyChangedRaceFixture.cs b/src/DynamicData.Tests/Binding/WhenPropertyChangedRaceFixture.cs index 5c8d073c8..3d5225e92 100644 --- a/src/DynamicData.Tests/Binding/WhenPropertyChangedRaceFixture.cs +++ b/src/DynamicData.Tests/Binding/WhenPropertyChangedRaceFixture.cs @@ -1,31 +1,17 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Linq; -using System.Threading; -using System.Threading.Tasks; - using DynamicData.Binding; -using DynamicData.Tests.Utilities; - -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.Binding; /// /// Multi-threaded race tests for . /// Each test forces concurrency between the operator's subscribe call (or chain re-walk) and one or more -/// notifiers firing on other threads. +/// notifiers firing on other threads. /// +[Collection(IntegrationTestFixtureBase.CollectionName)] public sealed class WhenPropertyChangedRaceFixture { private static readonly TimeSpan ConditionTimeout = TimeSpan.FromSeconds(30); diff --git a/src/DynamicData.Tests/Cache/AndFixture.cs b/src/DynamicData.Tests/Cache/AndFixture.cs index 0f0ec2ad0..79e4a0d89 100644 --- a/src/DynamicData.Tests/Cache/AndFixture.cs +++ b/src/DynamicData.Tests/Cache/AndFixture.cs @@ -1,13 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class AndFixture : AndFixtureBase diff --git a/src/DynamicData.Tests/Cache/AsyncDisposeManyFixture.IntegrationTests.cs b/src/DynamicData.Tests/Cache/AsyncDisposeManyFixture.IntegrationTests.cs index 3fe7de711..4d31b75d0 100644 --- a/src/DynamicData.Tests/Cache/AsyncDisposeManyFixture.IntegrationTests.cs +++ b/src/DynamicData.Tests/Cache/AsyncDisposeManyFixture.IntegrationTests.cs @@ -1,16 +1,4 @@ -using System; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Threading.Tasks; - using DynamicData.Kernel; -using DynamicData.Tests.Utilities; - -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.Cache; @@ -26,7 +14,7 @@ public class IntegrationTests public async Task ItemDisposalErrors_ErrorPropagatesToDisposalsCompleted(ItemType itemType) { using var source = new SourceCache(static item => item.Id); - using var sourceCompletionSource = new Subject(); + using var sourceCompletionSource = new Signal(); ValueRecordingObserver? disposalsCompletedResults = null; @@ -44,7 +32,6 @@ public async Task ItemDisposalErrors_ErrorPropagatesToDisposalsCompleted(ItemTyp disposalsCompletedResults.Should().NotBeNull("disposalsCompletedAccessor should have been invoked"); - source.AddOrUpdate(new[] { ItemBase.Create(type: itemType, id: 1, version: 1), @@ -61,7 +48,6 @@ public async Task ItemDisposalErrors_ErrorPropagatesToDisposalsCompleted(ItemTyp disposalsCompletedResults.RecordedValues.Should().BeEmpty("no disposals should have occurred"); disposalsCompletedResults.HasCompleted.Should().BeFalse("no disposals should have occurred"); - var error = new Exception("Test"); source.Items.ElementAt(1).FailDisposal(error); @@ -86,7 +72,7 @@ public async Task ItemDisposalErrors_ErrorPropagatesToDisposalsCompleted(ItemTyp public async Task ItemDisposalsComplete_DisposalsCompletedOccursAndCompletes(ItemType itemType) { using var source = new SourceCache(static item => item.Id); - using var sourceCompletionSource = new Subject(); + using var sourceCompletionSource = new Signal(); ValueRecordingObserver? disposalsCompletedResults = null; @@ -104,7 +90,6 @@ public async Task ItemDisposalsComplete_DisposalsCompletedOccursAndCompletes(Ite disposalsCompletedResults.Should().NotBeNull("disposalsCompletedAccessor should have been invoked"); - source.AddOrUpdate(new[] { ItemBase.Create(type: itemType, id: 1, version: 1), @@ -121,7 +106,6 @@ public async Task ItemDisposalsComplete_DisposalsCompletedOccursAndCompletes(Ite disposalsCompletedResults.RecordedValues.Should().BeEmpty("the source has not completed"); disposalsCompletedResults.HasCompleted.Should().BeFalse("the source has not completed"); - sourceCompletionSource.OnNext(Unit.Default); foreach (var item in source.Items) item.CompleteDisposal(); @@ -143,7 +127,7 @@ public async Task ItemDisposalsComplete_DisposalsCompletedOccursAndCompletes(Ite public async Task ItemDisposalsOccurOnMultipleThreads_DisposalIsThreadSafe() { using var source = new SourceCache(static item => item.Id); - using var sourceCompletionSource = new Subject(); + using var sourceCompletionSource = new Signal(); ValueRecordingObserver? disposalsCompletedResults = null; @@ -161,7 +145,6 @@ public async Task ItemDisposalsOccurOnMultipleThreads_DisposalIsThreadSafe() disposalsCompletedResults.Should().NotBeNull("disposalsCompletedAccessor should have been invoked"); - var items = Enumerable.Range(1, 100_000) .Select(id => new AsyncDisposableItem() { @@ -181,7 +164,6 @@ public async Task ItemDisposalsOccurOnMultipleThreads_DisposalIsThreadSafe() disposalsCompletedResults.RecordedValues.Should().BeEmpty("the source has not completed"); disposalsCompletedResults.HasCompleted.Should().BeFalse("the source has not completed"); - sourceCompletionSource.OnNext(); await Task.WhenAll(items .GroupBy(item => item.Id % 4) diff --git a/src/DynamicData.Tests/Cache/AsyncDisposeManyFixture.UnitTests.cs b/src/DynamicData.Tests/Cache/AsyncDisposeManyFixture.UnitTests.cs index c6eea36b0..c4ceff774 100644 --- a/src/DynamicData.Tests/Cache/AsyncDisposeManyFixture.UnitTests.cs +++ b/src/DynamicData.Tests/Cache/AsyncDisposeManyFixture.UnitTests.cs @@ -1,17 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Reactive.Subjects; - using DynamicData.Cache.Internal; -using DynamicData.Kernel; -using DynamicData.Tests.Utilities; - -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.Cache; @@ -26,7 +13,7 @@ public class UnitTests [InlineData(ItemType.ImmediateAsyncDisposable)] public void ItemsAreAddedMovedOrRefreshed_ItemsAreNotDisposed(ItemType itemType) { - using var source = new Subject>(); + using var source = new Signal>(); ValueRecordingObserver? disposalsCompletedResults = null; @@ -42,7 +29,6 @@ public void ItemsAreAddedMovedOrRefreshed_ItemsAreNotDisposed(ItemType itemType) disposalsCompletedResults.Should().NotBeNull("disposalsCompletedAccessor should have been invoked"); - // Addition var items = new List() { @@ -72,14 +58,13 @@ public void ItemsAreAddedMovedOrRefreshed_ItemsAreNotDisposed(ItemType itemType) disposalsCompletedResults.RecordedValues.Should().BeEmpty("the source has not completed"); disposalsCompletedResults.HasCompleted.Should().BeFalse("the source has not completed"); - // Movement items.Move(2, 0, items[2]); items.Move(2, 1, items[2]); source.OnNext(new ChangeSet() { - new(reason: ChangeReason.Moved, key: items[0].Id, current: items[0], previous: Optional.None(), currentIndex: 0, previousIndex: 2), - new(reason: ChangeReason.Moved, key: items[1].Id, current: items[1], previous: Optional.None(), currentIndex: 1, previousIndex: 2) + new(reason: ChangeReason.Moved, key: items[0].Id, current: items[0], previous: Optional.None, currentIndex: 0, previousIndex: 2), + new(reason: ChangeReason.Moved, key: items[1].Id, current: items[1], previous: Optional.None, currentIndex: 1, previousIndex: 2) }); results.Error.Should().BeNull(); @@ -96,7 +81,6 @@ public void ItemsAreAddedMovedOrRefreshed_ItemsAreNotDisposed(ItemType itemType) disposalsCompletedResults.RecordedValues.Should().BeEmpty("the source has not completed"); disposalsCompletedResults.HasCompleted.Should().BeFalse("the source has not completed"); - // Refreshing source.OnNext(new ChangeSet(items .Select((item, index) => new Change( @@ -150,7 +134,6 @@ public void ItemsAreRemoved_ItemsAreDisposedAfterDownstreamProcessing(ItemType i disposalsCompletedResults.Should().NotBeNull("disposalsCompletedAccessor should have been invoked"); - source.AddOrUpdate(new[] { ItemBase.Create(type: itemType, id: 1, version: 1), @@ -167,7 +150,6 @@ public void ItemsAreRemoved_ItemsAreDisposedAfterDownstreamProcessing(ItemType i disposalsCompletedResults.RecordedValues.Should().BeEmpty("the source has not completed"); disposalsCompletedResults.HasCompleted.Should().BeFalse("the source has not completed"); - var items = source.Items.ToArray(); source.Clear(); @@ -213,7 +195,6 @@ public void ItemsAreUpdated_PreviousItemsAreDisposedAfterDownstreamProcessing(It disposalsCompletedResults.Should().NotBeNull("disposalsCompletedAccessor should have been invoked"); - source.AddOrUpdate(new[] { ItemBase.Create(type: itemType, id: 1, version: 1), @@ -230,7 +211,6 @@ public void ItemsAreUpdated_PreviousItemsAreDisposedAfterDownstreamProcessing(It disposalsCompletedResults.RecordedValues.Should().BeEmpty("the source has not completed"); disposalsCompletedResults.HasCompleted.Should().BeFalse("the source has not completed"); - var previousItems = source.Items.ToArray(); source.AddOrUpdate(new[] { @@ -276,8 +256,7 @@ public void SourceCompletes_ItemsAreDisposedAndCompletionPropagates(SourceType s IObservable> source = (sourceType is SourceType.Immediate) ? Observable.Return(changeSet) - : new Subject>(); - + : new Signal>(); ValueRecordingObserver? disposalsCompletedResults = null; @@ -293,8 +272,7 @@ public void SourceCompletes_ItemsAreDisposedAndCompletionPropagates(SourceType s disposalsCompletedResults.Should().NotBeNull("disposalsCompletedAccessor should have been invoked"); - - if (source is Subject> subject) + if (source is Signal> subject) { subject.OnNext(changeSet); subject.OnCompleted(); @@ -332,8 +310,7 @@ public void SourceErrors_ItemsAreDisposedAndErrorPropagates(SourceType sourceTyp IObservable> source = (sourceType is SourceType.Immediate) ? Observable.Return(changeSet) .Concat(Observable.Throw>(error)) - : new Subject>(); - + : new Signal>(); ValueRecordingObserver? disposalsCompletedResults = null; @@ -349,8 +326,7 @@ public void SourceErrors_ItemsAreDisposedAndErrorPropagates(SourceType sourceTyp disposalsCompletedResults.Should().NotBeNull("disposalsCompletedAccessor should have been invoked"); - - if (source is Subject> subject) + if (source is Signal> subject) { subject.OnNext(changeSet); subject.OnError(error); diff --git a/src/DynamicData.Tests/Cache/AsyncDisposeManyFixture.cs b/src/DynamicData.Tests/Cache/AsyncDisposeManyFixture.cs index 38da53d5d..959042438 100644 --- a/src/DynamicData.Tests/Cache/AsyncDisposeManyFixture.cs +++ b/src/DynamicData.Tests/Cache/AsyncDisposeManyFixture.cs @@ -1,7 +1,4 @@ -using System; -using System.Threading.Tasks; - -namespace DynamicData.Tests.Cache; +namespace DynamicData.Tests.Cache; public static partial class AsyncDisposeManyFixture { diff --git a/src/DynamicData.Tests/Cache/AutoRefreshFixture.Base.cs b/src/DynamicData.Tests/Cache/AutoRefreshFixture.Base.cs index ebdc26950..e4daedcac 100644 --- a/src/DynamicData.Tests/Cache/AutoRefreshFixture.Base.cs +++ b/src/DynamicData.Tests/Cache/AutoRefreshFixture.Base.cs @@ -1,16 +1,3 @@ -using System; -using System.Linq; -using System.Reactive.Concurrency; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using Microsoft.Reactive.Testing; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.Cache; public static partial class AutoRefreshFixture @@ -31,7 +18,6 @@ public void ChangeSetBufferIsGiven_PropertyChangedNotificationsAreBufferedOnSche var scheduler = new TestScheduler(); - // UUT Initialization using var subscription = BuildUut( source: source.Connect(), @@ -46,7 +32,6 @@ public void ChangeSetBufferIsGiven_PropertyChangedNotificationsAreBufferedOnSche results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "3 items were added to the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (publish property change notification) ++item2.Value; @@ -54,7 +39,6 @@ public void ChangeSetBufferIsGiven_PropertyChangedNotificationsAreBufferedOnSche results.RecordedChangeSets.Skip(1).Should().BeEmpty("the property change notification should have been buffered"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (advance time, within buffer window) scheduler.AdvanceTo(TimeSpan.FromSeconds(5).Ticks); @@ -62,7 +46,6 @@ public void ChangeSetBufferIsGiven_PropertyChangedNotificationsAreBufferedOnSche results.RecordedChangeSets.Skip(1).Should().BeEmpty("the buffer window has not yet ended"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (advance time, to buffer window) scheduler.AdvanceTo(TimeSpan.FromSeconds(10).Ticks); @@ -74,7 +57,6 @@ public void ChangeSetBufferIsGiven_PropertyChangedNotificationsAreBufferedOnSche results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "no items should have changed, within the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (publish property change notification) ++item1.Value; @@ -82,7 +64,6 @@ public void ChangeSetBufferIsGiven_PropertyChangedNotificationsAreBufferedOnSche results.RecordedChangeSets.Skip(2).Should().BeEmpty("the property change notification should have been buffered"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (advance time, within buffer window) scheduler.AdvanceTo(TimeSpan.FromSeconds(15).Ticks); @@ -90,7 +71,6 @@ public void ChangeSetBufferIsGiven_PropertyChangedNotificationsAreBufferedOnSche results.RecordedChangeSets.Skip(2).Should().BeEmpty("the buffer window has not yet ended"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (publish additional property change notification) ++item3.Value; @@ -98,7 +78,6 @@ public void ChangeSetBufferIsGiven_PropertyChangedNotificationsAreBufferedOnSche results.RecordedChangeSets.Skip(2).Should().BeEmpty("the property change notification should have been buffered"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (advance time, to buffer window) scheduler.AdvanceTo(TimeSpan.FromSeconds(20).Ticks); @@ -110,7 +89,6 @@ public void ChangeSetBufferIsGiven_PropertyChangedNotificationsAreBufferedOnSche results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "no items should have changed, within the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (normal refresh) source.Refresh(item2); @@ -140,7 +118,6 @@ public void ItemIsAdded_SubscribesToPropertyChanged() results.RecordedChangeSets.Should().BeEmpty("no source operations were performed"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item1 = new Item() { Id = 1 }; var item2 = new Item() { Id = 2 }; @@ -162,7 +139,7 @@ public void ItemIsAdded_SubscribesToPropertyChanged() public void ItemIsMoved_NotificationPropagates() { // Setup - using var source = new Subject>(); + using var source = new Signal>(); var item1 = new Item() { Id = 1 }; var item2 = new Item() { Id = 2 }; @@ -192,7 +169,6 @@ public void ItemIsMoved_NotificationPropagates() "item indexes should propagate"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.OnNext(new ChangeSet() { @@ -226,7 +202,6 @@ public void ItemIsRefreshed_NotificationPropagates() source.AddOrUpdate(new[] { item1, item2, item3 }); - // UUT Initialization using var subscription = BuildUut(source.Connect()) .ValidateSynchronization() @@ -262,7 +237,6 @@ public void ItemIsRemoved_UnsubscribesFromPropertyChanged() source.AddOrUpdate(new[] { item1, item2, item3 }); - // UUT Initialization using var subscription = BuildUut(source.Connect()) .ValidateSynchronization() @@ -274,7 +248,6 @@ public void ItemIsRemoved_UnsubscribesFromPropertyChanged() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "3 items were added to the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.Remove(item2); @@ -300,7 +273,6 @@ public void ItemIsUpdated_ReSubscribesToPropertyChanged() source.AddOrUpdate(new[] { item1, item2, item3 }); - // UUT Initialization using var subscription = BuildUut(source.Connect()) .ValidateSynchronization() @@ -312,7 +284,6 @@ public void ItemIsUpdated_ReSubscribesToPropertyChanged() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "3 items were added to the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item4 = new Item() { Id = 2 }; source.AddOrUpdate(item4); @@ -340,7 +311,6 @@ public void PropertyChangedOccurs_ItemRefreshes() source.AddOrUpdate(new[] { item1, item2, item3 }); - // UUT Initialization using var subscription = BuildUut(source.Connect()) .ValidateSynchronization() @@ -352,7 +322,6 @@ public void PropertyChangedOccurs_ItemRefreshes() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "3 items were added to the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action ++item2.Value; @@ -379,7 +348,6 @@ public void PropertyChangeThrottleIsGiven_PropertyChangedNotificationsAreThrottl var scheduler = new TestScheduler(); - // UUT Initialization using var subscription = BuildUut( source: source.Connect(), @@ -394,7 +362,6 @@ public void PropertyChangeThrottleIsGiven_PropertyChangedNotificationsAreThrottl results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "3 items were added to the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (publish property change notification) ++item2.Value; @@ -402,7 +369,6 @@ public void PropertyChangeThrottleIsGiven_PropertyChangedNotificationsAreThrottl results.RecordedChangeSets.Skip(1).Should().BeEmpty("the throttle window has not yet ended"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (publish additional property change notification, immediately) ++item2.Value; @@ -410,7 +376,6 @@ public void PropertyChangeThrottleIsGiven_PropertyChangedNotificationsAreThrottl results.RecordedChangeSets.Skip(1).Should().BeEmpty("the throttle window has not yet ended"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (advance time to end of throttle window) scheduler.AdvanceTo(TimeSpan.FromSeconds(10).Ticks); @@ -422,7 +387,6 @@ public void PropertyChangeThrottleIsGiven_PropertyChangedNotificationsAreThrottl results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "no items should have changed, within the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (publish property change notification) ++item2.Value; @@ -430,7 +394,6 @@ public void PropertyChangeThrottleIsGiven_PropertyChangedNotificationsAreThrottl results.RecordedChangeSets.Skip(2).Should().BeEmpty("the throttle window has not yet ended"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (publish additional property change notification, within throttle window) scheduler.AdvanceTo(TimeSpan.FromSeconds(15).Ticks); ++item2.Value; @@ -440,7 +403,6 @@ public void PropertyChangeThrottleIsGiven_PropertyChangedNotificationsAreThrottl results.RecordedChangeSets.Skip(2).Should().BeEmpty("the throttle window has not yet ended"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (advance time to end of original throttle window) scheduler.AdvanceTo(TimeSpan.FromSeconds(20).Ticks); @@ -448,7 +410,6 @@ public void PropertyChangeThrottleIsGiven_PropertyChangedNotificationsAreThrottl results.RecordedChangeSets.Skip(2).Should().BeEmpty("the throttle window should have been extended"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (advance time to end of throttle window) scheduler.AdvanceTo(TimeSpan.FromSeconds(25).Ticks); @@ -469,7 +430,6 @@ public void SourceCompletesWhenEmpty_CompletionPropagates(NotificationStrategy n // Setup using var source = new TestSourceCache(Item.SelectId); - // UUT Initialization & Action if (notificationStrategy is NotificationStrategy.Immediate) source.Complete(); @@ -501,7 +461,6 @@ public void SourceCompletesWhenNotEmpty_CompletionDoesNotPropagate(NotificationS source.AddOrUpdate(new[] { item1, item2, item3 }); - // UUT Initialization & Action (source completion) if (notificationStrategy is NotificationStrategy.Immediate) source.Complete(); @@ -536,7 +495,6 @@ public void SourceFails_ErrorPropagates(NotificationStrategy notificationStrateg var error = new Exception("Test"); - // UUT Initialization & Action if (notificationStrategy is NotificationStrategy.Immediate) source.SetError(error); @@ -569,7 +527,7 @@ public void SourceIsNull_ThrowsException() public void SubscriptionIsDisposed_SubscriptionDisposalPropagates() { // Setup - using var source = new Subject>(); + using var source = new Signal>(); var item1 = new Item() { Id = 1 }; var item2 = new Item() { Id = 2 }; @@ -582,7 +540,6 @@ public void SubscriptionIsDisposed_SubscriptionDisposalPropagates() new Change(reason: ChangeReason.Add, key: item3.Id, current: item3) }; - // UUT Initialization using var subscription = BuildUut(source.Prepend(initialChangeset)) .ValidateSynchronization() @@ -594,7 +551,6 @@ public void SubscriptionIsDisposed_SubscriptionDisposalPropagates() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1, item2, item3 }, "3 items were added to the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action subscription.Dispose(); diff --git a/src/DynamicData.Tests/Cache/AutoRefreshFixture.WithPropertyAccessor.cs b/src/DynamicData.Tests/Cache/AutoRefreshFixture.WithPropertyAccessor.cs index c948aedb8..febeb74f8 100644 --- a/src/DynamicData.Tests/Cache/AutoRefreshFixture.WithPropertyAccessor.cs +++ b/src/DynamicData.Tests/Cache/AutoRefreshFixture.WithPropertyAccessor.cs @@ -1,13 +1,4 @@ -using System; -using System.Linq; using System.Linq.Expressions; -using System.Reactive.Concurrency; -using System.Reactive.Linq; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; namespace DynamicData.Tests.Cache; @@ -36,7 +27,6 @@ public void PropertyChangedNotificationDoesNotMatchPropertyAccessor_IgnoresNotif source.AddOrUpdate(new[] { item1, item2, item3 }); - // UUT Initialization using var subscription = BuildUut(source.Connect()) .ValidateSynchronization() @@ -48,7 +38,6 @@ public void PropertyChangedNotificationDoesNotMatchPropertyAccessor_IgnoresNotif results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "3 items were added to the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action ++item2.OtherValue; diff --git a/src/DynamicData.Tests/Cache/AutoRefreshFixture.WithoutPropertyAccessor.cs b/src/DynamicData.Tests/Cache/AutoRefreshFixture.WithoutPropertyAccessor.cs index 402d175ff..a0d7b3e67 100644 --- a/src/DynamicData.Tests/Cache/AutoRefreshFixture.WithoutPropertyAccessor.cs +++ b/src/DynamicData.Tests/Cache/AutoRefreshFixture.WithoutPropertyAccessor.cs @@ -1,12 +1,3 @@ -using System; -using System.Linq; -using System.Reactive.Concurrency; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.Cache; public static partial class AutoRefreshFixture @@ -26,7 +17,6 @@ public void PropertyChangedNotificationDoesNotSpecifyPropertyName_ItemRefreshes( source.AddOrUpdate(new[] { item1, item2, item3 }); - // UUT Initialization using var subscription = BuildUut(source.Connect()) .ValidateSynchronization() @@ -38,7 +28,6 @@ public void PropertyChangedNotificationDoesNotSpecifyPropertyName_ItemRefreshes( results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "3 items were added to the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action item2.RaiseAllPropertiesChanged(); diff --git a/src/DynamicData.Tests/Cache/AutoRefreshFixture.cs b/src/DynamicData.Tests/Cache/AutoRefreshFixture.cs index be5b936a3..e0422d6f7 100644 --- a/src/DynamicData.Tests/Cache/AutoRefreshFixture.cs +++ b/src/DynamicData.Tests/Cache/AutoRefreshFixture.cs @@ -1,6 +1,4 @@ -using System.ComponentModel; - -namespace DynamicData.Tests.Cache; +namespace DynamicData.Tests.Cache; public static partial class AutoRefreshFixture { diff --git a/src/DynamicData.Tests/Cache/AutoRefreshOnObservableFixture.Base.cs b/src/DynamicData.Tests/Cache/AutoRefreshOnObservableFixture.Base.cs index a09e0ea9c..190f1e1d6 100644 --- a/src/DynamicData.Tests/Cache/AutoRefreshOnObservableFixture.Base.cs +++ b/src/DynamicData.Tests/Cache/AutoRefreshOnObservableFixture.Base.cs @@ -1,17 +1,3 @@ -using System; -using System.Linq; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using Microsoft.Reactive.Testing; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.Cache; public static partial class AutoRefreshOnObservableFixture @@ -32,7 +18,6 @@ public void ChangeSetBufferIsGiven_ReevaluatorNotificationsAreBufferedOnSchedule var scheduler = new TestScheduler(); - // UUT Initialization using var subscription = BuildUut( source: source.Connect(), @@ -48,7 +33,6 @@ public void ChangeSetBufferIsGiven_ReevaluatorNotificationsAreBufferedOnSchedule results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "3 items were added to the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (publish reevaluator notification) ++item2.Value; @@ -56,7 +40,6 @@ public void ChangeSetBufferIsGiven_ReevaluatorNotificationsAreBufferedOnSchedule results.RecordedChangeSets.Skip(1).Should().BeEmpty("the reevaluator notification should have been buffered"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (advance time, within buffer window) scheduler.AdvanceTo(TimeSpan.FromSeconds(5).Ticks); @@ -64,7 +47,6 @@ public void ChangeSetBufferIsGiven_ReevaluatorNotificationsAreBufferedOnSchedule results.RecordedChangeSets.Skip(1).Should().BeEmpty("the buffer window has not yet ended"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (advance time, to buffer window) scheduler.AdvanceTo(TimeSpan.FromSeconds(10).Ticks); @@ -76,7 +58,6 @@ public void ChangeSetBufferIsGiven_ReevaluatorNotificationsAreBufferedOnSchedule results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "no items should have changed, within the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (publish reevaluator notification) ++item1.Value; @@ -84,7 +65,6 @@ public void ChangeSetBufferIsGiven_ReevaluatorNotificationsAreBufferedOnSchedule results.RecordedChangeSets.Skip(2).Should().BeEmpty("the reevaluator notification should have been buffered"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (advance time, within buffer window) scheduler.AdvanceTo(TimeSpan.FromSeconds(15).Ticks); @@ -92,7 +72,6 @@ public void ChangeSetBufferIsGiven_ReevaluatorNotificationsAreBufferedOnSchedule results.RecordedChangeSets.Skip(2).Should().BeEmpty("the buffer window has not yet ended"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (publish additional reevaluator notification) ++item3.Value; @@ -100,7 +79,6 @@ public void ChangeSetBufferIsGiven_ReevaluatorNotificationsAreBufferedOnSchedule results.RecordedChangeSets.Skip(2).Should().BeEmpty("the reevaluator notification should have been buffered"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (advance time, to buffer window) scheduler.AdvanceTo(TimeSpan.FromSeconds(20).Ticks); @@ -112,7 +90,6 @@ public void ChangeSetBufferIsGiven_ReevaluatorNotificationsAreBufferedOnSchedule results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "no items should have changed, within the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (normal refresh) source.Refresh(item2); @@ -136,7 +113,6 @@ public void ItemIsAdded_SubscribesToReevaluator() using var item2 = new Item() { Id = 2 }; using var item3 = new Item() { Id = 3 }; - // UUT Initialization using var subscription = BuildUut( source: source.Connect(), @@ -149,7 +125,6 @@ public void ItemIsAdded_SubscribesToReevaluator() results.RecordedChangeSets.Should().BeEmpty("no source operations were performed"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.AddOrUpdate(new[] { item1, item2, item3 }); @@ -167,7 +142,7 @@ public void ItemIsAdded_SubscribesToReevaluator() public void ItemIsMoved_NotificationPropagates() { // Setup - using var source = new Subject>(); + using var source = new Signal>(); using var item1 = new Item() { Id = 1 }; using var item2 = new Item() { Id = 2 }; @@ -199,7 +174,6 @@ public void ItemIsMoved_NotificationPropagates() "item indexes should propagate"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.OnNext(new ChangeSet() { @@ -235,7 +209,6 @@ public void ItemIsRefreshed_NotificationPropagates() var reevaluatorInvocationCount = 0; - // UUT Initialization using var subscription = BuildUut( source: source.Connect(), @@ -281,7 +254,6 @@ public void ItemIsRemoved_UnsubscribesFromReevaluator() source.AddOrUpdate(new[] { item1, item2, item3 }); - // UUT Initialization using var subscription = BuildUut( source: source.Connect(), @@ -295,7 +267,6 @@ public void ItemIsRemoved_UnsubscribesFromReevaluator() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "3 items were added to the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.Remove(item2); @@ -321,7 +292,6 @@ public void ItemIsUpdated_ReInvokesReevaluator() source.AddOrUpdate(new[] { item1, item2, item3 }); - // UUT Initialization using var subscription = BuildUut( source: source.Connect(), @@ -335,7 +305,6 @@ public void ItemIsUpdated_ReInvokesReevaluator() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "3 items were added to the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action using var item4 = new Item() { Id = 2 }; source.AddOrUpdate(item4); @@ -350,7 +319,6 @@ public void ItemIsUpdated_ReInvokesReevaluator() item1.HasObservers.Should().BeTrue("the item was not removed from the source"); item3.HasObservers.Should().BeTrue("the item was not removed from the source"); - // UUT Action (updated item publishes reevaluator notification) ++item4.Value; @@ -377,7 +345,6 @@ public void ReevaluatorCompletesWhenNotOnlyItemInSource_CompletionWaitsForSource source.AddOrUpdate(new[] { item1, item2, item3 }); - // UUT Initialization & Action (initial completion) if (notificationStrategy is NotificationStrategy.Immediate) item2.Complete(); @@ -397,7 +364,6 @@ public void ReevaluatorCompletesWhenNotOnlyItemInSource_CompletionWaitsForSource results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "3 items were added to the source"); results.HasCompleted.Should().BeFalse("not all notification sources have completed"); - // UUT Action (remaining reevaluator completions) item1.Complete(); item3.Complete(); @@ -406,7 +372,6 @@ public void ReevaluatorCompletesWhenNotOnlyItemInSource_CompletionWaitsForSource results.RecordedChangeSets.Skip(1).Should().BeEmpty("no source operations were performed"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (source completion) source.Complete(); @@ -427,7 +392,6 @@ public void ReevaluatorCompletesWhenOnlyItemInSource_CompletionWaitsForSourceCom source.AddOrUpdate(item); - // UUT Initialization & Action (reevaluator completion) if (notificationStrategy is NotificationStrategy.Immediate) item.Complete(); @@ -447,7 +411,6 @@ public void ReevaluatorCompletesWhenOnlyItemInSource_CompletionWaitsForSourceCom results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "1 item was added to the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (source completion) source.Complete(); @@ -468,7 +431,6 @@ public void ReevaluatorEmitsAsynchronously_ItemRefreshes() source.AddOrUpdate(new[] { item1, item2, item3 }); - // UUT Initialization using var subscription = BuildUut( source: source.Connect(), @@ -482,7 +444,6 @@ public void ReevaluatorEmitsAsynchronously_ItemRefreshes() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "3 items were added to the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action ++item2.Value; @@ -507,7 +468,6 @@ public void ReevaluatorEmitsImmediately_ItemDoesNotRefresh() source.AddOrUpdate(new[] { item1, item2, item3 }); - // UUT Initialization & Action using var subscription = BuildUut( source: source.Connect(), @@ -539,7 +499,6 @@ public void ReevaluatorFails_ErrorPropagates(NotificationStrategy notificationSt var error = new Exception("Test"); - // UUT Initialization & Action if (notificationStrategy is NotificationStrategy.Immediate) item2.SetError(error); @@ -572,7 +531,6 @@ public void ReevaluatorThrows_ExceptionPropagates() var error = new Exception("Test"); - // UUT Initialization using var subscription = BuildUut( source: source.Connect(), @@ -585,7 +543,6 @@ public void ReevaluatorThrows_ExceptionPropagates() results.RecordedChangeSets.Should().BeEmpty("no initial changesets were published"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action using var item = new Item() { Id = 1 }; source.AddOrUpdate(item); @@ -602,7 +559,6 @@ public void SourceCompletesWhenEmpty_CompletionPropagates(NotificationStrategy n // Setup using var source = new TestSourceCache(Item.SelectId); - // UUT Initialization & Action if (notificationStrategy is NotificationStrategy.Immediate) source.Complete(); @@ -636,7 +592,6 @@ public void SourceCompletesWhenNotEmpty_CompletionWaitsForReevaluatorCompletions source.AddOrUpdate(new[] { item1, item2, item3 }); - // UUT Initialization & Action (source completion) if (notificationStrategy is NotificationStrategy.Immediate) source.Complete(); @@ -656,7 +611,6 @@ public void SourceCompletesWhenNotEmpty_CompletionWaitsForReevaluatorCompletions results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "3 items were added to the source"); results.HasCompleted.Should().BeFalse("not all notification sources have completed"); - // UUT Action (initial reevaluator completion) item2.Complete(); @@ -664,7 +618,6 @@ public void SourceCompletesWhenNotEmpty_CompletionWaitsForReevaluatorCompletions results.RecordedChangeSets.Skip(1).Should().BeEmpty("no source operations were performed"); results.HasCompleted.Should().BeFalse("not all notification sources have completed"); - // UUT Action (remaining reevaluator completions) item1.Complete(); item3.Complete(); @@ -690,7 +643,6 @@ public void SourceFails_ErrorPropagates(NotificationStrategy notificationStrateg var error = new Exception("Test"); - // UUT Initialization & Action if (notificationStrategy is NotificationStrategy.Immediate) source.SetError(error); @@ -727,7 +679,7 @@ public void SourceIsNull_ThrowsException() public void SubscriptionIsDisposed_SubscriptionDisposalPropagates() { // Setup - using var source = new Subject>(); + using var source = new Signal>(); using var item1 = new Item() { Id = 1 }; using var item2 = new Item() { Id = 2 }; @@ -740,7 +692,6 @@ public void SubscriptionIsDisposed_SubscriptionDisposalPropagates() new Change(reason: ChangeReason.Add, key: item3.Id, current: item3) }; - // UUT Initialization using var subscription = BuildUut( source: source.Prepend(initialChangeset), @@ -754,7 +705,6 @@ public void SubscriptionIsDisposed_SubscriptionDisposalPropagates() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1, item2, item3 }, "3 items were added to the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action subscription.Dispose(); diff --git a/src/DynamicData.Tests/Cache/AutoRefreshOnObservableFixture.WithKey.cs b/src/DynamicData.Tests/Cache/AutoRefreshOnObservableFixture.WithKey.cs index 4fa7ec9d7..2fc259bb8 100644 --- a/src/DynamicData.Tests/Cache/AutoRefreshOnObservableFixture.WithKey.cs +++ b/src/DynamicData.Tests/Cache/AutoRefreshOnObservableFixture.WithKey.cs @@ -1,11 +1,3 @@ -using System; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Linq; - -using FluentAssertions; -using Xunit; - namespace DynamicData.Tests.Cache; public static partial class AutoRefreshOnObservableFixture diff --git a/src/DynamicData.Tests/Cache/AutoRefreshOnObservableFixture.WithoutKey.cs b/src/DynamicData.Tests/Cache/AutoRefreshOnObservableFixture.WithoutKey.cs index 3e96d6e96..585c2f8e4 100644 --- a/src/DynamicData.Tests/Cache/AutoRefreshOnObservableFixture.WithoutKey.cs +++ b/src/DynamicData.Tests/Cache/AutoRefreshOnObservableFixture.WithoutKey.cs @@ -1,11 +1,3 @@ -using System; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Linq; - -using FluentAssertions; -using Xunit; - namespace DynamicData.Tests.Cache; public static partial class AutoRefreshOnObservableFixture diff --git a/src/DynamicData.Tests/Cache/AutoRefreshOnObservableFixture.cs b/src/DynamicData.Tests/Cache/AutoRefreshOnObservableFixture.cs index 4e6f974a2..738f45a8f 100644 --- a/src/DynamicData.Tests/Cache/AutoRefreshOnObservableFixture.cs +++ b/src/DynamicData.Tests/Cache/AutoRefreshOnObservableFixture.cs @@ -1,9 +1,3 @@ -using System; -using System.Reactive; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Threading; - namespace DynamicData.Tests.Cache; public static partial class AutoRefreshOnObservableFixture @@ -23,25 +17,25 @@ public static IObservable ObserveValue(Item item) observer.OnNext(item._value); return item._valueChanged.SubscribeSafe(observer); }); - + public static IObservable ObserveValueChanged(Item item) => item._valueChanged.Select(static _ => Unit.Default); - + public static int SelectId(Item item) => item.Id; - + public Item() => _valueChanged = new(); - + public required int Id { get => _id; init => _id = value; } - + public bool HasObservers => _valueChanged.HasObservers; - + public int Value { get => _value; @@ -49,31 +43,31 @@ public int Value { if (_value == value) return; - + _value = value; _valueChanged.OnNext(value); } } - + public void Complete() => _valueChanged.OnCompleted(); - + public void Dispose() { if (Interlocked.Exchange(ref _hasDisposed, true)) return; - + _valueChanged.OnCompleted(); _valueChanged.Dispose(); } - + public void SetError(Exception error) => _valueChanged.OnError(error); - - private readonly int _id; - private readonly Subject _valueChanged; - - private bool _hasDisposed; - private int _value; + + private readonly int _id; + private readonly Signal _valueChanged; + + private bool _hasDisposed; + private int _value; } } diff --git a/src/DynamicData.Tests/Cache/BatchFixture.cs b/src/DynamicData.Tests/Cache/BatchFixture.cs index d22e060fe..e750f639d 100644 --- a/src/DynamicData.Tests/Cache/BatchFixture.cs +++ b/src/DynamicData.Tests/Cache/BatchFixture.cs @@ -1,13 +1,5 @@ -using System; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Microsoft.Reactive.Testing; - -using Xunit; - namespace DynamicData.Tests.Cache; public class BatchFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/BatchIfFixture.cs b/src/DynamicData.Tests/Cache/BatchIfFixture.cs index 87f47356e..c6e50dec1 100644 --- a/src/DynamicData.Tests/Cache/BatchIfFixture.cs +++ b/src/DynamicData.Tests/Cache/BatchIfFixture.cs @@ -1,20 +1,10 @@ -using System; -using System.Reactive.Linq; -using System.Reactive.Subjects; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Microsoft.Reactive.Testing; - -using Xunit; - namespace DynamicData.Tests.Cache; public class BatchIfFixture : IDisposable { - private readonly ISubject _pausingSubject = new Subject(); + private readonly ISignal _pausingSubject = new Signal(); private readonly ChangeSetAggregator _results; @@ -28,7 +18,7 @@ public BatchIfFixture() _source = new SourceCache(p => p.Key); _results = _source.Connect().BatchIf(_pausingSubject, _scheduler).AsAggregator(); - // _results = _source.Connect().BatchIf(new BehaviorSubject(true), scheduler: _scheduler).AsAggregator(); + // _results = _source.Connect().BatchIf(new StateSignal(true), scheduler: _scheduler).AsAggregator(); } [Fact] @@ -115,6 +105,7 @@ public void Dispose() { _results.Dispose(); _source.Dispose(); + _pausingSubject.Dispose(); } [Fact] diff --git a/src/DynamicData.Tests/Cache/BatchIfWithTimeOutFixture.cs b/src/DynamicData.Tests/Cache/BatchIfWithTimeOutFixture.cs index 239dba558..64f28e246 100644 --- a/src/DynamicData.Tests/Cache/BatchIfWithTimeOutFixture.cs +++ b/src/DynamicData.Tests/Cache/BatchIfWithTimeOutFixture.cs @@ -1,16 +1,5 @@ -using System; -using System.Reactive; -using System.Reactive.Linq; -using System.Reactive.Subjects; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Microsoft.Reactive.Testing; - -using Xunit; - namespace DynamicData.Tests.Cache; public class BatchIfWithTimeoutFixture : IDisposable @@ -30,7 +19,7 @@ public BatchIfWithTimeoutFixture() [Fact] public void InitialPause() { - var pausingSubject = new Subject(); + var pausingSubject = new Signal(); using var results = _source.Connect().BatchIf(pausingSubject, true, _scheduler).AsAggregator(); // no results because the initial pause state is pause _source.AddOrUpdate(new Person("A", 1)); @@ -57,7 +46,7 @@ public void InitialPause() [Fact] public void Timeout() { - var pausingSubject = new Subject(); + var pausingSubject = new Signal(); using var results = _source.Connect().BatchIf(pausingSubject, TimeSpan.FromSeconds(1), _scheduler).AsAggregator(); // no results because the initial pause state is pause _source.AddOrUpdate(new Person("A", 1)); @@ -88,7 +77,7 @@ public void Timeout() public class BatchIfWithTimeOutFixture : IDisposable { - private readonly ISubject _pausingSubject = new Subject(); + private readonly ISignal _pausingSubject = new Signal(); private readonly ChangeSetAggregator _results; @@ -128,6 +117,7 @@ public void Dispose() _results.Dispose(); _source.Dispose(); _pausingSubject.OnCompleted(); + _pausingSubject.Dispose(); } [Fact] diff --git a/src/DynamicData.Tests/Cache/BufferInitialFixture.cs b/src/DynamicData.Tests/Cache/BufferInitialFixture.cs index eafe1bf6e..656643b7c 100644 --- a/src/DynamicData.Tests/Cache/BufferInitialFixture.cs +++ b/src/DynamicData.Tests/Cache/BufferInitialFixture.cs @@ -1,15 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Microsoft.Reactive.Testing; - -using Xunit; - namespace DynamicData.Tests.Cache; public class BufferInitialFixture diff --git a/src/DynamicData.Tests/Cache/CrossCacheDeadlockStressTest.cs b/src/DynamicData.Tests/Cache/CrossCacheDeadlockStressTest.cs index 8915bc6bc..9bb1fb427 100644 --- a/src/DynamicData.Tests/Cache/CrossCacheDeadlockStressTest.cs +++ b/src/DynamicData.Tests/Cache/CrossCacheDeadlockStressTest.cs @@ -1,26 +1,10 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.Generic; -using System.Linq; -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Reactive.Threading.Tasks; -using System.Threading; -using System.Threading.Tasks; - using Bogus; using DynamicData.Binding; -using DynamicData.Kernel; - -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.Cache; @@ -217,16 +201,16 @@ public async Task AllOperators_CrossCache_NoDeadlock_CorrectResults() using var treeSource = new SourceCache(m => m.Id); // ── Subjects for dynamic parameters ───────────────────────── - using var pageRequests = new BehaviorSubject(new PageRequest(1, pageSize)); - using var virtualRequests = new BehaviorSubject(new VirtualRequest(0, virtualSize)); - using var pauseBatch = new BehaviorSubject(false); - using var forceTransform = new Subject>(); - using var switchSource = new BehaviorSubject>>(sourceA.Connect()); - using var comparerSubject = new BehaviorSubject>(RatingDescComparer.Instance); + using var pageRequests = new StateSignal(new PageRequest(1, pageSize)); + using var virtualRequests = new StateSignal(new VirtualRequest(0, virtualSize)); + using var pauseBatch = new StateSignal(false); + using var forceTransform = new Signal>(); + using var switchSource = new StateSignal>>(sourceA.Connect()); + using var comparerSubject = new StateSignal>(RatingDescComparer.Instance); // Stop signal for operators with a library gap — they don't forward OnCompleted: // Static Combiner (Or/And/Except), BatchIf, TransformToTree, Switch - using var stopSignal = new Subject(); + using var stopSignal = new Signal(); // ── Completion tracking ───────────────────────────────────── var completionTasks = new List(); @@ -476,8 +460,8 @@ void TrackIntoCache(IObservable> pipeline, SourceC .TakeUntil(stopSignal)); // Second Page + Virtualise + BatchIf uses on sourceB - using var pageBSubject = new BehaviorSubject(new PageRequest(1, pageSize)); - using var pauseB = new BehaviorSubject(false); + using var pageBSubject = new StateSignal(new PageRequest(1, pageSize)); + using var pauseB = new StateSignal(false); var pageBCache = TrackCache( sourceB.Connect() @@ -486,7 +470,7 @@ void TrackIntoCache(IObservable> pipeline, SourceC .BatchIf(pauseB, false, (TimeSpan?)null) // BatchIf [2] .TakeUntil(stopSignal)); - using var virtBRequests = new BehaviorSubject(new VirtualRequest(0, virtualSize)); + using var virtBRequests = new StateSignal(new VirtualRequest(0, virtualSize)); var virtBCache = TrackCache( sourceB.Connect() .Sort(PriorityAscComparer.Instance) // Sort [4] @@ -538,7 +522,7 @@ void TrackIntoCache(IObservable> pipeline, SourceC completionNames.Add("QueryWhenChanged-A"); // Second Switch + GroupOnImmutable + GroupOnObservable - using var switchSource2 = new BehaviorSubject>>(sourceB.Connect()); + using var switchSource2 = new StateSignal>>(sourceB.Connect()); var switchCache2 = TrackCache( switchSource2.Switch() // Switch [2] .TakeUntil(stopSignal)); @@ -657,7 +641,7 @@ void TrackIntoCache(IObservable> pipeline, SourceC var finalAKeys = new HashSet(sourceA.Keys); var finalBKeys = new HashSet(sourceB.Keys); - // 2. Complete all BehaviorSubjects so multi-source operators can complete + // 2. Complete all StateSignals so multi-source operators can complete forceTransform.OnCompleted(); pageRequests.OnCompleted(); pageBSubject.OnCompleted(); @@ -797,8 +781,6 @@ static int CountAll(IEnumerable> nodes) treeCache.Items.Any(n => n.Children.Count > 0).Should().BeTrue("Tree has child nodes"); treeCache2.Count.Should().BeGreaterThan(0, "Tree2 produces results"); - - // Side chains lastQuery.Should().NotBeNull("QueryWhenChanged(B) fired"); lastQueryA.Should().NotBeNull("QueryWhenChanged(A) fired"); diff --git a/src/DynamicData.Tests/Cache/DeadlockTortureTest.cs b/src/DynamicData.Tests/Cache/DeadlockTortureTest.cs index 4f4ddf9a4..c53d55776 100644 --- a/src/DynamicData.Tests/Cache/DeadlockTortureTest.cs +++ b/src/DynamicData.Tests/Cache/DeadlockTortureTest.cs @@ -2,18 +2,9 @@ // 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.Reactive.Subjects; -using System.Threading; -using System.Threading.Tasks; - using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; /// @@ -26,6 +17,7 @@ namespace DynamicData.Tests.Cache; /// On main (Synchronize(lock)): deadlocks reliably within seconds. /// On the PR branch (SynchronizeSafe queue-drain): no deadlock possible. /// +[Collection(IntegrationTestFixtureBase.CollectionName)] public sealed class DeadlockTortureTest { private const int ItemCount = 200; @@ -66,24 +58,24 @@ [Fact] public async Task GroupOn_DoesNotDeadlock() => [Fact] public async Task Page_DoesNotDeadlock() { - using var req = new BehaviorSubject(new PageRequest(1, 50)); + using var req = new StateSignal(new PageRequest(1, 50)); (await RunBidirectionalDeadlockTest(s => s.Sort(SortExpressionComparer.Ascending(p => p.Age)).Page(req))).Should().BeTrue(); } [Fact] public async Task Virtualise_DoesNotDeadlock() { - using var req = new BehaviorSubject(new VirtualRequest(0, 50)); + using var req = new StateSignal(new VirtualRequest(0, 50)); (await RunBidirectionalDeadlockTest(s => s.Sort(SortExpressionComparer.Ascending(p => p.Age)).Virtualise(req))).Should().BeTrue(); } [Fact] public async Task TransformWithForce_DoesNotDeadlock() { - using var force = new Subject>(); + using var force = new Signal>(); (await RunBidirectionalDeadlockTest(s => s.Transform((p, k) => new Person("T-" + p.Name, p.Age), force))).Should().BeTrue(); } [Fact] public async Task BatchIf_DoesNotDeadlock() => - (await RunBidirectionalDeadlockTest(s => s.BatchIf(new BehaviorSubject(false), false, (TimeSpan?)null))).Should().BeTrue(); + (await RunBidirectionalDeadlockTest(s => s.BatchIf(new StateSignal(false), false, (TimeSpan?)null))).Should().BeTrue(); [Fact] public async Task DisposeMany_DoesNotDeadlock() => (await RunBidirectionalDeadlockTest(s => s.DisposeMany())).Should().BeTrue(); @@ -93,8 +85,8 @@ [Fact] public async Task OnItemRemoved_DoesNotDeadlock() => [Fact] public async Task AllDangerous_Stacked_DoNotDeadlock() { - using var pageReq = new BehaviorSubject(new PageRequest(1, 100)); - using var force = new Subject>(); + using var pageReq = new StateSignal(new PageRequest(1, 100)); + using var force = new Signal>(); (await RunBidirectionalDeadlockTest( s => s.AutoRefresh(p => p.Age) .Filter(p => p.Age >= 0) @@ -108,8 +100,8 @@ [Fact] public async Task AllDangerous_Stacked_DoNotDeadlock() [Fact] public async Task MultiplePairs_Simultaneous_NoDeadlock() { - using var pageReq = new BehaviorSubject(new PageRequest(1, 50)); - using var virtReq = new BehaviorSubject(new VirtualRequest(0, 50)); + using var pageReq = new StateSignal(new PageRequest(1, 50)); + using var virtReq = new StateSignal(new VirtualRequest(0, 50)); var results = await Task.WhenAll( RunBidirectionalDeadlockTest(s => s.Sort(SortExpressionComparer.Ascending(p => p.Age)), 30), RunBidirectionalDeadlockTest(s => s.AutoRefresh(p => p.Age), 30), @@ -118,7 +110,7 @@ [Fact] public async Task MultiplePairs_Simultaneous_NoDeadlock() RunBidirectionalDeadlockTest(s => s.DisposeMany(), 30), RunBidirectionalDeadlockTest(s => s.Sort(SortExpressionComparer.Ascending(p => p.Age)).Page(pageReq), 30), RunBidirectionalDeadlockTest(s => s.Sort(SortExpressionComparer.Ascending(p => p.Age)).Virtualise(virtReq), 30), - RunBidirectionalDeadlockTest(s => s.BatchIf(new BehaviorSubject(false), false, (TimeSpan?)null), 30)); + RunBidirectionalDeadlockTest(s => s.BatchIf(new StateSignal(false), false, (TimeSpan?)null), 30)); results.Should().AllSatisfy(r => r.Should().BeTrue()); } diff --git a/src/DynamicData.Tests/Cache/DeferUntilLoadedFixture.cs b/src/DynamicData.Tests/Cache/DeferUntilLoadedFixture.cs index 1e19ba079..1961f4f2c 100644 --- a/src/DynamicData.Tests/Cache/DeferUntilLoadedFixture.cs +++ b/src/DynamicData.Tests/Cache/DeferUntilLoadedFixture.cs @@ -1,12 +1,5 @@ -using System; -using System.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class DeferAnsdSkipFixture diff --git a/src/DynamicData.Tests/Cache/DisposeManyFixture.cs b/src/DynamicData.Tests/Cache/DisposeManyFixture.cs index b196e1431..c78b21433 100644 --- a/src/DynamicData.Tests/Cache/DisposeManyFixture.cs +++ b/src/DynamicData.Tests/Cache/DisposeManyFixture.cs @@ -1,17 +1,8 @@ -using System; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public sealed class DisposeManyFixture : IDisposable { - private readonly Subject> _changeSetsSource; + private readonly Signal> _changeSetsSource; private readonly SourceCache _itemsSource; diff --git a/src/DynamicData.Tests/Cache/DistinctFixture.cs b/src/DynamicData.Tests/Cache/DistinctFixture.cs index fbbd36c7b..9f34eba0f 100644 --- a/src/DynamicData.Tests/Cache/DistinctFixture.cs +++ b/src/DynamicData.Tests/Cache/DistinctFixture.cs @@ -1,13 +1,5 @@ -using System; -using System.Linq; -using System.Reactive.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class DistinctFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/DynamicAndFixture.cs b/src/DynamicData.Tests/Cache/DynamicAndFixture.cs index c9b79b545..01a8e8617 100644 --- a/src/DynamicData.Tests/Cache/DynamicAndFixture.cs +++ b/src/DynamicData.Tests/Cache/DynamicAndFixture.cs @@ -1,12 +1,5 @@ -using System; -using System.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class DynamicAndFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/DynamicExceptFixture.cs b/src/DynamicData.Tests/Cache/DynamicExceptFixture.cs index 43a993e46..ad08a3681 100644 --- a/src/DynamicData.Tests/Cache/DynamicExceptFixture.cs +++ b/src/DynamicData.Tests/Cache/DynamicExceptFixture.cs @@ -1,12 +1,5 @@ -using System; -using System.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class DynamicExceptFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/DynamicOrFixture.cs b/src/DynamicData.Tests/Cache/DynamicOrFixture.cs index d02c37649..04533075d 100644 --- a/src/DynamicData.Tests/Cache/DynamicOrFixture.cs +++ b/src/DynamicData.Tests/Cache/DynamicOrFixture.cs @@ -1,12 +1,5 @@ -using System; -using System.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class DynamicOrFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/DynamicXorFixture.cs b/src/DynamicData.Tests/Cache/DynamicXorFixture.cs index e2e70119a..d32723a43 100644 --- a/src/DynamicData.Tests/Cache/DynamicXorFixture.cs +++ b/src/DynamicData.Tests/Cache/DynamicXorFixture.cs @@ -1,12 +1,5 @@ -using System; -using System.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class DynamicXorFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/EditDiffChangeSetFixture.cs b/src/DynamicData.Tests/Cache/EditDiffChangeSetFixture.cs index d178c9ea6..3f17c9516 100644 --- a/src/DynamicData.Tests/Cache/EditDiffChangeSetFixture.cs +++ b/src/DynamicData.Tests/Cache/EditDiffChangeSetFixture.cs @@ -1,11 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class EditDiffChangeSetFixture diff --git a/src/DynamicData.Tests/Cache/EditDiffChangeSetOptionalFixture.cs b/src/DynamicData.Tests/Cache/EditDiffChangeSetOptionalFixture.cs index 280f7db33..c55b2def6 100644 --- a/src/DynamicData.Tests/Cache/EditDiffChangeSetOptionalFixture.cs +++ b/src/DynamicData.Tests/Cache/EditDiffChangeSetOptionalFixture.cs @@ -1,19 +1,10 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reactive.Linq; -using DynamicData.Kernel; -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.Cache; public class EditDiffChangeSetOptionalFixture { - private static readonly Optional s_noPerson = Optional.None(); + private static readonly Optional s_noPerson = Optional.None; private const int MaxItems = 1097; @@ -184,7 +175,7 @@ public void ResultFailsIfAndOnlyIfSourceFails (bool failSource) receivedError.Should().Be(failSource ? testException : default); } - private static Optional CreatePerson(int id, string name) => Optional.Some(new Person(id, name)); + private static Optional CreatePerson(int id, string name) => Optional.Some(new Person(id, name)); private class PersonComparer : IEqualityComparer { diff --git a/src/DynamicData.Tests/Cache/EditDiffFixture.cs b/src/DynamicData.Tests/Cache/EditDiffFixture.cs index da98c9aa0..54451a1ff 100644 --- a/src/DynamicData.Tests/Cache/EditDiffFixture.cs +++ b/src/DynamicData.Tests/Cache/EditDiffFixture.cs @@ -1,11 +1,4 @@ -using System; -using System.Linq; - -using DynamicData.Tests.Domain; - -using FluentAssertions; - -using Xunit; +using DynamicData.Tests.Domain; namespace DynamicData.Tests.Cache; diff --git a/src/DynamicData.Tests/Cache/EnsureUniqueKeysFixture.cs b/src/DynamicData.Tests/Cache/EnsureUniqueKeysFixture.cs index fe2d05737..0162dfd54 100644 --- a/src/DynamicData.Tests/Cache/EnsureUniqueKeysFixture.cs +++ b/src/DynamicData.Tests/Cache/EnsureUniqueKeysFixture.cs @@ -1,9 +1,4 @@ -using System; -using System.Linq; using DynamicData.Tests.Domain; -using FluentAssertions; -using Mono.Cecil; -using Xunit; namespace DynamicData.Tests.Cache; @@ -18,7 +13,6 @@ public EnsureUniqueKeysFixture() _results = _source.Connect(suppressEmptyChangeSets: false).EnsureUniqueKeys().AsAggregator(); } - [Fact] public void UniqueForAdds() { @@ -67,7 +61,6 @@ public void Refresh() } - [Fact] public void CompoundRefresh1() { @@ -122,7 +115,6 @@ public void CompoundRefresh3() } - public void Dispose() { _source.Dispose(); diff --git a/src/DynamicData.Tests/Cache/ExceptFixture.cs b/src/DynamicData.Tests/Cache/ExceptFixture.cs index 17c4996e8..a7b68e912 100644 --- a/src/DynamicData.Tests/Cache/ExceptFixture.cs +++ b/src/DynamicData.Tests/Cache/ExceptFixture.cs @@ -1,12 +1,5 @@ -using System; -using System.Collections.Generic; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class ExceptFixture : ExceptFixtureBase diff --git a/src/DynamicData.Tests/Cache/ExpireAfterFixture.ForSource.cs b/src/DynamicData.Tests/Cache/ExpireAfterFixture.ForSource.cs index ad88ee3a8..17c05fc63 100644 --- a/src/DynamicData.Tests/Cache/ExpireAfterFixture.ForSource.cs +++ b/src/DynamicData.Tests/Cache/ExpireAfterFixture.ForSource.cs @@ -1,18 +1,8 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Reactive.Concurrency; -using System.Reactive.Linq; -using System.Threading.Tasks; using Bogus; -using FluentAssertions; -using Xunit; using Xunit.Abstractions; -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.Cache; public static partial class ExpireAfterFixture @@ -188,7 +178,6 @@ public void PollingIntervalIsGiven_RemovalsAreScheduledAtInterval() // Not testing Move changes, since ISourceCache doesn't actually provide an API to generate them. - // Verify initial state, after all emissions results.Error.Should().BeNull(); results.RecordedValues.Should().BeEmpty("no expirations should have occurred"); @@ -326,7 +315,6 @@ public void PollingIntervalIsNotGiven_RemovalsAreScheduledImmediately() // Not testing Move changes, since ISourceCache doesn't actually provide an API to generate them. - // Verify initial state, after all emissions results.Error.Should().BeNull(); results.RecordedValues.Should().BeEmpty("no expirations should have occurred"); @@ -411,7 +399,6 @@ public void SchedulerIsInaccurate_RemovalsAreNotSkipped() var item1 = new TestItem() { Id = 1, Expiration = DateTimeOffset.FromUnixTimeMilliseconds(10) }; source.AddOrUpdate(item1); - results.Error.Should().BeNull(); results.RecordedValues.Should().BeEmpty("no expirations should have occurred"); source.Items.Should().BeEquivalentTo(new[] { item1 }, "1 item was added"); diff --git a/src/DynamicData.Tests/Cache/ExpireAfterFixture.ForStream.cs b/src/DynamicData.Tests/Cache/ExpireAfterFixture.ForStream.cs index 868fad2fd..77e922049 100644 --- a/src/DynamicData.Tests/Cache/ExpireAfterFixture.ForStream.cs +++ b/src/DynamicData.Tests/Cache/ExpireAfterFixture.ForStream.cs @@ -1,18 +1,6 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Threading.Tasks; using Bogus; -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; namespace DynamicData.Tests.Cache; @@ -23,7 +11,7 @@ public sealed class ForStream [Fact] public void ExpiredItemIsRemoved_RemovalIsSkipped() { - using var source = new Subject>(); + using var source = new Signal>(); var scheduler = CreateTestScheduler(); @@ -72,7 +60,7 @@ public void ExpiredItemIsRemoved_RemovalIsSkipped() [Fact] public void ItemIsRemovedBeforeExpiration_ExpirationIsCancelled() { - using var source = new Subject>(); + using var source = new Signal>(); var scheduler = CreateTestScheduler(); @@ -124,7 +112,7 @@ public void ItemIsRemovedBeforeExpiration_ExpirationIsCancelled() [Fact] public void NextItemToExpireIsReplaced_ExpirationIsRescheduledIfNeeded() { - using var source = new Subject>(); + using var source = new Signal>(); var scheduler = CreateTestScheduler(); @@ -201,7 +189,7 @@ public void NextItemToExpireIsReplaced_ExpirationIsRescheduledIfNeeded() [Fact] public void PollingIntervalIsGiven_RemovalsAreScheduledAtInterval() { - using var source = new Subject>(); + using var source = new Signal>(); var scheduler = CreateTestScheduler(); @@ -287,7 +275,6 @@ public void PollingIntervalIsGiven_RemovalsAreScheduledAtInterval() new(reason: ChangeReason.Moved, key: item1.Id, current: item1, previous: default, currentIndex: 4, previousIndex: 1) }); - // Verify initial state, after all emissions results.Error.Should().BeNull(); results.RecordedChangeSets.Count.Should().Be(7, "8 source operations were performed, and 1 should have been ignored"); @@ -368,7 +355,7 @@ public void PollingIntervalIsGiven_RemovalsAreScheduledAtInterval() [Fact] public void PollingIntervalIsNotGiven_RemovalsAreScheduledImmediately() { - using var source = new Subject>(); + using var source = new Signal>(); var scheduler = CreateTestScheduler(); @@ -453,7 +440,6 @@ public void PollingIntervalIsNotGiven_RemovalsAreScheduledImmediately() new(reason: ChangeReason.Moved, key: item1.Id, current: item1, previous: default, currentIndex: 4, previousIndex: 1) }); - // Verify initial state, after all emissions results.Error.Should().BeNull(); results.RecordedChangeSets.Count.Should().Be(7, "8 source operations were performed, and 1 should have been ignored"); @@ -519,7 +505,7 @@ public void PollingIntervalIsNotGiven_RemovalsAreScheduledImmediately() [Fact] public void RemovalsArePending_CompletionWaitsForRemovals() { - using var source = new Subject>(); + using var source = new Signal>(); var scheduler = CreateTestScheduler(); @@ -571,7 +557,7 @@ public void RemovalsArePending_CompletionWaitsForRemovals() [Fact] public void SchedulerIsInaccurate_RemovalsAreNotSkipped() { - using var source = new Subject>(); + using var source = new Signal>(); var scheduler = new FakeScheduler() { @@ -592,7 +578,6 @@ public void SchedulerIsInaccurate_RemovalsAreNotSkipped() new(reason: ChangeReason.Add, key: item1.Id, current: item1) }); - results.Error.Should().BeNull(); results.RecordedChangeSets.Count.Should().Be(1, "1 source operation was performed"); results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1 }, "1 item was added"); @@ -609,7 +594,7 @@ public void SchedulerIsInaccurate_RemovalsAreNotSkipped() [Fact] public void SourceCompletes_CompletionIsPropagated() { - using var source = new Subject>(); + using var source = new Signal>(); var scheduler = CreateTestScheduler(); @@ -684,7 +669,7 @@ public void SourceCompletesImmediately_CompletionIsPropagated() [Fact] public void SourceErrors_ErrorIsPropagated() { - using var source = new Subject>(); + using var source = new Signal>(); var scheduler = CreateTestScheduler(); @@ -768,7 +753,7 @@ public void SourceIsNull_ThrowsException() [Fact] public async Task ThreadPoolSchedulerIsUsedWithoutPolling_ExpirationIsThreadSafe() { - using var source = new Subject>(); + using var source = new Signal>(); var scheduler = ThreadPoolScheduler.Instance; @@ -798,7 +783,7 @@ public async Task ThreadPoolSchedulerIsUsedWithoutPolling_ExpirationIsThreadSafe [Fact] public async Task ThreadPoolSchedulerIsUsedWithPolling_ExpirationIsThreadSafe() { - using var source = new Subject>(); + using var source = new Signal>(); var scheduler = ThreadPoolScheduler.Instance; @@ -830,14 +815,14 @@ public async Task ThreadPoolSchedulerIsUsedWithPolling_ExpirationIsThreadSafe() [Fact] public void TimeSelectorIsNull_ThrowsException() - => FluentActions.Invoking(() => new Subject>().ExpireAfter( + => FluentActions.Invoking(() => new Signal>().ExpireAfter( timeSelector: null!)) .Should().Throw(); [Fact] public void TimeSelectorThrows_ErrorIsPropagated() { - using var source = new Subject>(); + using var source = new Signal>(); var scheduler = CreateTestScheduler(); diff --git a/src/DynamicData.Tests/Cache/ExpireAfterFixture.cs b/src/DynamicData.Tests/Cache/ExpireAfterFixture.cs index f701d6a38..c5b2ee2d8 100644 --- a/src/DynamicData.Tests/Cache/ExpireAfterFixture.cs +++ b/src/DynamicData.Tests/Cache/ExpireAfterFixture.cs @@ -1,8 +1,3 @@ -using System; -using System.Reactive.Concurrency; - -using Microsoft.Reactive.Testing; - namespace DynamicData.Tests.Cache; public static partial class ExpireAfterFixture diff --git a/src/DynamicData.Tests/Cache/FilterFixture.Base.cs b/src/DynamicData.Tests/Cache/FilterFixture.Base.cs index dad57e373..5d8ae9492 100644 --- a/src/DynamicData.Tests/Cache/FilterFixture.Base.cs +++ b/src/DynamicData.Tests/Cache/FilterFixture.Base.cs @@ -1,13 +1,3 @@ -using System; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.Cache; public static partial class FilterFixture @@ -32,7 +22,6 @@ public void ExcludedItemsAreRemoved_NoChangesAreMade(EmptyChangesetPolicy emptyC new Item() { Id = 6, IsIncluded = false } }); - // UUT Initialization using var subscription = BuildUut( source: source.Connect(), @@ -47,7 +36,6 @@ public void ExcludedItemsAreRemoved_NoChangesAreMade(EmptyChangesetPolicy emptyC results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all matching items should have been added"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.Remove(source.Items.Where(static item => !item.IsIncluded).ToArray()); @@ -63,7 +51,6 @@ public void ExcludedItemsAreRemoved_NoChangesAreMade(EmptyChangesetPolicy emptyC } results.HasCompleted.Should().BeFalse("the source has not completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -76,7 +63,6 @@ public void ItemsAreAdded_MatchingItemsPropagate(EmptyChangesetPolicy emptyChang // Setup using var source = new TestSourceCache(Item.SelectId); - // UUT Intialization using var subscription = BuildUut( source: source.Connect(), @@ -90,7 +76,6 @@ public void ItemsAreAdded_MatchingItemsPropagate(EmptyChangesetPolicy emptyChang results.RecordedChangeSets.Should().BeEmpty("no source operations were performed"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.AddOrUpdate(new[] { @@ -107,7 +92,6 @@ public void ItemsAreAdded_MatchingItemsPropagate(EmptyChangesetPolicy emptyChang results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all matching items should have been added"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -118,7 +102,7 @@ public void ItemsAreAdded_MatchingItemsPropagate(EmptyChangesetPolicy emptyChang public void ItemsAreMoved_MovementsAreIgnored(EmptyChangesetPolicy emptyChangesetPolicy) { // Setup - using var source = new Subject>(); + using var source = new Signal>(); var items = new[] { @@ -147,7 +131,6 @@ public void ItemsAreMoved_MovementsAreIgnored(EmptyChangesetPolicy emptyChangese results.RecordedItemsByKey.Values.Should().BeEquivalentTo(items, "all matching items should have been added"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.OnNext(new ChangeSet() { @@ -167,7 +150,6 @@ public void ItemsAreMoved_MovementsAreIgnored(EmptyChangesetPolicy emptyChangese } results.HasCompleted.Should().BeFalse("the source has not completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -190,7 +172,6 @@ public void ItemsAreRefreshed_ItemsAreReFilteredOrRefreshed(EmptyChangesetPolicy new Item() { Id = 6, IsIncluded = false } }); - // UUT Initialization using var subscription = BuildUut( source: source.Connect(), @@ -205,7 +186,6 @@ public void ItemsAreRefreshed_ItemsAreReFilteredOrRefreshed(EmptyChangesetPolicy results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all matching items should have propagated"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (add items) foreach (var item in source.Items) item.IsIncluded = true; @@ -218,7 +198,6 @@ public void ItemsAreRefreshed_ItemsAreReFilteredOrRefreshed(EmptyChangesetPolicy results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all newly-matching items should have been added"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (remove items) foreach (var item in source.Items.Take(3)) item.IsIncluded = false; @@ -231,7 +210,6 @@ public void ItemsAreRefreshed_ItemsAreReFilteredOrRefreshed(EmptyChangesetPolicy results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all newly-excluded items should have been removed"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -254,7 +232,6 @@ public void ItemsAreUpdated_ItemsAreReFiltered(EmptyChangesetPolicy emptyChanges new Item() { Id = 6, IsIncluded = false } }); - // UUT Intialization using var subscription = BuildUut( source: source.Connect(), @@ -269,7 +246,6 @@ public void ItemsAreUpdated_ItemsAreReFiltered(EmptyChangesetPolicy emptyChanges results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all matching items should have propagated"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (add and update items) source.AddOrUpdate(new[] { @@ -286,7 +262,6 @@ public void ItemsAreUpdated_ItemsAreReFiltered(EmptyChangesetPolicy emptyChanges results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all newly-matching items should have been added"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (remove and update items) source.AddOrUpdate(new[] { @@ -303,7 +278,6 @@ public void ItemsAreUpdated_ItemsAreReFiltered(EmptyChangesetPolicy emptyChanges results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all newly-excluded items should have been removed"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -326,7 +300,6 @@ public void MatchingItemsAreRemoved_RemovalsPropagate(EmptyChangesetPolicy empty new Item() { Id = 6, IsIncluded = false } }); - // UUT Initialization using var subscription = BuildUut( source: source.Connect(), @@ -341,7 +314,6 @@ public void MatchingItemsAreRemoved_RemovalsPropagate(EmptyChangesetPolicy empty results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all matching items should have propagated"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.Remove(source.Items.Where(Item.FilterByIsIncluded).ToArray()); @@ -350,7 +322,6 @@ public void MatchingItemsAreRemoved_RemovalsPropagate(EmptyChangesetPolicy empty results.RecordedItemsByKey.Values.Should().BeEmpty("all matching items were removed"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } diff --git a/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicate.IntegrationTests.cs b/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicate.IntegrationTests.cs index 8b5c76e4c..02736c192 100644 --- a/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicate.IntegrationTests.cs +++ b/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicate.IntegrationTests.cs @@ -1,14 +1,4 @@ -using System; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Threading.Tasks; - using Bogus; -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; namespace DynamicData.Tests.Cache; @@ -36,9 +26,8 @@ public async Task NotificationsOccurOnDifferentThreads_OperatorIsThreadSafe() .Select(mask => new Func(item => Item.FilterByIdInclusionMask(mask, item))) .ToArray(); - using var source = new Subject>(); - using var predicateChanged = new Subject>(); - + using var source = new Signal>(); + using var predicateChanged = new Signal>(); // UUT Initialization using var subscription = source @@ -47,7 +36,6 @@ public async Task NotificationsOccurOnDifferentThreads_OperatorIsThreadSafe() .ValidateChangeSets(Item.SelectId) .RecordCacheItems(out var results); - // UUT Action await Task.WhenAll( Task.Run(() => @@ -67,7 +55,6 @@ await Task.WhenAll( results.RecordedItemsByKey.Values.Should().BeEquivalentTo(items.Items.Where(finalPredicate), "the source colleciton should be filtered to include only items matching the final predicate"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } diff --git a/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicate.UnitTests.cs b/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicate.UnitTests.cs index 5315727d1..be1cad062 100644 --- a/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicate.UnitTests.cs +++ b/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicate.UnitTests.cs @@ -1,14 +1,3 @@ -using System; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.Cache; public static partial class FilterFixture @@ -26,7 +15,6 @@ public void ChangesAreMadeBeforeInitialPredicateChangedValue_ItemsAreExcluded(Em // Setup using var source = new TestSourceCache(Item.SelectId); - // UUT Initialization using var subscription = source.Connect() .Filter( @@ -40,7 +28,6 @@ public void ChangesAreMadeBeforeInitialPredicateChangedValue_ItemsAreExcluded(Em results.RecordedChangeSets.Should().BeEmpty("no source operations were performed"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action // Add changes source.AddOrUpdate(new[] @@ -87,7 +74,6 @@ public void ChangesAreMadeBeforeInitialPredicateChangedValue_ItemsAreExcluded(Em } results.HasCompleted.Should().BeFalse("the source has not completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -99,7 +85,7 @@ public void PredicateChangedChanges_ItemsAreReFiltered(EmptyChangesetPolicy empt { // Setup using var source = new TestSourceCache(Item.SelectId); - using var predicateChanged = new BehaviorSubject>(Item.FilterByIsIncluded); + using var predicateChanged = new StateSignal>(Item.FilterByIsIncluded); source.AddOrUpdate(new[] { @@ -111,7 +97,6 @@ public void PredicateChangedChanges_ItemsAreReFiltered(EmptyChangesetPolicy empt new Item() { Id = 6, IsIncluded = false } }); - // UUT Initialization using var subscription = source.Connect() .Filter( @@ -126,7 +111,6 @@ public void PredicateChangedChanges_ItemsAreReFiltered(EmptyChangesetPolicy empt results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all matching items should have propagated"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action predicateChanged.OnNext(Item.FilterByEvenId); @@ -135,7 +119,6 @@ public void PredicateChangedChanges_ItemsAreReFiltered(EmptyChangesetPolicy empt results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByEvenId), "newly-matching items should have been added, and newly-excluded items should have been removed"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -159,11 +142,10 @@ public void PredicateChangedCompletesAfterInitialValue_CompletionWaitsForSourceC }); var predicateChanged = (completionStrategy is CompletionStrategy.Asynchronous) - ? new Subject>() + ? new Signal>() : Observable.Return(Item.FilterByIsIncluded); - var reapplyFilter = new Subject(); - + var reapplyFilter = new Signal(); // UUT Initialization & Action using var subscription = source.Connect() @@ -172,7 +154,7 @@ public void PredicateChangedCompletesAfterInitialValue_CompletionWaitsForSourceC .ValidateChangeSets(static item => item.Id) .RecordCacheItems(out var results); - if (predicateChanged is Subject> subject) + if (predicateChanged is Signal> subject) { subject.OnNext(Item.FilterByIsIncluded); subject.OnCompleted(); @@ -183,7 +165,6 @@ public void PredicateChangedCompletesAfterInitialValue_CompletionWaitsForSourceC results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all matching items should have propagated"); results.HasCompleted.Should().BeFalse("changes could still be generated by the source"); - // UUT Action source.Complete(); @@ -194,7 +175,6 @@ public void PredicateChangedCompletesAfterInitialValue_CompletionWaitsForSourceC results.RecordedChangeSets.Skip(1).Should().BeEmpty("no source operations were performed"); results.HasCompleted.Should().BeTrue("all source streams have completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -212,10 +192,9 @@ public void PredicateChangedCompletesBeforeInitialValue_CompletionPropagatesIfEm using var source = new TestSourceCache(Item.SelectId); var predicateChanged = (completionStrategy is CompletionStrategy.Asynchronous) - ? new Subject>() + ? new Signal>() : Observable.Empty>(); - // UUT Initialization & Action using var subscription = source.Connect() .Filter( @@ -225,7 +204,7 @@ public void PredicateChangedCompletesBeforeInitialValue_CompletionPropagatesIfEm .ValidateChangeSets(static item => item.Id) .RecordCacheItems(out var results); - if (predicateChanged is Subject> subject) + if (predicateChanged is Signal> subject) subject.OnCompleted(); results.Error.Should().BeNull(); @@ -247,10 +226,9 @@ public void PredicateChangedFails_ErrorPropagates(CompletionStrategy completionS var error = new Exception("Test"); var predicateChanged = (completionStrategy is CompletionStrategy.Asynchronous) - ? new Subject>() + ? new Signal>() : Observable.Throw>(error); - // UUT Initialization & Action using var subscription = source.Connect() .Filter( @@ -259,7 +237,7 @@ public void PredicateChangedFails_ErrorPropagates(CompletionStrategy completionS .ValidateChangeSets(static item => item.Id) .RecordCacheItems(out var results); - if (predicateChanged is Subject> subject) + if (predicateChanged is Signal> subject) subject.OnError(error); results.Error.Should().Be(error, "errors should propagate downstream"); @@ -311,7 +289,6 @@ public void SourceCompletesWhenEmpty_CompletionPropagatesWhenEmptyChangesetsAreS else results.HasCompleted.Should().BeTrue("the source has completed, and no further changesets can occur"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -334,8 +311,7 @@ public void SourceCompletesWhenNotEmpty_CompletionWaitsForPredicateChangedComple new Item() { Id = 6, IsIncluded = false } }); - using var predicateChanged = new BehaviorSubject>(Item.FilterByIsIncluded); - + using var predicateChanged = new StateSignal>(Item.FilterByIsIncluded); // UUT Initialization & Action if (completionStrategy is CompletionStrategy.Immediate) @@ -355,7 +331,6 @@ public void SourceCompletesWhenNotEmpty_CompletionWaitsForPredicateChangedComple results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all matching items should have propagated"); results.HasCompleted.Should().BeFalse("the collection could still change due to new predicates"); - // UUT Action predicateChanged.OnCompleted(); @@ -364,7 +339,6 @@ public void SourceCompletesWhenNotEmpty_CompletionWaitsForPredicateChangedComple results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "no changes should have been made"); results.HasCompleted.Should().BeTrue("all source streams have completed"); - // Final Verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -373,9 +347,8 @@ public void SourceCompletesWhenNotEmpty_CompletionWaitsForPredicateChangedComple public void SubscriptionIsDisposed_SubscriptionDisposalPropagates() { // Setup - using var source = new Subject>(); - using var predicateChanged = new BehaviorSubject>(Item.FilterByIsIncluded); - + using var source = new Signal>(); + using var predicateChanged = new StateSignal>(Item.FilterByIsIncluded); // UUT Initialization using var subscription = source @@ -388,7 +361,6 @@ public void SubscriptionIsDisposed_SubscriptionDisposalPropagates() results.RecordedChangeSets.Should().BeEmpty("no source operations were performed"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action subscription.Dispose(); diff --git a/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicateAndReFiltering.IntegrationTests.cs b/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicateAndReFiltering.IntegrationTests.cs index 8491fe821..5202453d1 100644 --- a/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicateAndReFiltering.IntegrationTests.cs +++ b/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicateAndReFiltering.IntegrationTests.cs @@ -1,15 +1,4 @@ -using System; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Threading.Tasks; - using Bogus; -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; namespace DynamicData.Tests.Cache; @@ -37,10 +26,9 @@ public async Task NotificationsOccurOnDifferentThreads_OperatorIsThreadSafe() .Select(mask => new Func(item => Item.FilterByIdInclusionMask(mask, item))) .ToArray(); - using var source = new Subject>(); - using var predicateChanged = new Subject>(); - using var reapplyFilter = new Subject(); - + using var source = new Signal>(); + using var predicateChanged = new Signal>(); + using var reapplyFilter = new Signal(); // UUT Initialization using var subscription = source @@ -51,7 +39,6 @@ public async Task NotificationsOccurOnDifferentThreads_OperatorIsThreadSafe() .ValidateChangeSets(Item.SelectId) .RecordCacheItems(out var results); - // UUT Action await Task.WhenAll( Task.Run(() => @@ -76,7 +63,6 @@ await Task.WhenAll( results.RecordedItemsByKey.Values.Should().BeEquivalentTo(items.Items.Where(finalPredicate), "the source colleciton should be filtered to include only items matching the final predicate"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } diff --git a/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicateAndReFiltering.UnitTests.cs b/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicateAndReFiltering.UnitTests.cs index e56101cb5..a50f5ad61 100644 --- a/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicateAndReFiltering.UnitTests.cs +++ b/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicateAndReFiltering.UnitTests.cs @@ -1,14 +1,3 @@ -using System; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.Cache; public static partial class FilterFixture @@ -26,7 +15,6 @@ public void ChangesAreMadeBeforeInitialPredicateChangedValue_ItemsAreExcluded(Em // Setup using var source = new TestSourceCache(Item.SelectId); - // UUT Initialization using var subscription = source.Connect() .Filter( @@ -41,7 +29,6 @@ public void ChangesAreMadeBeforeInitialPredicateChangedValue_ItemsAreExcluded(Em results.RecordedChangeSets.Should().BeEmpty("no source operations were performed"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action // Add changes source.AddOrUpdate(new[] @@ -84,7 +71,6 @@ public void ChangesAreMadeBeforeInitialPredicateChangedValue_ItemsAreExcluded(Em results.RecordedItemsByKey.Should().BeEmpty("the predicate has not initialized"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -96,7 +82,7 @@ public void PredicateChangedChanges_ItemsAreReFiltered(EmptyChangesetPolicy empt { // Setup using var source = new TestSourceCache(Item.SelectId); - using var predicateChanged = new BehaviorSubject>(Item.FilterByIsIncluded); + using var predicateChanged = new StateSignal>(Item.FilterByIsIncluded); source.AddOrUpdate(new[] { @@ -108,7 +94,6 @@ public void PredicateChangedChanges_ItemsAreReFiltered(EmptyChangesetPolicy empt new Item() { Id = 6, IsIncluded = false } }); - // UUT Initialization using var subscription = source.Connect() .Filter( @@ -124,7 +109,6 @@ public void PredicateChangedChanges_ItemsAreReFiltered(EmptyChangesetPolicy empt results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all matching items should have propagated"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action predicateChanged.OnNext(Item.FilterByEvenId); @@ -133,7 +117,6 @@ public void PredicateChangedChanges_ItemsAreReFiltered(EmptyChangesetPolicy empt results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByEvenId), "newly-matching items should have been added, and newly-excluded items should have been removed"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -161,11 +144,10 @@ public void PredicateChangedCompletesAfterInitialValue_CompletionWaitsForSourceA }); var predicateChanged = (completionStrategy is CompletionStrategy.Asynchronous) - ? new Subject>() + ? new Signal>() : Observable.Return(Item.FilterByIsIncluded); - var reapplyFilter = new Subject(); - + var reapplyFilter = new Signal(); // UUT Initialization & Action using var subscription = source.Connect() @@ -176,7 +158,7 @@ public void PredicateChangedCompletesAfterInitialValue_CompletionWaitsForSourceA .ValidateChangeSets(static item => item.Id) .RecordCacheItems(out var results); - if (predicateChanged is Subject> subject) + if (predicateChanged is Signal> subject) { subject.OnNext(Item.FilterByIsIncluded); subject.OnCompleted(); @@ -187,7 +169,6 @@ public void PredicateChangedCompletesAfterInitialValue_CompletionWaitsForSourceA results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all matching items should have propagated"); results.HasCompleted.Should().BeFalse("changes could still be generated by the source"); - // UUT Action (second completion) if (lastCompletion is DynamicParameter.ReapplyFilter) source.Complete(); @@ -201,7 +182,6 @@ public void PredicateChangedCompletesAfterInitialValue_CompletionWaitsForSourceA results.RecordedChangeSets.Skip(1).Should().BeEmpty("no source operations were performed"); results.HasCompleted.Should().BeFalse("changes could still be generated by the filtering sources"); - // UUT Action (last completion) if (lastCompletion is DynamicParameter.ReapplyFilter) reapplyFilter.OnCompleted(); @@ -215,7 +195,6 @@ public void PredicateChangedCompletesAfterInitialValue_CompletionWaitsForSourceA results.RecordedChangeSets.Skip(1).Should().BeEmpty("no source operations were performed"); results.HasCompleted.Should().BeTrue("all source streams have completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -233,10 +212,9 @@ public void PredicateChangedCompletesBeforeInitialValue_CompletionPropagatesIfEm using var source = new TestSourceCache(Item.SelectId); var predicateChanged = (completionStrategy is CompletionStrategy.Asynchronous) - ? new Subject>() + ? new Signal>() : Observable.Empty>(); - // UUT Initialization & Action using var subscription = source.Connect() .Filter( @@ -247,7 +225,7 @@ public void PredicateChangedCompletesBeforeInitialValue_CompletionPropagatesIfEm .ValidateChangeSets(static item => item.Id) .RecordCacheItems(out var results); - if (predicateChanged is Subject> subject) + if (predicateChanged is Signal> subject) subject.OnCompleted(); results.Error.Should().BeNull(); @@ -269,10 +247,9 @@ public void PredicateChangedFails_ErrorPropagates(CompletionStrategy completionS var error = new Exception("Test"); var predicateChanged = (completionStrategy is CompletionStrategy.Asynchronous) - ? new Subject>() + ? new Signal>() : Observable.Throw>(error); - // UUT Initialization & Action using var subscription = source.Connect() .Filter( @@ -282,7 +259,7 @@ public void PredicateChangedFails_ErrorPropagates(CompletionStrategy completionS .ValidateChangeSets(static item => item.Id) .RecordCacheItems(out var results); - if (predicateChanged is Subject> subject) + if (predicateChanged is Signal> subject) subject.OnError(error); results.Error.Should().Be(error, "errors should propagate downstream"); @@ -320,13 +297,12 @@ public void ReapplyFilterCompletes_CompletionWaitsForSourceAndPredicateChangedCo new Item() { Id = 6, IsIncluded = false } }); - var predicateChanged = new BehaviorSubject>(Item.FilterByIsIncluded); + var predicateChanged = new StateSignal>(Item.FilterByIsIncluded); var reapplyFilter = (completionStrategy is CompletionStrategy.Asynchronous) - ? new Subject() + ? new Signal() : Observable.Empty(); - // UUT Initialization & Action using var subscription = source.Connect() .Filter( @@ -336,7 +312,7 @@ public void ReapplyFilterCompletes_CompletionWaitsForSourceAndPredicateChangedCo .ValidateChangeSets(static item => item.Id) .RecordCacheItems(out var results); - if (reapplyFilter is Subject subject) + if (reapplyFilter is Signal subject) subject.OnCompleted(); results.Error.Should().BeNull(); @@ -344,7 +320,6 @@ public void ReapplyFilterCompletes_CompletionWaitsForSourceAndPredicateChangedCo results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all matching items should have propagated"); results.HasCompleted.Should().BeFalse("changes could still be generated by the source"); - // UUT Action (second completion) if (lastCompletion is DynamicParameter.PredicateChanged) source.Complete(); @@ -355,7 +330,6 @@ public void ReapplyFilterCompletes_CompletionWaitsForSourceAndPredicateChangedCo results.RecordedChangeSets.Skip(1).Should().BeEmpty("no source operations were performed"); results.HasCompleted.Should().BeFalse("changes could still be generated by other source streams"); - // UUT Action (last completion) if (lastCompletion is DynamicParameter.PredicateChanged) predicateChanged.OnCompleted(); @@ -366,7 +340,6 @@ public void ReapplyFilterCompletes_CompletionWaitsForSourceAndPredicateChangedCo results.RecordedChangeSets.Skip(1).Should().BeEmpty("no source operations were performed"); results.HasCompleted.Should().BeTrue("all input streams have completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -382,10 +355,9 @@ public void ReapplyFilterFails_ErrorPropagates(CompletionStrategy completionStra var error = new Exception("Test"); var reapplyFilter = (completionStrategy is CompletionStrategy.Asynchronous) - ? new Subject() + ? new Signal() : Observable.Throw(error); - // UUT Initialization & Action using var subscription = source.Connect() .Filter( @@ -395,7 +367,7 @@ public void ReapplyFilterFails_ErrorPropagates(CompletionStrategy completionStra .ValidateChangeSets(static item => item.Id) .RecordCacheItems(out var results); - if (reapplyFilter is Subject subject) + if (reapplyFilter is Signal subject) subject.OnError(error); results.Error.Should().Be(error, "errors should propagate downstream"); @@ -418,7 +390,7 @@ public void ReapplyFilterOccurs_ItemsAreReFiltered(EmptyChangesetPolicy emptyCha { // Setup using var source = new TestSourceCache(Item.SelectId); - using var reapplyFilter = new Subject(); + using var reapplyFilter = new Signal(); source.AddOrUpdate(new[] { @@ -432,7 +404,6 @@ public void ReapplyFilterOccurs_ItemsAreReFiltered(EmptyChangesetPolicy emptyCha var predicate = Item.FilterByIsIncluded; - // UUT Initialization using var subscription = source.Connect() .Filter( @@ -448,7 +419,6 @@ public void ReapplyFilterOccurs_ItemsAreReFiltered(EmptyChangesetPolicy emptyCha results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(predicate), "all matching items should have propagated"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.Items[1].IsIncluded = false; source.Items[5].IsIncluded = true; @@ -460,7 +430,6 @@ public void ReapplyFilterOccurs_ItemsAreReFiltered(EmptyChangesetPolicy emptyCha results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(predicate), "newly-matching items should have been added, and newly-excluded items should have been removed"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -477,7 +446,6 @@ public void SourceCompletesWhenEmpty_CompletionPropagatesWhenEmptyChangesetsAreS // Setup using var source = new TestSourceCache(Item.SelectId); - // UUT Initialization & Action if (completionStrategy is CompletionStrategy.Immediate) source.Complete(); @@ -503,7 +471,6 @@ public void SourceCompletesWhenEmpty_CompletionPropagatesWhenEmptyChangesetsAreS else results.HasCompleted.Should().BeTrue("the source has completed, and no further changesets can occur"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -530,9 +497,8 @@ public void SourceCompletesWhenNotEmpty_CompletionWaitsForPredicateChangedAndRea new Item() { Id = 6, IsIncluded = false } }); - using var predicateChanged = new BehaviorSubject>(Item.FilterByIsIncluded); - using var reapplyFilter = new Subject(); - + using var predicateChanged = new StateSignal>(Item.FilterByIsIncluded); + using var reapplyFilter = new Signal(); // UUT Initialization & Action if (completionStrategy is CompletionStrategy.Immediate) @@ -554,7 +520,6 @@ public void SourceCompletesWhenNotEmpty_CompletionWaitsForPredicateChangedAndRea results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all matching items should have propagated"); results.HasCompleted.Should().BeFalse("the collection could still change due to new predicates"); - // UUT Action (second completion) if (lastCompletion is DynamicParameter.PredicateChanged) reapplyFilter.OnCompleted(); @@ -566,7 +531,6 @@ public void SourceCompletesWhenNotEmpty_CompletionWaitsForPredicateChangedAndRea results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "no changes should have been made"); results.HasCompleted.Should().BeFalse("the collection could still change due to outstanding source streams"); - // UUT Action (last completion) if (lastCompletion is DynamicParameter.PredicateChanged) predicateChanged.OnCompleted(); @@ -578,7 +542,6 @@ public void SourceCompletesWhenNotEmpty_CompletionWaitsForPredicateChangedAndRea results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "no changes should have been made"); results.HasCompleted.Should().BeTrue("all source streams have completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -587,10 +550,9 @@ public void SourceCompletesWhenNotEmpty_CompletionWaitsForPredicateChangedAndRea public void SubscriptionIsDisposed_SubscriptionDisposalPropagates() { // Setup - using var source = new Subject>(); - using var predicateChanged = new BehaviorSubject>(Item.FilterByIsIncluded); - using var reapplyFilter = new Subject(); - + using var source = new Signal>(); + using var predicateChanged = new StateSignal>(Item.FilterByIsIncluded); + using var reapplyFilter = new Signal(); // UUT Initialization using var subscription = source @@ -605,7 +567,6 @@ public void SubscriptionIsDisposed_SubscriptionDisposalPropagates() results.RecordedChangeSets.Should().BeEmpty("no source operations were performed"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action subscription.Dispose(); diff --git a/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicateState.IntegrationTests.cs b/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicateState.IntegrationTests.cs index 49654ba16..60c098f04 100644 --- a/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicateState.IntegrationTests.cs +++ b/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicateState.IntegrationTests.cs @@ -1,13 +1,4 @@ -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Threading.Tasks; - using Bogus; -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; namespace DynamicData.Tests.Cache; @@ -34,9 +25,8 @@ public async Task NotificationsOccurOnDifferentThreads_OperatorIsThreadSafe() randomizer: randomizer) .ToArray(); - using var source = new Subject>(); - using var predicateState = new Subject(); - + using var source = new Signal>(); + using var predicateState = new Signal(); // UUT Initialization using var subscription = source @@ -47,7 +37,6 @@ public async Task NotificationsOccurOnDifferentThreads_OperatorIsThreadSafe() .ValidateChangeSets(Item.SelectId) .RecordCacheItems(out var results); - // UUT Action await Task.WhenAll( Task.Run(() => @@ -67,7 +56,6 @@ await Task.WhenAll( results.RecordedItemsByKey.Values.Should().BeEquivalentTo(items.Items.Where(item => Item.FilterByIdInclusionMask(finalPredicateState, item)), "the source colleciton should be filtered to include only items matching the final predicate"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } diff --git a/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicateState.UnitTests.cs b/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicateState.UnitTests.cs index 1b0c55c88..1c053daa4 100644 --- a/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicateState.UnitTests.cs +++ b/src/DynamicData.Tests/Cache/FilterFixture.DynamicPredicateState.UnitTests.cs @@ -1,13 +1,3 @@ -using System; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.Cache; public static partial class FilterFixture @@ -25,7 +15,6 @@ public void ChangesAreMadeBeforeInitialPredicateStateValue_ItemsAreExcluded(Empt // Setup using var source = new TestSourceCache(Item.SelectId); - // UUT Initialization using var subscription = source .Connect() @@ -41,7 +30,6 @@ public void ChangesAreMadeBeforeInitialPredicateStateValue_ItemsAreExcluded(Empt results.RecordedChangeSets.Should().BeEmpty("no source operations were performed"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action // Add changes source.AddOrUpdate(new[] @@ -84,7 +72,6 @@ public void ChangesAreMadeBeforeInitialPredicateStateValue_ItemsAreExcluded(Empt results.RecordedItemsByKey.Should().BeEmpty("the predicate has not initialized"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -117,10 +104,9 @@ public void PredicateStateCompletesAfterInitialValue_CompletionWaitsForSourceCom }); var predicateState = (completionStrategy is CompletionStrategy.Asynchronous) - ? new Subject() + ? new Signal() : Observable.Return(new object()); - // UUT Initialization & Action using var subscription = source.Connect() .Filter( @@ -130,7 +116,7 @@ public void PredicateStateCompletesAfterInitialValue_CompletionWaitsForSourceCom .ValidateChangeSets(static item => item.Id) .RecordCacheItems(out var results); - if (predicateState is Subject subject) + if (predicateState is Signal subject) { subject.OnNext(new()); subject.OnCompleted(); @@ -141,7 +127,6 @@ public void PredicateStateCompletesAfterInitialValue_CompletionWaitsForSourceCom results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all matching items should have propagated"); results.HasCompleted.Should().BeFalse("changes could still be generated by the source"); - // UUT Action source.Complete(); @@ -152,7 +137,6 @@ public void PredicateStateCompletesAfterInitialValue_CompletionWaitsForSourceCom results.RecordedChangeSets.Skip(1).Should().BeEmpty("no source operations were performed"); results.HasCompleted.Should().BeTrue("all source streams have completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -170,10 +154,9 @@ public void PredicateStateCompletesBeforeInitialValue_CompletionPropagatesIfEmpt using var source = new TestSourceCache(Item.SelectId); var predicateState = (completionStrategy is CompletionStrategy.Asynchronous) - ? new Subject() + ? new Signal() : Observable.Empty(); - // UUT Initialization & Action using var subscription = source.Connect() .Filter( @@ -184,7 +167,7 @@ public void PredicateStateCompletesBeforeInitialValue_CompletionPropagatesIfEmpt .ValidateChangeSets(static item => item.Id) .RecordCacheItems(out var results); - if (predicateState is Subject subject) + if (predicateState is Signal subject) subject.OnCompleted(); results.Error.Should().BeNull(); @@ -206,10 +189,9 @@ public void PredicateStateFails_ErrorPropagates(CompletionStrategy completionStr var error = new Exception("Test"); var predicateState = (completionStrategy is CompletionStrategy.Asynchronous) - ? new Subject() + ? new Signal() : Observable.Throw(error); - // UUT Initialization & Action using var subscription = source.Connect() .Filter( @@ -219,7 +201,7 @@ public void PredicateStateFails_ErrorPropagates(CompletionStrategy completionStr .ValidateChangeSets(Item.SelectId) .RecordCacheItems(out var results); - if (predicateState is Subject subject) + if (predicateState is Signal subject) subject.OnError(error); results.Error.Should().Be(error, "errors should propagate downstream"); @@ -242,7 +224,7 @@ public void PredicateStateChanges_ItemsAreReFiltered(EmptyChangesetPolicy emptyC { // Setup using var source = new TestSourceCache(Item.SelectId); - using var predicateState = new BehaviorSubject(0x5); + using var predicateState = new StateSignal(0x5); source.AddOrUpdate(new[] { @@ -254,7 +236,6 @@ public void PredicateStateChanges_ItemsAreReFiltered(EmptyChangesetPolicy emptyC new Item() { Id = 6, IsIncluded = false } }); - // UUT Initialization using var subscription = source.Connect() .Filter( @@ -270,7 +251,6 @@ public void PredicateStateChanges_ItemsAreReFiltered(EmptyChangesetPolicy emptyC results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(item => Item.FilterByIdInclusionMask(predicateState.Value, item)), "all matching items should have propagated"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action predicateState.OnNext(0xA); @@ -279,7 +259,6 @@ public void PredicateStateChanges_ItemsAreReFiltered(EmptyChangesetPolicy emptyC results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(item => Item.FilterByIdInclusionMask(predicateState.Value, item)), "newly-matching items should have been added, and newly-excluded items should have been removed"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -296,7 +275,6 @@ public void SourceCompletesWhenEmpty_CompletionPropagatesWhenEmptyChangesetsAreS // Setup using var source = new TestSourceCache(Item.SelectId); - // UUT Initialization & Action if (completionStrategy is CompletionStrategy.Immediate) source.Complete(); @@ -322,7 +300,6 @@ public void SourceCompletesWhenEmpty_CompletionPropagatesWhenEmptyChangesetsAreS else results.HasCompleted.Should().BeTrue("the source has completed, and no further changesets can occur"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -345,8 +322,7 @@ public void SourceCompletesWhenNotEmpty_CompletionWaitsForPredicateChangedComple new Item() { Id = 6, IsIncluded = false } }); - using var predicateState = new BehaviorSubject(new object()); - + using var predicateState = new StateSignal(new object()); // UUT Initialization & Action if (completionStrategy is CompletionStrategy.Immediate) @@ -368,7 +344,6 @@ public void SourceCompletesWhenNotEmpty_CompletionWaitsForPredicateChangedComple results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all matching items should have propagated"); results.HasCompleted.Should().BeFalse("the collection could still change due to new predicates"); - // UUT Action predicateState.OnCompleted(); @@ -377,7 +352,6 @@ public void SourceCompletesWhenNotEmpty_CompletionWaitsForPredicateChangedComple results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "no changes should have been made"); results.HasCompleted.Should().BeTrue("all source streams have completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -386,9 +360,8 @@ public void SourceCompletesWhenNotEmpty_CompletionWaitsForPredicateChangedComple public void SubscriptionIsDisposed_SubscriptionDisposalPropagates() { // Setup - using var source = new Subject>(); - using var predicateState = new BehaviorSubject(new()); - + using var source = new Signal>(); + using var predicateState = new StateSignal(new()); // UUT Initialization using var subscription = source @@ -403,7 +376,6 @@ public void SubscriptionIsDisposed_SubscriptionDisposalPropagates() results.RecordedChangeSets.Should().BeEmpty("no source operations were performed"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action subscription.Dispose(); diff --git a/src/DynamicData.Tests/Cache/FilterFixture.Static.cs b/src/DynamicData.Tests/Cache/FilterFixture.Static.cs index f3d61ac0a..f97ca1c5a 100644 --- a/src/DynamicData.Tests/Cache/FilterFixture.Static.cs +++ b/src/DynamicData.Tests/Cache/FilterFixture.Static.cs @@ -1,12 +1,3 @@ -using System; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.Cache; public static partial class FilterFixture @@ -30,7 +21,6 @@ public void SourceCompletes_CompletionPropagates(CompletionStrategy completionSt // Setup using var source = new TestSourceCache(Item.SelectId); - // UUT Initialization & Action if (completionStrategy is CompletionStrategy.Immediate) source.Complete(); @@ -48,7 +38,6 @@ public void SourceCompletes_CompletionPropagates(CompletionStrategy completionSt results.RecordedChangeSets.Should().BeEmpty("no source operations were performed"); results.HasCompleted.Should().BeTrue("the source has completed"); - // Final verification results.ShouldNotSupportSorting("sorting is not supported by filter operators"); } @@ -57,8 +46,7 @@ public void SourceCompletes_CompletionPropagates(CompletionStrategy completionSt public void SubscriptionIsDisposed_SubscriptionDisposalPropagates() { // Setup - using var source = new Subject>(); - + using var source = new Signal>(); // UUT Intialization using var subscription = source @@ -72,7 +60,6 @@ public void SubscriptionIsDisposed_SubscriptionDisposalPropagates() results.RecordedItemsByKey.Values.Should().BeEmpty("the source has not initialized"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action subscription.Dispose(); diff --git a/src/DynamicData.Tests/Cache/FilterFixture.cs b/src/DynamicData.Tests/Cache/FilterFixture.cs index d42e11579..bace7bca4 100644 --- a/src/DynamicData.Tests/Cache/FilterFixture.cs +++ b/src/DynamicData.Tests/Cache/FilterFixture.cs @@ -1,7 +1,3 @@ -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; - using Bogus; namespace DynamicData.Tests.Cache; diff --git a/src/DynamicData.Tests/Cache/FilterImmutableFixture.cs b/src/DynamicData.Tests/Cache/FilterImmutableFixture.cs index 5abf88761..cba3f621d 100644 --- a/src/DynamicData.Tests/Cache/FilterImmutableFixture.cs +++ b/src/DynamicData.Tests/Cache/FilterImmutableFixture.cs @@ -1,14 +1,3 @@ -using System; -using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using DynamicData.Tests.Utilities; - -using FluentAssertions; -using Xunit; - namespace DynamicData.Tests.Cache; public sealed class FilterImmutableFixture @@ -16,14 +5,13 @@ public sealed class FilterImmutableFixture [Fact] public void ItemsAreManipulated_UnmatchedItemsAreExcludedAndIndexesAreDiscarded() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .FilterImmutable(predicate: Item.Predicate) .ValidateChangeSets(Item.KeySelector) .RecordCacheItems(out var results); - // Add items var item1 = new Item() { Id = 1, IsIncluded = true }; var item2 = new Item() { Id = 2, IsIncluded = false }; @@ -37,7 +25,6 @@ public void ItemsAreManipulated_UnmatchedItemsAreExcludedAndIndexesAreDiscarded( results.RecordedChangeSets.Count.Should().Be(1, "1 source operation was performed"); results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1 }, "2 items were added, with 1 excluded"); - // Replace items, changing inclusion var item3 = new Item() { Id = item1.Id, IsIncluded = false }; var item4 = new Item() { Id = item2.Id, IsIncluded = true }; @@ -51,7 +38,6 @@ public void ItemsAreManipulated_UnmatchedItemsAreExcludedAndIndexesAreDiscarded( results.RecordedChangeSets.Skip(1).Count().Should().Be(1, "1 source operation was performed"); results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item4 }, "2 items were replaced, with 1 excluded"); - // Replace items, not changing inclusion var item5 = new Item() { Id = item3.Id, IsIncluded = false }; var item6 = new Item() { Id = item4.Id, IsIncluded = true }; @@ -65,7 +51,6 @@ public void ItemsAreManipulated_UnmatchedItemsAreExcludedAndIndexesAreDiscarded( results.RecordedChangeSets.Skip(2).Count().Should().Be(1, "1 source operation was performed"); results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item6 }, "2 items were replaced, with 1 excluded"); - // Refresh items source.OnNext(new ChangeSet() { @@ -77,7 +62,6 @@ public void ItemsAreManipulated_UnmatchedItemsAreExcludedAndIndexesAreDiscarded( results.RecordedChangeSets.Skip(3).Count().Should().Be(1, "1 source operation was performed"); results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item6 }, "2 items were refreshed, with 1 excluded"); - // Remove items source.OnNext(new ChangeSet() { @@ -103,7 +87,7 @@ public void ItemsAreManipulated_UnmatchedItemsAreExcludedAndIndexesAreDiscarded( [Fact] public void ItemsAreMoved_ChangesAreNotPropagated() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .FilterImmutable(predicate: Item.Predicate) @@ -122,7 +106,6 @@ public void ItemsAreMoved_ChangesAreNotPropagated() }); var changeSetsBeforeMove = results.RecordedChangeSets.Count; - // Move items source.OnNext(new ChangeSet() { @@ -144,7 +127,7 @@ public void PredicateIsNull_ThrowsException() [Fact] public void PredicateThrows_ExceptionIsCaptured() { - using var source = new Subject>(); + using var source = new Signal>(); var error = new Exception(); @@ -153,7 +136,6 @@ public void PredicateThrows_ExceptionIsCaptured() .ValidateChangeSets(Item.KeySelector) .RecordCacheItems(out var results); - var item1 = new Item() { Id = 1, IsIncluded = true }; source.OnNext(new ChangeSet() { @@ -168,14 +150,13 @@ public void PredicateThrows_ExceptionIsCaptured() [Fact] public void SourceCompletes_CompletionIsPropagated() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .FilterImmutable(predicate: Item.Predicate) .ValidateChangeSets(Item.KeySelector) .RecordCacheItems(out var results); - var item1 = new Item() { Id = 1, IsIncluded = true }; source.OnNext(new ChangeSet() { @@ -221,7 +202,6 @@ public void SourceCompletesImmediately_CompletionIsPropagated() .ValidateChangeSets(Item.KeySelector) .RecordCacheItems(out var results); - results.Error.Should().BeNull(); results.HasCompleted.Should().BeTrue(); results.RecordedChangeSets.Count.Should().Be(1, "1 source operation was performed"); @@ -231,7 +211,7 @@ public void SourceCompletesImmediately_CompletionIsPropagated() [Fact] public void SourceErrors_ErrorIsPropagated() { - using var source = new Subject>(); + using var source = new Signal>(); var error = new Exception(); @@ -240,7 +220,6 @@ public void SourceErrors_ErrorIsPropagated() .ValidateChangeSets(Item.KeySelector) .RecordCacheItems(out var results); - var item1 = new Item() { Id = 1, IsIncluded = true }; source.OnNext(new ChangeSet() { @@ -287,7 +266,6 @@ public void SourceErrorsImmediately_ErrorIsPropagated() .ValidateChangeSets(Item.KeySelector) .RecordCacheItems(out var results); - results.Error.Should().Be(error); results.HasCompleted.Should().BeFalse(); results.RecordedChangeSets.Count.Should().Be(1, "1 source operation was performed"); @@ -304,7 +282,7 @@ public void SourceIsNull_ThrowsException() [Fact] public void SuppressEmptyChangesetsIsFalse_EmptyChangesetsArePublished() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .FilterImmutable( @@ -313,7 +291,6 @@ public void SuppressEmptyChangesetsIsFalse_EmptyChangesetsArePublished() .ValidateChangeSets(Item.KeySelector) .RecordCacheItems(out var results); - ManipulateExcludedItems(source); results.Error.Should().BeNull(); @@ -325,14 +302,13 @@ public void SuppressEmptyChangesetsIsFalse_EmptyChangesetsArePublished() [Fact] public void SuppressEmptyChangesetsIsTrue_EmptyChangesetsAreNotPublished() { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .FilterImmutable(predicate: Item.Predicate) .ValidateChangeSets(Item.KeySelector) .RecordCacheItems(out var results); - ManipulateExcludedItems(source); results.Error.Should().BeNull(); @@ -340,7 +316,7 @@ public void SuppressEmptyChangesetsIsTrue_EmptyChangesetsAreNotPublished() results.RecordedChangeSets.Should().BeEmpty("no source operations should have generated changes"); } - private static void ManipulateExcludedItems(ISubject> source) + private static void ManipulateExcludedItems(ISignal> source) { var item1 = new Item() { Id = 1, IsIncluded = false }; source.OnNext(new ChangeSet() @@ -379,7 +355,7 @@ public void Update_PreviousMatchedCurrentDoesNot_EmitsRemoveCarryingPreviousAsCu // Per Change contract, a Remove change carries the item that was removed in Current. // For an Update where Previous matched the predicate but Current does not, the item that // leaves the filtered view is Previous (it was downstream; Current never reached downstream). - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .FilterImmutable(predicate: Item.Predicate) diff --git a/src/DynamicData.Tests/Cache/FilterOnConnectFixture.cs b/src/DynamicData.Tests/Cache/FilterOnConnectFixture.cs index 7ed96e287..197c6395b 100644 --- a/src/DynamicData.Tests/Cache/FilterOnConnectFixture.cs +++ b/src/DynamicData.Tests/Cache/FilterOnConnectFixture.cs @@ -1,8 +1,4 @@ -using System.Linq; -using FluentAssertions; -using Xunit; - -namespace DynamicData.Tests.Cache; +namespace DynamicData.Tests.Cache; /// /// See https://github.com/reactivemarbles/DynamicData/issues/400 diff --git a/src/DynamicData.Tests/Cache/FilterOnObservableFixture.cs b/src/DynamicData.Tests/Cache/FilterOnObservableFixture.cs index 84b487055..7c67774f6 100644 --- a/src/DynamicData.Tests/Cache/FilterOnObservableFixture.cs +++ b/src/DynamicData.Tests/Cache/FilterOnObservableFixture.cs @@ -1,13 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Subjects; using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; -using Microsoft.Reactive.Testing; -using Xunit; namespace DynamicData.Tests.Cache; @@ -115,7 +107,7 @@ public void ObservableFilterUsedToDetermineInclusion() public void ObservableFilterTriggersAddAndRemove() { // having - ISubject filterSubject = new Subject(); + ISignal filterSubject = new Signal(); using var filterStats = _source.Connect().FilterOnObservable(_ => filterSubject).AsAggregator(); @@ -138,7 +130,7 @@ public void ObservableFilterTriggersAddAndRemove() public void ObservableFilterDuplicateValuesHaveNoEffect() { // having - ISubject filterSubject = new Subject(); + ISignal filterSubject = new Signal(); using var filterStats = _source.Connect().FilterOnObservable(_ => filterSubject).AsAggregator(); @@ -165,7 +157,7 @@ public void ObservableFilterChangesCanBeBuffered() { // having TestScheduler? scheduler = new TestScheduler(); - ISubject filterSubject = new Subject(); + ISignal filterSubject = new Signal(); using var filterStats = _source.Connect().FilterOnObservable(_ => filterSubject, TimeSpan.FromSeconds(1), scheduler).AsAggregator(); diff --git a/src/DynamicData.Tests/Cache/FilterParallelFixture.cs b/src/DynamicData.Tests/Cache/FilterParallelFixture.cs index bc1411684..28f560efc 100644 --- a/src/DynamicData.Tests/Cache/FilterParallelFixture.cs +++ b/src/DynamicData.Tests/Cache/FilterParallelFixture.cs @@ -1,13 +1,6 @@ -using System; -using System.Linq; - using DynamicData.PLinq; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class FilterParallelFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/ForEachChangeFixture.cs b/src/DynamicData.Tests/Cache/ForEachChangeFixture.cs index 1da5de03a..3c36c1404 100644 --- a/src/DynamicData.Tests/Cache/ForEachChangeFixture.cs +++ b/src/DynamicData.Tests/Cache/ForEachChangeFixture.cs @@ -1,12 +1,5 @@ -using System; -using System.Collections.Generic; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class ForEachChangeFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/FromAsyncFixture.cs b/src/DynamicData.Tests/Cache/FromAsyncFixture.cs index 03f716d6d..8d2453fba 100644 --- a/src/DynamicData.Tests/Cache/FromAsyncFixture.cs +++ b/src/DynamicData.Tests/Cache/FromAsyncFixture.cs @@ -1,17 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; -using System.Threading.Tasks; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Microsoft.Reactive.Testing; - -using Xunit; - namespace DynamicData.Tests.Cache; public class FromAsyncFixture diff --git a/src/DynamicData.Tests/Cache/FullJoinFixture.cs b/src/DynamicData.Tests/Cache/FullJoinFixture.cs index e290edcf1..4a5a72d3d 100644 --- a/src/DynamicData.Tests/Cache/FullJoinFixture.cs +++ b/src/DynamicData.Tests/Cache/FullJoinFixture.cs @@ -1,12 +1,3 @@ -using System; -using System.Linq; - -using DynamicData.Kernel; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class FullJoinFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/FullJoinManyFixture.cs b/src/DynamicData.Tests/Cache/FullJoinManyFixture.cs index 0a0c99c29..3577eaa2f 100644 --- a/src/DynamicData.Tests/Cache/FullJoinManyFixture.cs +++ b/src/DynamicData.Tests/Cache/FullJoinManyFixture.cs @@ -1,13 +1,6 @@ -using System; -using System.Linq; - using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class FullJoinManyFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/GroupControllerFixture.cs b/src/DynamicData.Tests/Cache/GroupControllerFixture.cs index 31686cadd..9fcf881ad 100644 --- a/src/DynamicData.Tests/Cache/GroupControllerFixture.cs +++ b/src/DynamicData.Tests/Cache/GroupControllerFixture.cs @@ -1,14 +1,5 @@ -using System; -using System.Linq; -using System.Reactive; -using System.Reactive.Subjects; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class GroupControllerFixture : IDisposable @@ -25,14 +16,14 @@ public class GroupControllerFixture : IDisposable return p.Age <= 60 ? AgeBracket.Adult : AgeBracket.Pensioner; }; - private readonly ISubject _refresher; + private readonly Signal _refresher; private readonly ISourceCache _source; public GroupControllerFixture() { _source = new SourceCache(p => p.Name); - _refresher = new Subject(); + _refresher = new Signal(); _grouped = _source.Connect().Group(_grouper, _refresher).AsObservableCache(); } @@ -49,6 +40,7 @@ public void Dispose() { _source?.Dispose(); _grouped?.Dispose(); + _refresher.Dispose(); } [Fact] diff --git a/src/DynamicData.Tests/Cache/GroupControllerForFilteredItemsFixture.cs b/src/DynamicData.Tests/Cache/GroupControllerForFilteredItemsFixture.cs index 871f5498c..137f0ea48 100644 --- a/src/DynamicData.Tests/Cache/GroupControllerForFilteredItemsFixture.cs +++ b/src/DynamicData.Tests/Cache/GroupControllerForFilteredItemsFixture.cs @@ -1,14 +1,5 @@ -using System; -using System.Linq; -using System.Reactive; -using System.Reactive.Subjects; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class GroupControllerForFilteredItemsFixture : IDisposable @@ -25,7 +16,7 @@ public class GroupControllerForFilteredItemsFixture : IDisposable return p.Age <= 60 ? AgeBracket.Adult : AgeBracket.Pensioner; }; - private readonly ISubject _refreshSubject = new Subject(); + private readonly Signal _refreshSubject = new Signal(); private readonly ISourceCache _source; @@ -49,6 +40,7 @@ public void Dispose() { _source.Dispose(); _grouped.Dispose(); + _refreshSubject.Dispose(); } [Fact] diff --git a/src/DynamicData.Tests/Cache/GroupFixture.cs b/src/DynamicData.Tests/Cache/GroupFixture.cs index c0139eed5..940f7e9e1 100644 --- a/src/DynamicData.Tests/Cache/GroupFixture.cs +++ b/src/DynamicData.Tests/Cache/GroupFixture.cs @@ -1,15 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Reactive.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class GroupFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/GroupFromDistinctFixture.cs b/src/DynamicData.Tests/Cache/GroupFromDistinctFixture.cs index b5f3292dc..b7a7177f6 100644 --- a/src/DynamicData.Tests/Cache/GroupFromDistinctFixture.cs +++ b/src/DynamicData.Tests/Cache/GroupFromDistinctFixture.cs @@ -1,14 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; - using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class GroupFromDistinctFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/GroupImmutableFixture.cs b/src/DynamicData.Tests/Cache/GroupImmutableFixture.cs index 8640bccb7..f1acdfd90 100644 --- a/src/DynamicData.Tests/Cache/GroupImmutableFixture.cs +++ b/src/DynamicData.Tests/Cache/GroupImmutableFixture.cs @@ -1,13 +1,6 @@ -using System; -using System.Linq; - -using DynamicData.Kernel; +using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class GroupImmutableFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/GroupOnDynamicFixture.cs b/src/DynamicData.Tests/Cache/GroupOnDynamicFixture.cs index ab5fd0bd4..6eb66df47 100644 --- a/src/DynamicData.Tests/Cache/GroupOnDynamicFixture.cs +++ b/src/DynamicData.Tests/Cache/GroupOnDynamicFixture.cs @@ -1,17 +1,6 @@ -using System; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Threading.Tasks; - using Bogus; using DynamicData.Kernel; using DynamicData.Tests.Domain; -using DynamicData.Tests.Utilities; -using FluentAssertions; -using Xunit; - using Person = DynamicData.Tests.Domain.Person; namespace DynamicData.Tests.Cache; @@ -34,8 +23,8 @@ public class GroupOnDynamicFixture : IDisposable private readonly GroupChangeSetAggregator _groupResults; private readonly Faker _faker; private readonly Randomizer _randomizer; - private readonly BehaviorSubject?> _keySelectionSubject = new (null); - private readonly Subject _regroupSubject = new (); + private readonly StateSignal?> _keySelectionSubject = new (null); + private readonly Signal _regroupSubject = new (); public GroupOnDynamicFixture() { @@ -264,7 +253,6 @@ public void ResultIsCorrectAfterForcedRegroup() VerifyGroupingResults(); } - [Theory] [InlineData(false, false, false)] [InlineData(false, false, true)] diff --git a/src/DynamicData.Tests/Cache/GroupOnObservableFixture.cs b/src/DynamicData.Tests/Cache/GroupOnObservableFixture.cs index 1909b2abb..cb7881a46 100644 --- a/src/DynamicData.Tests/Cache/GroupOnObservableFixture.cs +++ b/src/DynamicData.Tests/Cache/GroupOnObservableFixture.cs @@ -1,17 +1,7 @@ -using System; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Threading.Tasks; - using Bogus; using DynamicData.Binding; using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; -using Xunit; - using Person = DynamicData.Tests.Domain.Person; namespace DynamicData.Tests.Cache; @@ -32,7 +22,7 @@ public class GroupOnObservableFixture : IDisposable private readonly SourceCache _cache = new (p => p.UniqueKey); private readonly ChangeSetAggregator _results; private readonly GroupChangeSetAggregator _groupResults; - private readonly Subject _grouperShutdown; + private readonly Signal _grouperShutdown; private readonly Faker _faker; private readonly Randomizer _randomizer = new(0x3141_5926); diff --git a/src/DynamicData.Tests/Cache/GroupOnPropertyFixture.cs b/src/DynamicData.Tests/Cache/GroupOnPropertyFixture.cs index 7af362118..609263f17 100644 --- a/src/DynamicData.Tests/Cache/GroupOnPropertyFixture.cs +++ b/src/DynamicData.Tests/Cache/GroupOnPropertyFixture.cs @@ -1,11 +1,5 @@ -using System; -using System.Linq; - -using DynamicData.Kernel; +using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.Cache; diff --git a/src/DynamicData.Tests/Cache/GroupOnPropertyWithImmutableStateFixture.cs b/src/DynamicData.Tests/Cache/GroupOnPropertyWithImmutableStateFixture.cs index 2321b6e3c..8411c2eb3 100644 --- a/src/DynamicData.Tests/Cache/GroupOnPropertyWithImmutableStateFixture.cs +++ b/src/DynamicData.Tests/Cache/GroupOnPropertyWithImmutableStateFixture.cs @@ -1,13 +1,6 @@ -using System; -using System.Linq; - -using DynamicData.Kernel; +using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class GroupOnPropertyWithImmutableStateFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/IgnoreUpdateFixture.cs b/src/DynamicData.Tests/Cache/IgnoreUpdateFixture.cs index 7223a17af..6af361809 100644 --- a/src/DynamicData.Tests/Cache/IgnoreUpdateFixture.cs +++ b/src/DynamicData.Tests/Cache/IgnoreUpdateFixture.cs @@ -1,10 +1,4 @@ -using System; - -using DynamicData.Tests.Domain; - -using FluentAssertions; - -using Xunit; +using DynamicData.Tests.Domain; namespace DynamicData.Tests.Cache; diff --git a/src/DynamicData.Tests/Cache/IncludeUpdateFixture.cs b/src/DynamicData.Tests/Cache/IncludeUpdateFixture.cs index 5895a7e71..ead2a0d13 100644 --- a/src/DynamicData.Tests/Cache/IncludeUpdateFixture.cs +++ b/src/DynamicData.Tests/Cache/IncludeUpdateFixture.cs @@ -1,10 +1,4 @@ -using System; - -using DynamicData.Tests.Domain; - -using FluentAssertions; - -using Xunit; +using DynamicData.Tests.Domain; namespace DynamicData.Tests.Cache; diff --git a/src/DynamicData.Tests/Cache/InnerJoinFixture.cs b/src/DynamicData.Tests/Cache/InnerJoinFixture.cs index bd5ffc054..8d7b3895a 100644 --- a/src/DynamicData.Tests/Cache/InnerJoinFixture.cs +++ b/src/DynamicData.Tests/Cache/InnerJoinFixture.cs @@ -1,11 +1,3 @@ -using System; - -using DynamicData.Tests.Utilities; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class InnerJoinFixture : IDisposable @@ -124,7 +116,6 @@ public void RefreshRightKey() var refreshItem = _right.Lookup(2).Value; - // Change pairing refreshItem.Name = "Device3"; _right.Refresh(refreshItem); @@ -132,7 +123,6 @@ public void RefreshRightKey() _result.Data.Count.Should().Be(3); _result.Data.Keys.Should().Contain(("Device3", 2)); - // Remove pairing refreshItem.Name = "Device4"; _right.Refresh(refreshItem); @@ -140,7 +130,6 @@ public void RefreshRightKey() _result.Data.Count.Should().Be(2); _result.Data.Keys.Should().NotContain(pair => pair.rightKey == 2); - // Restore pairing refreshItem.Name = "Device2"; _right.Refresh(refreshItem); @@ -148,7 +137,6 @@ public void RefreshRightKey() _result.Data.Count.Should().Be(3); _result.Data.Keys.Should().Contain(("Device2", 2)); - // No change _right.Refresh(refreshItem); @@ -231,28 +219,24 @@ public void UpdateRightKey() innerCache.AddOrUpdate(new DeviceMetaData(3,"Device3")); }); - // Change pairing _right.AddOrUpdate(new DeviceMetaData(2,"Device3")); _result.Data.Count.Should().Be(3); _result.Data.Keys.Should().Contain(("Device3", 2)); - // Remove pairing _right.AddOrUpdate(new DeviceMetaData(2,"Device4")); _result.Data.Count.Should().Be(2); _result.Data.Keys.Should().NotContain(pair => pair.rightKey == 2); - // Restore pairing _right.AddOrUpdate(new DeviceMetaData(2,"Device2")); _result.Data.Count.Should().Be(3); _result.Data.Keys.Should().Contain(("Device2", 2)); - // No change _right.AddOrUpdate(new DeviceMetaData(2,"Device2")); diff --git a/src/DynamicData.Tests/Cache/InnerJoinFixtureRaceCondition.cs b/src/DynamicData.Tests/Cache/InnerJoinFixtureRaceCondition.cs index bdad60ee9..61bdbc577 100644 --- a/src/DynamicData.Tests/Cache/InnerJoinFixtureRaceCondition.cs +++ b/src/DynamicData.Tests/Cache/InnerJoinFixtureRaceCondition.cs @@ -1,9 +1,3 @@ -using System; -using System.Reactive.Concurrency; -using System.Reactive.Linq; - -using Xunit; - namespace DynamicData.Tests.Cache; public class InnerJoinFixtureRaceCondition diff --git a/src/DynamicData.Tests/Cache/InnerJoinManyFixture.cs b/src/DynamicData.Tests/Cache/InnerJoinManyFixture.cs index edaad944a..dd822d7aa 100644 --- a/src/DynamicData.Tests/Cache/InnerJoinManyFixture.cs +++ b/src/DynamicData.Tests/Cache/InnerJoinManyFixture.cs @@ -1,13 +1,6 @@ -using System; -using System.Linq; - using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class InnerJoinManyFixture : IDisposable @@ -87,7 +80,6 @@ public void RefreshRightKey() var refreshPerson = _people.Lookup("Person #2").Value; - // Change pairing refreshPerson.ParentName = "Person #3"; _people.Refresh(refreshPerson); @@ -96,7 +88,6 @@ public void RefreshRightKey() _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent.Name, child.Name))).Should().NotContain(("Person #1", "Person #2")); _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent.Name, child.Name))).Should().Contain(("Person #3", "Person #2")); - // Remove pairing refreshPerson.ParentName = "Person #4"; _people.Refresh(refreshPerson); @@ -104,7 +95,6 @@ public void RefreshRightKey() _result.Data.Count.Should().Be(1); _result.Data.Items.SelectMany(family => family.Children.Select(child => (parentName: family.Parent.Name, chilldName: child.Name))).Should().NotContain(pair => pair.chilldName == "Person #2"); - // Restore pairing refreshPerson.ParentName = "Person #1"; _people.Refresh(refreshPerson); @@ -112,7 +102,6 @@ public void RefreshRightKey() _result.Data.Count.Should().Be(2); _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent.Name, child.Name))).Should().Contain(("Person #1", "Person #2")); - // No change _people.Refresh(refreshPerson); @@ -193,7 +182,6 @@ public void UpdateRightKey() innerCache.AddOrUpdate(new Person() { Name = "Person #3", ParentName = "Person #2" } ); }); - // Change pairing _people.AddOrUpdate(new Person() { Name = "Person #2", ParentName = "Person #3" }); @@ -201,21 +189,18 @@ public void UpdateRightKey() _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent.Name, child.Name))).Should().NotContain(("Person #1", "Person #2")); _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent.Name, child.Name))).Should().Contain(("Person #3", "Person #2")); - // Remove pairing _people.AddOrUpdate(new Person() { Name = "Person #2", ParentName = "Person #4" }); _result.Data.Count.Should().Be(1); _result.Data.Items.SelectMany(family => family.Children.Select(child => (parentName: family.Parent.Name, chilldName: child.Name))).Should().NotContain(pair => pair.chilldName == "Person #2"); - // Restore pairing _people.AddOrUpdate(new Person() { Name = "Person #2", ParentName = "Person #1" }); _result.Data.Count.Should().Be(2); _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent.Name, child.Name))).Should().Contain(("Person #1", "Person #2")); - // No change _people.AddOrUpdate(new Person() { Name = "Person #2", ParentName = "Person #1" }); diff --git a/src/DynamicData.Tests/Cache/KeyValueCollectionEx.cs b/src/DynamicData.Tests/Cache/KeyValueCollectionEx.cs index d8f46418e..f7f437f1c 100644 --- a/src/DynamicData.Tests/Cache/KeyValueCollectionEx.cs +++ b/src/DynamicData.Tests/Cache/KeyValueCollectionEx.cs @@ -1,6 +1,3 @@ -using System.Collections.Generic; -using System.Linq; - namespace DynamicData.Tests.Cache; public static class KeyValueCollectionEx diff --git a/src/DynamicData.Tests/Cache/LeftJoinFixture.cs b/src/DynamicData.Tests/Cache/LeftJoinFixture.cs index c5d0fcd35..074f7bbed 100644 --- a/src/DynamicData.Tests/Cache/LeftJoinFixture.cs +++ b/src/DynamicData.Tests/Cache/LeftJoinFixture.cs @@ -1,12 +1,4 @@ -using System; -using System.Linq; - using DynamicData.Kernel; -using DynamicData.Tests.Utilities; - -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.Cache; @@ -134,7 +126,6 @@ public void RefreshRightKey() var refreshItem = _right.Lookup(2).Value; - // Change pairing refreshItem.Name = "Device3"; _right.Refresh(refreshItem); @@ -143,7 +134,6 @@ public void RefreshRightKey() _result.Data.Items.Select(pair => (pair.Device.Name, pair.MetaData.ValueOrDefault()?.Key)).Should().NotContain(("Device2", 2)); _result.Data.Items.Select(pair => (pair.Device.Name, pair.MetaData.ValueOrDefault()?.Key)).Should().Contain(("Device3", 2)); - // Remove pairing refreshItem.Name = "Device4"; _right.Refresh(refreshItem); @@ -151,7 +141,6 @@ public void RefreshRightKey() _result.Data.Count.Should().Be(3); _result.Data.Items.Select(pair => (pair.Device.Name, pair.MetaData.ValueOrDefault()?.Key)).Should().NotContain(pair => pair.Key == 2); - // Restore pairing refreshItem.Name = "Device2"; _right.Refresh(refreshItem); @@ -159,7 +148,6 @@ public void RefreshRightKey() _result.Data.Count.Should().Be(3); _result.Data.Items.Select(pair => (pair.Device.Name, pair.MetaData.ValueOrDefault()?.Key)).Should().Contain(("Device2", 2)); - // No change _right.Refresh(refreshItem); @@ -238,7 +226,6 @@ public void UpdateRightKey() innerCache.AddOrUpdate(new DeviceMetaData(3,"Device3")); }); - // Change pairing _right.AddOrUpdate(new DeviceMetaData(2,"Device3")); @@ -246,21 +233,18 @@ public void UpdateRightKey() _result.Data.Items.Select(pair => (pair.Device.Name, pair.MetaData.ValueOrDefault()?.Key)).Should().NotContain(("Device2", 2)); _result.Data.Items.Select(pair => (pair.Device.Name, pair.MetaData.ValueOrDefault()?.Key)).Should().Contain(("Device3", 2)); - // Remove pairing _right.AddOrUpdate(new DeviceMetaData(2,"Device4")); _result.Data.Count.Should().Be(3); _result.Data.Items.Select(pair => (pair.Device.Name, pair.MetaData.ValueOrDefault()?.Key)).Should().NotContain(pair => pair.Key == 2); - // Restore pairing _right.AddOrUpdate(new DeviceMetaData(2,"Device2")); _result.Data.Count.Should().Be(3); _result.Data.Items.Select(pair => (pair.Device.Name, pair.MetaData.ValueOrDefault()?.Key)).Should().Contain(("Device2", 2)); - // No change _right.AddOrUpdate(new DeviceMetaData(2,"Device2")); diff --git a/src/DynamicData.Tests/Cache/LeftJoinManyFixture.cs b/src/DynamicData.Tests/Cache/LeftJoinManyFixture.cs index dd4b22056..b7ab8f226 100644 --- a/src/DynamicData.Tests/Cache/LeftJoinManyFixture.cs +++ b/src/DynamicData.Tests/Cache/LeftJoinManyFixture.cs @@ -1,13 +1,6 @@ -using System; -using System.Linq; - using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class LeftJoinManyFixture : IDisposable @@ -90,7 +83,6 @@ public void RefreshRightKey() var refreshPerson = _people.Lookup("Person #2").Value; - // Change pairing refreshPerson.ParentName = "Person #3"; _people.Refresh(refreshPerson); @@ -99,7 +91,6 @@ public void RefreshRightKey() _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent.Name, child.Name))).Should().NotContain(("Person #1", "Person #2")); _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent.Name, child.Name))).Should().Contain(("Person #3", "Person #2")); - // Remove pairing refreshPerson.ParentName = "Person #4"; _people.Refresh(refreshPerson); @@ -107,7 +98,6 @@ public void RefreshRightKey() _result.Data.Count.Should().Be(3); _result.Data.Items.SelectMany(family => family.Children.Select(child => (parentName: family.Parent.Name, chilldName: child.Name))).Should().NotContain(pair => pair.chilldName == "Person #2"); - // Restore pairing refreshPerson.ParentName = "Person #1"; _people.Refresh(refreshPerson); @@ -115,7 +105,6 @@ public void RefreshRightKey() _result.Data.Count.Should().Be(3); _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent.Name, child.Name))).Should().Contain(("Person #1", "Person #2")); - // No change _people.Refresh(refreshPerson); @@ -196,7 +185,6 @@ public void UpdateRightKey() innerCache.AddOrUpdate(new Person() { Name = "Person #3", ParentName = "Person #2" } ); }); - // Change pairing _people.AddOrUpdate(new Person() { Name = "Person #2", ParentName = "Person #3" }); @@ -204,21 +192,18 @@ public void UpdateRightKey() _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent.Name, child.Name))).Should().NotContain(("Person #1", "Person #2")); _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent.Name, child.Name))).Should().Contain(("Person #3", "Person #2")); - // Remove pairing _people.AddOrUpdate(new Person() { Name = "Person #2", ParentName = "Person #4" }); _result.Data.Count.Should().Be(3); _result.Data.Items.SelectMany(family => family.Children.Select(child => (parentName: family.Parent.Name, chilldName: child.Name))).Should().NotContain(pair => pair.chilldName == "Person #2"); - // Restore pairing _people.AddOrUpdate(new Person() { Name = "Person #2", ParentName = "Person #1" }); _result.Data.Count.Should().Be(3); _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent.Name, child.Name))).Should().Contain(("Person #1", "Person #2")); - // No change _people.AddOrUpdate(new Person() { Name = "Person #2", ParentName = "Person #1" }); diff --git a/src/DynamicData.Tests/Cache/MergeChangeSetsFixture.cs b/src/DynamicData.Tests/Cache/MergeChangeSetsFixture.cs index b6f3047e2..727cac4ab 100644 --- a/src/DynamicData.Tests/Cache/MergeChangeSetsFixture.cs +++ b/src/DynamicData.Tests/Cache/MergeChangeSetsFixture.cs @@ -1,14 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; using DynamicData.Kernel; using DynamicData.Tests.Domain; -using DynamicData.Tests.Utilities; -using FluentAssertions; -using Microsoft.Reactive.Testing; - -using Xunit; namespace DynamicData.Tests.Cache; diff --git a/src/DynamicData.Tests/Cache/MergeManyChangeSetsCacheFixture.cs b/src/DynamicData.Tests/Cache/MergeManyChangeSetsCacheFixture.cs index 949ec35bf..874d890b8 100644 --- a/src/DynamicData.Tests/Cache/MergeManyChangeSetsCacheFixture.cs +++ b/src/DynamicData.Tests/Cache/MergeManyChangeSetsCacheFixture.cs @@ -1,19 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Threading.Tasks; -using System.Threading.Tasks; using Bogus; using DynamicData.Kernel; using DynamicData.Tests.Domain; -using DynamicData.Tests.Utilities; -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.Cache; @@ -816,7 +803,6 @@ public void MergeManyChangeSetsWorksCorrectlyWithValueTypes() results.Summary.Overall.Removes.Should().Be(PricesPerMarket); } - [Theory] [InlineData(true)] [InlineData(false)] diff --git a/src/DynamicData.Tests/Cache/MergeManyChangeSetsCacheSourceCompareFixture.cs b/src/DynamicData.Tests/Cache/MergeManyChangeSetsCacheSourceCompareFixture.cs index ce6cc89fd..6b8b56823 100644 --- a/src/DynamicData.Tests/Cache/MergeManyChangeSetsCacheSourceCompareFixture.cs +++ b/src/DynamicData.Tests/Cache/MergeManyChangeSetsCacheSourceCompareFixture.cs @@ -1,19 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive; -using System.Reactive.Linq; -using System.Reactive.Threading.Tasks; -using System.Threading.Tasks; using Bogus; using DynamicData.Kernel; using DynamicData.Tests.Domain; -using DynamicData.Tests.Utilities; -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.Cache; diff --git a/src/DynamicData.Tests/Cache/MergeManyChangeSetsListFixture.cs b/src/DynamicData.Tests/Cache/MergeManyChangeSetsListFixture.cs index 49f566907..d2b767cb4 100644 --- a/src/DynamicData.Tests/Cache/MergeManyChangeSetsListFixture.cs +++ b/src/DynamicData.Tests/Cache/MergeManyChangeSetsListFixture.cs @@ -1,18 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Threading.Tasks; -using System.Threading.Tasks; using Bogus; using DynamicData.Kernel; using DynamicData.Tests.Domain; -using DynamicData.Tests.Utilities; -using FluentAssertions; -using Xunit; namespace DynamicData.Tests.Cache; diff --git a/src/DynamicData.Tests/Cache/MergeManyFixture.cs b/src/DynamicData.Tests/Cache/MergeManyFixture.cs index 553bf452e..c16cf06c0 100644 --- a/src/DynamicData.Tests/Cache/MergeManyFixture.cs +++ b/src/DynamicData.Tests/Cache/MergeManyFixture.cs @@ -1,11 +1,3 @@ -using System; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class MergeManyFixture : IDisposable @@ -67,9 +59,9 @@ public void RemovedItemWillNotCauseInvocation() stream.Dispose(); } - private class ObjectWithObservable(int id) + private class ObjectWithObservable(int id) : IDisposable { - private readonly ISubject _changed = new Subject(); + private readonly Signal _changed = new Signal(); private bool _value; @@ -82,5 +74,10 @@ public void InvokeObservable(bool value) _value = value; _changed.OnNext(value); } - } + + public void Dispose() + { + _changed.Dispose(); + } +} } diff --git a/src/DynamicData.Tests/Cache/MergeManyItemsFixture.cs b/src/DynamicData.Tests/Cache/MergeManyItemsFixture.cs index e2a6be748..86c9979a9 100644 --- a/src/DynamicData.Tests/Cache/MergeManyItemsFixture.cs +++ b/src/DynamicData.Tests/Cache/MergeManyItemsFixture.cs @@ -1,11 +1,3 @@ -using System; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class MergeManyItemsFixture : IDisposable @@ -79,9 +71,9 @@ public void RemovedItemWillNotCauseInvocation() stream.Dispose(); } - private class ObjectWithObservable(int id) + private class ObjectWithObservable(int id) : IDisposable { - private readonly ISubject _changed = new Subject(); + private readonly Signal _changed = new Signal(); private bool _value; @@ -94,5 +86,9 @@ public void InvokeObservable(bool value) _value = value; _changed.OnNext(value); } - } + public void Dispose() + { + _changed.Dispose(); + } +} } diff --git a/src/DynamicData.Tests/Cache/MergeManyWithKeyOverloadFixture.cs b/src/DynamicData.Tests/Cache/MergeManyWithKeyOverloadFixture.cs index e6987e846..3f15416e5 100644 --- a/src/DynamicData.Tests/Cache/MergeManyWithKeyOverloadFixture.cs +++ b/src/DynamicData.Tests/Cache/MergeManyWithKeyOverloadFixture.cs @@ -1,11 +1,3 @@ -using System; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class MergeManyWithKeyOverloadFixture : IDisposable @@ -184,9 +176,9 @@ public void MergedStreamFailsWhenSourceFails() receivedError.Should().Be(expectedError); } - private class ObjectWithObservable(int id) + private class ObjectWithObservable(int id) : IDisposable { - private readonly ISubject _changed = new Subject(); + private readonly Signal _changed = new Signal(); private bool _value; @@ -203,5 +195,7 @@ public void InvokeObservable(bool value) _value = value; _changed.OnNext(value); } + + public void Dispose() => _changed.Dispose(); } } diff --git a/src/DynamicData.Tests/Cache/MonitorStatusFixture.cs b/src/DynamicData.Tests/Cache/MonitorStatusFixture.cs index 6a6a33a38..280875e6b 100644 --- a/src/DynamicData.Tests/Cache/MonitorStatusFixture.cs +++ b/src/DynamicData.Tests/Cache/MonitorStatusFixture.cs @@ -1,13 +1,5 @@ -using System; -using System.Reactive.Linq; -using System.Reactive.Subjects; - using DynamicData.Kernel; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class MonitorStatusFixture @@ -17,7 +9,7 @@ public void InitialiStatusIsLoadding() { var invoked = false; var status = ConnectionStatus.Pending; - var subscription = new Subject().MonitorStatus().Subscribe( + var subscription = new Signal().MonitorStatus().Subscribe( s => { invoked = true; @@ -33,7 +25,7 @@ public void MultipleInvokesDoNotCallLoadedAgain() { var invoked = false; var invocations = 0; - var subject = new Subject(); + var subject = new Signal(); var subscription = subject.MonitorStatus().Where(status => status == ConnectionStatus.Loaded).Subscribe( s => { @@ -55,7 +47,7 @@ public void SetToError() { var invoked = false; var status = ConnectionStatus.Pending; - var subject = new Subject(); + var subject = new Signal(); Exception exception; var subscription = subject.MonitorStatus().Subscribe( @@ -78,7 +70,7 @@ public void SetToLoaded() { var invoked = false; var status = ConnectionStatus.Pending; - var subject = new Subject(); + var subject = new Signal(); var subscription = subject.MonitorStatus().Subscribe( s => { diff --git a/src/DynamicData.Tests/Cache/ObservableCachePreviewFixture.cs b/src/DynamicData.Tests/Cache/ObservableCachePreviewFixture.cs index 801b03040..781cd1c78 100644 --- a/src/DynamicData.Tests/Cache/ObservableCachePreviewFixture.cs +++ b/src/DynamicData.Tests/Cache/ObservableCachePreviewFixture.cs @@ -1,9 +1,4 @@ -using System; -using System.Linq; - -using DynamicData.Tests.Domain; - -using Xunit; +using DynamicData.Tests.Domain; namespace DynamicData.Tests.Cache; diff --git a/src/DynamicData.Tests/Cache/ObservableChangeSetFixture.cs b/src/DynamicData.Tests/Cache/ObservableChangeSetFixture.cs index 4460dad3e..8f88f8571 100644 --- a/src/DynamicData.Tests/Cache/ObservableChangeSetFixture.cs +++ b/src/DynamicData.Tests/Cache/ObservableChangeSetFixture.cs @@ -1,17 +1,5 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Threading.Tasks; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class ObservableChangeSetFixture @@ -27,7 +15,6 @@ private async Task AsyncSubscriptionCanReceiveMultipleResults() //the aim of this test is to ensure we can continuously receive subscriptions when we use the async subscribe overloads var result = new List(); - var observable = ObservableChangeSet.Create( async (changeSet, token) => { @@ -49,11 +36,9 @@ private async Task AsyncSubscriptionCanReceiveMultipleResults() i => i) .Select(cs => cs.Select(c => c.Current).ToList()); - var isComplete = false; Exception? error = null; - //load list of results var subscriber = observable .Subscribe(item => result.AddRange(item), ex => error = ex, () => isComplete = true); @@ -77,7 +62,6 @@ private async Task AsyncSubscriptionCanReceiveMultipleResults() subscriber.Dispose(); } - [Fact] public void HandlesAsyncError() { diff --git a/src/DynamicData.Tests/Cache/OfTypeFixture.cs b/src/DynamicData.Tests/Cache/OfTypeFixture.cs index ec38a0629..37098e472 100644 --- a/src/DynamicData.Tests/Cache/OfTypeFixture.cs +++ b/src/DynamicData.Tests/Cache/OfTypeFixture.cs @@ -1,11 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Bogus; +using Bogus; using DynamicData.Tests.Domain; -using DynamicData.Tests.Utilities; -using FluentAssertions; -using Xunit; using Xunit.Abstractions; namespace DynamicData.Tests.Cache; diff --git a/src/DynamicData.Tests/Cache/OnItemFixture.cs b/src/DynamicData.Tests/Cache/OnItemFixture.cs index 97aeebc75..2e6639ed6 100644 --- a/src/DynamicData.Tests/Cache/OnItemFixture.cs +++ b/src/DynamicData.Tests/Cache/OnItemFixture.cs @@ -1,11 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; -using Xunit; namespace DynamicData.Tests.Cache; @@ -128,7 +122,6 @@ public class Proxy public bool? Active { get; set; } - } public class ProxyEqualityComparer : IEqualityComparer diff --git a/src/DynamicData.Tests/Cache/OrFixture.cs b/src/DynamicData.Tests/Cache/OrFixture.cs index 720d3f0b4..add8e2358 100644 --- a/src/DynamicData.Tests/Cache/OrFixture.cs +++ b/src/DynamicData.Tests/Cache/OrFixture.cs @@ -1,13 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class OrFixture : OrFixtureBase diff --git a/src/DynamicData.Tests/Cache/PageFixture.cs b/src/DynamicData.Tests/Cache/PageFixture.cs index 8c31979d5..be343fd5a 100644 --- a/src/DynamicData.Tests/Cache/PageFixture.cs +++ b/src/DynamicData.Tests/Cache/PageFixture.cs @@ -1,15 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Subjects; - using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class PageFixture : IDisposable @@ -20,9 +11,9 @@ public class PageFixture : IDisposable private readonly RandomPersonGenerator _generator = new(); - private readonly ISubject _pager; + private readonly ISignal _pager; - private readonly ISubject> _sort; + private readonly ISignal> _sort; private readonly ISourceCache _source; @@ -30,8 +21,8 @@ public PageFixture() { _source = new SourceCache(p => p.Name); _comparer = SortExpressionComparer.Ascending(p => p.Name).ThenByAscending(p => p.Age); - _sort = new BehaviorSubject>(_comparer); - _pager = new BehaviorSubject(new PageRequest(1, 25)); + _sort = new StateSignal>(_comparer); + _pager = new StateSignal(new PageRequest(1, 25)); _aggregators = _source.Connect().Sort(_sort, resetThreshold: 200).Page(_pager).AsAggregator(); } @@ -68,6 +59,8 @@ public void Dispose() { _source.Dispose(); _aggregators.Dispose(); + _pager.Dispose(); + _sort.Dispose(); } [Fact] diff --git a/src/DynamicData.Tests/Cache/QueryWhenChangedFixture.cs b/src/DynamicData.Tests/Cache/QueryWhenChangedFixture.cs index 67f626b86..58feecef5 100644 --- a/src/DynamicData.Tests/Cache/QueryWhenChangedFixture.cs +++ b/src/DynamicData.Tests/Cache/QueryWhenChangedFixture.cs @@ -1,11 +1,5 @@ -using System; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class QueryWhenChangedFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/RefCountFixture.cs b/src/DynamicData.Tests/Cache/RefCountFixture.cs index 14be2f16b..73c54790a 100644 --- a/src/DynamicData.Tests/Cache/RefCountFixture.cs +++ b/src/DynamicData.Tests/Cache/RefCountFixture.cs @@ -1,14 +1,5 @@ -using System; -using System.Linq; -using System.Reactive.Linq; -using System.Threading.Tasks; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class RefCountFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/RightJoinFixture.cs b/src/DynamicData.Tests/Cache/RightJoinFixture.cs index 62aa30190..e2117db7f 100644 --- a/src/DynamicData.Tests/Cache/RightJoinFixture.cs +++ b/src/DynamicData.Tests/Cache/RightJoinFixture.cs @@ -1,12 +1,4 @@ -using System; -using System.Linq; - using DynamicData.Kernel; -using DynamicData.Tests.Utilities; - -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.Cache; @@ -135,7 +127,6 @@ public void RefreshRightKey() var refreshItem = _right.Lookup(2).Value; - // Change pairing refreshItem.Name = "Device3"; _right.Refresh(refreshItem); @@ -144,7 +135,6 @@ public void RefreshRightKey() _result.Data.Items.Select(pair => (pair.Device.ValueOrDefault()?.Name, pair.MetaData.Key)).Should().NotContain(("Device2", 2)); _result.Data.Items.Select(pair => (pair.Device.ValueOrDefault()?.Name, pair.MetaData.Key)).Should().Contain(("Device3", 2)); - // Remove pairing refreshItem.Name = "Device4"; _right.Refresh(refreshItem); @@ -153,7 +143,6 @@ public void RefreshRightKey() _result.Data.Items.Select(pair => (pair.Device.ValueOrDefault()?.Name, pair.MetaData.Key)).Should().NotContain(("Device3", 2)); _result.Data.Items.Select(pair => (pair.Device.ValueOrDefault()?.Name, pair.MetaData.Key)).Should().Contain((null, 2)); - // Restore pairing refreshItem.Name = "Device2"; _right.Refresh(refreshItem); @@ -162,7 +151,6 @@ public void RefreshRightKey() _result.Data.Items.Select(pair => (pair.Device.ValueOrDefault()?.Name, pair.MetaData.Key)).Should().NotContain((null, 2)); _result.Data.Items.Select(pair => (pair.Device.ValueOrDefault()?.Name, pair.MetaData.Key)).Should().Contain(("Device2", 2)); - // No change _right.Refresh(refreshItem); @@ -243,7 +231,6 @@ public void UpdateRightKey() innerCache.AddOrUpdate(new DeviceMetaData(3,"Device3")); }); - // Change pairing _right.AddOrUpdate(new DeviceMetaData(2,"Device3")); @@ -251,7 +238,6 @@ public void UpdateRightKey() _result.Data.Items.Select(pair => (pair.Device.ValueOrDefault()?.Name, pair.MetaData.Key)).Should().NotContain(("Device2", 2)); _result.Data.Items.Select(pair => (pair.Device.ValueOrDefault()?.Name, pair.MetaData.Key)).Should().Contain(("Device3", 2)); - // Remove pairing _right.AddOrUpdate(new DeviceMetaData(2,"Device4")); @@ -259,7 +245,6 @@ public void UpdateRightKey() _result.Data.Items.Select(pair => (pair.Device.ValueOrDefault()?.Name, pair.MetaData.Key)).Should().NotContain(("Device3", 2)); _result.Data.Items.Select(pair => (pair.Device.ValueOrDefault()?.Name, pair.MetaData.Key)).Should().Contain((null, 2)); - // Restore pairing _right.AddOrUpdate(new DeviceMetaData(2,"Device2")); @@ -267,7 +252,6 @@ public void UpdateRightKey() _result.Data.Items.Select(pair => (pair.Device.ValueOrDefault()?.Name, pair.MetaData.Key)).Should().NotContain((null, 2)); _result.Data.Items.Select(pair => (pair.Device.ValueOrDefault()?.Name, pair.MetaData.Key)).Should().Contain(("Device2", 2)); - // No change _right.AddOrUpdate(new DeviceMetaData(2,"Device2")); diff --git a/src/DynamicData.Tests/Cache/RightJoinManyFixture.cs b/src/DynamicData.Tests/Cache/RightJoinManyFixture.cs index 24f013c3a..cd211f8c0 100644 --- a/src/DynamicData.Tests/Cache/RightJoinManyFixture.cs +++ b/src/DynamicData.Tests/Cache/RightJoinManyFixture.cs @@ -1,13 +1,6 @@ -using System; -using System.Linq; - using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class RightJoinManyFixture : IDisposable @@ -88,7 +81,6 @@ public void RefreshRightKey() var refreshPerson = _people.Lookup("Person #2").Value; - // Change pairing refreshPerson.ParentName = "Person #3"; _people.Refresh(refreshPerson); @@ -97,7 +89,6 @@ public void RefreshRightKey() _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent?.Name, child.Name))).Should().NotContain(("Person #1", "Person #2")); _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent?.Name, child.Name))).Should().Contain(("Person #3", "Person #2")); - // Remove pairing refreshPerson.ParentName = "Person #4"; _people.Refresh(refreshPerson); @@ -106,7 +97,6 @@ public void RefreshRightKey() _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent?.Name, child.Name))).Should().NotContain(("Person #3", "Person #2")); _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent?.Name, child.Name))).Should().Contain((null, "Person #2")); - // Restore pairing refreshPerson.ParentName = "Person #1"; _people.Refresh(refreshPerson); @@ -115,7 +105,6 @@ public void RefreshRightKey() _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent?.Name, child.Name))).Should().NotContain((null, "Person #2")); _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent?.Name, child.Name))).Should().Contain(("Person #1", "Person #2")); - // No change _people.Refresh(refreshPerson); @@ -197,7 +186,6 @@ public void UpdateRightKey() innerCache.AddOrUpdate(new Person() { Name = "Person #3", ParentName = "Person #2" } ); }); - // Change pairing _people.AddOrUpdate(new Person() { Name = "Person #2", ParentName = "Person #3" }); @@ -205,7 +193,6 @@ public void UpdateRightKey() _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent?.Name, child.Name))).Should().NotContain(("Person #1", "Person #2")); _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent?.Name, child.Name))).Should().Contain(("Person #3", "Person #2")); - // Remove pairing _people.AddOrUpdate(new Person() { Name = "Person #2", ParentName = "Person #4" }); @@ -213,7 +200,6 @@ public void UpdateRightKey() _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent?.Name, child.Name))).Should().NotContain(("Person #3", "Person #2")); _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent?.Name, child.Name))).Should().Contain((null, "Person #2")); - // Restore pairing _people.AddOrUpdate(new Person() { Name = "Person #2", ParentName = "Person #1" }); @@ -221,7 +207,6 @@ public void UpdateRightKey() _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent?.Name, child.Name))).Should().NotContain((null, "Person #2")); _result.Data.Items.SelectMany(family => family.Children.Select(child => (family.Parent?.Name, child.Name))).Should().Contain(("Person #1", "Person #2")); - // No change _people.AddOrUpdate(new Person() { Name = "Person #2", ParentName = "Person #1" }); diff --git a/src/DynamicData.Tests/Cache/SizeLimitFixture.cs b/src/DynamicData.Tests/Cache/SizeLimitFixture.cs index 378b7fa6d..8be9ab701 100644 --- a/src/DynamicData.Tests/Cache/SizeLimitFixture.cs +++ b/src/DynamicData.Tests/Cache/SizeLimitFixture.cs @@ -1,15 +1,5 @@ -using System; -using System.Linq; -using System.Reactive.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Microsoft.Reactive.Testing; - -using Xunit; - namespace DynamicData.Tests.Cache; public class SizeLimitFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/SortAndBindFixture.cs b/src/DynamicData.Tests/Cache/SortAndBindFixture.cs index f4a68b701..fc6c8491d 100644 --- a/src/DynamicData.Tests/Cache/SortAndBindFixture.cs +++ b/src/DynamicData.Tests/Cache/SortAndBindFixture.cs @@ -1,15 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Disposables; using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; -using Xunit; namespace DynamicData.Tests.Cache; @@ -26,7 +16,6 @@ protected override (ChangeSetAggregator Aggregrator, IList Aggregrator, IList Aggregrator, IList _strings.Dispose(); } - - // Bind to a readonly observable collection - using default comparer public sealed class SortAndBindToReadOnlyObservableCollectionDefaultComparer : SortAndBindFixture { @@ -175,7 +160,6 @@ public void FiresResetWhenThresholdIsMet() _collectionChangedEventArgs.Count.Should().Be(5); _collectionChangedEventArgs.All(a=>a.Action == NotifyCollectionChangedAction.Add).Should().BeTrue(); - _collectionChangedEventArgs.Clear(); // fire 15 changes, we should get a refresh event @@ -194,7 +178,6 @@ public void FiresResetWhenThresholdIsMet() } - [Fact] [Description("Check reset is not fired")] public void NeverFireReset() @@ -209,7 +192,6 @@ public void NeverFireReset() _collectionChangedEventArgs.Count.Should().Be(5); _collectionChangedEventArgs.All(a => a.Action == NotifyCollectionChangedAction.Add).Should().BeTrue(); - _collectionChangedEventArgs.Clear(); // fire 15 changes, we should get a refresh event @@ -235,7 +217,6 @@ public void FireResetOnFirstTimeLoad() _collectionChangedEventArgs.Count.Should().Be(1); _collectionChangedEventArgs.All(a => a.Action == NotifyCollectionChangedAction.Reset).Should().BeTrue(); - _collectionChangedEventArgs.Clear(); // fire 15 changes, we should get a refresh event @@ -254,12 +235,9 @@ public void FireResetOnFirstTimeLoad() } - - public void Dispose() => _source.Dispose(); } - public abstract class SortAndBindFixture : IDisposable { @@ -270,7 +248,6 @@ public abstract class SortAndBindFixture : IDisposable protected readonly IComparer _comparer = Person.DefaultComparer; protected readonly ISourceCache _source = new SourceCache(p => p.Key); - public SortAndBindFixture() { // It's ok in this case to call VirtualMemberCallInConstructor @@ -287,10 +264,8 @@ public SortAndBindFixture() } - protected abstract (ChangeSetAggregator Aggregrator, IList List) SetUpTests(); - [Fact] public void InsertAtBeginning() { @@ -333,7 +308,6 @@ public void InsertAtEnd() _boundList.SequenceEqual(_source.Items.OrderBy(p => p, _comparer)).Should().BeTrue(); } - [Fact] public void InsertInMiddle() { @@ -353,7 +327,6 @@ public void InsertInMiddle() _boundList.SequenceEqual(_source.Items.OrderBy(p => p, _comparer)).Should().BeTrue(); } - [Fact] public void InsertSameLocation() { @@ -377,11 +350,9 @@ void UpdateAndAssetPosition(Person person, int expectedIndex) _boundList.Count.Should().Be(10); - _boundList.SequenceEqual(_source.Items.OrderBy(p => p, _comparer)).Should().BeTrue(); } - [Fact] public void Refresh() { @@ -396,7 +367,6 @@ public void Refresh() RefreshAtAndAssetPosition(toRefresh, p => p.Age = 20, 1); RefreshAtAndAssetPosition(toRefresh, p => p.Age = 25, 1); - // move after RefreshAtAndAssetPosition(toRefresh, p => p.Age = 45, 3); @@ -414,11 +384,9 @@ void RefreshAtAndAssetPosition(Person person, Action action,int expected _boundList.Count.Should().Be(10); - _boundList.SequenceEqual(_source.Items.OrderBy(p => p, _comparer)).Should().BeTrue(); } - [Fact] public void BatchOfVariousChanges() { @@ -452,7 +420,6 @@ public void BatchOfVariousChanges() expectedInOrder.SequenceEqual(_boundList).Should().BeTrue(); } - [Fact] public void BatchOfVariousEndingInClear() { @@ -470,7 +437,6 @@ public void BatchOfVariousEndingInClear() } - [Fact] public void LargeBatchChange() { @@ -478,12 +444,10 @@ public void LargeBatchChange() _source.AddOrUpdate(Enumerable.Range(0, 100).Select(i => new Person($"P{i}", i))); _source.AddOrUpdate(Enumerable.Range(100, 100).Select(i => new Person($"P{i}", i))); - _boundList.Count.Should().Be(200); _boundList.SequenceEqual(_source.Items.OrderBy(p => p, _comparer)).Should().BeTrue(); } - [Fact] public void BatchUpdateShiftingIndicies() { @@ -519,8 +483,6 @@ public void BatchUpdateShiftingIndicies() _boundList.SequenceEqual(expected).Should().BeTrue(); } - - [Fact] public void RemoveFirst() { @@ -549,7 +511,6 @@ public void RemoveFromEnd() _source.Remove(people[99].Key); _boundList.Count.Should().Be(99); - people.RemoveAt(99); people.OrderBy(p => p, _comparer).SequenceEqual(_boundList).Should().BeTrue(); } @@ -565,15 +526,12 @@ public void RemoveFromMiddle() _source.Remove(people[50].Key); _boundList.Count.Should().Be(99); - people.RemoveAt(IndexFromKey(people[50].Key)); int IndexFromKey(string key) => people.FindIndex(p => p.Key == key); - people.OrderBy(p => p, _comparer).SequenceEqual(_boundList).Should().BeTrue(); } - [Fact] public void SortInitialBatch() { @@ -594,14 +552,12 @@ public void UpdateFirst() var update = new Person(toUpdate.Name, toUpdate.Age + 5); - _source.AddOrUpdate(new Person(toUpdate.Name, toUpdate.Age + 5)); people[IndexFromKey(update.Key)] = new Person(toUpdate.Name, toUpdate.Age + 5); int IndexFromKey(string key) => people.FindIndex(p => p.Key == key); - people.OrderBy(p => p, _comparer).SequenceEqual(_boundList).Should().BeTrue(); } @@ -622,7 +578,6 @@ public void UpdateLast() people.OrderBy(p => p, _comparer).SequenceEqual(_boundList).Should().BeTrue(); } - [Fact] public void UpdateMiddle() { diff --git a/src/DynamicData.Tests/Cache/SortAndBindObservableFixture.cs b/src/DynamicData.Tests/Cache/SortAndBindObservableFixture.cs index 9698c86e3..97638d119 100644 --- a/src/DynamicData.Tests/Cache/SortAndBindObservableFixture.cs +++ b/src/DynamicData.Tests/Cache/SortAndBindObservableFixture.cs @@ -1,15 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Subjects; using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; -using Xunit; namespace DynamicData.Tests.Cache; - // Bind to a readonly observable collection public sealed class SortAndBindObservableToReadOnlyObservableCollection : SortAndBindObservableFixture { @@ -37,7 +30,6 @@ public abstract class SortAndBindObservableFixture : IDisposable { protected readonly ISourceCache Cache = new SourceCache(p => p.Name); - private readonly RandomPersonGenerator _generator = new(); private readonly ChangeSetAggregator _results; @@ -45,14 +37,12 @@ public abstract class SortAndBindObservableFixture : IDisposable private readonly SortExpressionComparer _oldestComparer = SortExpressionComparer.Descending(p => p.Age).ThenByAscending(p => p.Name); private readonly SortExpressionComparer _defaultComparer = SortExpressionComparer.Ascending(p => p.Name).ThenByAscending(p => p.Age); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "By Design.")] - protected readonly BehaviorSubject> ComparerObservable; - + private protected readonly StateSignal> ComparerObservable; protected SortAndBindObservableFixture() { - ComparerObservable = new BehaviorSubject>(_defaultComparer); + ComparerObservable = new StateSignal>(_defaultComparer); // It's ok in this case to call VirtualMemberCallInConstructor @@ -69,7 +59,6 @@ protected SortAndBindObservableFixture() protected abstract (ChangeSetAggregator Aggregrator, IList List) SetUpTests(); - [Fact] public void SortInitialBatch() { @@ -80,7 +69,6 @@ public void SortInitialBatch() _boundList.SequenceEqual(defaultOrder).Should().BeTrue(); } - [Fact] public void ChangeSort() { @@ -100,7 +88,6 @@ public void ChangeSort() _boundList.SequenceEqual(defaultOrder).Should().BeTrue(); } - public void Dispose() { Cache.Dispose(); diff --git a/src/DynamicData.Tests/Cache/SortAndPageAndBindFixture.cs b/src/DynamicData.Tests/Cache/SortAndPageAndBindFixture.cs index 3b3466c85..4e3d206a1 100644 --- a/src/DynamicData.Tests/Cache/SortAndPageAndBindFixture.cs +++ b/src/DynamicData.Tests/Cache/SortAndPageAndBindFixture.cs @@ -1,16 +1,8 @@ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Subjects; using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; -using Xunit; namespace DynamicData.Tests.Cache; - public sealed class SortAndPageAndBindWithImplicitOptionsFixtureReadOnlyCollection : SortAndPageAndBindFixtureBase { protected override (ChangeSetAggregator aggregator, IList list) SetUpTests() @@ -76,7 +68,7 @@ public abstract class SortAndPageAndBindFixtureBase : IDisposable protected readonly SourceCache Source = new(p => p.Name); protected readonly IComparer Comparer = SortExpressionComparer.Ascending(p => p.Age).ThenByAscending(p => p.Name); - protected readonly ISubject PageRequests = new BehaviorSubject(new PageRequest(0, 25)); + private protected readonly ISignal PageRequests = new StateSignal(new PageRequest(0, 25)); protected readonly ChangeSetAggregator Aggregator; protected readonly IList List; @@ -94,10 +86,8 @@ protected SortAndPageAndBindFixtureBase() List = args.list; } - protected abstract (ChangeSetAggregator aggregator, IList list) SetUpTests(); - [Fact] public void PageGreaterThanNumberOfPagesAvailable() { @@ -112,7 +102,6 @@ public void PageGreaterThanNumberOfPagesAvailable() List.Should().BeEquivalentTo(expectedResult); } - [Fact] public void OverlappingShift() { @@ -156,7 +145,6 @@ public void AddFirstInRange() List.SequenceEqual(expectedResult).Should().Be(true); } - [Fact] public void AddOutsideOfRange() { @@ -170,7 +158,6 @@ public void AddOutsideOfRange() // only the initials message should have been received Aggregator.Messages.Count.Should().Be(1); - people.Add(person); var expectedResult = people.OrderBy(p => p, Comparer).Take(25).ToList(); List.SequenceEqual(expectedResult).Should().Be(true); @@ -191,7 +178,6 @@ public void UpdateMoveOutOfRange() var changes = Aggregator.Messages[1]; changes.Count.Should().Be(2); - var firstChange = changes.First(); firstChange.Reason.Should().Be(ChangeReason.Remove); firstChange.Current.Should().Be(new Person("P012", 50)); @@ -235,8 +221,6 @@ public void UpdateStayRange() List.SequenceEqual(expectedResult).Should().Be(true); } - - [Fact] public void UpdateOutOfRange() { @@ -255,7 +239,6 @@ public void UpdateOutOfRange() List.SequenceEqual(expectedResult).Should().Be(true); } - [Fact] public void RemoveRange() { @@ -305,7 +288,6 @@ public void RemoveOutOfRange() List.SequenceEqual(expectedResult).Should().Be(true); } - [Fact] public void RefreshInRange() { @@ -374,17 +356,16 @@ public void RefreshWithInlineChangeOutsideRange() secondChange.Reason.Should().Be(ChangeReason.Add); secondChange.Current.Should().Be(new Person("P026", 26)); - var expectedResult = people.OrderBy(p => p, Comparer).Take(25).ToList(); List.SequenceEqual(expectedResult).Should().Be(true); } - public void Dispose() { Source.Dispose(); Aggregator.Dispose(); PageRequests.OnCompleted(); + PageRequests.Dispose(); } } diff --git a/src/DynamicData.Tests/Cache/SortAndPageFixture.cs b/src/DynamicData.Tests/Cache/SortAndPageFixture.cs index 19f177cd4..71c872993 100644 --- a/src/DynamicData.Tests/Cache/SortAndPageFixture.cs +++ b/src/DynamicData.Tests/Cache/SortAndPageFixture.cs @@ -1,25 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Subjects; -using System.Text; -using System.Threading.Tasks; using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; -using Xunit; namespace DynamicData.Tests.Cache; public sealed class SortAndPageWithComparerChangesFixture : SortAndPageFixtureBase { - private BehaviorSubject> _comparerSubject; + private StateSignal> _comparerSubject; private readonly IComparer _descComparer = SortExpressionComparer.Descending(p => p.Age).ThenByAscending(p => p.Name); protected override ChangeSetAggregator> SetUpTests() { - _comparerSubject = new BehaviorSubject>(Comparer); + _comparerSubject = new StateSignal>(Comparer); return Source.Connect() .SortAndPage(_comparerSubject, PageRequests) @@ -83,11 +75,10 @@ public abstract class SortAndPageFixtureBase : IDisposable protected readonly SourceCache Source = new(p => p.Name); protected readonly IComparer Comparer = SortExpressionComparer.Ascending(p => p.Age).ThenByAscending(p => p.Name); - protected readonly ISubject PageRequests = new BehaviorSubject(new PageRequest(1, 25)); + private protected readonly ISignal PageRequests = new StateSignal(new PageRequest(1, 25)); protected readonly ChangeSetAggregator> Aggregator; - protected SortAndPageFixtureBase() { // It's ok in this case to call VirtualMemberCallInConstructor @@ -98,10 +89,8 @@ protected SortAndPageFixtureBase() #pragma warning restore CA2214 } - protected abstract ChangeSetAggregator> SetUpTests(); - [Fact] public void InitialBatches() { @@ -113,7 +102,6 @@ public void InitialBatches() var actualResult = Aggregator.Data.Items.OrderBy(p => p, Comparer); actualResult.Should().BeEquivalentTo(expectedResult); - PageRequests.OnNext(new PageRequest(3, 25)); expectedResult = people.OrderBy(p => p, Comparer).Skip(50).Take(25).ToList(); @@ -127,8 +115,6 @@ public void InitialBatches() [Fact] public void ThrowsForNegativeSizeParameters() => Assert.Throws(() => PageRequests.OnNext(new PageRequest(1, -1))); - - [Fact] public void PageGreaterThanNumberOfPagesAvailable() { @@ -143,7 +129,6 @@ public void PageGreaterThanNumberOfPagesAvailable() actualResult.Should().BeEquivalentTo(expectedResult); } - [Fact] public void OverlappingShift() { @@ -189,7 +174,6 @@ public void AddFirstInRange() actualResult.SequenceEqual(expectedResult).Should().Be(true); } - [Fact] public void AddOutsideOfRange() { @@ -203,7 +187,6 @@ public void AddOutsideOfRange() // only the initials message should have been received Aggregator.Messages.Count.Should().Be(1); - people.Add(person); var expectedResult = people.OrderBy(p => p, Comparer).Take(25).ToList(); var actualResult = Aggregator.Data.Items.OrderBy(p => p, Comparer); @@ -225,7 +208,6 @@ public void UpdateMoveOutOfRange() var changes = Aggregator.Messages[1]; changes.Count.Should().Be(2); - var firstChange = changes.First(); firstChange.Reason.Should().Be(ChangeReason.Remove); firstChange.Current.Should().Be(new Person("P012", 50)); @@ -271,8 +253,6 @@ public void UpdateStayRange() actualResult.SequenceEqual(expectedResult).Should().Be(true); } - - [Fact] public void UpdateOutOfRange() { @@ -291,7 +271,6 @@ public void UpdateOutOfRange() actualResult.SequenceEqual(expectedResult).Should().Be(true); } - [Fact] public void RemoveRange() { @@ -341,7 +320,6 @@ public void RemoveOutOfRange() actualResult.SequenceEqual(expectedResult).Should().Be(true); } - [Fact] public void RefreshInRange() { @@ -410,7 +388,6 @@ public void RefreshWithInlineChangeOutsideRange() secondChange.Reason.Should().Be(ChangeReason.Add); secondChange.Current.Should().Be(new Person("P026", 26)); - var expectedResult = people.OrderBy(p => p, Comparer).Take(25).ToList(); var actualResult = Aggregator.Data.Items.OrderBy(p => p, Comparer); actualResult.SequenceEqual(expectedResult).Should().Be(true); @@ -421,5 +398,6 @@ public void Dispose() Source.Dispose(); Aggregator.Dispose(); PageRequests.OnCompleted(); + PageRequests.Dispose(); } } diff --git a/src/DynamicData.Tests/Cache/SortAndVirtualizeAndBindFixture.cs b/src/DynamicData.Tests/Cache/SortAndVirtualizeAndBindFixture.cs index bb8896aac..b367c2e17 100644 --- a/src/DynamicData.Tests/Cache/SortAndVirtualizeAndBindFixture.cs +++ b/src/DynamicData.Tests/Cache/SortAndVirtualizeAndBindFixture.cs @@ -1,15 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Subjects; using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; -using Xunit; namespace DynamicData.Tests.Cache; - public sealed class SortAndVirtualizeAndBindWithImplicitOptionsFixtureReadOnlyCollection : SortAndVirtualizeAndBindFixtureBase { protected override (ChangeSetAggregator aggregator, IList list) SetUpTests() @@ -75,7 +68,7 @@ public abstract class SortAndVirtualizeAndBindFixtureBase : IDisposable protected readonly SourceCache Source = new(p => p.Name); protected readonly IComparer Comparer = SortExpressionComparer.Ascending(p => p.Age).ThenByAscending(p => p.Name); - protected readonly ISubject VirtualRequests = new BehaviorSubject(new VirtualRequest(0, 25)); + private protected readonly ISignal VirtualRequests = new StateSignal(new VirtualRequest(0, 25)); protected readonly ChangeSetAggregator Aggregator; protected readonly IList List; @@ -93,10 +86,8 @@ protected SortAndVirtualizeAndBindFixtureBase() List = args.list; } - protected abstract (ChangeSetAggregator aggregator, IList list) SetUpTests(); - [Fact] public void InitialBatches() { @@ -107,22 +98,15 @@ public void InitialBatches() var expectedResult = people.OrderBy(p => p, Comparer).Take(25).ToList(); List.Should().BeEquivalentTo(expectedResult); - VirtualRequests.OnNext(new VirtualRequest(25, 50)); expectedResult = people.OrderBy(p => p, Comparer).Skip(25).Take(50).ToList(); List.Should().BeEquivalentTo(expectedResult); - VirtualRequests.OnNext(new VirtualRequest(40, 50)); expectedResult = people.OrderBy(p => p, Comparer).Skip(40).Take(50).ToList(); List.Should().BeEquivalentTo(expectedResult); } - - - - - [Fact] public void OverlappingShift() { @@ -166,14 +150,12 @@ public void AddFirstInRange() List.SequenceEqual(expectedResult).Should().Be(true); } - [Fact] public void AddOutsideOfRange() { var people = Enumerable.Range(1, 100).Select(i => new Person($"P{i:000}", i)).OrderBy(p => Guid.NewGuid()).ToList(); Source.AddOrUpdate(people); - // insert right at end var person = new Person("X_Last", 100); Source.AddOrUpdate(person); @@ -181,7 +163,6 @@ public void AddOutsideOfRange() // only the initials message should have been received Aggregator.Messages.Count.Should().Be(1); - people.Add(person); var expectedResult = people.OrderBy(p => p, Comparer).Take(25).ToList(); List.SequenceEqual(expectedResult).Should().Be(true); @@ -202,7 +183,6 @@ public void UpdateMoveOutOfRange() var changes = Aggregator.Messages[1]; changes.Count.Should().Be(2); - var firstChange = changes.First(); firstChange.Reason.Should().Be(ChangeReason.Remove); firstChange.Current.Should().Be(new Person("P012", 50)); @@ -246,8 +226,6 @@ public void UpdateStayRange() List.SequenceEqual(expectedResult).Should().Be(true); } - - [Fact] public void UpdateOutOfRange() { @@ -265,7 +243,6 @@ public void UpdateOutOfRange() List.SequenceEqual(expectedResult).Should().Be(true); } - [Fact] public void RemoveRange() { @@ -313,7 +290,6 @@ public void RemoveOutOfRange() List.SequenceEqual(expectedResult).Should().Be(true); } - [Fact] public void RefreshInRange() { @@ -381,7 +357,6 @@ public void RefreshWithInlineChangeOutsideRange() secondChange.Reason.Should().Be(ChangeReason.Add); secondChange.Current.Should().Be(new Person("P026", 26)); - var expectedResult = people.OrderBy(p => p, Comparer).Take(25).ToList(); List.SequenceEqual(expectedResult).Should().Be(true); } @@ -391,5 +366,6 @@ public void Dispose() Source.Dispose(); Aggregator.Dispose(); VirtualRequests.OnCompleted(); + VirtualRequests.Dispose(); } } diff --git a/src/DynamicData.Tests/Cache/SortAndVirtualizeFixture.cs b/src/DynamicData.Tests/Cache/SortAndVirtualizeFixture.cs index f5d865369..8b58cee45 100644 --- a/src/DynamicData.Tests/Cache/SortAndVirtualizeFixture.cs +++ b/src/DynamicData.Tests/Cache/SortAndVirtualizeFixture.cs @@ -1,28 +1,17 @@ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Subjects; using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; -using Xunit; namespace DynamicData.Tests.Cache; - - - - public sealed class SortAndVirtualizeWithComparerChangesFixture : SortAndVirtualizeFixtureBase { - private BehaviorSubject> _comparerSubject ; + private StateSignal> _comparerSubject ; private readonly IComparer _descComparer = SortExpressionComparer.Descending(p => p.Age).ThenByAscending(p => p.Name); protected override ChangeSetAggregator> SetUpTests() { - _comparerSubject = new BehaviorSubject>(Comparer); + _comparerSubject = new StateSignal>(Comparer); return Source.Connect() .SortAndVirtualize(_comparerSubject, VirtualRequests) @@ -85,11 +74,10 @@ public abstract class SortAndVirtualizeFixtureBase : IDisposable protected readonly SourceCache Source = new(p => p.Name); protected readonly IComparer Comparer = SortExpressionComparer.Ascending(p => p.Age).ThenByAscending(p => p.Name); - protected readonly ISubject VirtualRequests = new BehaviorSubject(new VirtualRequest(0, 25)); + private protected readonly ISignal VirtualRequests = new StateSignal(new VirtualRequest(0, 25)); protected readonly ChangeSetAggregator> Aggregator; - protected SortAndVirtualizeFixtureBase() { // It's ok in this case to call VirtualMemberCallInConstructor @@ -100,10 +88,8 @@ protected SortAndVirtualizeFixtureBase() #pragma warning restore CA2214 } - protected abstract ChangeSetAggregator> SetUpTests(); - [Fact] public void InitialBatches() { @@ -115,7 +101,6 @@ public void InitialBatches() var actualResult = Aggregator.Data.Items.OrderBy(p => p, Comparer); actualResult.Should().BeEquivalentTo(expectedResult); - VirtualRequests.OnNext(new VirtualRequest(25,50)); expectedResult = people.OrderBy(p => p, Comparer).Skip(25).Take(50).ToList(); @@ -123,8 +108,6 @@ public void InitialBatches() actualResult.Should().BeEquivalentTo(expectedResult); } - - [Fact] public void OverlappingShift() { @@ -170,7 +153,6 @@ public void AddFirstInRange() actualResult.SequenceEqual(expectedResult).Should().Be(true); } - [Fact] public void AddOutsideOfRange() { @@ -184,7 +166,6 @@ public void AddOutsideOfRange() // only the initials message should have been received Aggregator.Messages.Count.Should().Be(1); - people.Add(person); var expectedResult = people.OrderBy(p => p, Comparer).Take(25).ToList(); var actualResult = Aggregator.Data.Items.OrderBy(p => p, Comparer); @@ -206,7 +187,6 @@ public void UpdateMoveOutOfRange() var changes = Aggregator.Messages[1]; changes.Count.Should().Be(2); - var firstChange = changes.First(); firstChange.Reason.Should().Be(ChangeReason.Remove); firstChange.Current.Should().Be(new Person("P012", 50)); @@ -252,8 +232,6 @@ public void UpdateStayRange() actualResult.SequenceEqual(expectedResult).Should().Be(true); } - - [Fact] public void UpdateOutOfRange() { @@ -272,7 +250,6 @@ public void UpdateOutOfRange() actualResult.SequenceEqual(expectedResult).Should().Be(true); } - [Fact] public void RemoveRange() { @@ -322,7 +299,6 @@ public void RemoveOutOfRange() actualResult.SequenceEqual(expectedResult).Should().Be(true); } - [Fact] public void RefreshInRange() { @@ -391,7 +367,6 @@ public void RefreshWithInlineChangeOutsideRange() secondChange.Reason.Should().Be(ChangeReason.Add); secondChange.Current.Should().Be(new Person("P026", 26)); - var expectedResult = people.OrderBy(p => p, Comparer).Take(25).ToList(); var actualResult = Aggregator.Data.Items.OrderBy(p => p, Comparer); actualResult.SequenceEqual(expectedResult).Should().Be(true); @@ -402,5 +377,6 @@ public void Dispose() Source.Dispose(); Aggregator.Dispose(); VirtualRequests.OnCompleted(); + VirtualRequests.Dispose(); } } diff --git a/src/DynamicData.Tests/Cache/SortFixture.cs b/src/DynamicData.Tests/Cache/SortFixture.cs index 0f228b173..c15914836 100644 --- a/src/DynamicData.Tests/Cache/SortFixture.cs +++ b/src/DynamicData.Tests/Cache/SortFixture.cs @@ -1,21 +1,7 @@ -#region - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive; -using System.Reactive.Subjects; - using DynamicData.Binding; using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - -#endregion - namespace DynamicData.Tests.Cache; public class SortFixtureWithReorder : IDisposable @@ -263,7 +249,7 @@ public void Dispose() public void DoesNotThrow1() { var cache = new SourceCache(d => d.Id); - var sortPump = new Subject(); + var sortPump = new Signal(); var disposable = cache.Connect().Sort(SortExpressionComparer.Ascending(d => d.Id), sortPump).Subscribe(); disposable.Dispose(); @@ -273,7 +259,7 @@ public void DoesNotThrow1() public void DoesNotThrow2() { var cache = new SourceCache(d => d.Id); - var disposable = cache.Connect().Sort(new BehaviorSubject>(SortExpressionComparer.Ascending(d => d.Id))).Subscribe(); + var disposable = cache.Connect().Sort(new StateSignal>(SortExpressionComparer.Ascending(d => d.Id))).Subscribe(); disposable.Dispose(); } @@ -364,7 +350,7 @@ public void SortAfterFilter() { var source = new SourceCache(p => p.Key); - var filterSubject = new BehaviorSubject>(p => true); + var filterSubject = new StateSignal>(p => true); var agg = new SortedChangeSetAggregator(source.Connect().Filter(filterSubject).Group(x => (TestString)x.Key).Transform(x => new ViewModel(x.Key)).Sort(new ViewModel.Comparer())); @@ -385,7 +371,7 @@ public void SortAfterFilterList() { var source = new SourceList(); - var filterSubject = new BehaviorSubject>(p => true); + var filterSubject = new StateSignal>(p => true); var agg = source.Connect().Filter(filterSubject).Transform(x => new ViewModel(x.Name)).Sort(new ViewModel.Comparer()).AsAggregator(); @@ -789,7 +775,7 @@ public void Dispose() public void DoesNotThrow1() { var cache = new SourceCache(d => d.Id); - var sortPump = new Subject(); + var sortPump = new Signal(); var disposable = cache.Connect().Sort(SortExpressionComparer.Ascending(d => d.Id), sortPump).Subscribe(); disposable.Dispose(); @@ -799,7 +785,7 @@ public void DoesNotThrow1() public void DoesNotThrow2() { var cache = new SourceCache(d => d.Id); - var disposable = cache.Connect().Sort(new BehaviorSubject>(SortExpressionComparer.Ascending(d => d.Id))).Subscribe(); + var disposable = cache.Connect().Sort(new StateSignal>(SortExpressionComparer.Ascending(d => d.Id))).Subscribe(); disposable.Dispose(); } @@ -890,7 +876,7 @@ public void SortAfterFilter() { var source = new SourceCache(p => p.Key); - var filterSubject = new BehaviorSubject>(p => true); + var filterSubject = new StateSignal>(p => true); var agg = new SortedChangeSetAggregator(source.Connect().Filter(filterSubject).Group(x => (TestString)x.Key).Transform(x => new ViewModel(x.Key)).Sort(new ViewModel.Comparer())); @@ -911,7 +897,7 @@ public void SortAfterFilterList() { var source = new SourceList(); - var filterSubject = new BehaviorSubject>(p => true); + var filterSubject = new StateSignal>(p => true); var agg = source.Connect().Filter(filterSubject).Transform(x => new ViewModel(x.Name)).Sort(new ViewModel.Comparer()).AsAggregator(); diff --git a/src/DynamicData.Tests/Cache/SortObservableFixtureFixture.cs b/src/DynamicData.Tests/Cache/SortObservableFixtureFixture.cs index 6175a0f5c..6f8d61fe7 100644 --- a/src/DynamicData.Tests/Cache/SortObservableFixtureFixture.cs +++ b/src/DynamicData.Tests/Cache/SortObservableFixtureFixture.cs @@ -1,15 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Subjects; - using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class SortObservableFixture : IDisposable @@ -19,7 +10,7 @@ public class SortObservableFixture : IDisposable private readonly SortExpressionComparer _comparer; [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "By Design.")] - private readonly BehaviorSubject> _comparerObservable; + private readonly StateSignal> _comparerObservable; private readonly RandomPersonGenerator _generator = new(); @@ -30,7 +21,7 @@ public class SortObservableFixture : IDisposable public SortObservableFixture() { _comparer = SortExpressionComparer.Ascending(p => p.Name).ThenByAscending(p => p.Age); - _comparerObservable = new BehaviorSubject>(_comparer); + _comparerObservable = new StateSignal>(_comparer); _cache = new SourceCache(p => p.Name); // _sortController = new SortController(_comparer); diff --git a/src/DynamicData.Tests/Cache/SourceCacheFixture.cs b/src/DynamicData.Tests/Cache/SourceCacheFixture.cs index 99b79fbfd..fa88d6f01 100644 --- a/src/DynamicData.Tests/Cache/SourceCacheFixture.cs +++ b/src/DynamicData.Tests/Cache/SourceCacheFixture.cs @@ -1,16 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; -using System.Threading; -using System.Threading.Tasks; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class SourceCacheFixture : IDisposable @@ -162,8 +151,6 @@ public void EmptyChangesWithFilter() change!.Count.Should().Be(0); } - - [Fact] public void StaticFilterRemove() { @@ -174,7 +161,6 @@ public void StaticFilterRemove() cache.AddOrUpdate(Enumerable.Range(1,10).Select(i=> new SomeObject(i,i))); - above5.Items.Should().BeEquivalentTo(Enumerable.Range(6, 5).Select(i => new SomeObject(i, i))); below5.Items.Should().BeEquivalentTo(Enumerable.Range(1, 5).Select(i => new SomeObject(i, i))); @@ -184,14 +170,12 @@ public void StaticFilterRemove() above5.Count.Should().Be(4); below5.Count.Should().Be(6); - above5.Items.Should().BeEquivalentTo(Enumerable.Range(7, 4).Select(i => new SomeObject(i, i))); below5.Items.Should().BeEquivalentTo(Enumerable.Range(1, 6).Select(i => new SomeObject(i, i == 6 ? -1 : i))); } public record class SomeObject(int Id, int Value); - [Fact] public async Task MultiCacheFanInDoesNotDeadlock() { diff --git a/src/DynamicData.Tests/Cache/SubscribeManyFixture.cs b/src/DynamicData.Tests/Cache/SubscribeManyFixture.cs index 5d6d26729..513c955a6 100644 --- a/src/DynamicData.Tests/Cache/SubscribeManyFixture.cs +++ b/src/DynamicData.Tests/Cache/SubscribeManyFixture.cs @@ -1,11 +1,3 @@ -using System; -using System.Linq; -using System.Reactive.Disposables; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class SubscribeManyFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/SuspendNotificationsFixture.cs b/src/DynamicData.Tests/Cache/SuspendNotificationsFixture.cs index db39604a5..84ae7be03 100644 --- a/src/DynamicData.Tests/Cache/SuspendNotificationsFixture.cs +++ b/src/DynamicData.Tests/Cache/SuspendNotificationsFixture.cs @@ -1,16 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; -using System.Threading; -using System.Threading.Tasks; using DynamicData.Kernel; -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.Cache; +[Collection(IntegrationTestFixtureBase.CollectionName)] public sealed class SuspendNotificationsFixture : IDisposable { private readonly SourceCache _source = new(static x => x); diff --git a/src/DynamicData.Tests/Cache/SwitchFixture.cs b/src/DynamicData.Tests/Cache/SwitchFixture.cs index 5a5b76555..589cfeea4 100644 --- a/src/DynamicData.Tests/Cache/SwitchFixture.cs +++ b/src/DynamicData.Tests/Cache/SwitchFixture.cs @@ -1,13 +1,5 @@ -using System; -using System.Linq; -using System.Reactive.Subjects; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class SwitchFixture @@ -16,10 +8,9 @@ public class SwitchFixture public void ClearsForNewSource() { using var source = new SourceCache(p => p.Name); - using var switchable = new BehaviorSubject>(source); + using var switchable = new StateSignal>(source); var results = switchable.Switch().AsAggregator(); - var inital = Enumerable.Range(1, 100).Select(i => new Person("Person" + i, i)).ToArray(); source.AddOrUpdate(inital); @@ -42,10 +33,9 @@ public void ClearsForNewSource() public void PoulatesFirstSource() { using var source = new SourceCache(p => p.Name); - using var switchable = new BehaviorSubject>(source); + using var switchable = new StateSignal>(source); var results = switchable.Switch().AsAggregator(); - var inital = Enumerable.Range(1, 100).Select(i => new Person("Person" + i, i)).ToArray(); source.AddOrUpdate(inital); @@ -56,10 +46,9 @@ public void PoulatesFirstSource() public void PropagatesOuterErrors() { using var source = new SourceCache(p => p.Name); - using var switchable = new BehaviorSubject>(source); + using var switchable = new StateSignal>(source); var results = switchable.Switch().AsAggregator(); - var inital = Enumerable.Range(1, 100).Select(i => new Person("Person" + i, i)).ToArray(); source.AddOrUpdate(inital); @@ -73,14 +62,13 @@ public void PropagatesOuterErrors() public void PropagatesInnerErrors() { using var source = new SourceCache(p => p.Name); - using var switchable = new BehaviorSubject>>(source.Connect()); + using var switchable = new StateSignal>>(source.Connect()); var results = switchable.Switch().AsAggregator(); - var inital = Enumerable.Range(1, 100).Select(i => new Person("Person" + i, i)).ToArray(); source.AddOrUpdate(inital); - using var source2 = new BehaviorSubject>(ChangeSet.Empty); + using var source2 = new StateSignal>(ChangeSet.Empty); switchable.OnNext(source2); diff --git a/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.Items.IntegrationTests.cs b/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.Items.IntegrationTests.cs index 8dd0ae91e..621014bc4 100644 --- a/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.Items.IntegrationTests.cs +++ b/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.Items.IntegrationTests.cs @@ -1,14 +1,3 @@ -using System; -using System.Linq; -using System.Reactive.Concurrency; -using System.Reactive.Linq; -using System.Threading.Tasks; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.Cache; public static partial class ToObservableChangeSetFixture @@ -27,7 +16,7 @@ public async Task MultipleSubscriptionsRunInParallel_SchedulerUsageIsThreadSafe( { IScheduler scheduler = schedulerType switch { - SchedulerType.Default => DefaultScheduler.Instance, + SchedulerType.Default => Scheduler.Default, SchedulerType.NewThread => new NewThreadScheduler(), SchedulerType.TaskPool => TaskPoolScheduler.Default, SchedulerType.ThreadPool => ThreadPoolScheduler.Instance, diff --git a/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.Items.UnitTests.cs b/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.Items.UnitTests.cs index 892f55535..254ebbea3 100644 --- a/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.Items.UnitTests.cs +++ b/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.Items.UnitTests.cs @@ -1,15 +1,3 @@ -using System; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using Microsoft.Reactive.Testing; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.Cache; public static partial class ToObservableChangeSetFixture @@ -22,11 +10,10 @@ public class UnitTests public void ExpireAfterThrows_ErrorPropagates() { // Setup - using var source = new Subject(); + using var source = new Signal(); var error = new Exception("Test Exception"); - // UUT Initialization using var subscription = source .ToObservableChangeSet( @@ -43,7 +30,6 @@ public void ExpireAfterThrows_ErrorPropagates() results.RecordedItemsByKey.Should().BeEmpty("no items have been emitted by the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item1 = new Item() { Id = 1 }; source.OnNext(item1); @@ -54,14 +40,13 @@ public void ExpireAfterThrows_ErrorPropagates() results.RecordedChangeSets.Skip(1).Count().Should().Be(1, "1 item was emitted before an error occurred"); results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1 }, "1 item was emitted before an error occurred"); - results.ShouldNotSupportSorting($"Cache source operators do not support sorting"); } [Fact] public void KeySelectorIsNull_ThrowsException() => FluentActions.Invoking(() => ObservableCacheEx.ToObservableChangeSet( - source: new Subject(), + source: new Signal(), keySelector: null!)) .Should().Throw(); @@ -69,11 +54,10 @@ public void KeySelectorIsNull_ThrowsException() public void KeySelectorThrows_ErrorPropagates() { // Setup - using var source = new Subject(); + using var source = new Signal(); var error = new Exception("Test Exception"); - // UUT Initialization using var subscription = source .ToObservableChangeSet(static item => (item.Error is not null) @@ -88,7 +72,6 @@ public void KeySelectorThrows_ErrorPropagates() results.RecordedItemsByKey.Should().BeEmpty("no items have been emitted by the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item1 = new Item() { Id = 1 }; source.OnNext(item1); @@ -99,7 +82,6 @@ public void KeySelectorThrows_ErrorPropagates() results.RecordedChangeSets.Skip(1).Count().Should().Be(1, "1 item was emitted before an error occurred"); results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1 }, "1 item was emitted before an error occurred"); - results.ShouldNotSupportSorting($"Cache source operators do not support sorting"); } @@ -107,8 +89,7 @@ public void KeySelectorThrows_ErrorPropagates() public void SizeLimitIsExceeded_OldestItemsAreRemoved() { // Setup - using var source = new Subject(); - + using var source = new Signal(); // UUT Initialization using var subscription = source @@ -124,7 +105,6 @@ public void SizeLimitIsExceeded_OldestItemsAreRemoved() results.RecordedItemsByKey.Values.Should().BeEmpty("no items have been emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action: Not enough items to reach the limit var item1 = new Item() { Id = 1 }; source.OnNext(item1); @@ -143,7 +123,6 @@ public void SizeLimitIsExceeded_OldestItemsAreRemoved() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1, item2, item3, item4 }, "4 items were emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action: Limit is reached var item5 = new Item() { Id = 5 }; source.OnNext(item5); @@ -153,7 +132,6 @@ public void SizeLimitIsExceeded_OldestItemsAreRemoved() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1, item2, item3, item4, item5 }, "1 item was emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action: New item exceeds the limit var item6 = new Item() { Id = 6 }; source.OnNext(item6); @@ -163,7 +141,6 @@ public void SizeLimitIsExceeded_OldestItemsAreRemoved() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item2, item3, item4, item5, item6 }, "1 item was emitted, and 1 was evicted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action: Replacement leaves all other items in-place var item7 = new Item() { Id = 4 }; source.OnNext(item7); @@ -184,14 +161,13 @@ public void SourceCompletesWhenExpirationsArePending_CompletionWaitsForExpiratio var source = sourceType switch { - SourceType.Asynchronous => new Subject(), + SourceType.Asynchronous => new Signal(), SourceType.Immediate => Observable.Return(item), _ => throw new ArgumentOutOfRangeException(nameof(sourceType)) }; var scheduler = new TestScheduler(); - // UUT Initialization & Action using var subscription = source .ToObservableChangeSet( @@ -202,7 +178,7 @@ public void SourceCompletesWhenExpirationsArePending_CompletionWaitsForExpiratio .ValidateChangeSets(Item.SelectId) .RecordCacheItems(out var results); - if (source is Subject subject) + if (source is Signal subject) { subject.OnNext(item); subject.OnCompleted(); @@ -216,7 +192,6 @@ public void SourceCompletesWhenExpirationsArePending_CompletionWaitsForExpiratio results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item }, "1 item was emitted"); results.HasCompleted.Should().BeFalse("1 item has yet to expire"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(10).Ticks); @@ -228,7 +203,6 @@ public void SourceCompletesWhenExpirationsArePending_CompletionWaitsForExpiratio results.RecordedItemsByKey.Values.Should().BeEmpty("all items have expired"); results.HasCompleted.Should().BeTrue("the source, and all outstanding expirations, have completed"); - results.ShouldNotSupportSorting(); } @@ -242,14 +216,13 @@ public void SourceCompletesWhenNoExpirationsArePending_CompletionPropagates(Sour var source = sourceType switch { - SourceType.Asynchronous => new Subject(), + SourceType.Asynchronous => new Signal(), SourceType.Immediate => Observable.Return(item), _ => throw new ArgumentOutOfRangeException(nameof(sourceType)) }; var scheduler = new TestScheduler(); - // UUT Initialization & Action using var subscription = source .ToObservableChangeSet( @@ -260,7 +233,7 @@ public void SourceCompletesWhenNoExpirationsArePending_CompletionPropagates(Sour .ValidateChangeSets(Item.SelectId) .RecordCacheItems(out var results); - if (source is Subject subject) + if (source is Signal subject) { subject.OnNext(item); subject.OnCompleted(); @@ -274,7 +247,6 @@ public void SourceCompletesWhenNoExpirationsArePending_CompletionPropagates(Sour results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item }, "1 item was emitted"); results.HasCompleted.Should().BeTrue("the source has completed, and no items remain to be expired"); - results.ShouldNotSupportSorting(); } @@ -282,11 +254,10 @@ public void SourceCompletesWhenNoExpirationsArePending_CompletionPropagates(Sour public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsRescheduled() { // Setup - using var source = new Subject(); + using var source = new Signal(); var scheduler = new TestScheduler(); - // UUT Initialization using var subscription = source .ToObservableChangeSet( @@ -302,7 +273,6 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch results.RecordedItemsByKey.Values.Should().BeEmpty("no items have been emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item1 = new Item() { Id = 1, Lifetime = TimeSpan.FromSeconds(1) }; source.OnNext(item1); @@ -313,7 +283,6 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1 }, "1 item was emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item2 = new Item() { Id = 2 }; source.OnNext(item2); @@ -324,7 +293,6 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1, item2 }, "1 item was emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item3 = new Item() { Id = 3, Lifetime = TimeSpan.FromSeconds(1) }; source.OnNext(item3); @@ -335,7 +303,6 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1, item2, item3 }, "1 item was emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item4 = new Item() { Id = 1, Lifetime = TimeSpan.FromSeconds(1) }; source.OnNext(item4); @@ -346,7 +313,6 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item4, item2, item3 }, "1 item was emitted, and replaced an existing item"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item5 = new Item() { Id = 2 }; source.OnNext(item5); @@ -357,7 +323,6 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item4, item5, item3 }, "1 item was emitted, and replaced an existing item"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item6 = new Item() { Id = 3, Lifetime = TimeSpan.FromSeconds(3) }; source.OnNext(item6); @@ -368,7 +333,6 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item4, item5, item6 }, "1 item was emitted, and replaced an existing item"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(1).Ticks); @@ -377,7 +341,6 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item5, item6 }, "1 item reached its expiration"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(2).Ticks); @@ -386,7 +349,6 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item5, item6 }, "no changes were made"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(3).Ticks); @@ -395,7 +357,6 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item5 }, "1 item reached its expiration"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(4).Ticks); @@ -404,7 +365,6 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item5 }, "no changes were made"); results.HasCompleted.Should().BeFalse("the source has not completed"); - results.ShouldNotSupportSorting(); } @@ -412,11 +372,10 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch public void SourceEmitsUniqueItems_ItemsAreAddedAndRemovedWhenExpired() { // Setup - using var source = new Subject(); + using var source = new Signal(); var scheduler = new TestScheduler(); - // UUT Initialization using var subscription = source .ToObservableChangeSet( @@ -432,7 +391,6 @@ public void SourceEmitsUniqueItems_ItemsAreAddedAndRemovedWhenExpired() results.RecordedItemsByKey.Values.Should().BeEmpty("no items have been emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item1 = new Item() { Id = 1, Lifetime = TimeSpan.FromSeconds(3) }; source.OnNext(item1); @@ -443,7 +401,6 @@ public void SourceEmitsUniqueItems_ItemsAreAddedAndRemovedWhenExpired() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1 }, "1 item was emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item2 = new Item() { Id = 2 }; source.OnNext(item2); @@ -454,7 +411,6 @@ public void SourceEmitsUniqueItems_ItemsAreAddedAndRemovedWhenExpired() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1, item2 }, "1 item was emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item3 = new Item() { Id = 3, Lifetime = TimeSpan.FromSeconds(1) }; source.OnNext(item3); @@ -465,7 +421,6 @@ public void SourceEmitsUniqueItems_ItemsAreAddedAndRemovedWhenExpired() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1, item2, item3 }, "1 item was emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(1).Ticks); @@ -474,7 +429,6 @@ public void SourceEmitsUniqueItems_ItemsAreAddedAndRemovedWhenExpired() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1, item2 }, "1 item expired, and 1 had its lifetime extended"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(2).Ticks); @@ -483,7 +437,6 @@ public void SourceEmitsUniqueItems_ItemsAreAddedAndRemovedWhenExpired() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1, item2 }, "no changes were made"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(3).Ticks); @@ -492,7 +445,6 @@ public void SourceEmitsUniqueItems_ItemsAreAddedAndRemovedWhenExpired() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item2 }, "1 item reached its expiration"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(4).Ticks); @@ -501,7 +453,6 @@ public void SourceEmitsUniqueItems_ItemsAreAddedAndRemovedWhenExpired() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item2 }, "no changes were made"); results.HasCompleted.Should().BeFalse("the source has not completed"); - results.ShouldNotSupportSorting(); } @@ -515,12 +466,11 @@ public void SourceFails_ErrorPropagates(SourceType sourceType) var source = sourceType switch { - SourceType.Asynchronous => new Subject(), + SourceType.Asynchronous => new Signal(), SourceType.Immediate => Observable.Throw(error), _ => throw new ArgumentOutOfRangeException(nameof(sourceType)) }; - // UUT Initialization & Action using var subscription = source .ToObservableChangeSet(Item.SelectId) @@ -528,7 +478,7 @@ public void SourceFails_ErrorPropagates(SourceType sourceType) .ValidateChangeSets(Item.SelectId) .RecordCacheItems(out var results); - if (source is Subject subject) + if (source is Signal subject) subject.OnError(error); results.Error.Should().BeSameAs(error, "errors should propagate"); diff --git a/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.Sequences.IntegrationTests.cs b/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.Sequences.IntegrationTests.cs index f6dece980..5e98bd03a 100644 --- a/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.Sequences.IntegrationTests.cs +++ b/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.Sequences.IntegrationTests.cs @@ -1,14 +1,3 @@ -using System; -using System.Linq; -using System.Reactive.Concurrency; -using System.Reactive.Linq; -using System.Threading.Tasks; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.Cache; public static partial class ToObservableChangeSetFixture @@ -27,7 +16,7 @@ public async Task MultipleSubscriptionsRunInParallel_SchedulerUsageIsThreadSafe( { IScheduler scheduler = schedulerType switch { - SchedulerType.Default => DefaultScheduler.Instance, + SchedulerType.Default => Scheduler.Default, SchedulerType.NewThread => new NewThreadScheduler(), SchedulerType.TaskPool => TaskPoolScheduler.Default, SchedulerType.ThreadPool => ThreadPoolScheduler.Instance, diff --git a/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.Sequences.UnitTests.cs b/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.Sequences.UnitTests.cs index 94213315a..1e8e2e443 100644 --- a/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.Sequences.UnitTests.cs +++ b/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.Sequences.UnitTests.cs @@ -1,16 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using Microsoft.Reactive.Testing; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.Cache; public static partial class ToObservableChangeSetFixture @@ -23,11 +10,10 @@ public class UnitTests public void ExpireAfterThrows_ErrorPropagates() { // Setup - using var source = new Subject>(); + using var source = new Signal>(); var error = new Exception("Test Exception"); - // UUT Initialization using var subscription = source .ToObservableChangeSet( @@ -44,7 +30,6 @@ public void ExpireAfterThrows_ErrorPropagates() results.RecordedItemsByKey.Should().BeEmpty("no items have been emitted by the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item1 = new Item() { Id = 1 }; source.OnNext(new[] @@ -58,14 +43,13 @@ public void ExpireAfterThrows_ErrorPropagates() results.RecordedChangeSets.Skip(1).Should().BeEmpty("an error occurred during processing of the sequence"); results.RecordedItemsByKey.Values.Should().BeEmpty("an error occurred during processing of the sequence"); - results.ShouldNotSupportSorting($"Cache source operators do not support sorting"); } [Fact] public void KeySelectorIsNull_ThrowsException() => FluentActions.Invoking(() => ObservableCacheEx.ToObservableChangeSet( - source: new Subject>(), + source: new Signal>(), keySelector: null!)) .Should().Throw(); @@ -73,11 +57,10 @@ public void KeySelectorIsNull_ThrowsException() public void KeySelectorThrows_ErrorPropagates() { // Setup - using var source = new Subject>(); + using var source = new Signal>(); var error = new Exception("Test Exception"); - // UUT Initialization using var subscription = source .ToObservableChangeSet(static item => (item.Error is not null) @@ -92,7 +75,6 @@ public void KeySelectorThrows_ErrorPropagates() results.RecordedItemsByKey.Should().BeEmpty("no items have been emitted by the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item1 = new Item() { Id = 1 }; source.OnNext(new[] @@ -106,7 +88,6 @@ public void KeySelectorThrows_ErrorPropagates() results.RecordedChangeSets.Skip(1).Should().BeEmpty("an error occurred during processing of the sequence"); results.RecordedItemsByKey.Values.Should().BeEmpty("an error occurred during processing of the sequence"); - results.ShouldNotSupportSorting($"Cache source operators do not support sorting"); } @@ -114,8 +95,7 @@ public void KeySelectorThrows_ErrorPropagates() public void SizeLimitIsExceeded_OldestItemsAreRemoved() { // Setup - using var source = new Subject>(); - + using var source = new Signal>(); // UUT Initialization using var subscription = source @@ -131,7 +111,6 @@ public void SizeLimitIsExceeded_OldestItemsAreRemoved() results.RecordedItemsByKey.Values.Should().BeEmpty("no source items have been emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action: Not enough items to reach the limit var item1 = new Item() { Id = 1 }; var item2 = new Item() { Id = 2 }; @@ -150,7 +129,6 @@ public void SizeLimitIsExceeded_OldestItemsAreRemoved() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1, item2, item3, item4 }, "4 source items were emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action: Limit is reached var item5 = new Item() { Id = 5 }; source.OnNext(new[] { item5 }); @@ -160,7 +138,6 @@ public void SizeLimitIsExceeded_OldestItemsAreRemoved() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1, item2, item3, item4, item5 }, "1 source item was emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action: New item exceeds the limit var item6 = new Item() { Id = 6 }; source.OnNext(new[] { item6 }); @@ -170,7 +147,6 @@ public void SizeLimitIsExceeded_OldestItemsAreRemoved() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item2, item3, item4, item5, item6 }, "1 source item was emitted, and 1 was evicted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action: Replacement leaves all other items in-place var item7 = new Item() { Id = 4 }; source.OnNext(new[] { item7 }); @@ -196,14 +172,13 @@ public void SourceCompletesWhenExpirationsArePending_CompletionWaitsForExpiratio var source = sourceType switch { - SourceType.Asynchronous => new Subject>(), + SourceType.Asynchronous => new Signal>(), SourceType.Immediate => Observable.Return>(items), _ => throw new ArgumentOutOfRangeException(nameof(sourceType)) }; var scheduler = new TestScheduler(); - // UUT Initialization & Action using var subscription = source .ToObservableChangeSet( @@ -214,7 +189,7 @@ public void SourceCompletesWhenExpirationsArePending_CompletionWaitsForExpiratio .ValidateChangeSets(Item.SelectId) .RecordCacheItems(out var results); - if (source is Subject> subject) + if (source is Signal> subject) { subject.OnNext(items); subject.OnCompleted(); @@ -228,7 +203,6 @@ public void SourceCompletesWhenExpirationsArePending_CompletionWaitsForExpiratio results.RecordedItemsByKey.Values.Should().BeEquivalentTo(items, "3 items were emitted"); results.HasCompleted.Should().BeFalse("2 items have yet to expire"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(30).Ticks); @@ -240,7 +214,6 @@ public void SourceCompletesWhenExpirationsArePending_CompletionWaitsForExpiratio results.RecordedItemsByKey.Values.Should().BeEquivalentTo(items.Where(static item => item.Lifetime is null), "all expirable items have expired"); results.HasCompleted.Should().BeTrue("the source, and all outstanding expirations, have completed"); - results.ShouldNotSupportSorting(); } @@ -259,14 +232,13 @@ public void SourceCompletesWhenNoExpirationsArePending_CompletionPropagates(Sour var source = sourceType switch { - SourceType.Asynchronous => new Subject>(), + SourceType.Asynchronous => new Signal>(), SourceType.Immediate => Observable.Return>(items), _ => throw new ArgumentOutOfRangeException(nameof(sourceType)) }; var scheduler = new TestScheduler(); - // UUT Initialization & Action using var subscription = source .ToObservableChangeSet( @@ -277,7 +249,7 @@ public void SourceCompletesWhenNoExpirationsArePending_CompletionPropagates(Sour .ValidateChangeSets(Item.SelectId) .RecordCacheItems(out var results); - if (source is Subject> subject) + if (source is Signal> subject) { subject.OnNext(items); subject.OnCompleted(); @@ -291,7 +263,6 @@ public void SourceCompletesWhenNoExpirationsArePending_CompletionPropagates(Sour results.RecordedItemsByKey.Values.Should().BeEquivalentTo(items, "3 items were emitted"); results.HasCompleted.Should().BeTrue("the source has completed, and no items remain to be expired"); - results.ShouldNotSupportSorting(); } @@ -299,11 +270,10 @@ public void SourceCompletesWhenNoExpirationsArePending_CompletionPropagates(Sour public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsRescheduled() { // Setup - using var source = new Subject>(); + using var source = new Signal>(); var scheduler = new TestScheduler(); - // UUT Initialization using var subscription = source .ToObservableChangeSet( @@ -319,7 +289,6 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch results.RecordedItemsByKey.Values.Should().BeEmpty("no source items have been emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item1 = new Item() { Id = 1, Lifetime = TimeSpan.FromSeconds(1) }; var item2 = new Item() { Id = 2 }; @@ -332,7 +301,6 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1, item2, item3 }, "3 items were emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item4 = new Item() { Id = 1, Lifetime = TimeSpan.FromSeconds(1) }; var item5 = new Item() { Id = 2 }; @@ -345,7 +313,6 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item4, item5, item6 }, "3 items were emitted, and replaced existing items"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(1).Ticks); @@ -354,7 +321,6 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item5, item6 }, "1 source item reached its expiration"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(2).Ticks); @@ -363,7 +329,6 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item5, item6 }, "no changes were made"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(3).Ticks); @@ -372,7 +337,6 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item5 }, "1 source item reached its expiration"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(4).Ticks); @@ -381,7 +345,6 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item5 }, "no changes were made"); results.HasCompleted.Should().BeFalse("the source has not completed"); - results.ShouldNotSupportSorting(); } @@ -389,11 +352,10 @@ public void SourceEmitsRepeatedItems_RepeatedItemsAreUpdatedAndExpirationIsResch public void SourceEmitsUniqueItems_ItemsAreAddedAndRemovedWhenExpired() { // Setup - using var source = new Subject>(); + using var source = new Signal>(); var scheduler = new TestScheduler(); - // UUT Initialization using var subscription = source .ToObservableChangeSet( @@ -409,7 +371,6 @@ public void SourceEmitsUniqueItems_ItemsAreAddedAndRemovedWhenExpired() results.RecordedItemsByKey.Values.Should().BeEmpty("no source items have been emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item1 = new Item() { Id = 1, Lifetime = TimeSpan.FromSeconds(3) }; var item2 = new Item() { Id = 2 }; @@ -422,7 +383,6 @@ public void SourceEmitsUniqueItems_ItemsAreAddedAndRemovedWhenExpired() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1, item2, item3 }, "3 items were emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(1).Ticks); @@ -431,7 +391,6 @@ public void SourceEmitsUniqueItems_ItemsAreAddedAndRemovedWhenExpired() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1, item2 }, "1 item expired, and 1 had its lifetime extended"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(2).Ticks); @@ -440,7 +399,6 @@ public void SourceEmitsUniqueItems_ItemsAreAddedAndRemovedWhenExpired() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item1, item2 }, "no changes were made"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(3).Ticks); @@ -449,7 +407,6 @@ public void SourceEmitsUniqueItems_ItemsAreAddedAndRemovedWhenExpired() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item2 }, "1 item reached its expiration"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(4).Ticks); @@ -458,7 +415,6 @@ public void SourceEmitsUniqueItems_ItemsAreAddedAndRemovedWhenExpired() results.RecordedItemsByKey.Values.Should().BeEquivalentTo(new[] { item2 }, "no changes were made"); results.HasCompleted.Should().BeFalse("the source has not completed"); - results.ShouldNotSupportSorting(); } @@ -472,12 +428,11 @@ public void SourceFails_ErrorPropagates(SourceType sourceType) var source = sourceType switch { - SourceType.Asynchronous => new Subject>(), + SourceType.Asynchronous => new Signal>(), SourceType.Immediate => Observable.Throw>(error), _ => throw new ArgumentOutOfRangeException(nameof(sourceType)) }; - // UUT Initialization & Action using var subscription = source .ToObservableChangeSet(Item.SelectId) @@ -485,7 +440,7 @@ public void SourceFails_ErrorPropagates(SourceType sourceType) .ValidateChangeSets(Item.SelectId) .RecordCacheItems(out var results); - if (source is Subject> subject) + if (source is Signal> subject) subject.OnError(error); results.Error.Should().BeSameAs(error, "errors should propagate"); diff --git a/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.cs b/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.cs index c1239a1d8..6de05c9c4 100644 --- a/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.cs +++ b/src/DynamicData.Tests/Cache/ToObservableChangeSetFixture.cs @@ -1,6 +1,4 @@ -using System; - -namespace DynamicData.Tests.Cache; +namespace DynamicData.Tests.Cache; public static partial class ToObservableChangeSetFixture { diff --git a/src/DynamicData.Tests/Cache/ToObservableOptionalFixture.cs b/src/DynamicData.Tests/Cache/ToObservableOptionalFixture.cs index d4ea2fd7e..4ca357412 100644 --- a/src/DynamicData.Tests/Cache/ToObservableOptionalFixture.cs +++ b/src/DynamicData.Tests/Cache/ToObservableOptionalFixture.cs @@ -1,13 +1,4 @@ -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reactive.Linq; -using System.Threading.Tasks; -using DynamicData.Kernel; -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.Cache; diff --git a/src/DynamicData.Tests/Cache/ToSortedCollectionFixture.cs b/src/DynamicData.Tests/Cache/ToSortedCollectionFixture.cs index 5c7e84f23..53cb6c84b 100644 --- a/src/DynamicData.Tests/Cache/ToSortedCollectionFixture.cs +++ b/src/DynamicData.Tests/Cache/ToSortedCollectionFixture.cs @@ -1,18 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; - using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Microsoft.Reactive.Testing; - -using Xunit; - namespace DynamicData.Tests.Cache; public class ToSortedCollectionFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/TransformAsyncFixture.cs b/src/DynamicData.Tests/Cache/TransformAsyncFixture.cs index 7483eccb4..92b64e4d5 100644 --- a/src/DynamicData.Tests/Cache/TransformAsyncFixture.cs +++ b/src/DynamicData.Tests/Cache/TransformAsyncFixture.cs @@ -1,14 +1,5 @@ -using System; -using System.Collections.ObjectModel; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Threading.Tasks; using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; -using Xunit; namespace DynamicData.Tests.Cache; @@ -123,7 +114,7 @@ public async Task RemoveFlowsToTheEnd() public void ReTransformAll() { var people = Enumerable.Range(1, 10).Select(i => new Person("Name" + i, i)).ToArray(); - var forceTransform = new Subject(); + var forceTransform = new Signal(); using var stub = new TransformStub(forceTransform); stub.Source.AddOrUpdate(people); @@ -146,7 +137,7 @@ public void ReTransformAll() public void ReTransformSelected() { var people = Enumerable.Range(1, 10).Select(i => new Person("Name" + i, i)).ToArray(); - var forceTransform = new Subject>(); + var forceTransform = new Signal>(); using var stub = new TransformStub(forceTransform); stub.Source.AddOrUpdate(people); @@ -199,9 +190,6 @@ public void Update() stub.Results.Messages[1].Updates.Should().Be(1, "Should be 1 update"); } - - - [Theory, InlineData(true), InlineData(false)] public void TransformOnRefresh(bool transformOnRefresh) { @@ -218,13 +206,11 @@ public void TransformOnRefresh(bool transformOnRefresh) person.Age = 21; - results.Data.Count.Should().Be(1); results.Data.Lookup("SomeOne").Value.AgeGroup.Should().Be(transformOnRefresh ? "Adult": "Child"); } - [Theory, InlineData(10), InlineData(100)] public async Task WithMaxConcurrency(int maxConcurrency) @@ -237,7 +223,6 @@ public async Task WithMaxConcurrency(int maxConcurrency) So it works, but how can it be tested in a scientific way ?? */ - const int transformCount = 100; using var source = new SourceCache(p => p.Name); diff --git a/src/DynamicData.Tests/Cache/TransformFixture.cs b/src/DynamicData.Tests/Cache/TransformFixture.cs index d149276cd..79374df37 100644 --- a/src/DynamicData.Tests/Cache/TransformFixture.cs +++ b/src/DynamicData.Tests/Cache/TransformFixture.cs @@ -1,14 +1,5 @@ -using System; -using System.Linq; -using System.Reactive; -using System.Reactive.Subjects; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class TransformFixture @@ -75,7 +66,7 @@ public void Remove() public void ReTransformAll() { var people = Enumerable.Range(1, 10).Select(i => new Person("Name" + i, i)).ToArray(); - var forceTransform = new Subject(); + var forceTransform = new Signal(); using var stub = new TransformStub(forceTransform); stub.Source.AddOrUpdate(people); @@ -98,7 +89,7 @@ public void ReTransformAll() public void ReTransformSelected() { var people = Enumerable.Range(1, 10).Select(i => new Person("Name" + i, i)).ToArray(); - var forceTransform = new Subject>(); + var forceTransform = new Signal>(); using var stub = new TransformStub(forceTransform); stub.Source.AddOrUpdate(people); diff --git a/src/DynamicData.Tests/Cache/TransformFixtureParallel.cs b/src/DynamicData.Tests/Cache/TransformFixtureParallel.cs index 5ff66a0d0..53de7438f 100644 --- a/src/DynamicData.Tests/Cache/TransformFixtureParallel.cs +++ b/src/DynamicData.Tests/Cache/TransformFixtureParallel.cs @@ -1,13 +1,6 @@ -using System; -using System.Linq; - using DynamicData.PLinq; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class TransformFixtureParallel : IDisposable diff --git a/src/DynamicData.Tests/Cache/TransformImmutableFixture.cs b/src/DynamicData.Tests/Cache/TransformImmutableFixture.cs index 4b529b345..2a5623569 100644 --- a/src/DynamicData.Tests/Cache/TransformImmutableFixture.cs +++ b/src/DynamicData.Tests/Cache/TransformImmutableFixture.cs @@ -1,12 +1,3 @@ -using System; -using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using FluentAssertions; -using Xunit; - namespace DynamicData.Tests.Cache; public sealed class TransformImmutableFixture @@ -14,13 +5,12 @@ public sealed class TransformImmutableFixture [Fact] public void ItemsAreManipulated_ItemsAreTransformed() { - using var source = new Subject>(); + using var source = new Signal>(); using var results = source .TransformImmutable(transformFactory: Item.NameSelector) .AsAggregator(); - // Additions var item1 = new Item() { Id = 1, Name = "Item #1" }; var item2 = new Item() { Id = 2, Name = "Item #2" }; @@ -37,7 +27,6 @@ public void ItemsAreManipulated_ItemsAreTransformed() results.Messages.ElementAt(0).Select(change => change.PreviousIndex).Should().BeEquivalentTo(operation1.Select(change => change.PreviousIndex), "indexes should be preserved"); results.Data.Items.Should().BeEquivalentTo(new[] { item1.Name, item2.Name }, "2 items were added"); - // Replace items, changing inclusion var item3 = new Item() { Id = item1.Id, Name = "Item #3" }; var item4 = new Item() { Id = item2.Id, Name = "Item #4" }; @@ -54,7 +43,6 @@ public void ItemsAreManipulated_ItemsAreTransformed() results.Messages.ElementAt(1).Select(change => change.PreviousIndex).Should().BeEquivalentTo(operation2.Select(change => change.PreviousIndex), "indexes should be preserved"); results.Data.Items.Should().BeEquivalentTo(new[] { item3.Name, item4.Name }, "2 items were replaced"); - // Refresh items var operation3 = new ChangeSet() { @@ -69,7 +57,6 @@ public void ItemsAreManipulated_ItemsAreTransformed() results.Messages.ElementAt(2).Select(change => change.PreviousIndex).Should().BeEquivalentTo(operation3.Select(change => change.PreviousIndex), "indexes should be preserved"); results.Data.Items.Should().BeEquivalentTo(new[] { item3.Name, item4.Name }, "2 items were refreshed"); - // Move items var operation4 = new ChangeSet() { @@ -84,7 +71,6 @@ public void ItemsAreManipulated_ItemsAreTransformed() results.Messages.ElementAt(3).Select(change => change.PreviousIndex).Should().BeEquivalentTo(operation4.Select(change => change.PreviousIndex), "indexes should be preserved"); results.Data.Items.Should().BeEquivalentTo(new[] { item4.Name, item3.Name }, "2 items were moved"); - // Remove items var operation5 = new ChangeSet() { @@ -99,20 +85,18 @@ public void ItemsAreManipulated_ItemsAreTransformed() results.Messages.ElementAt(4).Select(change => change.PreviousIndex).Should().BeEquivalentTo(operation5.Select(change => change.PreviousIndex), "indexes should be preserved"); results.Data.Items.Should().BeEmpty("2 items were removed"); - results.IsCompleted.Should().BeFalse(); } [Fact] public void SourceCompletes_CompletionIsPropagated() { - using var source = new Subject>(); + using var source = new Signal>(); using var results = source .TransformImmutable(transformFactory: Item.NameSelector) .AsAggregator(); - var item1 = new Item() { Id = 1, Name = "Item #1" }; source.OnNext(new ChangeSet() { @@ -125,7 +109,6 @@ public void SourceCompletes_CompletionIsPropagated() results.Messages.Count.Should().Be(1, "1 source operation was performed"); results.Data.Items.Should().BeEquivalentTo(new[] { item1.Name }, "1 item was added"); - // Make sure no extraneous notifications are published. var item2 = new Item() { Id = 2, Name = "Item #2" }; source.OnNext(new ChangeSet() @@ -159,7 +142,6 @@ public void SourceCompletesImmediately_CompletionIsPropagated() .TransformImmutable(transformFactory: Item.NameSelector) .AsAggregator(); - results.Error.Should().BeNull(); results.IsCompleted.Should().BeTrue(); results.Messages.Count.Should().Be(1, "1 source operation was performed"); @@ -169,7 +151,7 @@ public void SourceCompletesImmediately_CompletionIsPropagated() [Fact] public void SourceErrors_ErrorIsPropagated() { - using var source = new Subject>(); + using var source = new Signal>(); var error = new Exception(); @@ -177,7 +159,6 @@ public void SourceErrors_ErrorIsPropagated() .TransformImmutable(transformFactory: Item.NameSelector) .AsAggregator(); - var item1 = new Item() { Id = 1, Name = "Item #1" }; source.OnNext(new ChangeSet() { @@ -190,7 +171,6 @@ public void SourceErrors_ErrorIsPropagated() results.Messages.Count.Should().Be(1, "1 source operation was performed"); results.Data.Items.Should().BeEquivalentTo(new[] { item1.Name }, "1 item was added"); - // Make sure no extraneous notifications are published. var item2 = new Item() { Id = 2, Name = "Item #2" }; source.OnNext(new ChangeSet() @@ -223,7 +203,6 @@ public void SourceErrorsImmediately_ErrorIsPropagated() .TransformImmutable(transformFactory: Item.NameSelector) .AsAggregator(); - results.Error.Should().Be(error); results.IsCompleted.Should().BeFalse(); results.Messages.Count.Should().Be(1, "1 source operation was performed"); @@ -247,7 +226,7 @@ public void TransformFactoryIsNull_ThrowsException() [Fact] public void TransformFactoryThrows_ExceptionIsCaptured() { - using var source = new Subject>(); + using var source = new Signal>(); var error = new Exception(); @@ -255,7 +234,6 @@ public void TransformFactoryThrows_ExceptionIsCaptured() .TransformImmutable(transformFactory: _ => throw error) .AsAggregator(); - var item1 = new Item() { Id = 1, Name = "Item #1" }; source.OnNext(new ChangeSet() { @@ -271,13 +249,12 @@ public void TransformFactoryThrows_ExceptionIsCaptured() [Fact] public void TDestinationIsValueType_DoesNotThrowException() { - using var source = new Subject>(); + using var source = new Signal>(); using var results = source .TransformImmutable(transformFactory: static value => value.Length) .AsAggregator(); - source.OnNext(new ChangeSet() { new(reason: ChangeReason.Add, key: "Item #1", current: "Item #1", index: 0) diff --git a/src/DynamicData.Tests/Cache/TransformManyAsyncFixture.cs b/src/DynamicData.Tests/Cache/TransformManyAsyncFixture.cs index 3781b1a2a..d0caa8758 100644 --- a/src/DynamicData.Tests/Cache/TransformManyAsyncFixture.cs +++ b/src/DynamicData.Tests/Cache/TransformManyAsyncFixture.cs @@ -1,15 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Reactive.Linq; -using System.Threading.Tasks; using Bogus; using DynamicData.Kernel; using DynamicData.Tests.Domain; -using DynamicData.Tests.Utilities; -using FluentAssertions; -using Xunit; namespace DynamicData.Tests.Cache; diff --git a/src/DynamicData.Tests/Cache/TransformManyFixture.cs b/src/DynamicData.Tests/Cache/TransformManyFixture.cs index fdfe3a1dd..f9c03954d 100644 --- a/src/DynamicData.Tests/Cache/TransformManyFixture.cs +++ b/src/DynamicData.Tests/Cache/TransformManyFixture.cs @@ -1,11 +1,4 @@ -using System; - -using DynamicData.Tests.Domain; -using DynamicData.Tests.Utilities; - -using FluentAssertions; - -using Xunit; +using DynamicData.Tests.Domain; namespace DynamicData.Tests.Cache; diff --git a/src/DynamicData.Tests/Cache/TransformManyObservableCacheFixture.cs b/src/DynamicData.Tests/Cache/TransformManyObservableCacheFixture.cs index a15d1c051..004898a2c 100644 --- a/src/DynamicData.Tests/Cache/TransformManyObservableCacheFixture.cs +++ b/src/DynamicData.Tests/Cache/TransformManyObservableCacheFixture.cs @@ -1,249 +1,241 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Tests.Domain; - -using FluentAssertions; - -using Xunit; - -namespace DynamicData.Tests.Cache; - -public class TransformManyObservableCollectionFixture -{ - [Fact] - public void FlattenObservableCollection() - { - var children = Enumerable.Range(1, 100).Select(i => new Person("Name" + i, i)).ToArray(); - - var childIndex = 0; - var parents = Enumerable.Range(1, 50).Select( - i => - { - var parent = new Parent( - i, - new[] - { - children[childIndex], - children[childIndex + 1] - }); - - childIndex += 2; - return parent; - }).ToArray(); - - using var source = new SourceCache(x => x.Id); - using var aggregator = source.Connect().TransformMany(p => p.Children, c => c.Name).AsAggregator(); - source.AddOrUpdate(parents); - - aggregator.Data.Count.Should().Be(100); - - //add a child to an observable collection and check the new item is added - parents[0].Children.Add(new Person("NewlyAddded", 100)); - aggregator.Data.Count.Should().Be(101); - - ////remove first parent and check children have gone - source.RemoveKey(1); - aggregator.Data.Count.Should().Be(98); - - //check items can be cleared and then added back in - var childrenInZero = parents[1].Children.ToArray(); - parents[1].Children.Clear(); - aggregator.Data.Count.Should().Be(96); - parents[1].Children.AddRange(childrenInZero); - aggregator.Data.Count.Should().Be(98); - - //replace produces an update - var replacedChild = parents[1].Children[0]; - parents[1].Children[0] = new Person("Replacement", 100); - aggregator.Data.Count.Should().Be(98); - - aggregator.Data.Lookup(replacedChild.Key).HasValue.Should().BeFalse(); - aggregator.Data.Lookup("Replacement").HasValue.Should().BeTrue(); - } - - [Fact] - public void FlattenReadOnlyObservableCollection() - { - var children = Enumerable.Range(1, 100).Select(i => new Person("Name" + i, i)).ToArray(); - - var childIndex = 0; - var parents = Enumerable.Range(1, 50).Select( - i => - { - var parent = new Parent( - i, - new[] - { - children[childIndex], - children[childIndex + 1] - }); - - childIndex += 2; - return parent; - }).ToArray(); - - using var source = new SourceCache(x => x.Id); - using var aggregator = source.Connect().TransformMany(p => p.ChildrenReadonly, c => c.Name).AsAggregator(); - source.AddOrUpdate(parents); - - aggregator.Data.Count.Should().Be(100); - - //add a child to an observable collection and check the new item is added - parents[0].Children.Add(new Person("NewlyAddded", 100)); - aggregator.Data.Count.Should().Be(101); - - ////remove first parent and check children have gone - source.RemoveKey(1); - aggregator.Data.Count.Should().Be(98); - - //check items can be cleared and then added back in - var childrenInZero = parents[1].Children.ToArray(); - parents[1].Children.Clear(); - aggregator.Data.Count.Should().Be(96); - parents[1].Children.AddRange(childrenInZero); - aggregator.Data.Count.Should().Be(98); - - //replace produces an update - var replacedChild = parents[1].Children[0]; - parents[1].Children[0] = new Person("Replacement", 100); - aggregator.Data.Count.Should().Be(98); - - aggregator.Data.Lookup(replacedChild.Key).HasValue.Should().BeFalse(); - aggregator.Data.Lookup("Replacement").HasValue.Should().BeTrue(); - } - - [Fact] - public void FlattenObservableCache() - { - var children = Enumerable.Range(1, 100).Select(i => new Person("Name" + i, i)).ToArray(); - - var childIndex = 0; - var parents = Enumerable.Range(1, 50).Select( - i => - { - var parent = new Parent( - i, - new[] - { - children[childIndex], - children[childIndex + 1] - }); - - childIndex += 2; - return parent; - }).ToArray(); - - using var source = new SourceCache(x => x.Id); - using var aggregator = source.Connect().TransformMany(p => p.ChildrenCache, c => c.Name).AsAggregator(); - source.AddOrUpdate(parents); - - aggregator.Data.Count.Should().Be(100); - - //add a child to an observable collection and check the new item is added - parents[0].Children.Add(new Person("NewlyAddded", 100)); - aggregator.Data.Count.Should().Be(101); - - ////remove first parent and check children have gone - source.RemoveKey(1); - aggregator.Data.Count.Should().Be(98); - - //check items can be cleared and then added back in - var childrenInZero = parents[1].Children.ToArray(); - parents[1].Children.Clear(); - aggregator.Data.Count.Should().Be(96); - parents[1].Children.AddRange(childrenInZero); - aggregator.Data.Count.Should().Be(98); - - //replace produces an update - var replacedChild = parents[1].Children[0]; - parents[1].Children[0] = new Person("Replacement", 100); - aggregator.Data.Count.Should().Be(98); - - aggregator.Data.Lookup(replacedChild.Key).HasValue.Should().BeFalse(); - aggregator.Data.Lookup("Replacement").HasValue.Should().BeTrue(); - } - - [Fact] - public void ObservableCollectionWithoutInitialData() - { - using var parents = new SourceCache(d => d.Id); - var collection = parents.Connect().TransformMany(d => d.Children, p => p.Name).AsObservableCache(); - - var parent = new Parent(1); - parents.AddOrUpdate(parent); - - collection.Count.Should().Be(0); - - parent.Children.Add(new Person("child1", 1)); - collection.Count.Should().Be(1); - - parent.Children.Add(new Person("child2", 2)); - collection.Count.Should().Be(2); - } - - [Fact] - public void ReadOnlyObservableCollectionWithoutInitialData() - { - using var parents = new SourceCache(d => d.Id); - var collection = parents.Connect().TransformMany(d => d.ChildrenReadonly, p => p.Name).AsObservableCache(); - - var parent = new Parent(1); - parents.AddOrUpdate(parent); - - collection.Count.Should().Be(0); - - parent.Children.Add(new Person("child1", 1)); - collection.Count.Should().Be(1); - - parent.Children.Add(new Person("child2", 2)); - collection.Count.Should().Be(2); - } - - [Fact] - public void ObservableCacheWithoutInitialData() - { - using var parents = new SourceCache(d => d.Id); - var collection = parents.Connect().TransformMany(d => d.ChildrenCache, p => p.Name).AsObservableCache(); - - var parent = new Parent(1); - parents.AddOrUpdate(parent); - - collection.Count.Should().Be(0); - - parent.Children.Add(new Person("child1", 1)); - collection.Count.Should().Be(1); - - parent.Children.Add(new Person("child2", 2)); - collection.Count.Should().Be(2); - } - - private class Parent - { - public Parent(int id, IEnumerable children) - { - Id = id; - Children = new ObservableCollection(children); - ChildrenReadonly = new ReadOnlyObservableCollection(Children); - ChildrenCache = Children.ToObservableChangeSet(x => x.Name).AsObservableCache(); - } - - public Parent(int id) - { - Id = id; - Children = new ObservableCollection(); - ChildrenReadonly = new ReadOnlyObservableCollection(Children); - ChildrenCache = Children.ToObservableChangeSet(x => x.Name).AsObservableCache(); - } - - public ObservableCollection Children { get; } - +using DynamicData.Binding; +using DynamicData.Tests.Domain; + +namespace DynamicData.Tests.Cache; + +public class TransformManyObservableCollectionFixture +{ + [Fact] + public void FlattenObservableCollection() + { + var children = Enumerable.Range(1, 100).Select(i => new Person("Name" + i, i)).ToArray(); + + var childIndex = 0; + var parents = Enumerable.Range(1, 50).Select( + i => + { + var parent = new Parent( + i, + new[] + { + children[childIndex], + children[childIndex + 1] + }); + + childIndex += 2; + return parent; + }).ToArray(); + + using var source = new SourceCache(x => x.Id); + using var aggregator = source.Connect().TransformMany(p => p.Children, c => c.Name).AsAggregator(); + source.AddOrUpdate(parents); + + aggregator.Data.Count.Should().Be(100); + + //add a child to an observable collection and check the new item is added + parents[0].Children.Add(new Person("NewlyAddded", 100)); + aggregator.Data.Count.Should().Be(101); + + ////remove first parent and check children have gone + source.RemoveKey(1); + aggregator.Data.Count.Should().Be(98); + + //check items can be cleared and then added back in + var childrenInZero = parents[1].Children.ToArray(); + parents[1].Children.Clear(); + aggregator.Data.Count.Should().Be(96); + parents[1].Children.AddRange(childrenInZero); + aggregator.Data.Count.Should().Be(98); + + //replace produces an update + var replacedChild = parents[1].Children[0]; + parents[1].Children[0] = new Person("Replacement", 100); + aggregator.Data.Count.Should().Be(98); + + aggregator.Data.Lookup(replacedChild.Key).HasValue.Should().BeFalse(); + aggregator.Data.Lookup("Replacement").HasValue.Should().BeTrue(); + } + + [Fact] + public void FlattenReadOnlyObservableCollection() + { + var children = Enumerable.Range(1, 100).Select(i => new Person("Name" + i, i)).ToArray(); + + var childIndex = 0; + var parents = Enumerable.Range(1, 50).Select( + i => + { + var parent = new Parent( + i, + new[] + { + children[childIndex], + children[childIndex + 1] + }); + + childIndex += 2; + return parent; + }).ToArray(); + + using var source = new SourceCache(x => x.Id); + using var aggregator = source.Connect().TransformMany(p => p.ChildrenReadonly, c => c.Name).AsAggregator(); + source.AddOrUpdate(parents); + + aggregator.Data.Count.Should().Be(100); + + //add a child to an observable collection and check the new item is added + parents[0].Children.Add(new Person("NewlyAddded", 100)); + aggregator.Data.Count.Should().Be(101); + + ////remove first parent and check children have gone + source.RemoveKey(1); + aggregator.Data.Count.Should().Be(98); + + //check items can be cleared and then added back in + var childrenInZero = parents[1].Children.ToArray(); + parents[1].Children.Clear(); + aggregator.Data.Count.Should().Be(96); + parents[1].Children.AddRange(childrenInZero); + aggregator.Data.Count.Should().Be(98); + + //replace produces an update + var replacedChild = parents[1].Children[0]; + parents[1].Children[0] = new Person("Replacement", 100); + aggregator.Data.Count.Should().Be(98); + + aggregator.Data.Lookup(replacedChild.Key).HasValue.Should().BeFalse(); + aggregator.Data.Lookup("Replacement").HasValue.Should().BeTrue(); + } + + [Fact] + public void FlattenObservableCache() + { + var children = Enumerable.Range(1, 100).Select(i => new Person("Name" + i, i)).ToArray(); + + var childIndex = 0; + var parents = Enumerable.Range(1, 50).Select( + i => + { + var parent = new Parent( + i, + new[] + { + children[childIndex], + children[childIndex + 1] + }); + + childIndex += 2; + return parent; + }).ToArray(); + + using var source = new SourceCache(x => x.Id); + using var aggregator = source.Connect().TransformMany(p => p.ChildrenCache, c => c.Name).AsAggregator(); + source.AddOrUpdate(parents); + + aggregator.Data.Count.Should().Be(100); + + //add a child to an observable collection and check the new item is added + parents[0].Children.Add(new Person("NewlyAddded", 100)); + aggregator.Data.Count.Should().Be(101); + + ////remove first parent and check children have gone + source.RemoveKey(1); + aggregator.Data.Count.Should().Be(98); + + //check items can be cleared and then added back in + var childrenInZero = parents[1].Children.ToArray(); + parents[1].Children.Clear(); + aggregator.Data.Count.Should().Be(96); + parents[1].Children.AddRange(childrenInZero); + aggregator.Data.Count.Should().Be(98); + + //replace produces an update + var replacedChild = parents[1].Children[0]; + parents[1].Children[0] = new Person("Replacement", 100); + aggregator.Data.Count.Should().Be(98); + + aggregator.Data.Lookup(replacedChild.Key).HasValue.Should().BeFalse(); + aggregator.Data.Lookup("Replacement").HasValue.Should().BeTrue(); + } + + [Fact] + public void ObservableCollectionWithoutInitialData() + { + using var parents = new SourceCache(d => d.Id); + var collection = parents.Connect().TransformMany(d => d.Children, p => p.Name).AsObservableCache(); + + var parent = new Parent(1); + parents.AddOrUpdate(parent); + + collection.Count.Should().Be(0); + + parent.Children.Add(new Person("child1", 1)); + collection.Count.Should().Be(1); + + parent.Children.Add(new Person("child2", 2)); + collection.Count.Should().Be(2); + } + + [Fact] + public void ReadOnlyObservableCollectionWithoutInitialData() + { + using var parents = new SourceCache(d => d.Id); + var collection = parents.Connect().TransformMany(d => d.ChildrenReadonly, p => p.Name).AsObservableCache(); + + var parent = new Parent(1); + parents.AddOrUpdate(parent); + + collection.Count.Should().Be(0); + + parent.Children.Add(new Person("child1", 1)); + collection.Count.Should().Be(1); + + parent.Children.Add(new Person("child2", 2)); + collection.Count.Should().Be(2); + } + + [Fact] + public void ObservableCacheWithoutInitialData() + { + using var parents = new SourceCache(d => d.Id); + var collection = parents.Connect().TransformMany(d => d.ChildrenCache, p => p.Name).AsObservableCache(); + + var parent = new Parent(1); + parents.AddOrUpdate(parent); + + collection.Count.Should().Be(0); + + parent.Children.Add(new Person("child1", 1)); + collection.Count.Should().Be(1); + + parent.Children.Add(new Person("child2", 2)); + collection.Count.Should().Be(2); + } + + private class Parent + { + public Parent(int id, IEnumerable children) + { + Id = id; + Children = new ObservableCollection(children); + ChildrenReadonly = new ReadOnlyObservableCollection(Children); + ChildrenCache = Children.ToObservableChangeSet(x => x.Name).AsObservableCache(); + } + + public Parent(int id) + { + Id = id; + Children = new ObservableCollection(); + ChildrenReadonly = new ReadOnlyObservableCollection(Children); + ChildrenCache = Children.ToObservableChangeSet(x => x.Name).AsObservableCache(); + } + + public ObservableCollection Children { get; } + public ReadOnlyObservableCollection ChildrenReadonly { get; } - public IObservableCache ChildrenCache { get; } - - public int Id { get; } - } -} + public IObservableCache ChildrenCache { get; } + + public int Id { get; } + } +} diff --git a/src/DynamicData.Tests/Cache/TransformManyRefreshFixture.cs b/src/DynamicData.Tests/Cache/TransformManyRefreshFixture.cs index 6d14af74c..1daadcb3e 100644 --- a/src/DynamicData.Tests/Cache/TransformManyRefreshFixture.cs +++ b/src/DynamicData.Tests/Cache/TransformManyRefreshFixture.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections.Generic; - -using DynamicData.Tests.Domain; - -using FluentAssertions; - -using Xunit; +using DynamicData.Tests.Domain; namespace DynamicData.Tests.Cache; diff --git a/src/DynamicData.Tests/Cache/TransformManySimpleFixture.cs b/src/DynamicData.Tests/Cache/TransformManySimpleFixture.cs index 409436e1b..c6912058c 100644 --- a/src/DynamicData.Tests/Cache/TransformManySimpleFixture.cs +++ b/src/DynamicData.Tests/Cache/TransformManySimpleFixture.cs @@ -1,10 +1,4 @@ -using System; - -using DynamicData.Tests.Domain; - -using FluentAssertions; - -using Xunit; +using DynamicData.Tests.Domain; namespace DynamicData.Tests.Cache; diff --git a/src/DynamicData.Tests/Cache/TransformOnObservableFixture.cs b/src/DynamicData.Tests/Cache/TransformOnObservableFixture.cs index cc37806b7..a9010090e 100644 --- a/src/DynamicData.Tests/Cache/TransformOnObservableFixture.cs +++ b/src/DynamicData.Tests/Cache/TransformOnObservableFixture.cs @@ -1,18 +1,6 @@ -using System; -using System.Linq; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Threading.Tasks; using Bogus; using DynamicData.Kernel; using DynamicData.Tests.Domain; -using DynamicData.Tests.Utilities; - -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.Cache; diff --git a/src/DynamicData.Tests/Cache/TransformSafeAsyncFixture.cs b/src/DynamicData.Tests/Cache/TransformSafeAsyncFixture.cs index 6c1f34eff..b3b2bffa1 100644 --- a/src/DynamicData.Tests/Cache/TransformSafeAsyncFixture.cs +++ b/src/DynamicData.Tests/Cache/TransformSafeAsyncFixture.cs @@ -1,15 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Threading.Tasks; -using DynamicData.Cache; using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; -using Xunit; namespace DynamicData.Tests.Cache; @@ -19,7 +9,7 @@ public class TransformSafeAsyncFixture public void ReTransformAll() { var people = Enumerable.Range(1, 10).Select(i => new Person("Name" + i, i)).ToArray(); - var forceTransform = new Subject(); + var forceTransform = new Signal(); using var stub = new TransformStub(forceTransform); stub.Source.AddOrUpdate(people); @@ -42,7 +32,7 @@ public void ReTransformAll() public void ReTransformSelected() { var people = Enumerable.Range(1, 10).Select(i => new Person("Name" + i, i)).ToArray(); - var forceTransform = new Subject>(); + var forceTransform = new Signal>(); using var stub = new TransformStub(forceTransform); stub.Source.AddOrUpdate(people); @@ -201,13 +191,11 @@ public void TransformOnRefresh(bool transformOnRefresh) person.Age = 21; - results.Data.Count.Should().Be(1); results.Data.Lookup("SomeOne").Value.AgeGroup.Should().Be(transformOnRefresh ? "Adult" : "Child"); errorCount.Should().Be(0); } - [Theory, InlineData(10), InlineData(100)] public async Task WithMaxConcurrency(int maxConcurrency) @@ -235,16 +223,13 @@ public async Task WithMaxConcurrency(int maxConcurrency) , TransformAsyncOptions.Default with { MaximumConcurrency = maxConcurrency }) .AsAggregator(); - source.AddOrUpdate(Enumerable.Range(1, transformCount).Select(l => new Person("Person" + l, l))); - await results.Data.CountChanged.Where(c => c == transformCount).Take(1); errorCount.Should().Be(0); } - private class TransformStub : IDisposable { public TransformStub() diff --git a/src/DynamicData.Tests/Cache/TransformSafeFixture.cs b/src/DynamicData.Tests/Cache/TransformSafeFixture.cs index cd9aabc7d..6483ea993 100644 --- a/src/DynamicData.Tests/Cache/TransformSafeFixture.cs +++ b/src/DynamicData.Tests/Cache/TransformSafeFixture.cs @@ -1,14 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; - using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class TransformSafeFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/TransformSafeParallelFixture.cs b/src/DynamicData.Tests/Cache/TransformSafeParallelFixture.cs index dace5e75d..557e45e7a 100644 --- a/src/DynamicData.Tests/Cache/TransformSafeParallelFixture.cs +++ b/src/DynamicData.Tests/Cache/TransformSafeParallelFixture.cs @@ -1,14 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; - using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class TransformSafeParallelFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/TransformTreeFixture.cs b/src/DynamicData.Tests/Cache/TransformTreeFixture.cs index 33bf44573..d46a151f1 100644 --- a/src/DynamicData.Tests/Cache/TransformTreeFixture.cs +++ b/src/DynamicData.Tests/Cache/TransformTreeFixture.cs @@ -1,17 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Subjects; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class TransformTreeFixture : IDisposable { - private readonly BehaviorSubject, bool>> _filter; + private readonly StateSignal, bool>> _filter; private readonly IObservableCache, int> _result; @@ -21,7 +12,7 @@ public TransformTreeFixture() { _sourceCache = new SourceCache(e => e.Id); - _filter = new BehaviorSubject, bool>>(n => n.IsRoot); + _filter = new StateSignal, bool>>(n => n.IsRoot); _result = _sourceCache.Connect().TransformToTree(e => e.BossId, _filter).AsObservableCache(); } diff --git a/src/DynamicData.Tests/Cache/TransformTreeWithRefreshFixture.cs b/src/DynamicData.Tests/Cache/TransformTreeWithRefreshFixture.cs index 68748a69b..41c936083 100644 --- a/src/DynamicData.Tests/Cache/TransformTreeWithRefreshFixture.cs +++ b/src/DynamicData.Tests/Cache/TransformTreeWithRefreshFixture.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections.Generic; - -using DynamicData.Binding; - -using FluentAssertions; - -using Xunit; +using DynamicData.Binding; namespace DynamicData.Tests.Cache; diff --git a/src/DynamicData.Tests/Cache/TransformWithInlineUpdateFixture.cs b/src/DynamicData.Tests/Cache/TransformWithInlineUpdateFixture.cs index 0be034a13..4af0db106 100644 --- a/src/DynamicData.Tests/Cache/TransformWithInlineUpdateFixture.cs +++ b/src/DynamicData.Tests/Cache/TransformWithInlineUpdateFixture.cs @@ -1,11 +1,5 @@ -using System; -using System.Linq; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class TransformWithInlineUpdateFixture @@ -76,7 +70,6 @@ public void Remove() stub.Results.Data.Count.Should().Be(0, "Should be nothing cached"); } - [Fact] public void TransformOnRefresh() { diff --git a/src/DynamicData.Tests/Cache/TrueForAllFixture.cs b/src/DynamicData.Tests/Cache/TrueForAllFixture.cs index f9dece32b..92299e74e 100644 --- a/src/DynamicData.Tests/Cache/TrueForAllFixture.cs +++ b/src/DynamicData.Tests/Cache/TrueForAllFixture.cs @@ -1,11 +1,3 @@ -using System; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class TrueForAllFixture : IDisposable @@ -90,9 +82,9 @@ public void MultipleValuesReturnTrue() subscribed.Dispose(); } - private class ObjectWithObservable(int id) + private class ObjectWithObservable(int id) : IDisposable { - private readonly ISubject _changed = new Subject(); + private readonly ISignal _changed = new Signal(); public int Id { get; } = id; @@ -105,5 +97,10 @@ public void InvokeObservable(bool value) Value = value; _changed.OnNext(value); } - } + + public void Dispose() + { + _changed.Dispose(); + } +} } diff --git a/src/DynamicData.Tests/Cache/TrueForAnyFixture.cs b/src/DynamicData.Tests/Cache/TrueForAnyFixture.cs index ad5d9086d..556acf6b4 100644 --- a/src/DynamicData.Tests/Cache/TrueForAnyFixture.cs +++ b/src/DynamicData.Tests/Cache/TrueForAnyFixture.cs @@ -1,13 +1,3 @@ -using System; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using DynamicData.Tests.Utilities; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class TrueForAnyFixture : IDisposable @@ -99,9 +89,9 @@ public void ValuesPublishedOnSubscriptionDoNotTriggerPrematureOutput() results.RecordedValues[0].Should().Be(true, because: "One of the two items in the source has a true value"); } - private class ObjectWithObservable(int id) + private class ObjectWithObservable(int id) : IDisposable { - private readonly ISubject _changed = new Subject(); + private readonly ISignal _changed = new Signal(); public int Id { get; } = id; @@ -114,5 +104,10 @@ public void InvokeObservable(bool value) Value = value; _changed.OnNext(value); } - } + + public void Dispose() + { + _changed.Dispose(); + } +} } diff --git a/src/DynamicData.Tests/Cache/WatchFixture.cs b/src/DynamicData.Tests/Cache/WatchFixture.cs index 6cc1116b3..b2fc22345 100644 --- a/src/DynamicData.Tests/Cache/WatchFixture.cs +++ b/src/DynamicData.Tests/Cache/WatchFixture.cs @@ -1,10 +1,3 @@ -using System; -using System.Linq; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class WatchFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/WatcherFixture.cs b/src/DynamicData.Tests/Cache/WatcherFixture.cs index 23faff11f..a174c4028 100644 --- a/src/DynamicData.Tests/Cache/WatcherFixture.cs +++ b/src/DynamicData.Tests/Cache/WatcherFixture.cs @@ -1,22 +1,6 @@ -#region - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; - using DynamicData.Experimental; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Microsoft.Reactive.Testing; - -using Xunit; - -#endregion - namespace DynamicData.Tests.Cache; public class WatcherFixture : IDisposable diff --git a/src/DynamicData.Tests/Cache/XorFixture.cs b/src/DynamicData.Tests/Cache/XorFixture.cs index 8a320430a..047b9eed7 100644 --- a/src/DynamicData.Tests/Cache/XorFixture.cs +++ b/src/DynamicData.Tests/Cache/XorFixture.cs @@ -1,12 +1,5 @@ -using System; -using System.Collections.Generic; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Cache; public class XOrFixture : XOrFixtureBase diff --git a/src/DynamicData.Tests/Domain/Animal.cs b/src/DynamicData.Tests/Domain/Animal.cs index 744b058ee..141356077 100644 --- a/src/DynamicData.Tests/Domain/Animal.cs +++ b/src/DynamicData.Tests/Domain/Animal.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Threading; +using System.Diagnostics.CodeAnalysis; using DynamicData.Binding; namespace DynamicData.Tests.Domain; diff --git a/src/DynamicData.Tests/Domain/AnimalOwner.cs b/src/DynamicData.Tests/Domain/AnimalOwner.cs index 26049705b..fed1bf82b 100644 --- a/src/DynamicData.Tests/Domain/AnimalOwner.cs +++ b/src/DynamicData.Tests/Domain/AnimalOwner.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections; -using System.Collections.ObjectModel; -using System.Reactive.Disposables; using DynamicData.Binding; namespace DynamicData.Tests.Domain; diff --git a/src/DynamicData.Tests/Domain/Fakers.cs b/src/DynamicData.Tests/Domain/Fakers.cs index 720b74565..fc828e702 100644 --- a/src/DynamicData.Tests/Domain/Fakers.cs +++ b/src/DynamicData.Tests/Domain/Fakers.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; -using Bogus; -using DynamicData.Tests.Utilities; +using Bogus; namespace DynamicData.Tests.Domain; diff --git a/src/DynamicData.Tests/Domain/Market.cs b/src/DynamicData.Tests/Domain/Market.cs index a100e6df3..5306022e9 100644 --- a/src/DynamicData.Tests/Domain/Market.cs +++ b/src/DynamicData.Tests/Domain/Market.cs @@ -1,11 +1,5 @@ -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reactive.Linq; -using System.Threading; using DynamicData.Kernel; -using DynamicData.Tests.Utilities; namespace DynamicData.Tests.Domain; @@ -128,7 +122,6 @@ public int Compare([DisallowNull] IMarket x, [DisallowNull] IMarket y) => } } - internal sealed class FixedMarket : IMarket { public FixedMarket(Func getPrice, int minId, int maxId, bool completable = true) diff --git a/src/DynamicData.Tests/Domain/MarketPrice.cs b/src/DynamicData.Tests/Domain/MarketPrice.cs index f8fb2c6ad..c2ef85ab5 100644 --- a/src/DynamicData.Tests/Domain/MarketPrice.cs +++ b/src/DynamicData.Tests/Domain/MarketPrice.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Bogus; diff --git a/src/DynamicData.Tests/Domain/ParentAndChildren.cs b/src/DynamicData.Tests/Domain/ParentAndChildren.cs index 746d45fa9..ef371598a 100644 --- a/src/DynamicData.Tests/Domain/ParentAndChildren.cs +++ b/src/DynamicData.Tests/Domain/ParentAndChildren.cs @@ -1,6 +1,4 @@ -using System; - -using DynamicData.Kernel; +using DynamicData.Kernel; namespace DynamicData.Tests.Domain; diff --git a/src/DynamicData.Tests/Domain/Person.cs b/src/DynamicData.Tests/Domain/Person.cs index bca018cb6..fca3a2220 100644 --- a/src/DynamicData.Tests/Domain/Person.cs +++ b/src/DynamicData.Tests/Domain/Person.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; - -using DynamicData.Binding; +using DynamicData.Binding; namespace DynamicData.Tests.Domain; diff --git a/src/DynamicData.Tests/Domain/PersonObs.cs b/src/DynamicData.Tests/Domain/PersonObs.cs index 08bd463b7..c613b2e51 100644 --- a/src/DynamicData.Tests/Domain/PersonObs.cs +++ b/src/DynamicData.Tests/Domain/PersonObs.cs @@ -1,13 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Reactive.Subjects; - namespace DynamicData.Tests.Domain; [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1001:Types that own disposable fields should be disposable", Justification = "Acceptable in test.")] public class PersonObs(string name, int age, string gender = "F", string? parentName = null) : IEquatable { - private readonly BehaviorSubject _age = new BehaviorSubject(age); + private readonly StateSignal _age = new StateSignal(age); public PersonObs(string firstname, string lastname, int age, string gender = "F", string? parentName = null) : this(firstname + " " + lastname, age, gender, parentName) diff --git a/src/DynamicData.Tests/Domain/PersonWithChildren.cs b/src/DynamicData.Tests/Domain/PersonWithChildren.cs index 9930cf7cb..d9eca2f3c 100644 --- a/src/DynamicData.Tests/Domain/PersonWithChildren.cs +++ b/src/DynamicData.Tests/Domain/PersonWithChildren.cs @@ -1,6 +1,3 @@ -using System.Collections.Generic; -using System.Linq; - namespace DynamicData.Tests.Domain; public class PersonWithChildren : IKey diff --git a/src/DynamicData.Tests/Domain/PersonWithEmployment.cs b/src/DynamicData.Tests/Domain/PersonWithEmployment.cs index c95e9ce75..18c2e3a44 100644 --- a/src/DynamicData.Tests/Domain/PersonWithEmployment.cs +++ b/src/DynamicData.Tests/Domain/PersonWithEmployment.cs @@ -1,5 +1,3 @@ -using System; - namespace DynamicData.Tests.Domain; public class PersonWithEmployment(IGroup source) : IDisposable diff --git a/src/DynamicData.Tests/Domain/PersonWithFriends.cs b/src/DynamicData.Tests/Domain/PersonWithFriends.cs index 43db20717..ab78e321b 100644 --- a/src/DynamicData.Tests/Domain/PersonWithFriends.cs +++ b/src/DynamicData.Tests/Domain/PersonWithFriends.cs @@ -1,6 +1,3 @@ -using System.Collections.Generic; -using System.Linq; - using DynamicData.Binding; namespace DynamicData.Tests.Domain; diff --git a/src/DynamicData.Tests/Domain/PersonWithGender.cs b/src/DynamicData.Tests/Domain/PersonWithGender.cs index dc4ac6c09..1276dadd2 100644 --- a/src/DynamicData.Tests/Domain/PersonWithGender.cs +++ b/src/DynamicData.Tests/Domain/PersonWithGender.cs @@ -1,6 +1,4 @@ -using System; - -namespace DynamicData.Tests.Domain; +namespace DynamicData.Tests.Domain; public record PersonWithAgeGroup(Person Person, string AgeGroup); public class PersonWithGender : IEquatable diff --git a/src/DynamicData.Tests/Domain/PersonWithRelations.cs b/src/DynamicData.Tests/Domain/PersonWithRelations.cs index f488865f5..6e9103f9c 100644 --- a/src/DynamicData.Tests/Domain/PersonWithRelations.cs +++ b/src/DynamicData.Tests/Domain/PersonWithRelations.cs @@ -1,6 +1,3 @@ -using System.Collections.Generic; -using System.Linq; - namespace DynamicData.Tests.Domain; public class PersonWithRelations : IKey diff --git a/src/DynamicData.Tests/Domain/RandomPersonGenerator.cs b/src/DynamicData.Tests/Domain/RandomPersonGenerator.cs index 77a799667..9094f2c76 100644 --- a/src/DynamicData.Tests/Domain/RandomPersonGenerator.cs +++ b/src/DynamicData.Tests/Domain/RandomPersonGenerator.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace DynamicData.Tests.Domain; +namespace DynamicData.Tests.Domain; public class RandomPersonGenerator { diff --git a/src/DynamicData.Tests/Domain/SelfObservingPerson.cs b/src/DynamicData.Tests/Domain/SelfObservingPerson.cs index 7a11ea949..65da4e13a 100644 --- a/src/DynamicData.Tests/Domain/SelfObservingPerson.cs +++ b/src/DynamicData.Tests/Domain/SelfObservingPerson.cs @@ -1,6 +1,3 @@ -using System; -using System.Reactive.Linq; - namespace DynamicData.Tests.Domain; public class SelfObservingPerson : IDisposable diff --git a/src/DynamicData.Tests/DynamicData.Tests.csproj b/src/DynamicData.Tests/DynamicData.Tests.csproj index 4b9e28c65..51808d43e 100644 --- a/src/DynamicData.Tests/DynamicData.Tests.csproj +++ b/src/DynamicData.Tests/DynamicData.Tests.csproj @@ -1,12 +1,13 @@  - net9.0 + net10.0 $(NoWarn);CS0618;CA1801;CA1812;CA1816;CA1062;CA1063;CS8767;CS8602;CS8618;IDE1006 2.* + @@ -17,11 +18,41 @@ - all runtime; build; native; contentfiles; analyzers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/DynamicData.Tests/EnumerableExFixtures.cs b/src/DynamicData.Tests/EnumerableExFixtures.cs index d25cc8325..c6d46fd6e 100644 --- a/src/DynamicData.Tests/EnumerableExFixtures.cs +++ b/src/DynamicData.Tests/EnumerableExFixtures.cs @@ -1,10 +1,4 @@ -using System; - -using DynamicData.Tests.Domain; - -using FluentAssertions; - -using Xunit; +using DynamicData.Tests.Domain; namespace DynamicData.Tests; diff --git a/src/DynamicData.Tests/EnumerableIListFixture.cs b/src/DynamicData.Tests/EnumerableIListFixture.cs index 2c18d4302..f317455d3 100644 --- a/src/DynamicData.Tests/EnumerableIListFixture.cs +++ b/src/DynamicData.Tests/EnumerableIListFixture.cs @@ -1,11 +1,5 @@ -using System; using DynamicData.Cache.Internal; -using System.Collections.Generic; -using System.Reactive.Concurrency; -using System.Reactive.Linq; -using System.Reactive.Subjects; using DynamicData.Kernel; -using Xunit; namespace DynamicData.Tests { @@ -47,10 +41,10 @@ public void EnumerableIListTests() [Fact] public void ExceptionTests() { - var exSubject = new Subject(); + var exSubject = new Signal(); object exceptionRecived = default!; - exSubject.ObserveOn(ImmediateScheduler.Instance).Subscribe(ex => { exceptionRecived = ex; }); + exSubject.ObserveOn(Scheduler.Immediate).Subscribe(ex => { exceptionRecived = ex; }); exSubject.OnNext(new UnspecifiedIndexException()); Assert.IsType(exceptionRecived); diff --git a/src/DynamicData.Tests/IntegrationTestFixtureBase.cs b/src/DynamicData.Tests/IntegrationTestFixtureBase.cs index d28b8cf87..e7f7c91af 100644 --- a/src/DynamicData.Tests/IntegrationTestFixtureBase.cs +++ b/src/DynamicData.Tests/IntegrationTestFixtureBase.cs @@ -1,6 +1,4 @@ -using Xunit; - -namespace DynamicData.Tests; +namespace DynamicData.Tests; [Collection(CollectionName)] [CollectionDefinition(CollectionName, DisableParallelization = true)] diff --git a/src/DynamicData.Tests/Internal/BitsetFixture.cs b/src/DynamicData.Tests/Internal/BitsetFixture.cs index fd33ce09e..2cb633bf2 100644 --- a/src/DynamicData.Tests/Internal/BitsetFixture.cs +++ b/src/DynamicData.Tests/Internal/BitsetFixture.cs @@ -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 DynamicData.Internal; -using FluentAssertions; -using Xunit; - namespace DynamicData.Tests.Internal; public class BitsetFixture diff --git a/src/DynamicData.Tests/Internal/CacheParentSubscriptionFixture.cs b/src/DynamicData.Tests/Internal/CacheParentSubscriptionFixture.cs index 5f11b9fc5..0e8dccbaa 100644 --- a/src/DynamicData.Tests/Internal/CacheParentSubscriptionFixture.cs +++ b/src/DynamicData.Tests/Internal/CacheParentSubscriptionFixture.cs @@ -1,29 +1,16 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.Generic; -using System.Linq; -using System.Reactive.Subjects; -using System.Threading; -using System.Threading.Tasks; - using Bogus; -using DynamicData.Internal; -using DynamicData.Tests.Utilities; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Internal; /// /// Tests for /// behavioral contracts using a minimal concrete subclass. /// +[Collection(IntegrationTestFixtureBase.CollectionName)] public sealed class CacheParentSubscriptionFixture { private const int SeedMin = 1; @@ -60,11 +47,11 @@ public void ParentOnNext_CalledForEachChangeSet() public void ChildOnNext_CalledForEachEmission() { using var source = new SourceCache(x => x.Key); - var childSubjects = new List>(); + var childSubjects = new List>(); var observer = new TestObserver(); using var sub = new TestSubscription(observer, key => { - var subj = new Subject(); + var subj = new Signal(); childSubjects.Add(subj); return subj; }); @@ -110,7 +97,7 @@ public void Batching_ChildUpdatesSettleBeforeEmit() using var sub = new TestSubscription(observer, key => { Interlocked.Increment(ref childCount); - return new BehaviorSubject($"sync-{key}"); + return new StateSignal($"sync-{key}"); }); sub.ExposeCreateParent(source.Connect()); @@ -129,11 +116,11 @@ public void Batching_ChildUpdatesSettleBeforeEmit() public void Completion_RequiresParentAndAllChildren() { using var source = new TestSourceCache(x => x.Key); - var childSubjects = new List>(); + var childSubjects = new List>(); var observer = new TestObserver(); using var sub = new TestSubscription(observer, key => { - var subj = new Subject(); + var subj = new Signal(); childSubjects.Add(subj); return subj; }); @@ -165,11 +152,11 @@ public void Completion_ParentOnly_NoChildren() public void Disposal_StopsAllEmissions() { using var source = new SourceCache(x => x.Key); - var childSubjects = new List>(); + var childSubjects = new List>(); var observer = new TestObserver(); var sub = new TestSubscription(observer, key => { - var subj = new Subject(); + var subj = new Signal(); childSubjects.Add(subj); return subj; }); @@ -211,7 +198,7 @@ public void Serialization_ParentAndChildDoNotInterleave() observer, key => { - var subj = new Subject(); + var subj = new Signal(); return subj; }, onParent: () => { lock (callLog) callLog.Add("P-start"); Thread.Sleep(1); lock (callLog) callLog.Add("P-end"); }, @@ -372,4 +359,4 @@ private sealed class TestObserver : IObserver> public void OnError(Exception error) => Error = error; public void OnCompleted() => IsCompleted = true; } -} \ No newline at end of file +} diff --git a/src/DynamicData.Tests/Internal/CrossCacheDeadlockRepro.cs b/src/DynamicData.Tests/Internal/CrossCacheDeadlockRepro.cs new file mode 100644 index 000000000..808a32696 --- /dev/null +++ b/src/DynamicData.Tests/Internal/CrossCacheDeadlockRepro.cs @@ -0,0 +1,55 @@ +// CrossCacheDeadlockRepro.cs +// Reproduction for: Cross-cache deadlock when concurrent updates notify subscribers that modify other SourceCache instances +// Requires: DynamicData 9.x, xunit, FluentAssertions +// +// This test deadlocks on DynamicData main. It should complete in under 10 seconds. + +namespace DynamicData.Tests.Internal; + +public class CrossCacheDeadlockRepro : IDisposable +{ + private readonly SourceCache _cacheA = new(static x => x.GetHashCode()); + private readonly SourceCache _cacheB = new(static x => x.GetHashCode()); + + [Fact] + public void ConcurrentPopulateIntoShouldNotDeadlock() + { + // Arrange + using var destination = new SourceCache(static x => x.GetHashCode()); + using var subA = _cacheA.Connect().PopulateInto(destination); + using var subB = _cacheB.Connect().PopulateInto(destination); + + var count = 0; + using var feedback = destination.Connect().Subscribe(_ => Interlocked.Increment(ref count)); + + // Act — concurrent updates from two threads + var completed = Task.WaitAll( + [ + Task.Run(() => + { + for (var i = 0; i < 100; i++) + { + _cacheA.AddOrUpdate($"A-{i}"); + } + }), + Task.Run(() => + { + for (var i = 0; i < 100; i++) + { + _cacheB.AddOrUpdate($"B-{i}"); + } + }), + ], + TimeSpan.FromSeconds(10)); + + // Assert + completed.Should().BeTrue("concurrent PopulateInto should not deadlock"); + count.Should().BeGreaterThan(0, "destination should have received changeset notifications"); + } + + public void Dispose() + { + _cacheA.Dispose(); + _cacheB.Dispose(); + } +} diff --git a/src/DynamicData.Tests/Internal/DeliveryQueueFixture.cs b/src/DynamicData.Tests/Internal/DeliveryQueueFixture.cs index 3062e331f..32537540d 100644 --- a/src/DynamicData.Tests/Internal/DeliveryQueueFixture.cs +++ b/src/DynamicData.Tests/Internal/DeliveryQueueFixture.cs @@ -1,23 +1,10 @@ -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -using DynamicData.Internal; -using FluentAssertions; -using Xunit; namespace DynamicData.Tests.Internal; public class DeliveryQueueFixture { -#if NET9_0_OR_GREATER private readonly Lock _gate = new(); -#else - private readonly object _gate = new(); -#endif /// Helper observer that captures OnNext items into a list. private sealed class ListObserver : IObserver @@ -55,12 +42,14 @@ public void OnCompleted() { } } private static void EnqueueAndDeliver(DeliveryQueue queue, T item) + where T : notnull { using var scope = queue.AcquireLock(); scope.EnqueueNext(item); } private static void TriggerDelivery(DeliveryQueue queue) + where T : notnull { using var scope = queue.AcquireLock(); } @@ -557,4 +546,4 @@ public void ErrorTerminatesAndClearsPending() observer.Error.Should().BeSameAs(error); queue.IsTerminated.Should().BeTrue(); } -} \ No newline at end of file +} diff --git a/src/DynamicData.Tests/Internal/KeyedDisposableFixture.cs b/src/DynamicData.Tests/Internal/KeyedDisposableFixture.cs index 2296564ef..32029cf40 100644 --- a/src/DynamicData.Tests/Internal/KeyedDisposableFixture.cs +++ b/src/DynamicData.Tests/Internal/KeyedDisposableFixture.cs @@ -2,13 +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.Collections.Generic; - -using DynamicData.Internal; -using FluentAssertions; -using Xunit; - namespace DynamicData.Tests.Internal; public class KeyedDisposableFixture diff --git a/src/DynamicData.Tests/Internal/NotificationFixture.cs b/src/DynamicData.Tests/Internal/NotificationFixture.cs index d221b0044..5bc10eefd 100644 --- a/src/DynamicData.Tests/Internal/NotificationFixture.cs +++ b/src/DynamicData.Tests/Internal/NotificationFixture.cs @@ -2,13 +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.Reactive; - -using DynamicData.Internal; -using FluentAssertions; -using Xunit; - namespace DynamicData.Tests.Internal; public class NotificationFixture diff --git a/src/DynamicData.Tests/Internal/SharedDeliveryQueueFixture.cs b/src/DynamicData.Tests/Internal/SharedDeliveryQueueFixture.cs index e67444cb6..42fb3b23e 100644 --- a/src/DynamicData.Tests/Internal/SharedDeliveryQueueFixture.cs +++ b/src/DynamicData.Tests/Internal/SharedDeliveryQueueFixture.cs @@ -2,26 +2,13 @@ // 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.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -using DynamicData.Internal; -using FluentAssertions; -using Xunit; namespace DynamicData.Tests.Internal; public class SharedDeliveryQueueFixture { -#if NET9_0_OR_GREATER private readonly Lock _gate = new(); -#else - private readonly object _gate = new(); -#endif [Fact] public void SingleSourceDeliversItems() @@ -184,4 +171,4 @@ private sealed class TestObserver(Action onNext) : IObserver public void OnError(Exception error) => Error = error; public void OnCompleted() => IsCompleted = true; } -} \ No newline at end of file +} diff --git a/src/DynamicData.Tests/Internal/SwappableLockFixture.cs b/src/DynamicData.Tests/Internal/SwappableLockFixture.cs index 4b3bd980e..d4e8b0533 100644 --- a/src/DynamicData.Tests/Internal/SwappableLockFixture.cs +++ b/src/DynamicData.Tests/Internal/SwappableLockFixture.cs @@ -2,11 +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.Threading; -using FluentAssertions; -using Xunit; - namespace DynamicData.Tests.Internal; public sealed class SwappableLockFixture diff --git a/src/DynamicData.Tests/Issues/EmptyToChangeSetIssue.cs b/src/DynamicData.Tests/Issues/EmptyToChangeSetIssue.cs index 1044f5cd2..e9d2c71e3 100644 --- a/src/DynamicData.Tests/Issues/EmptyToChangeSetIssue.cs +++ b/src/DynamicData.Tests/Issues/EmptyToChangeSetIssue.cs @@ -1,8 +1,4 @@ -using System.Collections.ObjectModel; -using System.Reactive; using DynamicData.Binding; -using FluentAssertions; -using Xunit; namespace DynamicData.Tests.Issues { diff --git a/src/DynamicData.Tests/Issues/OnItemRemovedIssue.cs b/src/DynamicData.Tests/Issues/OnItemRemovedIssue.cs index d257b8abb..890d7601c 100644 --- a/src/DynamicData.Tests/Issues/OnItemRemovedIssue.cs +++ b/src/DynamicData.Tests/Issues/OnItemRemovedIssue.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; using DynamicData.Binding; -using Xunit; namespace DynamicData.Tests.Issues { @@ -53,7 +49,6 @@ public class Proxy public bool? Active { get; set; } - } public class ProxyEqualityComparer : IEqualityComparer diff --git a/src/DynamicData.Tests/Kernal/CacheUpdaterFixture.cs b/src/DynamicData.Tests/Kernal/CacheUpdaterFixture.cs index 438e1bd5f..5cb886e77 100644 --- a/src/DynamicData.Tests/Kernal/CacheUpdaterFixture.cs +++ b/src/DynamicData.Tests/Kernal/CacheUpdaterFixture.cs @@ -1,12 +1,6 @@ -using System.Linq; - -using DynamicData.Cache.Internal; +using DynamicData.Cache.Internal; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Kernal; public class CacheUpdaterFixture diff --git a/src/DynamicData.Tests/Kernal/DistinctUpdateFixture.cs b/src/DynamicData.Tests/Kernal/DistinctUpdateFixture.cs index e7850ce8b..7b7410cdd 100644 --- a/src/DynamicData.Tests/Kernal/DistinctUpdateFixture.cs +++ b/src/DynamicData.Tests/Kernal/DistinctUpdateFixture.cs @@ -1,12 +1,5 @@ -using System; - -using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Kernal; public class DistinctUpdateFixture @@ -20,7 +13,7 @@ public void Add() update.Key.Should().Be(person); update.Reason.Should().Be(ChangeReason.Add); update.Current.Should().Be(person); - update.Previous.Should().Be(Optional.None()); + update.Previous.Should().Be(Optional.None); } [Fact] @@ -32,7 +25,7 @@ public void Remove() update.Key.Should().Be(person); update.Reason.Should().Be(ChangeReason.Remove); update.Current.Should().Be(person); - update.Previous.Should().Be(Optional.None()); + update.Previous.Should().Be(Optional.None); } [Fact] diff --git a/src/DynamicData.Tests/Kernal/EnumerableEx.cs b/src/DynamicData.Tests/Kernal/EnumerableEx.cs index 315c392bb..1c837bf55 100644 --- a/src/DynamicData.Tests/Kernal/EnumerableEx.cs +++ b/src/DynamicData.Tests/Kernal/EnumerableEx.cs @@ -1,21 +1,11 @@ -using System; -using System.Collections.Generic; - namespace DynamicData.Tests.Cache; public static class EnumerableEx { public static IEnumerable CurrentNextZip(this IEnumerable source, Func selector) { - if (source is null) - { - throw new ArgumentNullException(nameof(source)); - } - - if (selector is null) - { - throw new ArgumentNullException(nameof(selector)); - } + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(selector); var enumerator = source.GetEnumerator(); if (enumerator.MoveNext()) @@ -35,15 +25,8 @@ public static IEnumerable CurrentNextZip(this IEnumerable PrevCurrentNextZip(this IEnumerable source, Func selector) { - if (source is null) - { - throw new ArgumentNullException(nameof(source)); - } - - if (selector is null) - { - throw new ArgumentNullException(nameof(selector)); - } + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(selector); var enumerator = source.GetEnumerator(); if (enumerator.MoveNext()) @@ -65,15 +48,8 @@ public static IEnumerable PrevCurrentNextZip(this IEnumerab public static IEnumerable PrevCurrentZip(this IEnumerable source, Func selector) { - if (source is null) - { - throw new ArgumentNullException(nameof(source)); - } - - if (selector is null) - { - throw new ArgumentNullException(nameof(selector)); - } + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(selector); var enumerator = source.GetEnumerator(); if (enumerator.MoveNext()) diff --git a/src/DynamicData.Tests/Kernal/KeyValueFixture.cs b/src/DynamicData.Tests/Kernal/KeyValueFixture.cs index ead9fb9da..fcfac36ad 100644 --- a/src/DynamicData.Tests/Kernal/KeyValueFixture.cs +++ b/src/DynamicData.Tests/Kernal/KeyValueFixture.cs @@ -1,10 +1,4 @@ -using System.Collections.Generic; - -using DynamicData.Tests.Domain; - -using FluentAssertions; - -using Xunit; +using DynamicData.Tests.Domain; namespace DynamicData.Tests.Kernal; diff --git a/src/DynamicData.Tests/Kernal/OptionFixture.cs b/src/DynamicData.Tests/Kernal/OptionFixture.cs index e93c17e0b..63e251291 100644 --- a/src/DynamicData.Tests/Kernal/OptionFixture.cs +++ b/src/DynamicData.Tests/Kernal/OptionFixture.cs @@ -1,12 +1,7 @@ -using System; -using System.Globalization; +using System.Globalization; using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Kernal; public class OptionFixture @@ -52,22 +47,22 @@ public void OptionIfHasValueInvokedIfOptionHasValue() [Fact] public void OptionNoneHasNoValue() { - var option = Optional.None>(); + var option = Optional>.None; option.HasValue.Should().BeFalse(); } [Fact] public void OptionSetToNullHasNoValue1() { - Person? person = null; - var option = Optional.Some(person); + Person person = default!; + var option = Optional.Some(person); option.HasValue.Should().BeFalse(); } [Fact] public void OptionSetToNullHasNoValue2() { - Person? person = null; + Person person = default!; Optional option = person; option.HasValue.Should().BeFalse(); } @@ -76,7 +71,7 @@ public void OptionSetToNullHasNoValue2() public void OptionSomeHasValue() { var person = new Person("Name", 20); - var option = Optional.Some(person); + var option = Optional.Some(person); option.HasValue.Should().BeTrue(); ReferenceEquals(person, option.Value).Should().BeTrue(); } @@ -90,7 +85,7 @@ public void OptionConvertThrowsIfConverterIsNull() try { - Optional.None().Convert(converter!); + Optional.None.Convert(converter!); } catch (ArgumentNullException) { @@ -103,13 +98,13 @@ public void OptionConvertThrowsIfConverterIsNull() [Fact] public void OptionConvertToOptionalInvokesConverterWithValue() { - var option = Optional.Some(string.Empty); + var option = Optional.Some(string.Empty); var invoked = false; Optional Converter(string input) { invoked = true; - return Optional.Some(input); + return Optional.Some(input); } var result = option.Convert(Converter); @@ -121,13 +116,13 @@ Optional Converter(string input) [Fact] public void OptionConvertToOptionalInvokesConverterOnlyWithValue() { - var option = Optional.None(); + var option = Optional.None; var invoked = false; Optional Converter(string input) { invoked = true; - return Optional.Some(input); + return Optional.Some(input); } var result = option.Convert(Converter); @@ -141,7 +136,7 @@ public void OptionConvertToOptionalCanReturnValue() { const int TestData = 37; - var option = Optional.Some(TestData.ToString()); + var option = Optional.Some(TestData.ToString()); var result = option.Convert(ParseInt); @@ -152,7 +147,7 @@ public void OptionConvertToOptionalCanReturnValue() [Fact] public void OptionConvertToOptionalCanReturnNone() { - var option = Optional.Some("Not An Int"); + var option = Optional.Some("Not An Int"); var result = option.Convert(ParseInt); @@ -168,7 +163,7 @@ public void OptionConvertToOptionalThrowsIfConverterIsNull() try { - Optional.None().Convert(converter!); + Optional.None.Convert(converter!); } catch (ArgumentNullException) { @@ -181,13 +176,13 @@ public void OptionConvertToOptionalThrowsIfConverterIsNull() [Fact] public void OptionOrElseInvokesWithoutValue() { - var option = Optional.None(); + var option = Optional.None; var invoked = false; Optional Fallback() { invoked = true; - return Optional.None(); + return Optional.None; } var result = option.OrElse(Fallback); @@ -198,13 +193,13 @@ Optional Fallback() [Fact] public void OptionOrElseInvokesOnlyWithoutValue() { - var option = Optional.Some(string.Empty); + var option = Optional.Some(string.Empty); var invoked = false; Optional Fallback() { invoked = true; - return Optional.None(); + return Optional.None; } var result = option.OrElse(Fallback); @@ -217,7 +212,7 @@ public void OptionOrElseCanReturnValue() { const string TestString = nameof(TestString); - var option = Optional.None(); + var option = Optional.None; var result = option.OrElse(() => TestString); result.HasValue.Should().BeTrue(); @@ -227,8 +222,8 @@ public void OptionOrElseCanReturnValue() [Fact] public void OptionOrElseCanReturnNone() { - var option = Optional.None(); - var result = option.OrElse(Optional.None); + var option = Optional.None; + var result = option.OrElse(() => Optional.None); result.HasValue.Should().BeFalse(); } @@ -238,9 +233,9 @@ public void OptionOrElseCanBeChained() { const int Expected = unchecked((int)0xc001d00d); - var option = Optional.None(); - var result = option.OrElse(Optional.None) - .OrElse(() => Optional.Some(Expected.ToString("x"))) + var option = Optional.None; + var result = option.OrElse(() => Optional.None) + .OrElse(() => Optional.Some(Expected.ToString("x"))) .Convert(s => ParseInt(s).OrElse(() => ParseHex(s))); result.HasValue.Should().BeTrue(); @@ -254,9 +249,9 @@ public void OptionOrElseThrowsIfFallbackIsNull() try { - Optional.None().OrElse(null!); + Optional.None.OrElse(null!); } - catch(ArgumentNullException) + catch (ArgumentNullException) { caught = true; } @@ -265,8 +260,8 @@ public void OptionOrElseThrowsIfFallbackIsNull() } private static Optional ParseInt(string input) => - int.TryParse(input, out var result) ? Optional.Some(result) : Optional.None(); + int.TryParse(input, out var result) ? Optional.Some(result) : Optional.None; private static Optional ParseHex(string input) => - int.TryParse(input, NumberStyles.HexNumber, null, out var result) ? Optional.Some(result) : Optional.None(); + int.TryParse(input, NumberStyles.HexNumber, null, out var result) ? Optional.Some(result) : Optional.None; } diff --git a/src/DynamicData.Tests/Kernal/OptionObservableFixture.cs b/src/DynamicData.Tests/Kernal/OptionObservableFixture.cs index 06095bc20..69b517430 100644 --- a/src/DynamicData.Tests/Kernal/OptionObservableFixture.cs +++ b/src/DynamicData.Tests/Kernal/OptionObservableFixture.cs @@ -1,13 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; using DynamicData.Kernel; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Kernal; public class OptionObservableFixture @@ -15,13 +7,13 @@ public class OptionObservableFixture private const int NoneCount = 5; private const int SomeCount = 10; - private static Optional NotConvertableToInt { get; } = Optional.Some("NOT AN INT"); + private static Optional NotConvertableToInt { get; } = Optional.Some("NOT AN INT"); private static IEnumerable IntEnum { get; } = Enumerable.Range(0, SomeCount); private static IEnumerable StringEnum { get; } = IntEnum.Select(n => n.ToString()); - private static IEnumerable> OptIntEnum { get; } = IntEnum.Select(i => Optional.Some(i)); - private static IEnumerable> OptNoneIntEnum { get; } = Enumerable.Repeat(Optional.None(), NoneCount); - private static IEnumerable> OptNoneStringEnum { get; } = Enumerable.Repeat(Optional.None(), NoneCount); - private static IEnumerable> OptStringEnum { get; } = StringEnum.Select(str => Optional.Some(str)); + private static IEnumerable> OptIntEnum { get; } = IntEnum.Select(i => Optional.Some(i)); + private static IEnumerable> OptNoneIntEnum { get; } = Enumerable.Repeat(Optional.None, NoneCount); + private static IEnumerable> OptNoneStringEnum { get; } = Enumerable.Repeat(Optional.None, NoneCount); + private static IEnumerable> OptStringEnum { get; } = StringEnum.Select(str => Optional.Some(str)); private static IEnumerable> OptStringWithNoneEnum { get; } = OptNoneStringEnum.Concat(OptStringEnum); private static IEnumerable> OptStringWithBadEnum { get; } = OptStringEnum.Prepend(NotConvertableToInt); private static IEnumerable> OptStringWithBadAndNoneEnum { get; } = OptStringWithNoneEnum.Prepend(NotConvertableToInt); @@ -126,7 +118,7 @@ public void ConvertOptionalWillConvertValues() // then intList.Should().BeSubsetOf(results); - results.Should().Contain(Optional.None()); + results.Should().Contain(Optional.None); } [Fact] @@ -164,7 +156,7 @@ public void ConvertOrConvertsOrFallsback() public void OrElseFallsback() { // having - var observable = OptIntEnum.ToObservable().StartWith(Optional.None()); + var observable = OptIntEnum.ToObservable().StartWith(Optional.None); // when var results = observable.OrElse(() => -1).ToEnumerable(); @@ -280,7 +272,7 @@ public void ValueOrThrowFailsWithGeneratedError() } private static Optional ParseIntOpt(string input) => - int.TryParse(input, out var result) ? Optional.Some(result) : Optional.None(); + int.TryParse(input, out var result) ? Optional.Some(result) : Optional.None; private static int ParseInt(string input) => int.Parse(input); } diff --git a/src/DynamicData.Tests/Kernal/SourceUpdaterFixture.cs b/src/DynamicData.Tests/Kernal/SourceUpdaterFixture.cs index 45c6f965e..2dfbc47dd 100644 --- a/src/DynamicData.Tests/Kernal/SourceUpdaterFixture.cs +++ b/src/DynamicData.Tests/Kernal/SourceUpdaterFixture.cs @@ -1,12 +1,6 @@ -using System.Linq; - using DynamicData.Cache.Internal; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Kernal; public class SourceUpdaterFixture diff --git a/src/DynamicData.Tests/Kernal/UpdateFixture.cs b/src/DynamicData.Tests/Kernal/UpdateFixture.cs index 3431a2b9a..6bf136be0 100644 --- a/src/DynamicData.Tests/Kernal/UpdateFixture.cs +++ b/src/DynamicData.Tests/Kernal/UpdateFixture.cs @@ -1,12 +1,5 @@ -using System; - -using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.Kernal; public class UpdateFixture @@ -20,7 +13,7 @@ public void Add() update.Key.Should().Be("Person"); update.Reason.Should().Be(ChangeReason.Add); update.Current.Should().Be(person); - update.Previous.Should().Be(Optional.None()); + update.Previous.Should().Be(Optional.None); } [Fact] @@ -32,7 +25,7 @@ public void Remove() update.Key.Should().Be("Person"); update.Reason.Should().Be(ChangeReason.Remove); update.Current.Should().Be(person); - update.Previous.Should().Be(Optional.None()); + update.Previous.Should().Be(Optional.None); } [Fact] diff --git a/src/DynamicData.Tests/List/AndFixture.cs b/src/DynamicData.Tests/List/AndFixture.cs index 1d18596ad..5aefaf48a 100644 --- a/src/DynamicData.Tests/List/AndFixture.cs +++ b/src/DynamicData.Tests/List/AndFixture.cs @@ -1,11 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class AndFixture : AndFixtureBase diff --git a/src/DynamicData.Tests/List/AutoRefreshFixture.cs b/src/DynamicData.Tests/List/AutoRefreshFixture.cs index e6e88aa72..ffe6e1925 100644 --- a/src/DynamicData.Tests/List/AutoRefreshFixture.cs +++ b/src/DynamicData.Tests/List/AutoRefreshFixture.cs @@ -1,16 +1,7 @@ -using System; -using System.Linq; - using DynamicData.Binding; using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Microsoft.Reactive.Testing; - -using Xunit; - namespace DynamicData.Tests.List; public class AutoRefreshFixture diff --git a/src/DynamicData.Tests/List/BatchFixture.cs b/src/DynamicData.Tests/List/BatchFixture.cs index c93fe2d08..aab59ecbf 100644 --- a/src/DynamicData.Tests/List/BatchFixture.cs +++ b/src/DynamicData.Tests/List/BatchFixture.cs @@ -1,14 +1,5 @@ -using System; -using System.Reactive.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Microsoft.Reactive.Testing; - -using Xunit; - namespace DynamicData.Tests.List; public class BatchFixture : IDisposable diff --git a/src/DynamicData.Tests/List/BatchIfFixture.cs b/src/DynamicData.Tests/List/BatchIfFixture.cs index ebbb28484..7847241b4 100644 --- a/src/DynamicData.Tests/List/BatchIfFixture.cs +++ b/src/DynamicData.Tests/List/BatchIfFixture.cs @@ -1,20 +1,10 @@ -using System; -using System.Reactive.Linq; -using System.Reactive.Subjects; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Microsoft.Reactive.Testing; - -using Xunit; - namespace DynamicData.Tests.List; public class BatchIfFixture : IDisposable { - private readonly ISubject _pausingSubject = new Subject(); + private readonly ISignal _pausingSubject = new Signal(); private readonly ChangeSetAggregator _results; @@ -24,7 +14,7 @@ public class BatchIfFixture : IDisposable public BatchIfFixture() { - _pausingSubject = new Subject(); + _pausingSubject = new Signal(); _scheduler = new TestScheduler(); _source = new SourceList(); _results = _source.Connect().BufferIf(_pausingSubject, _scheduler).AsAggregator(); @@ -98,6 +88,7 @@ public void Dispose() { _results.Dispose(); _source.Dispose(); + _pausingSubject.Dispose(); } [Fact] diff --git a/src/DynamicData.Tests/List/BatchIfWithTimeOutFixture.cs b/src/DynamicData.Tests/List/BatchIfWithTimeOutFixture.cs index f192a17ca..d47171388 100644 --- a/src/DynamicData.Tests/List/BatchIfWithTimeOutFixture.cs +++ b/src/DynamicData.Tests/List/BatchIfWithTimeOutFixture.cs @@ -1,19 +1,10 @@ -using System; -using System.Reactive.Subjects; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Microsoft.Reactive.Testing; - -using Xunit; - namespace DynamicData.Tests.List; public class BatchIfWithTimeOutFixture : IDisposable { - private readonly ISubject _pausingSubject = new Subject(); + private readonly ISignal _pausingSubject = new Signal(); private readonly ChangeSetAggregator _results; @@ -23,7 +14,7 @@ public class BatchIfWithTimeOutFixture : IDisposable public BatchIfWithTimeOutFixture() { - _pausingSubject = new Subject(); + _pausingSubject = new Signal(); _scheduler = new TestScheduler(); _source = new SourceList(); _results = _source.Connect().BufferIf(_pausingSubject, TimeSpan.FromMinutes(1), _scheduler).AsAggregator(); @@ -54,6 +45,7 @@ public void Dispose() _results.Dispose(); _source.Dispose(); _pausingSubject.OnCompleted(); + _pausingSubject?.Dispose(); } [Fact] diff --git a/src/DynamicData.Tests/List/BufferFixture.cs b/src/DynamicData.Tests/List/BufferFixture.cs index e02330f98..ce80cb19f 100644 --- a/src/DynamicData.Tests/List/BufferFixture.cs +++ b/src/DynamicData.Tests/List/BufferFixture.cs @@ -1,14 +1,5 @@ -using System; -using System.Reactive.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Microsoft.Reactive.Testing; - -using Xunit; - namespace DynamicData.Tests.List; public class BufferFixture : IDisposable diff --git a/src/DynamicData.Tests/List/BufferInitialFixture.cs b/src/DynamicData.Tests/List/BufferInitialFixture.cs index cd32c1b23..7fc18ec09 100644 --- a/src/DynamicData.Tests/List/BufferInitialFixture.cs +++ b/src/DynamicData.Tests/List/BufferInitialFixture.cs @@ -1,15 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Microsoft.Reactive.Testing; - -using Xunit; - namespace DynamicData.Tests.List; public class BufferInitialFixture diff --git a/src/DynamicData.Tests/List/CastFixture.cs b/src/DynamicData.Tests/List/CastFixture.cs index f6a5ac9f9..38b5d57e0 100644 --- a/src/DynamicData.Tests/List/CastFixture.cs +++ b/src/DynamicData.Tests/List/CastFixture.cs @@ -1,11 +1,4 @@ -using System; -using System.Linq; - -using FluentAssertions; - -using Xunit; - -namespace DynamicData.Tests.List; +namespace DynamicData.Tests.List; public class CastFixture : IDisposable { diff --git a/src/DynamicData.Tests/List/ChangeAwareListFixture.cs b/src/DynamicData.Tests/List/ChangeAwareListFixture.cs index c8b65086c..209e932b4 100644 --- a/src/DynamicData.Tests/List/ChangeAwareListFixture.cs +++ b/src/DynamicData.Tests/List/ChangeAwareListFixture.cs @@ -1,11 +1,4 @@ -using System; -using System.Linq; - -using DynamicData.Kernel; - -using FluentAssertions; - -using Xunit; +using DynamicData.Kernel; namespace DynamicData.Tests.List; diff --git a/src/DynamicData.Tests/List/ChangeSetFixture.cs b/src/DynamicData.Tests/List/ChangeSetFixture.cs index 28027926d..43246c806 100644 --- a/src/DynamicData.Tests/List/ChangeSetFixture.cs +++ b/src/DynamicData.Tests/List/ChangeSetFixture.cs @@ -1,19 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Management; -using System.Reflection; -using System.Threading.Channels; - -using Argon; - -using Bogus; - -using FluentAssertions; - -using Xunit; +using Bogus; using Xunit.Abstractions; -using Xunit.Sdk; namespace DynamicData.Tests.List; diff --git a/src/DynamicData.Tests/List/CloneChangesFixture.cs b/src/DynamicData.Tests/List/CloneChangesFixture.cs index b043cf78f..e30a9c600 100644 --- a/src/DynamicData.Tests/List/CloneChangesFixture.cs +++ b/src/DynamicData.Tests/List/CloneChangesFixture.cs @@ -1,13 +1,5 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; - using DynamicData.Kernel; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class CloneChangesFixture diff --git a/src/DynamicData.Tests/List/CloneFixture.cs b/src/DynamicData.Tests/List/CloneFixture.cs index 4e050e305..5126cd305 100644 --- a/src/DynamicData.Tests/List/CloneFixture.cs +++ b/src/DynamicData.Tests/List/CloneFixture.cs @@ -1,13 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; - -using DynamicData.Tests.Domain; - -using FluentAssertions; - -using Xunit; +using DynamicData.Tests.Domain; namespace DynamicData.Tests.List; diff --git a/src/DynamicData.Tests/List/CreationFixtures.cs b/src/DynamicData.Tests/List/CreationFixtures.cs index ae7ca0d0b..f44f9b651 100644 --- a/src/DynamicData.Tests/List/CreationFixtures.cs +++ b/src/DynamicData.Tests/List/CreationFixtures.cs @@ -1,11 +1,3 @@ -using System; -using System.Reactive.Linq; -using System.Threading.Tasks; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class ListCreationFixtures @@ -19,7 +11,7 @@ public void Create() ObservableChangeSet.Create( async list => { - var value = await CreateTask(10); + var value = await CreateTask(10); list.Add(value); return () => { }; })); diff --git a/src/DynamicData.Tests/List/DeadlockTortureTest.cs b/src/DynamicData.Tests/List/DeadlockTortureTest.cs new file mode 100644 index 000000000..cb2b8f85e --- /dev/null +++ b/src/DynamicData.Tests/List/DeadlockTortureTest.cs @@ -0,0 +1,133 @@ +using DynamicData.Binding; +using DynamicData.Tests; +using DynamicData.Tests.Domain; + +namespace DynamicData.Tests.List; + +/// +/// Deadlock torture test for list operators. Every operator that previously used +/// Synchronize(locker) (holding the lock during downstream delivery) is wired into a +/// bidirectional cross-list pipeline. Two threads write simultaneously, creating the ABBA +/// lock cycle: +/// Thread A: sourceA._locker -> operator lock -> PopulateInto -> sourceB._locker +/// Thread B: sourceB._locker -> operator lock -> PopulateInto -> sourceA._locker +/// +/// On origin/main (Synchronize(lock)): deadlocks reliably within seconds. +/// On the PR branch (SynchronizeSafe queue-drain): no deadlock possible. +/// +[Collection(IntegrationTestFixtureBase.CollectionName)] +public sealed class DeadlockTortureTest +{ + private const int ItemCount = 200; + private const int Iterations = 50; + private const int TimeoutSeconds = 15; + + private static async Task RunBidirectionalDeadlockTest( + Func>, IObservable>> pipeline, + int iterations = Iterations) + { + for (var iter = 0; iter < iterations; iter++) + { + using var sourceA = new SourceList(); + using var sourceB = new SourceList(); + + // Filter prefixes prevent the feedback loop: items from A flowing into B keep their + // "A" prefix, so the B-to-A filter rejects them. Likewise for the reverse direction. + using var aToB = pipeline(sourceA.Connect().Filter(p => p.Name.StartsWith("A"))).PopulateInto(sourceB); + using var bToA = pipeline(sourceB.Connect().Filter(p => p.Name.StartsWith("B"))).PopulateInto(sourceA); + + using var barrier = new Barrier(2); + var taskA = Task.Run(() => { barrier.SignalAndWait(); for (var i = 0; i < ItemCount; i++) sourceA.Add(new Person("A-" + iter + "-" + i, i)); }); + var taskB = Task.Run(() => { barrier.SignalAndWait(); for (var i = 0; i < ItemCount; i++) sourceB.Add(new Person("B-" + iter + "-" + i, i)); }); + + var completed = Task.WhenAll(taskA, taskB); + if (await Task.WhenAny(completed, Task.Delay(TimeSpan.FromSeconds(TimeoutSeconds))) != completed) + return false; + } + return true; + } + + [Fact] + public async Task Sort_DoesNotDeadlock() => + (await RunBidirectionalDeadlockTest(s => s.Sort(SortExpressionComparer.Ascending(p => p.Age)))).Should().BeTrue(); + + [Fact] + public async Task AutoRefresh_DoesNotDeadlock() => + (await RunBidirectionalDeadlockTest(s => s.AutoRefresh(p => p.Age))).Should().BeTrue(); + + [Fact] + public async Task Page_DoesNotDeadlock() + { + using var req = new StateSignal(new PageRequest(1, 50)); + (await RunBidirectionalDeadlockTest(s => s.Sort(SortExpressionComparer.Ascending(p => p.Age)).Page(req))).Should().BeTrue(); + } + + [Fact] + public async Task Virtualise_DoesNotDeadlock() + { + using var req = new StateSignal(new VirtualRequest(0, 50)); + (await RunBidirectionalDeadlockTest(s => s.Sort(SortExpressionComparer.Ascending(p => p.Age)).Virtualise(req))).Should().BeTrue(); + } + + [Fact] + public async Task FilterDynamic_DoesNotDeadlock() + { + using var pred = new StateSignal>(_ => true); + (await RunBidirectionalDeadlockTest(s => s.Filter(pred))).Should().BeTrue(); + } + + [Fact] + public async Task BufferIf_DoesNotDeadlock() => + (await RunBidirectionalDeadlockTest(s => s.BufferIf(new StateSignal(false), false, (TimeSpan?)null))).Should().BeTrue(); + + [Fact] + public async Task DisposeMany_DoesNotDeadlock() => + (await RunBidirectionalDeadlockTest(s => s.DisposeMany())).Should().BeTrue(); + + [Fact] + public async Task GroupOn_DoesNotDeadlock() => + (await RunBidirectionalDeadlockTest(s => s.GroupOn(p => p.Age % 3).MergeMany(g => g.List.Connect()))).Should().BeTrue(); + + [Fact] + public async Task TransformMany_DoesNotDeadlock() => + (await RunBidirectionalDeadlockTest(s => s.TransformMany(p => new[] { p }))).Should().BeTrue(); + + [Fact] + public async Task AllDangerous_Stacked_DoNotDeadlock() + { + using var pageReq = new StateSignal(new PageRequest(1, 100)); + using var pred = new StateSignal>(_ => true); + (await RunBidirectionalDeadlockTest( + s => s.AutoRefresh(p => p.Age) + .Filter(pred) + .DisposeMany() + .Sort(SortExpressionComparer.Ascending(p => p.Age)) + .Page(pageReq), + iterations: Iterations * 2)).Should().BeTrue(); + } + + [Fact] + public async Task ThreeWayCircular_DoesNotDeadlock() + { + for (var iter = 0; iter < Iterations; iter++) + { + using var a = new SourceList(); + using var b = new SourceList(); + using var c = new SourceList(); + + using var ab = a.Connect().Filter(p => p.Name.StartsWith("A")).Sort(SortExpressionComparer.Ascending(p => p.Age)).PopulateInto(b); + using var bc = b.Connect().Filter(p => p.Name.StartsWith("A")).AutoRefresh(p => p.Age).PopulateInto(c); + using var ca = c.Connect().Filter(p => p.Name.StartsWith("A")).Transform(p => new Person("C-" + p.Name, p.Age)).Filter(p => p.Name.StartsWith("C")).PopulateInto(a); + + using var barrier = new Barrier(3); + var tasks = new[] + { + Task.Run(() => { barrier.SignalAndWait(); for (var i = 0; i < ItemCount; i++) a.Add(new Person("A-" + iter + "-" + i, i)); }), + Task.Run(() => { barrier.SignalAndWait(); for (var i = 0; i < ItemCount; i++) b.Add(new Person("B-" + iter + "-" + i, i)); }), + Task.Run(() => { barrier.SignalAndWait(); for (var i = 0; i < ItemCount; i++) c.Add(new Person("CC-" + iter + "-" + i, i)); }), + }; + var completed = Task.WhenAll(tasks); + (await Task.WhenAny(completed, Task.Delay(TimeSpan.FromSeconds(TimeoutSeconds)))).Should().BeSameAs(completed, "iteration " + iter); + } + } +} diff --git a/src/DynamicData.Tests/List/DeferUntilLoadedFixture.cs b/src/DynamicData.Tests/List/DeferUntilLoadedFixture.cs index 1f8f725b3..d2e67588b 100644 --- a/src/DynamicData.Tests/List/DeferUntilLoadedFixture.cs +++ b/src/DynamicData.Tests/List/DeferUntilLoadedFixture.cs @@ -1,12 +1,5 @@ -using System; -using System.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class DeferAnsdSkipFixture diff --git a/src/DynamicData.Tests/List/DisposeManyFixture.cs b/src/DynamicData.Tests/List/DisposeManyFixture.cs index 2d06a1122..9fb6a064e 100644 --- a/src/DynamicData.Tests/List/DisposeManyFixture.cs +++ b/src/DynamicData.Tests/List/DisposeManyFixture.cs @@ -1,18 +1,8 @@ -using System; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public sealed class DisposeManyFixture : IDisposable { - private readonly Subject> _changeSetsSource; + private readonly Signal> _changeSetsSource; private readonly SourceList _itemsSource; diff --git a/src/DynamicData.Tests/List/DistinctValuesFixture.cs b/src/DynamicData.Tests/List/DistinctValuesFixture.cs index 1a7bcf214..cb039e1b7 100644 --- a/src/DynamicData.Tests/List/DistinctValuesFixture.cs +++ b/src/DynamicData.Tests/List/DistinctValuesFixture.cs @@ -1,12 +1,5 @@ -using System; -using System.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class DistinctValuesFixture : IDisposable diff --git a/src/DynamicData.Tests/List/DynamicAndFixture.cs b/src/DynamicData.Tests/List/DynamicAndFixture.cs index 7ae39e813..3d690a6eb 100644 --- a/src/DynamicData.Tests/List/DynamicAndFixture.cs +++ b/src/DynamicData.Tests/List/DynamicAndFixture.cs @@ -1,10 +1,3 @@ -using System; -using System.Linq; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class DynamicAndFixture : IDisposable diff --git a/src/DynamicData.Tests/List/DynamicExceptFixture.cs b/src/DynamicData.Tests/List/DynamicExceptFixture.cs index 3fbf868ef..efeb8f94e 100644 --- a/src/DynamicData.Tests/List/DynamicExceptFixture.cs +++ b/src/DynamicData.Tests/List/DynamicExceptFixture.cs @@ -1,10 +1,3 @@ -using System; -using System.Linq; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class DynamicExceptFixture : IDisposable diff --git a/src/DynamicData.Tests/List/DynamicOrFixture.cs b/src/DynamicData.Tests/List/DynamicOrFixture.cs index 415c37330..4099e7b3b 100644 --- a/src/DynamicData.Tests/List/DynamicOrFixture.cs +++ b/src/DynamicData.Tests/List/DynamicOrFixture.cs @@ -1,10 +1,3 @@ -using System; -using System.Linq; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class DynamicOrRefreshFixture diff --git a/src/DynamicData.Tests/List/DynamicXOrFixture.cs b/src/DynamicData.Tests/List/DynamicXOrFixture.cs index ef6326976..e407c6d6a 100644 --- a/src/DynamicData.Tests/List/DynamicXOrFixture.cs +++ b/src/DynamicData.Tests/List/DynamicXOrFixture.cs @@ -1,10 +1,3 @@ -using System; -using System.Linq; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class DynamicXOrFixture : IDisposable diff --git a/src/DynamicData.Tests/List/EditDiffFixture.cs b/src/DynamicData.Tests/List/EditDiffFixture.cs index 3a96e6195..0ad08df4d 100644 --- a/src/DynamicData.Tests/List/EditDiffFixture.cs +++ b/src/DynamicData.Tests/List/EditDiffFixture.cs @@ -1,11 +1,4 @@ -using System; -using System.Linq; - -using DynamicData.Tests.Domain; - -using FluentAssertions; - -using Xunit; +using DynamicData.Tests.Domain; namespace DynamicData.Tests.List; diff --git a/src/DynamicData.Tests/List/ExceptFixture.cs b/src/DynamicData.Tests/List/ExceptFixture.cs index d74eff07b..d12b303fd 100644 --- a/src/DynamicData.Tests/List/ExceptFixture.cs +++ b/src/DynamicData.Tests/List/ExceptFixture.cs @@ -1,11 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class ExceptFixture : ExceptFixtureBase diff --git a/src/DynamicData.Tests/List/ExpireAfterFixture.cs b/src/DynamicData.Tests/List/ExpireAfterFixture.cs index 9ad68eb16..8cf9a3bc5 100644 --- a/src/DynamicData.Tests/List/ExpireAfterFixture.cs +++ b/src/DynamicData.Tests/List/ExpireAfterFixture.cs @@ -1,20 +1,8 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Reactive.Concurrency; -using System.Reactive.Linq; -using System.Threading.Tasks; - -using Microsoft.Reactive.Testing; using Bogus; -using FluentAssertions; -using Xunit; using Xunit.Abstractions; -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.List; public sealed class ExpireAfterFixture @@ -195,7 +183,6 @@ public void PollingIntervalIsGiven_RemovalsAreScheduledAtInterval() // Not testing Refresh changes, since ISourceList doesn't actually provide an API to generate them. - // Verify initial state, after all emissions results.Error.Should().BeNull(); results.RecordedValues.Should().BeEmpty("no expirations should have occurred"); @@ -331,7 +318,6 @@ public void PollingIntervalIsNotGiven_RemovalsAreScheduledImmediately() source.Move(2, 3); scheduler.AdvanceBy(1); - // Verify initial state, after all emissions results.Error.Should().BeNull(); results.RecordedValues.Should().BeEmpty("no expirations should have occurred"); @@ -415,7 +401,6 @@ public void SchedulerIsInaccurate_RemovalsAreNotSkipped() var item1 = new TestItem() { Id = 1, Expiration = DateTimeOffset.FromUnixTimeMilliseconds(10) }; source.Add(item1); - results.Error.Should().BeNull(); results.RecordedValues.Should().BeEmpty("no expirations should have occurred"); source.Items.Should().BeEquivalentTo(new[] { item1 }, "1 item was added"); diff --git a/src/DynamicData.Tests/List/FilterControllerFixtureWithClearAndReplace.cs b/src/DynamicData.Tests/List/FilterControllerFixtureWithClearAndReplace.cs index 781165835..33034b4f2 100644 --- a/src/DynamicData.Tests/List/FilterControllerFixtureWithClearAndReplace.cs +++ b/src/DynamicData.Tests/List/FilterControllerFixtureWithClearAndReplace.cs @@ -1,18 +1,10 @@ -using System; -using System.Linq; -using System.Reactive.Subjects; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class FilterControllerFixtureWithClearAndReplace : IDisposable { - private readonly ISubject> _filter; + private readonly ISignal> _filter; private readonly ChangeSetAggregator _results; @@ -21,7 +13,7 @@ public class FilterControllerFixtureWithClearAndReplace : IDisposable public FilterControllerFixtureWithClearAndReplace() { _source = new SourceList(); - _filter = new BehaviorSubject>(p => p.Age > 20); + _filter = new StateSignal>(p => p.Age > 20); _results = _source.Connect().Filter(_filter, ListFilterPolicy.ClearAndReplace).AsAggregator(); } @@ -149,6 +141,7 @@ public void Dispose() { _source.Dispose(); _results.Dispose(); + _filter.Dispose(); } [Fact] @@ -249,7 +242,7 @@ public void UpdateNotMatched() [Fact] public void VeryLargeDataSet() { - var filter = new BehaviorSubject>(i => false); + var filter = new StateSignal>(i => false); var source = new SourceList(); var result = source.Connect().Filter(filter, ListFilterPolicy.ClearAndReplace).AsObservableList(); diff --git a/src/DynamicData.Tests/List/FilterControllerFixtureWithDiffSet.cs b/src/DynamicData.Tests/List/FilterControllerFixtureWithDiffSet.cs index fb3b6e855..49ed4a3d7 100644 --- a/src/DynamicData.Tests/List/FilterControllerFixtureWithDiffSet.cs +++ b/src/DynamicData.Tests/List/FilterControllerFixtureWithDiffSet.cs @@ -1,18 +1,10 @@ -using System; -using System.Linq; -using System.Reactive.Subjects; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class FilterControllerFixtureWithDiffSet : IDisposable { - private readonly ISubject> _filter; + private readonly ISignal> _filter; private readonly ChangeSetAggregator _results; @@ -21,7 +13,7 @@ public class FilterControllerFixtureWithDiffSet : IDisposable public FilterControllerFixtureWithDiffSet() { _source = new SourceList(); - _filter = new BehaviorSubject>(p => p.Age > 20); + _filter = new StateSignal>(p => p.Age > 20); _results = _source.Connect().Filter(_filter).AsAggregator(); } @@ -149,6 +141,7 @@ public void Dispose() { _source.Dispose(); _results.Dispose(); + _filter.Dispose(); } [Fact] diff --git a/src/DynamicData.Tests/List/FilterFixture.Base.cs b/src/DynamicData.Tests/List/FilterFixture.Base.cs index 3282e1fe8..0881f6cb9 100644 --- a/src/DynamicData.Tests/List/FilterFixture.Base.cs +++ b/src/DynamicData.Tests/List/FilterFixture.Base.cs @@ -1,11 +1,3 @@ -using System; -using System.Linq; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.List; public static partial class FilterFixture @@ -30,7 +22,6 @@ public void ExcludedItemsAreRemoved_NoChangesAreMade(EmptyChangesetPolicy emptyC new Item() { Id = 6, IsIncluded = false } }); - // UUT Initialization using var subscription = BuildUut( source: source.Connect(), @@ -47,7 +38,6 @@ public void ExcludedItemsAreRemoved_NoChangesAreMade(EmptyChangesetPolicy emptyC config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.RemoveMany(source.Items.Where(static item => !item.IsIncluded).ToArray()); @@ -74,7 +64,6 @@ public void ItemsAreAdded_MatchingItemsPropagate(EmptyChangesetPolicy emptyChang // Setup using var source = new TestSourceList(); - // UUT Intialization using var subscription = BuildUut( source: source.Connect(), @@ -89,7 +78,6 @@ public void ItemsAreAdded_MatchingItemsPropagate(EmptyChangesetPolicy emptyChang results.RecordedItems.Should().BeEmpty("no items have been added to the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.AddRange(new[] { @@ -127,7 +115,6 @@ public void ItemsAreMoved_MatchingMovementsPropagate(EmptyChangesetPolicy emptyC new Item() { Id = 6, IsIncluded = false } }); - // UUT Initialization using var subscription = BuildUut( source: source.Connect(), @@ -144,7 +131,6 @@ public void ItemsAreMoved_MatchingMovementsPropagate(EmptyChangesetPolicy emptyC config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action: Moves for matching items, source.Edit(items => { @@ -159,7 +145,6 @@ public void ItemsAreMoved_MatchingMovementsPropagate(EmptyChangesetPolicy emptyC config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action: Moves for excluded items source.Edit(items => { @@ -200,7 +185,6 @@ public void ItemsAreRefreshed_ItemsAreReFilteredOrRefreshed(EmptyChangesetPolicy new Item() { Id = 6, IsIncluded = false } }); - // UUT Initialization using var subscription = BuildUut( source: source.Connect(), @@ -215,7 +199,6 @@ public void ItemsAreRefreshed_ItemsAreReFilteredOrRefreshed(EmptyChangesetPolicy results.RecordedItems.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all matching items should have propagated"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (add items) foreach (var item in source.Items) item.IsIncluded = true; @@ -228,7 +211,6 @@ public void ItemsAreRefreshed_ItemsAreReFilteredOrRefreshed(EmptyChangesetPolicy results.RecordedItems.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all newly-matching items should have been added"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (remove items) foreach (var item in source.Items.Take(3)) item.IsIncluded = false; @@ -260,7 +242,6 @@ public void ItemsAreReplaced_ItemsAreReFiltered(EmptyChangesetPolicy emptyChange new Item() { Id = 6, IsIncluded = false } }); - // UUT Intialization using var subscription = BuildUut( source: source.Connect(), @@ -277,7 +258,6 @@ public void ItemsAreReplaced_ItemsAreReFiltered(EmptyChangesetPolicy emptyChange config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (add and replace items) source.Edit(items => { @@ -296,7 +276,6 @@ public void ItemsAreReplaced_ItemsAreReFiltered(EmptyChangesetPolicy emptyChange config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (remove and replace items) source.Edit(items => { @@ -334,7 +313,6 @@ public void MatchingItemsAreRemoved_RemovalsPropagate(EmptyChangesetPolicy empty new Item() { Id = 6, IsIncluded = false } }); - // UUT Initialization using var subscription = BuildUut( source: source.Connect(), @@ -351,7 +329,6 @@ public void MatchingItemsAreRemoved_RemovalsPropagate(EmptyChangesetPolicy empty config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.RemoveMany(source.Items.Where(Item.FilterByIsIncluded).ToArray()); diff --git a/src/DynamicData.Tests/List/FilterFixture.Static.cs b/src/DynamicData.Tests/List/FilterFixture.Static.cs index 927deae5d..47f13f793 100644 --- a/src/DynamicData.Tests/List/FilterFixture.Static.cs +++ b/src/DynamicData.Tests/List/FilterFixture.Static.cs @@ -1,13 +1,3 @@ -using System; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.List; public static partial class FilterFixture @@ -30,7 +20,6 @@ public void DuplicateItemsAreAdded_ItemsAreTrackedSeparately() 2 }); - // UUT Initialization using var subscription = source.Connect() .Filter(static item => (item % 2) is 0) @@ -45,7 +34,6 @@ public void DuplicateItemsAreAdded_ItemsAreTrackedSeparately() config: options => options.WithoutStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.Remove(2); @@ -56,7 +44,6 @@ public void DuplicateItemsAreAdded_ItemsAreTrackedSeparately() config: options => options.WithoutStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.Remove(3); @@ -64,7 +51,6 @@ public void DuplicateItemsAreAdded_ItemsAreTrackedSeparately() results.RecordedChangeSets.Skip(2).Should().BeEmpty("an operation was performed upon an excluded item"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.Remove(2); @@ -82,7 +68,6 @@ public void ExcludedItemIsAdded_NoChangesAreMade() // Setup using var source = new TestSourceList(); - // UUT Initialization using var subscription = source.Connect() .Filter(Item.FilterByIsIncluded) @@ -95,7 +80,6 @@ public void ExcludedItemIsAdded_NoChangesAreMade() results.RecordedItems.Should().BeEmpty("no items have been added to the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.Add(new Item() { Id = 1, IsIncluded = false }); @@ -120,7 +104,6 @@ public void ExcludedItemIsRemoved_NoChangesAreMade() new Item() { Id = 6, IsIncluded = false } }); - // UUT Initialization using var subscription = source.Connect() .Filter(Item.FilterByIsIncluded) @@ -135,7 +118,6 @@ public void ExcludedItemIsRemoved_NoChangesAreMade() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.RemoveAt(5); @@ -160,7 +142,6 @@ public void ExcludedItemsAreRemoved_NoChangesAreMade() new Item() { Id = 6, IsIncluded = false } }); - // UUT Initialization using var subscription = source.Connect() .Filter(Item.FilterByIsIncluded) @@ -175,7 +156,6 @@ public void ExcludedItemsAreRemoved_NoChangesAreMade() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.RemoveMany(source.Items.Where(static item => !item.IsIncluded).ToArray()); @@ -190,7 +170,6 @@ public void ItemsAreAdded_MatchingItemsPropagate() // Setup using var source = new TestSourceList(); - // UUT Initialization using var subscription = source.Connect() .Filter(Item.FilterByIsIncluded) @@ -203,7 +182,6 @@ public void ItemsAreAdded_MatchingItemsPropagate() results.RecordedItems.Should().BeEmpty("no items have been added to the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.AddRange(new[] { @@ -239,7 +217,6 @@ public void ItemsAreMoved_MatchingMovementsPropagate() new Item() { Id = 6, IsIncluded = false } }); - // UUT Initialization using var subscription = source.Connect() .Filter(Item.FilterByIsIncluded) @@ -254,7 +231,6 @@ public void ItemsAreMoved_MatchingMovementsPropagate() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action: Moves for matching items, source.Edit(items => { @@ -269,7 +245,6 @@ public void ItemsAreMoved_MatchingMovementsPropagate() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action: Moves for excluded items source.Edit(items => { @@ -298,7 +273,6 @@ public void ItemsAreRefreshed_ItemsAreReFilteredOrRefreshed() new Item() { Id = 6, IsIncluded = false } }); - // UUT Initialization using var subscription = source.Connect() .Filter(Item.FilterByIsIncluded) @@ -311,7 +285,6 @@ public void ItemsAreRefreshed_ItemsAreReFilteredOrRefreshed() results.RecordedItems.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all matching items should have propagated"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (add items) foreach (var item in source.Items) item.IsIncluded = true; @@ -324,7 +297,6 @@ public void ItemsAreRefreshed_ItemsAreReFilteredOrRefreshed() results.RecordedItems.Should().BeEquivalentTo(source.Items.Where(Item.FilterByIsIncluded), "all newly-matching items should have been added"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (remove items) foreach (var item in source.Items.Take(3)) item.IsIncluded = false; @@ -354,7 +326,6 @@ public void ItemsAreReplaced_ItemsAreReFiltered() new Item() { Id = 6, IsIncluded = false } }); - // UUT Initialization using var subscription = source.Connect() .Filter(Item.FilterByIsIncluded) @@ -369,7 +340,6 @@ public void ItemsAreReplaced_ItemsAreReFiltered() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (add and replace items) source.Edit(items => { @@ -388,7 +358,6 @@ public void ItemsAreReplaced_ItemsAreReFiltered() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action (remove and replace items) source.Edit(items => { @@ -414,7 +383,6 @@ public void MatchingItemIsAdded_ItemPropagates() // Setup using var source = new TestSourceList(); - // UUT Initialization using var subscription = source.Connect() .Filter(Item.FilterByIsIncluded) @@ -427,7 +395,6 @@ public void MatchingItemIsAdded_ItemPropagates() results.RecordedItems.Should().BeEmpty("no items have been added to the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.Add(new Item() { Id = 1, IsIncluded = true }); @@ -453,7 +420,6 @@ public void MatchingItemIsRemoved_RemovalPropagates() new Item() { Id = 6, IsIncluded = false } }); - // UUT Initialization using var subscription = source.Connect() .Filter(Item.FilterByIsIncluded) @@ -468,7 +434,6 @@ public void MatchingItemIsRemoved_RemovalPropagates() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var removedItem = source.Items[2]; source.RemoveAt(2); @@ -497,7 +462,6 @@ public void MatchingItemsAreRemoved_RemovalsPropagate() new Item() { Id = 6, IsIncluded = false } }); - // UUT Initialization using var subscription = source.Connect() .Filter(Item.FilterByIsIncluded) @@ -512,7 +476,6 @@ public void MatchingItemsAreRemoved_RemovalsPropagate() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action source.RemoveMany(source.Items.Where(Item.FilterByIsIncluded).ToArray()); @@ -546,7 +509,6 @@ public void SourceCompletes_CompletionPropagates(SourceType sourceType) if (sourceType is SourceType.Immediate) source.Complete(); - // UUT Initialization & Action using var subscription = source.Connect() .Filter(Item.FilterByIsIncluded) @@ -613,8 +575,7 @@ public void SourceIsNull_ThrowsException() public void SubscriptionIsDisposed_SubscriptionDisposalPropagates() { // Setup - using var source = new Subject>(); - + using var source = new Signal>(); // UUT Initialization using var subscription = source @@ -628,7 +589,6 @@ public void SubscriptionIsDisposed_SubscriptionDisposalPropagates() results.RecordedItems.Should().BeEmpty("the source has not initialized"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action subscription.Dispose(); diff --git a/src/DynamicData.Tests/List/FilterFixture.WithPredicateState.cs b/src/DynamicData.Tests/List/FilterFixture.WithPredicateState.cs index d3f28efd3..ea8877618 100644 --- a/src/DynamicData.Tests/List/FilterFixture.WithPredicateState.cs +++ b/src/DynamicData.Tests/List/FilterFixture.WithPredicateState.cs @@ -1,16 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Threading; -using System.Threading.Tasks; - using Bogus; -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; using Xunit.Abstractions; namespace DynamicData.Tests.List; @@ -30,7 +18,7 @@ public WithPredicateState(ITestOutputHelper output) public void ChangesAreMadeAfterInitialPredicateState_ItemsAreFiltered(ListFilterPolicy filterPolicy) { using var source = new TestSourceList(); - using var predicateState = new Subject(); + using var predicateState = new Signal(); using var subscription = source .Connect() @@ -42,13 +30,11 @@ public void ChangesAreMadeAfterInitialPredicateState_ItemsAreFiltered(ListFilter .ValidateChangeSets() .RecordListItems(out var results); - // Set initial state predicateState.OnNext(new()); results.RecordedChangeSets.Should().BeEmpty("no source operations have been performed"); - // Test Add, with an included item var item1 = new Item() { Id = 1, IsIncluded = true }; source.Add(item1); @@ -56,14 +42,12 @@ public void ChangesAreMadeAfterInitialPredicateState_ItemsAreFiltered(ListFilter results.RecordedChangeSets.Count.Should().Be(1, "one source operation was performed, with one included item added"); ShouldBeValid(results, EnumerateFilteredItems()); - // Test Add, with an excluded item var item2 = new Item() { Id = 2, IsIncluded = false }; source.Add(item2); results.RecordedChangeSets.Skip(1).Should().BeEmpty("one source operation was performed, but no included items were affected"); - // Test AddRange, with both included and excluded items var item3 = new Item() { Id = 3, IsIncluded = false }; var item4 = new Item() { Id = 4, IsIncluded = true }; @@ -76,7 +60,6 @@ public void ChangesAreMadeAfterInitialPredicateState_ItemsAreFiltered(ListFilter results.RecordedChangeSets.Skip(1).Count().Should().Be(1, "one source operation was performed, with 3 included items added"); ShouldBeValid(results, EnumerateFilteredItems()); - // Test Refresh, with no item mutations. source.Refresh(Enumerable.Range(0, source.Count)); @@ -85,7 +68,6 @@ public void ChangesAreMadeAfterInitialPredicateState_ItemsAreFiltered(ListFilter results.RecordedChangeSets.Skip(2).First().Select(static change => change.Item.Current).Should().BeEquivalentTo(EnumerateFilteredItems(), "all included items should have been refreshed"); ShouldBeValid(results, EnumerateFilteredItems()); - // Test Refresh, with item mutations affecting filtering. item1.IsIncluded = !item1.IsIncluded; item3.IsIncluded = !item3.IsIncluded; @@ -96,27 +78,23 @@ public void ChangesAreMadeAfterInitialPredicateState_ItemsAreFiltered(ListFilter results.RecordedChangeSets.Skip(3).Count().Should().Be(1, "one source operation was performed, with items being included and excluded"); ShouldBeValid(results, EnumerateFilteredItems()); - // Test Remove, with an included item source.RemoveAt(3); results.RecordedChangeSets.Skip(4).Count().Should().Be(1, "one source operation was performed, with one included item affected"); ShouldBeValid(results, EnumerateFilteredItems()); - // Test Remove, with an excluded item source.RemoveAt(3); results.RecordedChangeSets.Skip(5).Should().BeEmpty("one source operation was performed, but no included items were affected"); - // Test Remove, with both included and excluded items source.RemoveRange(index: 2, count: 2); results.RecordedChangeSets.Skip(5).Count().Should().Be(1, "one source operation was performed, with one included item affected"); ShouldBeValid(results, EnumerateFilteredItems()); - // Test Replace, not affecting filtering var item9 = new Item() { Id = 9, IsIncluded = false }; var item10 = new Item() { Id = 10, IsIncluded = true }; @@ -129,7 +107,6 @@ public void ChangesAreMadeAfterInitialPredicateState_ItemsAreFiltered(ListFilter results.RecordedChangeSets.Skip(6).Count().Should().Be(1, "one source operation was performed, with one included item affected"); ShouldBeValid(results, EnumerateFilteredItems()); - // Test Replace, affecting filtering var item11 = new Item() { Id = 11, IsIncluded = true }; var item12 = new Item() { Id = 12, IsIncluded = false }; @@ -142,7 +119,6 @@ public void ChangesAreMadeAfterInitialPredicateState_ItemsAreFiltered(ListFilter results.RecordedChangeSets.Skip(7).Count().Should().Be(1, "one source operation was performed, with one included item affected"); ShouldBeValid(results, EnumerateFilteredItems()); - // Test Move of an included item, relative to another included item var item13 = new Item() { Id = 13, IsIncluded = true }; source.Add(item13); @@ -160,7 +136,6 @@ public void ChangesAreMadeAfterInitialPredicateState_ItemsAreFiltered(ListFilter } ShouldBeValid(results, EnumerateFilteredItems()); - // Test Move of an excluded item source.Move(4, 2); @@ -175,7 +150,6 @@ public void ChangesAreMadeAfterInitialPredicateState_ItemsAreFiltered(ListFilter break; } - // Test Clear, with included items source.Clear(); @@ -191,7 +165,6 @@ public void ChangesAreMadeAfterInitialPredicateState_ItemsAreFiltered(ListFilter } ShouldBeValid(results, EnumerateFilteredItems()); - // Test Clear, with only excluded items source.Add(new Item() { Id = 14, IsIncluded = false }); source.Clear(); @@ -208,7 +181,6 @@ public void ChangesAreMadeAfterInitialPredicateState_ItemsAreFiltered(ListFilter } ShouldBeValid(results, EnumerateFilteredItems()); - IEnumerable EnumerateFilteredItems() => source.Items.Where(static item => item.IsIncluded); } @@ -219,7 +191,7 @@ IEnumerable EnumerateFilteredItems() public void ChangesAreMadeAfterMultiplePredicateStateChanges_ItemsAreFilteredWithLatestPredicateState(ListFilterPolicy filterPolicy) { using var source = new SourceList(); - using var predicateState = new BehaviorSubject(1); + using var predicateState = new StateSignal(1); using var subscription = source .Connect() @@ -231,14 +203,12 @@ public void ChangesAreMadeAfterMultiplePredicateStateChanges_ItemsAreFilteredWit .ValidateChangeSets() .RecordListItems(out var results); - // Publish multiple state changes predicateState.OnNext(2); predicateState.OnNext(3); results.RecordedChangeSets.Should().BeEmpty("no source operations have been performed"); - // Test filtering of items, by state source.AddRange(new[] { @@ -258,7 +228,7 @@ public void ChangesAreMadeAfterMultiplePredicateStateChanges_ItemsAreFilteredWit public void ChangesAreMadeBeforeInitialPredicateState_ItemsAreFilteredOnPredicateState(ListFilterPolicy filterPolicy) { using var source = new TestSourceList(); - using var predicateState = new Subject(); + using var predicateState = new Signal(); using var subscription = source .Connect() @@ -270,10 +240,8 @@ public void ChangesAreMadeBeforeInitialPredicateState_ItemsAreFilteredOnPredicat .ValidateChangeSets() .RecordListItems(out var results); - results.RecordedChangeSets.Should().BeEmpty("no source operations have been performed"); - // Test Add, with an included item var item1 = new Item() { Id = 1, IsIncluded = true }; source.Add(item1); @@ -338,7 +306,6 @@ public void ChangesAreMadeBeforeInitialPredicateState_ItemsAreFilteredOnPredicat results.RecordedChangeSets.Should().BeEmpty("the predicate state has not initialized"); - // Set initial state predicateState.OnNext(new()); @@ -350,7 +317,7 @@ public void ChangesAreMadeBeforeInitialPredicateState_ItemsAreFilteredOnPredicat public void FilterPolicyIsClearAndReplace_ReFilteringPreservesOrder() { using var source = new SourceList(); - using var predicateState = new BehaviorSubject(1); + using var predicateState = new StateSignal(1); using var subscription = source .Connect() @@ -362,7 +329,6 @@ public void FilterPolicyIsClearAndReplace_ReFilteringPreservesOrder() .ValidateChangeSets() .RecordListItems(out var results); - // Test filtering of items, by state source.AddRange(new[] { @@ -404,7 +370,7 @@ public void PredicateIsNull_ExceptionIsThrown() public void PredicateStateChanges_ItemsAreReFiltered(ListFilterPolicy filterPolicy) { using var source = new SourceList(); - using var predicateState = new BehaviorSubject(1); + using var predicateState = new StateSignal(1); using var subscription = source .Connect() @@ -416,7 +382,6 @@ public void PredicateStateChanges_ItemsAreReFiltered(ListFilterPolicy filterPoli .ValidateChangeSets() .RecordListItems(out var results); - // Test filtering of items, by state source.AddRange(new[] { @@ -429,14 +394,12 @@ public void PredicateStateChanges_ItemsAreReFiltered(ListFilterPolicy filterPoli results.RecordedChangeSets.Count.Should().Be(1, "one source operation was performed"); ShouldBeValid(results, EnumerateFilteredItems()); - // Publish a state change, to change the filtering predicateState.OnNext(2); results.RecordedChangeSets.Skip(1).Count().Should().Be(1, "one source operation was performed"); ShouldBeValid(results, EnumerateFilteredItems()); - IEnumerable EnumerateFilteredItems() => source.Items.Where(item => item.Id == predicateState.Value); } @@ -446,7 +409,7 @@ IEnumerable EnumerateFilteredItems() [InlineData(ListFilterPolicy.ClearAndReplace)] public void PredicateStateCompletesAfterInitialValue_CompletionWaitsForSourceCompletion(ListFilterPolicy filterPolicy) { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .Filter( @@ -471,7 +434,7 @@ public void PredicateStateCompletesAfterInitialValue_CompletionWaitsForSourceCom [InlineData(ListFilterPolicy.ClearAndReplace)] public void PredicateStateCompletesImmediately_CompletionIsPropagated(ListFilterPolicy filterPolicy) { - using var source = new Subject>(); + using var source = new Signal>(); using var subscription = source .Filter( @@ -493,8 +456,8 @@ public void PredicateStateCompletesImmediately_CompletionIsPropagated(ListFilter [InlineData(ListFilterPolicy.ClearAndReplace)] public void PredicateStateErrors_ErrorIsPropagated(ListFilterPolicy filterPolicy) { - using var source = new Subject>(); - using var predicateState = new Subject(); + using var source = new Signal>(); + using var predicateState = new Signal(); using var subscription = source .Filter( @@ -505,7 +468,6 @@ public void PredicateStateErrors_ErrorIsPropagated(ListFilterPolicy filterPolicy .ValidateChangeSets() .RecordListItems(out var results); - var error = new Exception("This is a test."); predicateState.OnError(error); @@ -521,7 +483,7 @@ public void PredicateStateErrors_ErrorIsPropagated(ListFilterPolicy filterPolicy [InlineData(ListFilterPolicy.ClearAndReplace)] public void PredicateStateErrorsImmediately_ErrorIsPropagated(ListFilterPolicy filterPolicy) { - using var source = new Subject>(); + using var source = new Signal>(); var error = new Exception("This is a test."); @@ -566,10 +528,9 @@ public async Task SourceAndPredicateStateNotifyFromDifferentThreads_FilteringIsT valueCount: 5_000, randomizer: randomizer); + using var source = new Signal>(); - using var source = new Subject>(); - - using var predicateState = new Subject(); + using var predicateState = new Signal(); using var subscription = source .Filter( @@ -620,9 +581,9 @@ await Task.WhenAll( [InlineData(ListFilterPolicy.ClearAndReplace)] public void SourceCompletesWhenEmpty_CompletionIsPropagated(ListFilterPolicy filterPolicy) { - using var source = new Subject>(); + using var source = new Signal>(); - using var predicateState = new Subject(); + using var predicateState = new Signal(); using var subscription = source .Filter( @@ -646,9 +607,9 @@ public void SourceCompletesWhenEmpty_CompletionIsPropagated(ListFilterPolicy fil [InlineData(ListFilterPolicy.ClearAndReplace)] public void SourceCompletesWhenNotEmpty_CompletionWaitsForStateCompletion(ListFilterPolicy filterPolicy) { - using var source = new Subject>(); + using var source = new Signal>(); - using var predicateState = new Subject(); + using var predicateState = new Signal(); using var subscription = source .Filter( @@ -676,7 +637,7 @@ public void SourceCompletesWhenNotEmpty_CompletionWaitsForStateCompletion(ListFi [InlineData(ListFilterPolicy.ClearAndReplace)] public void SourceCompletesImmediately_CompletionIsPropagated(ListFilterPolicy filterPolicy) { - using var predicateState = new Subject(); + using var predicateState = new Signal(); using var subscription = Observable.Empty>() .Filter( @@ -698,9 +659,9 @@ public void SourceCompletesImmediately_CompletionIsPropagated(ListFilterPolicy f [InlineData(ListFilterPolicy.ClearAndReplace)] public void SourceErrors_ErrorIsPropagated(ListFilterPolicy filterPolicy) { - using var source = new Subject>(); + using var source = new Signal>(); - using var predicateState = new Subject(); + using var predicateState = new Signal(); using var subscription = source .Filter( @@ -711,7 +672,6 @@ public void SourceErrors_ErrorIsPropagated(ListFilterPolicy filterPolicy) .ValidateChangeSets() .RecordListItems(out var results); - var error = new Exception("This is a test."); source.OnError(error); @@ -727,7 +687,7 @@ public void SourceErrors_ErrorIsPropagated(ListFilterPolicy filterPolicy) [InlineData(ListFilterPolicy.ClearAndReplace)] public void SourceErrorsImmediately_ErrorIsPropagated(ListFilterPolicy filterPolicy) { - using var predicateState = new Subject(); + using var predicateState = new Signal(); var error = new Exception("This is a test."); @@ -760,9 +720,9 @@ public void SourceIsNull_ExceptionIsThrown() [InlineData(ListFilterPolicy.ClearAndReplace)] public void SubscriptionIsDisposed_UnsubscriptionIsPropagated(ListFilterPolicy filterPolicy) { - using var source = new Subject>(); + using var source = new Signal>(); - using var predicateState = new Subject(); + using var predicateState = new Signal(); using var subscription = source .Filter( @@ -773,7 +733,6 @@ public void SubscriptionIsDisposed_UnsubscriptionIsPropagated(ListFilterPolicy f .ValidateChangeSets() .RecordListItems(out var results); - subscription.Dispose(); source.HasObservers.Should().BeFalse("subscription disposal should be propagated to all input streams"); @@ -787,9 +746,9 @@ public void SubscriptionIsDisposed_UnsubscriptionIsPropagated(ListFilterPolicy f [InlineData("predicateState", "source")] public void SuppressEmptyChangeSetsIsFalse_EmptyChangesetsArePropagatedAndOnlyFinalCompletionIsPropagated(params string[] completionOrder) { - using var source = new Subject>(); + using var source = new Signal>(); - using var predicateState = new Subject(); + using var predicateState = new Signal(); using var subscription = source .Filter( @@ -800,7 +759,6 @@ public void SuppressEmptyChangeSetsIsFalse_EmptyChangesetsArePropagatedAndOnlyFi .ValidateChangeSets() .RecordListItems(out var results); - // Initialize the predicate predicateState.OnNext(new object()); @@ -808,7 +766,6 @@ public void SuppressEmptyChangeSetsIsFalse_EmptyChangesetsArePropagatedAndOnlyFi results.RecordedChangeSets[0].Should().BeEmpty("there are no items in the collection"); ShouldBeValid(results, Enumerable.Empty()); - // Publish an empty changeset source.OnNext(ChangeSet.Empty); @@ -816,7 +773,6 @@ public void SuppressEmptyChangeSetsIsFalse_EmptyChangesetsArePropagatedAndOnlyFi results.RecordedChangeSets.Skip(1).First().Should().BeEmpty("the source changeset was empty"); ShouldBeValid(results, Enumerable.Empty()); - // Publish a changeset with only excluded items source.OnNext(new ChangeSet() { diff --git a/src/DynamicData.Tests/List/FilterOnObservableFixture.cs b/src/DynamicData.Tests/List/FilterOnObservableFixture.cs index 8cb3e152b..1f6edf46d 100644 --- a/src/DynamicData.Tests/List/FilterOnObservableFixture.cs +++ b/src/DynamicData.Tests/List/FilterOnObservableFixture.cs @@ -1,13 +1,5 @@ -using System; -using System.Linq; -using System.Reactive.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; //TODO: To optimise this, we need to introduce replace range, or specify a buffer diff --git a/src/DynamicData.Tests/List/FilterOnPropertyFixture.cs b/src/DynamicData.Tests/List/FilterOnPropertyFixture.cs index 9d9dbfe03..010f5f9af 100644 --- a/src/DynamicData.Tests/List/FilterOnPropertyFixture.cs +++ b/src/DynamicData.Tests/List/FilterOnPropertyFixture.cs @@ -1,12 +1,5 @@ -using System; -using System.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class FilterOnPropertyFixture diff --git a/src/DynamicData.Tests/List/FilterWithObservable.cs b/src/DynamicData.Tests/List/FilterWithObservable.cs index 9a71359c5..061151d04 100644 --- a/src/DynamicData.Tests/List/FilterWithObservable.cs +++ b/src/DynamicData.Tests/List/FilterWithObservable.cs @@ -1,20 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Subjects; - using DynamicData.Aggregation; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class FilterWithObservable : IDisposable { - private readonly BehaviorSubject> _filter; + private readonly StateSignal> _filter; private readonly ChangeSetAggregator _results; @@ -23,7 +14,7 @@ public class FilterWithObservable : IDisposable public FilterWithObservable() { _source = new SourceList(); - _filter = new BehaviorSubject>(p => p.Age > 20); + _filter = new StateSignal>(p => p.Age > 20); _results = _source.Connect().Filter(_filter).AsAggregator(); } @@ -122,7 +113,7 @@ public void BatchSuccessiveUpdates() [Fact] public void ChainFilters() { - var filter2 = new BehaviorSubject>(person1 => person1.Age > 20); + var filter2 = new StateSignal>(person1 => person1.Age > 20); var stream = _source.Connect().Filter(_filter).Filter(filter2); diff --git a/src/DynamicData.Tests/List/ForEachChangeFixture.cs b/src/DynamicData.Tests/List/ForEachChangeFixture.cs index 589397d31..891143069 100644 --- a/src/DynamicData.Tests/List/ForEachChangeFixture.cs +++ b/src/DynamicData.Tests/List/ForEachChangeFixture.cs @@ -1,13 +1,6 @@ -using System; -using System.Collections.Generic; - using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class ForEachChangeFixture : IDisposable diff --git a/src/DynamicData.Tests/List/FromAsyncFixture.cs b/src/DynamicData.Tests/List/FromAsyncFixture.cs index 2f1913c48..8b0d74893 100644 --- a/src/DynamicData.Tests/List/FromAsyncFixture.cs +++ b/src/DynamicData.Tests/List/FromAsyncFixture.cs @@ -1,17 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; -using System.Threading.Tasks; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Microsoft.Reactive.Testing; - -using Xunit; - namespace DynamicData.Tests.List; public class FromAsyncFixture diff --git a/src/DynamicData.Tests/List/GroupImmutableFixture.cs b/src/DynamicData.Tests/List/GroupImmutableFixture.cs index cbf674654..19047ab37 100644 --- a/src/DynamicData.Tests/List/GroupImmutableFixture.cs +++ b/src/DynamicData.Tests/List/GroupImmutableFixture.cs @@ -1,20 +1,11 @@ -using System; -using System.Linq; -using System.Reactive; -using System.Reactive.Subjects; - using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class GroupImmutableFixture : IDisposable { - private readonly ISubject _regrouper; + private readonly ISignal _regrouper; private readonly ChangeSetAggregator> _results; @@ -23,7 +14,7 @@ public class GroupImmutableFixture : IDisposable public GroupImmutableFixture() { _source = new SourceList(); - _regrouper = new Subject(); + _regrouper = new Signal(); _results = _source.Connect().GroupWithImmutableState(p => p.Age, _regrouper).AsAggregator(); } @@ -67,6 +58,7 @@ public void Dispose() { _source.Dispose(); _results.Dispose(); + _regrouper.Dispose(); } [Fact] diff --git a/src/DynamicData.Tests/List/GroupOnFixture.cs b/src/DynamicData.Tests/List/GroupOnFixture.cs index 61164d3c3..abcaee0cb 100644 --- a/src/DynamicData.Tests/List/GroupOnFixture.cs +++ b/src/DynamicData.Tests/List/GroupOnFixture.cs @@ -1,12 +1,5 @@ -using System; -using System.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class GroupOnFixture : IDisposable diff --git a/src/DynamicData.Tests/List/GroupOnPropertyFixture.cs b/src/DynamicData.Tests/List/GroupOnPropertyFixture.cs index aa9442888..fa6e18d86 100644 --- a/src/DynamicData.Tests/List/GroupOnPropertyFixture.cs +++ b/src/DynamicData.Tests/List/GroupOnPropertyFixture.cs @@ -1,13 +1,6 @@ -using System; -using System.Linq; - -using DynamicData.Kernel; +using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class GroupOnPropertyFixture : IDisposable diff --git a/src/DynamicData.Tests/List/GroupOnPropertyWithImmutableStateFixture.cs b/src/DynamicData.Tests/List/GroupOnPropertyWithImmutableStateFixture.cs index 0f55473d4..c2cd4a72c 100644 --- a/src/DynamicData.Tests/List/GroupOnPropertyWithImmutableStateFixture.cs +++ b/src/DynamicData.Tests/List/GroupOnPropertyWithImmutableStateFixture.cs @@ -1,13 +1,6 @@ -using System; -using System.Linq; - -using DynamicData.Kernel; +using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class GroupOnPropertyWithImmutableStateFixture : IDisposable diff --git a/src/DynamicData.Tests/List/MergeChangeSetsFixture.cs b/src/DynamicData.Tests/List/MergeChangeSetsFixture.cs index 253a70070..62191c640 100644 --- a/src/DynamicData.Tests/List/MergeChangeSetsFixture.cs +++ b/src/DynamicData.Tests/List/MergeChangeSetsFixture.cs @@ -1,17 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Threading.Tasks; using Bogus; using DynamicData.Kernel; using DynamicData.Tests.Domain; -using DynamicData.Tests.Utilities; -using FluentAssertions; -using Microsoft.Reactive.Testing; -using Xunit; using System.Collections.Concurrent; namespace DynamicData.Tests.List; @@ -273,7 +262,6 @@ public async Task ResultContainsChildrenAddedWithInsertEnum() CheckResultContents(_animalOwners, results); } - [Fact] public async Task ResultContainsCorrectItemsAfterChildReplacementObs() { diff --git a/src/DynamicData.Tests/List/MergeManyChangeSetsCacheFixture.cs b/src/DynamicData.Tests/List/MergeManyChangeSetsCacheFixture.cs index bdac643d9..14adff0ac 100644 --- a/src/DynamicData.Tests/List/MergeManyChangeSetsCacheFixture.cs +++ b/src/DynamicData.Tests/List/MergeManyChangeSetsCacheFixture.cs @@ -1,18 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive; -using System.Reactive.Linq; -using System.Threading.Tasks; using Bogus; using DynamicData.Kernel; using DynamicData.Tests.Domain; -using DynamicData.Tests.Utilities; -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.List; @@ -48,7 +36,6 @@ public MergeManyChangeSetsCacheFixture() _marketListResults = _marketList.Connect().AsAggregator(); } - [Theory] [InlineData(5, 7)] [InlineData(10, 50)] @@ -120,6 +107,7 @@ IObservable AddRemovePrices(Market market, int priceCount, int para while (adding); // Verify the results + await WaitForResultContentsAsync(_marketListResults, priceResults, TimeSpan.FromSeconds(5)); CheckResultContents(_marketListResults, priceResults); } @@ -838,11 +826,33 @@ private void CheckResultContents(ChangeSetAggregator marketResults, Cha priceResults.Data.Items.Count.Should().Be(expectedPrices.Count); } + private async Task WaitForResultContentsAsync(ChangeSetAggregator marketResults, ChangeSetAggregator priceResults, TimeSpan timeout) + { + var deadline = DateTimeOffset.UtcNow + timeout; + while (!ResultContentsMatch(marketResults, priceResults) && DateTimeOffset.UtcNow < deadline) + { + await Task.Delay(TimeSpan.FromMilliseconds(10)); + } + } + + private bool ResultContentsMatch(ChangeSetAggregator marketResults, ChangeSetAggregator priceResults) + { + var expectedMarkets = _marketList.Items.ToList(); + var expectedPrices = expectedMarkets.SelectMany(market => ((Market)market).PricesCache.Items).ToList(); + var actualMarkets = marketResults.Data.Items; + var actualPrices = priceResults.Data.Items; + + return actualMarkets.Count == expectedMarkets.Count && + actualPrices.Count == expectedPrices.Count && + expectedMarkets.All(actualMarkets.Contains) && + expectedPrices.All(actualPrices.Contains); + } + private void DisposeMarkets() { - _marketList.Items.ForEach(m => (m as IDisposable)?.Dispose()); + var markets = _marketList.Items.ToArray(); + markets.ForEach(m => (m as IDisposable)?.Dispose()); _marketList.Dispose(); - _marketList.Clear(); } private decimal GetRandomPrice() => MarketPrice.RandomPrice(_randomizer, BasePrice, PriceOffset); diff --git a/src/DynamicData.Tests/List/MergeManyChangeSetsFixture.cs b/src/DynamicData.Tests/List/MergeManyChangeSetsFixture.cs index ea8019ea4..befd01636 100644 --- a/src/DynamicData.Tests/List/MergeManyChangeSetsFixture.cs +++ b/src/DynamicData.Tests/List/MergeManyChangeSetsFixture.cs @@ -1,8 +1,3 @@ -using System.Linq; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class MergeManyChangeSetsFixture @@ -44,12 +39,10 @@ public void MergeManyShouldWork() a.ReplaceAt(0,100); new[] { 2, 100 }.Should().BeEquivalentTo(d.Items); - var f = new SourceList(); f.AddRange(Enumerable.Range(10,5)); parent.ReplaceAt(2,f); - new[] { 2, 100, 10,11,12,13,14 }.Should().BeEquivalentTo(d.Items); } } diff --git a/src/DynamicData.Tests/List/MergeManyChangeSetsListFixture.cs b/src/DynamicData.Tests/List/MergeManyChangeSetsListFixture.cs index 41cec34e1..de6ad188c 100644 --- a/src/DynamicData.Tests/List/MergeManyChangeSetsListFixture.cs +++ b/src/DynamicData.Tests/List/MergeManyChangeSetsListFixture.cs @@ -1,17 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Threading.Tasks; using Bogus; using DynamicData.Kernel; using DynamicData.Tests.Domain; -using DynamicData.Tests.Utilities; -using FluentAssertions; -using Xunit; namespace DynamicData.Tests.List; @@ -540,9 +529,10 @@ private static void CheckResultContents(IEnumerable owners, ChangeS public void Dispose() { - _animalOwners.Items.ForEach(owner => owner.Dispose()); + var owners = _animalOwners.Items.ToArray(); _animalOwnerResults.Dispose(); _animalResults.Dispose(); + owners.ForEach(owner => owner.Dispose()); _animalOwners.Dispose(); } } diff --git a/src/DynamicData.Tests/List/MergeManyFixture.cs b/src/DynamicData.Tests/List/MergeManyFixture.cs index 997e91f9f..f8d70c674 100644 --- a/src/DynamicData.Tests/List/MergeManyFixture.cs +++ b/src/DynamicData.Tests/List/MergeManyFixture.cs @@ -1,11 +1,3 @@ -using System; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class MergeManyFixture : IDisposable @@ -154,9 +146,9 @@ public void MergedStreamFailsWhenSourceFails() receivedError.Should().Be(expectedError); } - private class ObjectWithObservable(int id) + private class ObjectWithObservable(int id) : IDisposable { - private readonly ISubject _changed = new Subject(); + private readonly ISignal _changed = new Signal(); private bool _value; @@ -173,5 +165,7 @@ public void InvokeObservable(bool value) _value = value; _changed.OnNext(value); } + + public void Dispose() => _changed.Dispose(); } } diff --git a/src/DynamicData.Tests/List/OnItemAddedFixture.cs b/src/DynamicData.Tests/List/OnItemAddedFixture.cs index 3585426cb..7e7ab673c 100644 --- a/src/DynamicData.Tests/List/OnItemAddedFixture.cs +++ b/src/DynamicData.Tests/List/OnItemAddedFixture.cs @@ -1,12 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.List; public class OnItemAddedFixture @@ -56,7 +47,6 @@ public void ItemIsAdded_AddActionIsInvoked( because: "the collection contained initial items"); addActionInvocations.Clear(); - // UUT Action source.Insert( index: insertionIndex, @@ -112,7 +102,6 @@ public void ItemIsMoved_AddActionIsNotInvoked( because: "the collection contained initial items"); addActionInvocations.Clear(); - // UUT Action source.Move( original: originalIndex, @@ -165,7 +154,6 @@ public void ItemIsRefreshed_AddActionIsNotInvoked( because: "the collection contained initial items"); addActionInvocations.Clear(); - // UUT Action source.Refresh(refreshIndex); @@ -217,7 +205,6 @@ public void ItemIsRemoved_AddActionIsNotInvoked( because: "the collection contained initial items"); addActionInvocations.Clear(); - // UUT Action source.RemoveAt(removalIndex); @@ -268,7 +255,6 @@ public void ItemIsReplaced_AddActionIsInvokedForNewItem( because: "the collection contained initial items"); addActionInvocations.Clear(); - // UUT Action source.ReplaceAt( index: replacementIndex, @@ -323,7 +309,6 @@ public void ItemRangeIsRemoved_AddActionIsNotInvoked( because: "the collection contained initial items"); addActionInvocations.Clear(); - // UUT Action source.RemoveRange( index: removalIndex, @@ -372,7 +357,6 @@ public void ItemsAreCleared_AddActionIsNotInvoked(int initialItemCount) because: "the collection contained initial items"); addActionInvocations.Clear(); - // UUT Action source.Clear(); @@ -422,7 +406,6 @@ public void SourceCompletesAsynchronously_CompletionPropagates() because: "the collection contained initial items"); addActionInvocations.Clear(); - // UUT Action source.Complete(); @@ -504,7 +487,6 @@ public void SourceFailsAsynchronously_CompletionPropagates() because: "the collection contained initial items"); addActionInvocations.Clear(); - // UUT Action var error = new Exception(); source.SetError(error); diff --git a/src/DynamicData.Tests/List/OnItemRefreshedFixture.cs b/src/DynamicData.Tests/List/OnItemRefreshedFixture.cs index c044d8e89..e3098aea5 100644 --- a/src/DynamicData.Tests/List/OnItemRefreshedFixture.cs +++ b/src/DynamicData.Tests/List/OnItemRefreshedFixture.cs @@ -1,12 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.List; public class OnItemRefreshedFixture @@ -49,7 +40,6 @@ public void ItemIsAdded_RefreshActionIsNotInvoked( refreshActionInvocations.Should().BeEmpty("no items were refreshed within the collection"); - // UUT Action source.Insert( index: insertionIndex, @@ -101,7 +91,6 @@ public void ItemIsMoved_RefreshActionIsNotInvoked( refreshActionInvocations.Should().BeEmpty("no items were refreshed within the collection"); - // UUT Action source.Move( original: originalIndex, @@ -150,7 +139,6 @@ public void ItemIsRefreshed_RefreshActionIsNotInvoked( refreshActionInvocations.Should().BeEmpty("no items were refreshed within the collection"); - // UUT Action var refreshedItem = source.Items[refreshIndex]; source.Refresh(refreshIndex); @@ -199,7 +187,6 @@ public void ItemIsRemoved_RefreshActionIsInvoked( refreshActionInvocations.Should().BeEmpty("no items were refreshed within the collection"); - // UUT Action var removedItem = source.Items[removalIndex]; source.RemoveAt(removalIndex); @@ -247,7 +234,6 @@ public void ItemIsReplaced_RefreshActionIsInvokedForOldItem( refreshActionInvocations.Should().BeEmpty("no items were refreshed within the collection"); - // UUT Action source.ReplaceAt( index: replacementIndex, @@ -298,7 +284,6 @@ public void ItemRangeIsRemoved_RefreshActionIsInvokedForEachItem( refreshActionInvocations.Should().BeEmpty("no items were refreshed within the collection"); - // UUT Action source.RemoveRange( index: removalIndex, @@ -343,7 +328,6 @@ public void ItemsAreCleared_RefreshActionIsInvokedForEachItem(int initialItemCou refreshActionInvocations.Should().BeEmpty("no items were refreshed within the collection"); - // UUT Action source.Clear(); @@ -389,7 +373,6 @@ public void SourceCompletesAsynchronously_CompletionPropagates() refreshActionInvocations.Should().BeEmpty("no items have been removed from the collection"); - // UUT Action source.Complete(); @@ -463,7 +446,6 @@ public void SourceFailsAsynchronously_CompletionPropagates() refreshActionInvocations.Should().BeEmpty("no items have been removed from the collection"); - // UUT Action var error = new Exception(); source.SetError(error); diff --git a/src/DynamicData.Tests/List/OnItemRemovedFixture.cs b/src/DynamicData.Tests/List/OnItemRemovedFixture.cs index 6b91fc11d..84ed635d6 100644 --- a/src/DynamicData.Tests/List/OnItemRemovedFixture.cs +++ b/src/DynamicData.Tests/List/OnItemRemovedFixture.cs @@ -1,13 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.List; public class OnItemRemovedFixture @@ -74,7 +64,6 @@ public void InvokeOnUnsubscribeIsRequested_RemoveActionIsInvokedForEachRemaining removeActionInvocations.Should().BeEmpty("no items have been removed from the collection"); - // UUT Setup: Remove some items, to ensure correct tracking of remaining items. var removedItems = source.Items .Skip(removalIndex) @@ -142,7 +131,6 @@ public void InvokeOnUnsubscribeIsNotRequested_RemoveActionIsNotInvokedOnCompleti removeActionInvocations.Should().BeEmpty("no items have been removed from the collection"); - // UUT Setup: Remove some items, to ensure correct tracking of remaining items. var removedItems = source.Items .Skip(removalIndex) @@ -206,7 +194,6 @@ public void ItemIsAdded_RemoveActionIsNotInvoked( removeActionInvocations.Should().BeEmpty("no items have been removed from the collection"); - // UUT Action source.Insert( index: insertionIndex, @@ -252,7 +239,6 @@ public void ItemIsMoved_RemoveActionIsNotInvoked( removeActionInvocations.Should().BeEmpty("no items have been removed from the collection"); - // UUT Action source.Move( original: originalIndex, @@ -296,7 +282,6 @@ public void ItemIsRemoved_RemoveActionIsInvoked( removeActionInvocations.Should().BeEmpty("no items have been removed from the collection"); - // UUT Action var removedItem = source.Items[removalIndex]; source.RemoveAt(removalIndex); @@ -339,7 +324,6 @@ public void ItemIsRefreshed_RemoveActionIsNotInvoked( removeActionInvocations.Should().BeEmpty("no items have been removed from the collection"); - // UUT Action source.Refresh(refreshIndex); @@ -380,7 +364,6 @@ public void ItemIsReplaced_RemoveActionIsInvokedForOldItem( removeActionInvocations.Should().BeEmpty("no items have been removed from the collection"); - // UUT Action var replacedItem = source.Items[replacementIndex]; source.ReplaceAt( @@ -429,7 +412,6 @@ public void ItemRangeIsRemoved_RemoveActionIsInvokedForEachItem( removeActionInvocations.Should().BeEmpty("no items have been removed from the collection"); - // UUT Action var removedItems = source.Items .Skip(removalIndex) @@ -473,7 +455,6 @@ public void ItemsAreCleared_RemoveActionIsInvokedForEachItem(int initialItemCoun removeActionInvocations.Should().BeEmpty("no items have been removed from the collection"); - // UUT Action var clearedItems = source.Items .ToArray(); @@ -520,7 +501,6 @@ public void SourceCompletesAsynchronously_CompletionPropagates(bool invokeOnUnsu removeActionInvocations.Should().BeEmpty("no items have been removed from the collection"); - // UUT Action source.Complete(); @@ -602,7 +582,6 @@ public void SourceFailsAsynchronously_CompletionPropagates(bool invokeOnUnsubscr removeActionInvocations.Should().BeEmpty("no items have been removed from the collection"); - // UUT Action var error = new Exception(); source.SetError(error); diff --git a/src/DynamicData.Tests/List/OrFixture.cs b/src/DynamicData.Tests/List/OrFixture.cs index c4c973aa2..86c989da2 100644 --- a/src/DynamicData.Tests/List/OrFixture.cs +++ b/src/DynamicData.Tests/List/OrFixture.cs @@ -1,12 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -using FluentAssertions; - -using Xunit; - -namespace DynamicData.Tests.List; +namespace DynamicData.Tests.List; public class OrFixture : OrFixtureBase { diff --git a/src/DynamicData.Tests/List/PageFixture.cs b/src/DynamicData.Tests/List/PageFixture.cs index 5091b06b7..0bec20b9f 100644 --- a/src/DynamicData.Tests/List/PageFixture.cs +++ b/src/DynamicData.Tests/List/PageFixture.cs @@ -1,23 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Subjects; - using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class PageFixture : IDisposable { private readonly RandomPersonGenerator _generator = new(); - private readonly ISubject _requestSubject = new BehaviorSubject(new PageRequest(1, 25)); + private readonly ISignal _requestSubject = new StateSignal(new PageRequest(1, 25)); private readonly ChangeSetAggregator _results; @@ -34,6 +24,7 @@ public void Dispose() _requestSubject.OnCompleted(); _source.Dispose(); _results.Dispose(); + _requestSubject.Dispose(); } [Fact] @@ -150,7 +141,7 @@ public class PageFixtureWithNoInitialData [Fact] public void SimplePaging() { - using var pager = new BehaviorSubject(new PageRequest(0, 0)); + using var pager = new StateSignal(new PageRequest(0, 0)); using var sourceList = new SourceList(); using var sut = new SimplePaging(sourceList, pager); // Add items to source @@ -168,7 +159,6 @@ public void SimplePaging() sut.Paged.Count.Should().Be(3); } - [Fact] public void DoesNotThrowWithDuplicates() { @@ -179,7 +169,7 @@ public void DoesNotThrowWithDuplicates() var source = new SourceList(); source.AddRange(Enumerable.Repeat("item", 10)); source.Connect() - .Page(new BehaviorSubject(new PageRequest(0, 3))) + .Page(new StateSignal(new PageRequest(0, 3))) .Clone(result) .Subscribe(); diff --git a/src/DynamicData.Tests/List/QueryWhenChangedFixture.cs b/src/DynamicData.Tests/List/QueryWhenChangedFixture.cs index 49c5cd27e..cee9f0dc5 100644 --- a/src/DynamicData.Tests/List/QueryWhenChangedFixture.cs +++ b/src/DynamicData.Tests/List/QueryWhenChangedFixture.cs @@ -1,11 +1,5 @@ -using System; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class QueryWhenChangedFixture : IDisposable diff --git a/src/DynamicData.Tests/List/RecursiveTransformManyFixture.cs b/src/DynamicData.Tests/List/RecursiveTransformManyFixture.cs index 1d79be23e..b950dd172 100644 --- a/src/DynamicData.Tests/List/RecursiveTransformManyFixture.cs +++ b/src/DynamicData.Tests/List/RecursiveTransformManyFixture.cs @@ -1,11 +1,4 @@ -using System; - using DynamicData.Tests.Domain; -using DynamicData.Tests.Utilities; - -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.List; diff --git a/src/DynamicData.Tests/List/RefCountFixture.cs b/src/DynamicData.Tests/List/RefCountFixture.cs index c0d86e06d..2c9463ec6 100644 --- a/src/DynamicData.Tests/List/RefCountFixture.cs +++ b/src/DynamicData.Tests/List/RefCountFixture.cs @@ -1,14 +1,5 @@ -using System; -using System.Linq; -using System.Reactive.Linq; -using System.Threading.Tasks; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class RefCountFixture : IDisposable diff --git a/src/DynamicData.Tests/List/RemoveManyFixture.cs b/src/DynamicData.Tests/List/RemoveManyFixture.cs index b0809011e..fff714584 100644 --- a/src/DynamicData.Tests/List/RemoveManyFixture.cs +++ b/src/DynamicData.Tests/List/RemoveManyFixture.cs @@ -1,11 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class RemoveManyFixture diff --git a/src/DynamicData.Tests/List/ReverseFixture.cs b/src/DynamicData.Tests/List/ReverseFixture.cs index 68cb3685d..dd3e87329 100644 --- a/src/DynamicData.Tests/List/ReverseFixture.cs +++ b/src/DynamicData.Tests/List/ReverseFixture.cs @@ -1,11 +1,4 @@ -using System; -using System.Linq; - -using FluentAssertions; - -using Xunit; - -namespace DynamicData.Tests.List; +namespace DynamicData.Tests.List; public class ReverseFixture : IDisposable { diff --git a/src/DynamicData.Tests/List/SelectFixture.cs b/src/DynamicData.Tests/List/SelectFixture.cs index 6fd4fe0f7..d7899ef62 100644 --- a/src/DynamicData.Tests/List/SelectFixture.cs +++ b/src/DynamicData.Tests/List/SelectFixture.cs @@ -1,13 +1,6 @@ -using System; -using System.Linq; - -using DynamicData.Alias; +using DynamicData.Alias; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class SelectFixture : IDisposable diff --git a/src/DynamicData.Tests/List/SizeLimitFixture.cs b/src/DynamicData.Tests/List/SizeLimitFixture.cs index f07825d14..26f972b74 100644 --- a/src/DynamicData.Tests/List/SizeLimitFixture.cs +++ b/src/DynamicData.Tests/List/SizeLimitFixture.cs @@ -1,14 +1,5 @@ -using System; -using System.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Microsoft.Reactive.Testing; - -using Xunit; - namespace DynamicData.Tests.List; public class SizeLimitFixture : IDisposable diff --git a/src/DynamicData.Tests/List/SortFixture.cs b/src/DynamicData.Tests/List/SortFixture.cs index 7f2a7dea3..232f5197d 100644 --- a/src/DynamicData.Tests/List/SortFixture.cs +++ b/src/DynamicData.Tests/List/SortFixture.cs @@ -1,21 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Subjects; using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class SortChangedFixture { private static readonly IComparer DefaultComparer = SortExpressionComparer.Ascending(x => x.Number); - /// /// See https://github.com/reactivemarbles/DynamicData/issues/473 /// @@ -23,7 +14,7 @@ public class SortChangedFixture public void SortsWithoutError() { var source = new SourceList(); - var sorter = new Subject>(); + var sorter = new Signal>(); source.AddRange(Enumerable.Range(1, 10).Select(i => new ListItem(i))); @@ -36,11 +27,9 @@ public void SortsWithoutError() sorter.OnNext(SortExpressionComparer.Descending(x => x.Number)); - bound.Select(x => x.Number).Should().BeInDescendingOrder(); } - private class ListItem(int number) : IComparable { public int Number { get; } = number; diff --git a/src/DynamicData.Tests/List/SortMutableFixture.cs b/src/DynamicData.Tests/List/SortMutableFixture.cs index 63eab9e03..4686763d1 100644 --- a/src/DynamicData.Tests/List/SortMutableFixture.cs +++ b/src/DynamicData.Tests/List/SortMutableFixture.cs @@ -1,28 +1,18 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive; -using System.Reactive.Subjects; - using DynamicData.Binding; using DynamicData.Kernel; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class SortMutableFixture : IDisposable { - private readonly ISubject> _changeComparer; + private readonly ISignal> _changeComparer; private readonly IComparer _comparer = SortExpressionComparer.Ascending(p => p.Age).ThenByAscending(p => p.Name); private readonly RandomPersonGenerator _generator = new(); - private readonly ISubject _resort; + private readonly ISignal _resort; private readonly ChangeSetAggregator _results; @@ -31,8 +21,8 @@ public class SortMutableFixture : IDisposable public SortMutableFixture() { _source = new SourceList(); - _changeComparer = new BehaviorSubject>(_comparer); - _resort = new Subject(); + _changeComparer = new StateSignal>(_comparer); + _resort = new Signal(); _results = _source.Connect().Sort(_changeComparer, resetThreshold: 25, resort: _resort).AsAggregator(); } @@ -59,6 +49,8 @@ public void Dispose() { _results.Dispose(); _source.Dispose(); + _changeComparer.Dispose(); + _resort.Dispose(); } [Fact] diff --git a/src/DynamicData.Tests/List/SortPrimitiveFixture.cs b/src/DynamicData.Tests/List/SortPrimitiveFixture.cs index d5e03c2a5..6ddcb6297 100644 --- a/src/DynamicData.Tests/List/SortPrimitiveFixture.cs +++ b/src/DynamicData.Tests/List/SortPrimitiveFixture.cs @@ -1,13 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; - using DynamicData.Binding; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class SortPrimitiveFixture : IDisposable diff --git a/src/DynamicData.Tests/List/SourceListFixture.cs b/src/DynamicData.Tests/List/SourceListFixture.cs index dc37bdaed..6396e6f46 100644 --- a/src/DynamicData.Tests/List/SourceListFixture.cs +++ b/src/DynamicData.Tests/List/SourceListFixture.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using FluentAssertions; -using Xunit; - namespace DynamicData.Tests.List; public class SourceListFixture @@ -17,7 +11,6 @@ public void InitialChangeIsRange() source.Connect().Subscribe(changeSets.Add).Dispose(); - changeSets[0].First().Type.Should().Be(ChangeType.Range); changeSets[0].First().Range.Index.Should().Be(0); } diff --git a/src/DynamicData.Tests/List/SourceListPreviewFixture.cs b/src/DynamicData.Tests/List/SourceListPreviewFixture.cs index cb61bbfb5..075711eae 100644 --- a/src/DynamicData.Tests/List/SourceListPreviewFixture.cs +++ b/src/DynamicData.Tests/List/SourceListPreviewFixture.cs @@ -1,9 +1,4 @@ -using System; -using System.Linq; - -using Xunit; - -namespace DynamicData.Tests.List; +namespace DynamicData.Tests.List; public class SourceListPreviewFixture : IDisposable { diff --git a/src/DynamicData.Tests/List/SubscribeManyFixture.cs b/src/DynamicData.Tests/List/SubscribeManyFixture.cs index 8f8995a2e..75278af0e 100644 --- a/src/DynamicData.Tests/List/SubscribeManyFixture.cs +++ b/src/DynamicData.Tests/List/SubscribeManyFixture.cs @@ -1,11 +1,3 @@ -using System; -using System.Linq; -using System.Reactive.Disposables; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class SubscribeManyFixture : IDisposable diff --git a/src/DynamicData.Tests/List/SwitchFixture.cs b/src/DynamicData.Tests/List/SwitchFixture.cs index abcdaa8d3..b9cfb0b05 100644 --- a/src/DynamicData.Tests/List/SwitchFixture.cs +++ b/src/DynamicData.Tests/List/SwitchFixture.cs @@ -1,11 +1,3 @@ -using System; -using System.Linq; -using System.Reactive.Subjects; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class SwitchFixture : IDisposable @@ -14,12 +6,12 @@ public class SwitchFixture : IDisposable private readonly ISourceList _source; - private readonly ISubject> _switchable; + private readonly ISignal> _switchable; public SwitchFixture() { _source = new SourceList(); - _switchable = new BehaviorSubject>(_source); + _switchable = new StateSignal>(_source); _results = _switchable.Switch().AsAggregator(); } @@ -48,6 +40,7 @@ public void Dispose() { _source.Dispose(); _results.Dispose(); + _switchable.Dispose(); } [Fact] diff --git a/src/DynamicData.Tests/List/ToCollectionFixture.cs b/src/DynamicData.Tests/List/ToCollectionFixture.cs index a46aa790f..e7cfed1fe 100644 --- a/src/DynamicData.Tests/List/ToCollectionFixture.cs +++ b/src/DynamicData.Tests/List/ToCollectionFixture.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Reactive.Linq; -using FluentAssertions; -using Xunit; - namespace DynamicData.Tests.List; public class ToCollectionFixture diff --git a/src/DynamicData.Tests/List/ToObservableChangeSetFixture.Items.IntegrationTests.cs b/src/DynamicData.Tests/List/ToObservableChangeSetFixture.Items.IntegrationTests.cs index 0a60bc356..ccb6b45a4 100644 --- a/src/DynamicData.Tests/List/ToObservableChangeSetFixture.Items.IntegrationTests.cs +++ b/src/DynamicData.Tests/List/ToObservableChangeSetFixture.Items.IntegrationTests.cs @@ -1,14 +1,3 @@ -using System; -using System.Linq; -using System.Reactive.Concurrency; -using System.Reactive.Linq; -using System.Threading.Tasks; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.List; public static partial class ToObservableChangeSetFixture @@ -27,7 +16,7 @@ public async Task MultipleSubscriptionsRunInParallel_SchedulerUsageIsThreadSafe( { IScheduler scheduler = schedulerType switch { - SchedulerType.Default => DefaultScheduler.Instance, + SchedulerType.Default => Scheduler.Default, SchedulerType.NewThread => new NewThreadScheduler(), SchedulerType.TaskPool => TaskPoolScheduler.Default, SchedulerType.ThreadPool => ThreadPoolScheduler.Instance, diff --git a/src/DynamicData.Tests/List/ToObservableChangeSetFixture.Items.UnitTests.cs b/src/DynamicData.Tests/List/ToObservableChangeSetFixture.Items.UnitTests.cs index 887a230d4..44cb136e2 100644 --- a/src/DynamicData.Tests/List/ToObservableChangeSetFixture.Items.UnitTests.cs +++ b/src/DynamicData.Tests/List/ToObservableChangeSetFixture.Items.UnitTests.cs @@ -1,15 +1,3 @@ -using System; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using Microsoft.Reactive.Testing; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.List; public static partial class ToObservableChangeSetFixture @@ -22,11 +10,10 @@ public class UnitTests public void ExpireAfterThrows_ErrorPropagates() { // Setup - using var source = new Subject(); + using var source = new Signal(); var error = new Exception("Test Exception"); - // UUT Initialization using var subscription = source .ToObservableChangeSet(expireAfter: static item => (item.Error is not null) @@ -41,7 +28,6 @@ public void ExpireAfterThrows_ErrorPropagates() results.RecordedItems.Should().BeEmpty("no items have been emitted by the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item1 = new Item() { Id = 1 }; source.OnNext(item1); @@ -57,8 +43,7 @@ public void ExpireAfterThrows_ErrorPropagates() public void SizeLimitIsExceeded_OldestItemsAreRemoved() { // Setup - using var source = new Subject(); - + using var source = new Signal(); // UUT Initialization using var subscription = source @@ -72,7 +57,6 @@ public void SizeLimitIsExceeded_OldestItemsAreRemoved() results.RecordedItems.Should().BeEmpty("no items have been emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action: Not enough items to reach the limit var item1 = new Item() { Id = 1 }; source.OnNext(item1); @@ -93,7 +77,6 @@ public void SizeLimitIsExceeded_OldestItemsAreRemoved() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action: Limit is reached var item5 = new Item() { Id = 5 }; source.OnNext(item5); @@ -105,7 +88,6 @@ public void SizeLimitIsExceeded_OldestItemsAreRemoved() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action: New item exceeds the limit var item6 = new Item() { Id = 6 }; source.OnNext(item6); @@ -128,14 +110,13 @@ public void SourceCompletesWhenExpirationsArePending_CompletionWaitsForExpiratio var source = sourceType switch { - SourceType.Asynchronous => new Subject(), + SourceType.Asynchronous => new Signal(), SourceType.Immediate => Observable.Return(item), _ => throw new ArgumentOutOfRangeException(nameof(sourceType)) }; var scheduler = new TestScheduler(); - // UUT Initialization & Action using var subscription = source .ToObservableChangeSet( @@ -145,7 +126,7 @@ public void SourceCompletesWhenExpirationsArePending_CompletionWaitsForExpiratio .ValidateChangeSets() .RecordListItems(out var results); - if (source is Subject subject) + if (source is Signal subject) { subject.OnNext(item); subject.OnCompleted(); @@ -161,7 +142,6 @@ public void SourceCompletesWhenExpirationsArePending_CompletionWaitsForExpiratio config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("1 item has yet to expire"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(10).Ticks); @@ -184,14 +164,13 @@ public void SourceCompletesWhenNoExpirationsArePending_CompletionPropagates(Sour var source = sourceType switch { - SourceType.Asynchronous => new Subject(), + SourceType.Asynchronous => new Signal(), SourceType.Immediate => Observable.Return(item), _ => throw new ArgumentOutOfRangeException(nameof(sourceType)) }; var scheduler = new TestScheduler(); - // UUT Initialization & Action using var subscription = source .ToObservableChangeSet( @@ -201,7 +180,7 @@ public void SourceCompletesWhenNoExpirationsArePending_CompletionPropagates(Sour .ValidateChangeSets() .RecordListItems(out var results); - if (source is Subject subject) + if (source is Signal subject) { subject.OnNext(item); subject.OnCompleted(); @@ -222,11 +201,10 @@ public void SourceCompletesWhenNoExpirationsArePending_CompletionPropagates(Sour public void SourceEmitsItems_ItemsAreAddedAndRemovedWhenExpired() { // Setup - using var source = new Subject(); + using var source = new Signal(); var scheduler = new TestScheduler(); - // UUT Initialization using var subscription = source .ToObservableChangeSet( @@ -241,7 +219,6 @@ public void SourceEmitsItems_ItemsAreAddedAndRemovedWhenExpired() results.RecordedItems.Should().BeEmpty("no items have been emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item1 = new Item() { Id = 1, Lifetime = TimeSpan.FromSeconds(3) }; source.OnNext(item1); @@ -254,7 +231,6 @@ public void SourceEmitsItems_ItemsAreAddedAndRemovedWhenExpired() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item2 = new Item() { Id = 2 }; source.OnNext(item2); @@ -267,7 +243,6 @@ public void SourceEmitsItems_ItemsAreAddedAndRemovedWhenExpired() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item3 = new Item() { Id = 3, Lifetime = TimeSpan.FromSeconds(1) }; source.OnNext(item3); @@ -280,7 +255,6 @@ public void SourceEmitsItems_ItemsAreAddedAndRemovedWhenExpired() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(1).Ticks); @@ -291,7 +265,6 @@ public void SourceEmitsItems_ItemsAreAddedAndRemovedWhenExpired() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(2).Ticks); @@ -302,7 +275,6 @@ public void SourceEmitsItems_ItemsAreAddedAndRemovedWhenExpired() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(3).Ticks); @@ -313,7 +285,6 @@ public void SourceEmitsItems_ItemsAreAddedAndRemovedWhenExpired() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(4).Ticks); @@ -335,12 +306,11 @@ public void SourceFails_ErrorPropagates(SourceType sourceType) var source = sourceType switch { - SourceType.Asynchronous => new Subject(), + SourceType.Asynchronous => new Signal(), SourceType.Immediate => Observable.Throw(error), _ => throw new ArgumentOutOfRangeException(nameof(sourceType)) }; - // UUT Initialization & Action using var subscription = source .ToObservableChangeSet() @@ -348,7 +318,7 @@ public void SourceFails_ErrorPropagates(SourceType sourceType) .ValidateChangeSets() .RecordListItems(out var results); - if (source is Subject subject) + if (source is Signal subject) subject.OnError(error); results.Error.Should().BeSameAs(error, "errors should propagate"); diff --git a/src/DynamicData.Tests/List/ToObservableChangeSetFixture.Sequences.IntegrationTests.cs b/src/DynamicData.Tests/List/ToObservableChangeSetFixture.Sequences.IntegrationTests.cs index 505404290..f56f02469 100644 --- a/src/DynamicData.Tests/List/ToObservableChangeSetFixture.Sequences.IntegrationTests.cs +++ b/src/DynamicData.Tests/List/ToObservableChangeSetFixture.Sequences.IntegrationTests.cs @@ -1,14 +1,3 @@ -using System; -using System.Linq; -using System.Reactive.Concurrency; -using System.Reactive.Linq; -using System.Threading.Tasks; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.List; public static partial class ToObservableChangeSetFixture @@ -27,7 +16,7 @@ public async Task MultipleSubscriptionsRunInParallel_SchedulerUsageIsThreadSafe( { IScheduler scheduler = schedulerType switch { - SchedulerType.Default => DefaultScheduler.Instance, + SchedulerType.Default => Scheduler.Default, SchedulerType.NewThread => new NewThreadScheduler(), SchedulerType.TaskPool => TaskPoolScheduler.Default, SchedulerType.ThreadPool => ThreadPoolScheduler.Instance, diff --git a/src/DynamicData.Tests/List/ToObservableChangeSetFixture.Sequences.UnitTests.cs b/src/DynamicData.Tests/List/ToObservableChangeSetFixture.Sequences.UnitTests.cs index 55b1fdaae..fc7f246f8 100644 --- a/src/DynamicData.Tests/List/ToObservableChangeSetFixture.Sequences.UnitTests.cs +++ b/src/DynamicData.Tests/List/ToObservableChangeSetFixture.Sequences.UnitTests.cs @@ -1,16 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using Microsoft.Reactive.Testing; - -using FluentAssertions; -using Xunit; - -using DynamicData.Tests.Utilities; - namespace DynamicData.Tests.List; public static partial class ToObservableChangeSetFixture @@ -23,11 +10,10 @@ public class UnitTests public void ExpireAfterThrows_ErrorPropagates() { // Setup - using var source = new Subject>(); + using var source = new Signal>(); var error = new Exception("Test Exception"); - // UUT Initialization using var subscription = source .ToObservableChangeSet(expireAfter: static item => (item.Error is not null) @@ -42,7 +28,6 @@ public void ExpireAfterThrows_ErrorPropagates() results.RecordedItems.Should().BeEmpty("no items have been emitted by the source"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item1 = new Item() { Id = 1 }; source.OnNext(new[] @@ -61,8 +46,7 @@ public void ExpireAfterThrows_ErrorPropagates() public void SizeLimitIsExceeded_OldestItemsAreRemoved() { // Setup - using var source = new Subject>(); - + using var source = new Signal>(); // UUT Initialization using var subscription = source @@ -76,7 +60,6 @@ public void SizeLimitIsExceeded_OldestItemsAreRemoved() results.RecordedItems.Should().BeEmpty("no source items have been emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action: Not enough items to reach the limit var item1 = new Item() { Id = 1 }; var item2 = new Item() { Id = 2 }; @@ -97,7 +80,6 @@ public void SizeLimitIsExceeded_OldestItemsAreRemoved() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action: Limit is reached var item5 = new Item() { Id = 5 }; source.OnNext(new[] { item5 }); @@ -109,7 +91,6 @@ public void SizeLimitIsExceeded_OldestItemsAreRemoved() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action: New item exceeds the limit var item6 = new Item() { Id = 6 }; source.OnNext(new[] { item6 }); @@ -137,14 +118,13 @@ public void SourceCompletesWhenExpirationsArePending_CompletionWaitsForExpiratio var source = sourceType switch { - SourceType.Asynchronous => new Subject>(), + SourceType.Asynchronous => new Signal>(), SourceType.Immediate => Observable.Return>(items), _ => throw new ArgumentOutOfRangeException(nameof(sourceType)) }; var scheduler = new TestScheduler(); - // UUT Initialization & Action using var subscription = source .ToObservableChangeSet( @@ -154,7 +134,7 @@ public void SourceCompletesWhenExpirationsArePending_CompletionWaitsForExpiratio .ValidateChangeSets() .RecordListItems(out var results); - if (source is Subject> subject) + if (source is Signal> subject) { subject.OnNext(items); subject.OnCompleted(); @@ -170,7 +150,6 @@ public void SourceCompletesWhenExpirationsArePending_CompletionWaitsForExpiratio config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("2 items have yet to expire"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(30).Ticks); @@ -200,14 +179,13 @@ public void SourceCompletesWhenNoExpirationsArePending_CompletionPropagates(Sour var source = sourceType switch { - SourceType.Asynchronous => new Subject>(), + SourceType.Asynchronous => new Signal>(), SourceType.Immediate => Observable.Return>(items), _ => throw new ArgumentOutOfRangeException(nameof(sourceType)) }; var scheduler = new TestScheduler(); - // UUT Initialization & Action using var subscription = source .ToObservableChangeSet( @@ -217,7 +195,7 @@ public void SourceCompletesWhenNoExpirationsArePending_CompletionPropagates(Sour .ValidateChangeSets() .RecordListItems(out var results); - if (source is Subject> subject) + if (source is Signal> subject) { subject.OnNext(items); subject.OnCompleted(); @@ -238,11 +216,10 @@ public void SourceCompletesWhenNoExpirationsArePending_CompletionPropagates(Sour public void SourceEmitsItems_ItemsAreAddedAndRemovedWhenExpired() { // Setup - using var source = new Subject>(); + using var source = new Signal>(); var scheduler = new TestScheduler(); - // UUT Initialization using var subscription = source .ToObservableChangeSet( @@ -257,7 +234,6 @@ public void SourceEmitsItems_ItemsAreAddedAndRemovedWhenExpired() results.RecordedItems.Should().BeEmpty("no source items have been emitted"); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action var item1 = new Item() { Id = 1, Lifetime = TimeSpan.FromSeconds(3) }; var item2 = new Item() { Id = 2 }; @@ -272,7 +248,6 @@ public void SourceEmitsItems_ItemsAreAddedAndRemovedWhenExpired() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(1).Ticks); @@ -283,7 +258,6 @@ public void SourceEmitsItems_ItemsAreAddedAndRemovedWhenExpired() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(2).Ticks); @@ -294,7 +268,6 @@ public void SourceEmitsItems_ItemsAreAddedAndRemovedWhenExpired() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(3).Ticks); @@ -305,7 +278,6 @@ public void SourceEmitsItems_ItemsAreAddedAndRemovedWhenExpired() config: options => options.WithStrictOrdering()); results.HasCompleted.Should().BeFalse("the source has not completed"); - // UUT Action scheduler.AdvanceTo(TimeSpan.FromSeconds(4).Ticks); @@ -327,12 +299,11 @@ public void SourceFails_ErrorPropagates(SourceType sourceType) var source = sourceType switch { - SourceType.Asynchronous => new Subject>(), + SourceType.Asynchronous => new Signal>(), SourceType.Immediate => Observable.Throw>(error), _ => throw new ArgumentOutOfRangeException(nameof(sourceType)) }; - // UUT Initialization & Action using var subscription = source .ToObservableChangeSet() @@ -340,7 +311,7 @@ public void SourceFails_ErrorPropagates(SourceType sourceType) .ValidateChangeSets() .RecordListItems(out var results); - if (source is Subject> subject) + if (source is Signal> subject) subject.OnError(error); results.Error.Should().BeSameAs(error, "errors should propagate"); diff --git a/src/DynamicData.Tests/List/ToObservableChangeSetFixture.cs b/src/DynamicData.Tests/List/ToObservableChangeSetFixture.cs index e42998264..351fb6450 100644 --- a/src/DynamicData.Tests/List/ToObservableChangeSetFixture.cs +++ b/src/DynamicData.Tests/List/ToObservableChangeSetFixture.cs @@ -1,6 +1,4 @@ -using System; - -namespace DynamicData.Tests.List; +namespace DynamicData.Tests.List; public static partial class ToObservableChangeSetFixture { diff --git a/src/DynamicData.Tests/List/TransformAsyncFixture.cs b/src/DynamicData.Tests/List/TransformAsyncFixture.cs index 9d0bdb447..100b9e9f8 100755 --- a/src/DynamicData.Tests/List/TransformAsyncFixture.cs +++ b/src/DynamicData.Tests/List/TransformAsyncFixture.cs @@ -1,10 +1,4 @@ -using System; -using System.Linq; -using System.Threading.Tasks; - using DynamicData.Tests.Domain; -using FluentAssertions; -using Xunit; namespace DynamicData.Tests.List; diff --git a/src/DynamicData.Tests/List/TransformFixture.cs b/src/DynamicData.Tests/List/TransformFixture.cs index 0841ae4f4..d3000eedf 100644 --- a/src/DynamicData.Tests/List/TransformFixture.cs +++ b/src/DynamicData.Tests/List/TransformFixture.cs @@ -1,11 +1,5 @@ -using System; -using System.Linq; - using DynamicData.Tests.Domain; -using FluentAssertions; -using Xunit; - namespace DynamicData.Tests.List; public class TransformFixture : IDisposable @@ -131,7 +125,6 @@ public void Update() _results.Messages[0].Replaced.Should().Be(0, "Should be 1 update"); } - [Fact] public void MultipleSubscribersShouldNotShareState() { diff --git a/src/DynamicData.Tests/List/TransformManyFixture.cs b/src/DynamicData.Tests/List/TransformManyFixture.cs index caf8bc6c7..d0eb2c86c 100644 --- a/src/DynamicData.Tests/List/TransformManyFixture.cs +++ b/src/DynamicData.Tests/List/TransformManyFixture.cs @@ -1,13 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; - using DynamicData.Tests.Domain; -using DynamicData.Tests.Utilities; - -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.List; diff --git a/src/DynamicData.Tests/List/TransformManyObservableCollectionFixture.cs b/src/DynamicData.Tests/List/TransformManyObservableCollectionFixture.cs index f713c1ee9..44390b73b 100644 --- a/src/DynamicData.Tests/List/TransformManyObservableCollectionFixture.cs +++ b/src/DynamicData.Tests/List/TransformManyObservableCollectionFixture.cs @@ -1,12 +1,4 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; - -using DynamicData.Tests.Domain; - -using FluentAssertions; - -using Xunit; +using DynamicData.Tests.Domain; namespace DynamicData.Tests.List; diff --git a/src/DynamicData.Tests/List/TransformManyProjectionFixture.cs b/src/DynamicData.Tests/List/TransformManyProjectionFixture.cs index 49c34de51..db2dd9280 100644 --- a/src/DynamicData.Tests/List/TransformManyProjectionFixture.cs +++ b/src/DynamicData.Tests/List/TransformManyProjectionFixture.cs @@ -1,13 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; - -using DynamicData.Binding; - -using FluentAssertions; - -using Xunit; +using DynamicData.Binding; namespace DynamicData.Tests.List; diff --git a/src/DynamicData.Tests/List/TransformManyRefreshFixture.cs b/src/DynamicData.Tests/List/TransformManyRefreshFixture.cs index f77aa95eb..3107f9c01 100644 --- a/src/DynamicData.Tests/List/TransformManyRefreshFixture.cs +++ b/src/DynamicData.Tests/List/TransformManyRefreshFixture.cs @@ -1,12 +1,4 @@ -using System; -using System.Collections.Generic; - using DynamicData.Tests.Domain; -using DynamicData.Tests.Utilities; - -using FluentAssertions; - -using Xunit; namespace DynamicData.Tests.List; diff --git a/src/DynamicData.Tests/List/VirtualisationFixture.cs b/src/DynamicData.Tests/List/VirtualisationFixture.cs index 66f681bff..66cf6513d 100644 --- a/src/DynamicData.Tests/List/VirtualisationFixture.cs +++ b/src/DynamicData.Tests/List/VirtualisationFixture.cs @@ -1,21 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Subjects; - using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class VirtualisationFixture : IDisposable { private readonly RandomPersonGenerator _generator = new(); - private readonly ISubject _requestSubject = new BehaviorSubject(new VirtualRequest(0, 25)); + private readonly ISignal _requestSubject = new StateSignal(new VirtualRequest(0, 25)); private readonly ChangeSetAggregator _results; @@ -31,6 +22,7 @@ public void Dispose() { _source.Dispose(); _results.Dispose(); + _requestSubject.Dispose(); } [Fact] @@ -130,7 +122,6 @@ public void VirtualiseInitial() _results.Data.Items.Should().BeEquivalentTo(expected); } - [Fact] public void DoesNotThrowWithDuplicates() { @@ -141,7 +132,7 @@ public void DoesNotThrowWithDuplicates() var source = new SourceList(); source.AddRange(Enumerable.Repeat("item", 10)); source.Connect() - .Virtualise(new BehaviorSubject(new VirtualRequest(0, 3))) + .Virtualise(new StateSignal(new VirtualRequest(0, 3))) .Clone(result) .Subscribe(); diff --git a/src/DynamicData.Tests/List/XOrFixture.cs b/src/DynamicData.Tests/List/XOrFixture.cs index 8f66b0d20..f22f68286 100644 --- a/src/DynamicData.Tests/List/XOrFixture.cs +++ b/src/DynamicData.Tests/List/XOrFixture.cs @@ -1,11 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests.List; public class XOrFixture : XOrFixtureBase diff --git a/src/DynamicData.Tests/ObservableCollectionExFixture.cs b/src/DynamicData.Tests/ObservableCollectionExFixture.cs index 0d93711a9..ea6bcc619 100644 --- a/src/DynamicData.Tests/ObservableCollectionExFixture.cs +++ b/src/DynamicData.Tests/ObservableCollectionExFixture.cs @@ -1,12 +1,6 @@ - -using System.Collections.ObjectModel; using DynamicData.Binding; using DynamicData.Tests.Domain; -using FluentAssertions; - -using Xunit; - namespace DynamicData.Tests; public class ObservableCollectionExFixture @@ -35,7 +29,6 @@ public void CanConvertToObservableChangeSetCache() one.Should().BeEquivalentTo(_person1); } - [Fact] public void ReplacingAnItemWithSameProducesUpdate() { diff --git a/src/DynamicData.Tests/Utilities/CacheChangeSetAssertions.cs b/src/DynamicData.Tests/Utilities/CacheChangeSetAssertions.cs index bb4e7cb3f..a9148584b 100644 --- a/src/DynamicData.Tests/Utilities/CacheChangeSetAssertions.cs +++ b/src/DynamicData.Tests/Utilities/CacheChangeSetAssertions.cs @@ -1,9 +1,4 @@ -using System.Collections.Generic; -using System.Linq; - -using FluentAssertions; - -namespace DynamicData.Tests.Utilities; +namespace DynamicData.Tests.Utilities; public static class CacheChangeSetAssertions { diff --git a/src/DynamicData.Tests/Utilities/CacheItemRecordingObserver.cs b/src/DynamicData.Tests/Utilities/CacheItemRecordingObserver.cs index 7f0eba1b9..4ad7df3e2 100644 --- a/src/DynamicData.Tests/Utilities/CacheItemRecordingObserver.cs +++ b/src/DynamicData.Tests/Utilities/CacheItemRecordingObserver.cs @@ -1,6 +1,3 @@ -using System.Collections.Generic; -using System.Reactive.Concurrency; - namespace DynamicData.Tests.Utilities; public sealed class CacheItemRecordingObserver diff --git a/src/DynamicData.Tests/Utilities/CacheItemRecordingObserverAssertions.cs b/src/DynamicData.Tests/Utilities/CacheItemRecordingObserverAssertions.cs index 6dfbfa047..caf8e91f8 100644 --- a/src/DynamicData.Tests/Utilities/CacheItemRecordingObserverAssertions.cs +++ b/src/DynamicData.Tests/Utilities/CacheItemRecordingObserverAssertions.cs @@ -1,6 +1,4 @@ -using FluentAssertions; - -namespace DynamicData.Tests.Utilities; +namespace DynamicData.Tests.Utilities; public static class CacheItemRecordingObserverAssertions { diff --git a/src/DynamicData.Tests/Utilities/ComparerExtensions.cs b/src/DynamicData.Tests/Utilities/ComparerExtensions.cs index 76433346f..efdc80bab 100644 --- a/src/DynamicData.Tests/Utilities/ComparerExtensions.cs +++ b/src/DynamicData.Tests/Utilities/ComparerExtensions.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace DynamicData.Tests.Utilities; @@ -17,7 +15,6 @@ internal sealed class NoOpEqualityComparer : IEqualityComparer public int GetHashCode([DisallowNull] T obj) => throw new NotImplementedException(); } - internal sealed class InvertedComparer(IComparer original) : IComparer { private readonly IComparer _original = original; @@ -25,7 +22,6 @@ internal sealed class InvertedComparer(IComparer original) : IComparer public int Compare(T x, T y) => _original.Compare(x, y) * -1; } - internal static class ComparerExtensions { public static IComparer Invert(this IComparer comparer) => new InvertedComparer(comparer); diff --git a/src/DynamicData.Tests/Utilities/FakeScheduler.cs b/src/DynamicData.Tests/Utilities/FakeScheduler.cs index fc2b19bbf..99fb5e5dd 100644 --- a/src/DynamicData.Tests/Utilities/FakeScheduler.cs +++ b/src/DynamicData.Tests/Utilities/FakeScheduler.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; +using System.Diagnostics; namespace DynamicData.Tests.Utilities; @@ -11,6 +8,7 @@ internal sealed class FakeScheduler private readonly List _scheduledActions; private DateTimeOffset _now; + private long? _originClockTicks; public FakeScheduler() => _scheduledActions = new(); @@ -24,6 +22,35 @@ public DateTimeOffset Now set => _now = value; } + public long Timestamp + { + get + { + _originClockTicks ??= _now.Ticks; + return ToTimestamp(_now.Ticks); + } + } + + public void Schedule(IWorkItem item) => + _scheduledActions.Add( + new ScheduledAction( + dueTime: null, + onInvoked: () => + { + item.Execute(); + return Disposable.Empty; + })); + + public void Schedule(IWorkItem item, long dueTimestamp) => + _scheduledActions.Add( + new ScheduledAction( + dueTime: new DateTimeOffset(ToClockTicks(dueTimestamp), TimeSpan.Zero), + onInvoked: () => + { + item.Execute(); + return Disposable.Empty; + })); + public IDisposable Schedule( TState state, Func action) @@ -91,6 +118,31 @@ private IDisposable ScheduleCore( return Disposable.Create(scheduledAction.Cancel); } + private long ToTimestamp(long ticks) + { + _originClockTicks ??= ticks; + var elapsedTicks = ticks - _originClockTicks.Value; + if (elapsedTicks <= 0) + { + return 0; + } + + var value = elapsedTicks / (double)TimeSpan.TicksPerSecond * Stopwatch.Frequency; + return value >= long.MaxValue ? long.MaxValue : (long)value; + } + + private long ToClockTicks(long timestamp) + { + var originClockTicks = _originClockTicks ?? 0; + if (timestamp <= 0) + { + return originClockTicks; + } + + var value = timestamp / (double)Stopwatch.Frequency * TimeSpan.TicksPerSecond; + return value >= long.MaxValue - originClockTicks ? long.MaxValue : originClockTicks + (long)value; + } + public sealed class ScheduledAction { private readonly Func _onInvoked; diff --git a/src/DynamicData.Tests/Utilities/FakerExtensions.cs b/src/DynamicData.Tests/Utilities/FakerExtensions.cs index 1388d3b80..ca70fed85 100644 --- a/src/DynamicData.Tests/Utilities/FakerExtensions.cs +++ b/src/DynamicData.Tests/Utilities/FakerExtensions.cs @@ -1,7 +1,3 @@ -using System; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Concurrency; using Bogus; namespace DynamicData.Tests.Utilities; @@ -14,5 +10,5 @@ public static IObservable IntervalGenerate(this Faker faker, Randomizer public static IObservable IntervalGenerate(this Faker faker, TimeSpan period, IScheduler? scheduler = null) where T : class => - Observable.Interval(period, scheduler ?? DefaultScheduler.Instance).Select(_ => faker.Generate()); + Observable.Interval(period, scheduler ?? Scheduler.Default).Select(_ => faker.Generate()); } diff --git a/src/DynamicData.Tests/Utilities/FunctionalExtensions.cs b/src/DynamicData.Tests/Utilities/FunctionalExtensions.cs index cb3c819cf..bce089232 100644 --- a/src/DynamicData.Tests/Utilities/FunctionalExtensions.cs +++ b/src/DynamicData.Tests/Utilities/FunctionalExtensions.cs @@ -1,6 +1,4 @@ -using System; - -namespace DynamicData.Tests.Utilities; +namespace DynamicData.Tests.Utilities; internal static class FunctionalExtensions { diff --git a/src/DynamicData.Tests/Utilities/ListChangeSetAssertions.cs b/src/DynamicData.Tests/Utilities/ListChangeSetAssertions.cs index d0b336e16..22f14d758 100644 --- a/src/DynamicData.Tests/Utilities/ListChangeSetAssertions.cs +++ b/src/DynamicData.Tests/Utilities/ListChangeSetAssertions.cs @@ -1,9 +1,4 @@ -using System.Collections.Generic; -using System.Linq; - -using FluentAssertions; - -namespace DynamicData.Tests.Utilities; +namespace DynamicData.Tests.Utilities; public static class ListChangeSetAssertions { diff --git a/src/DynamicData.Tests/Utilities/ListItemRecordingObserver.cs b/src/DynamicData.Tests/Utilities/ListItemRecordingObserver.cs index 09eaa8819..b9ccb2a5b 100644 --- a/src/DynamicData.Tests/Utilities/ListItemRecordingObserver.cs +++ b/src/DynamicData.Tests/Utilities/ListItemRecordingObserver.cs @@ -1,6 +1,3 @@ -using System.Collections.Generic; -using System.Reactive.Concurrency; - namespace DynamicData.Tests.Utilities; public sealed class ListItemRecordingObserver diff --git a/src/DynamicData.Tests/Utilities/ObservableEx.cs b/src/DynamicData.Tests/Utilities/ObservableEx.cs index df45f9f75..fdeb4ba65 100644 --- a/src/DynamicData.Tests/Utilities/ObservableEx.cs +++ b/src/DynamicData.Tests/Utilities/ObservableEx.cs @@ -1,7 +1,3 @@ -using System; -using System.Reactive.Concurrency; -using System.Reactive.Linq; - namespace DynamicData.Tests.Utilities; /// @@ -33,6 +29,6 @@ IDisposable HandleNext(IScheduler _, long counter) return sch.Schedule(0, nextInterval(), HandleNext); } - return ScheduleFirst(scheduler ?? DefaultScheduler.Instance); + return ScheduleFirst(scheduler ?? Scheduler.Default); }); } diff --git a/src/DynamicData.Tests/Utilities/ObservableExtensions.cs b/src/DynamicData.Tests/Utilities/ObservableExtensions.cs index acd336d0e..1e5d49749 100644 --- a/src/DynamicData.Tests/Utilities/ObservableExtensions.cs +++ b/src/DynamicData.Tests/Utilities/ObservableExtensions.cs @@ -1,14 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using System.Threading; - -using FluentAssertions; - namespace DynamicData.Tests.Utilities; internal static class ObservableExtensions diff --git a/src/DynamicData.Tests/Utilities/ObservableSpy.cs b/src/DynamicData.Tests/Utilities/ObservableSpy.cs index b4ae7bbc8..3108eeef1 100644 --- a/src/DynamicData.Tests/Utilities/ObservableSpy.cs +++ b/src/DynamicData.Tests/Utilities/ObservableSpy.cs @@ -1,9 +1,4 @@ -using System; -using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; using System.Runtime.InteropServices; -using System.Threading; using Xunit.Abstractions; namespace DynamicData.Tests.Utilities; diff --git a/src/DynamicData.Tests/Utilities/RandomizerExtensions.cs b/src/DynamicData.Tests/Utilities/RandomizerExtensions.cs index eb9f4a490..ad27a89a7 100644 --- a/src/DynamicData.Tests/Utilities/RandomizerExtensions.cs +++ b/src/DynamicData.Tests/Utilities/RandomizerExtensions.cs @@ -1,6 +1,4 @@ -using System; using System.Diagnostics; -using System.Reactive.Concurrency; using Bogus; namespace DynamicData.Tests.Utilities; diff --git a/src/DynamicData.Tests/Utilities/RawAnonymousObservable.cs b/src/DynamicData.Tests/Utilities/RawAnonymousObservable.cs index 06168b009..618ec6922 100644 --- a/src/DynamicData.Tests/Utilities/RawAnonymousObservable.cs +++ b/src/DynamicData.Tests/Utilities/RawAnonymousObservable.cs @@ -1,6 +1,4 @@ -using System; - -namespace DynamicData.Tests.Utilities; +namespace DynamicData.Tests.Utilities; internal static class RawAnonymousObservable { diff --git a/src/DynamicData.Tests/Utilities/RawAnonymousObserver.cs b/src/DynamicData.Tests/Utilities/RawAnonymousObserver.cs index 0567e5676..b5ad8a843 100644 --- a/src/DynamicData.Tests/Utilities/RawAnonymousObserver.cs +++ b/src/DynamicData.Tests/Utilities/RawAnonymousObserver.cs @@ -1,6 +1,4 @@ -using System; - -namespace DynamicData.Tests.Utilities; +namespace DynamicData.Tests.Utilities; internal static class RawAnonymousObserver { diff --git a/src/DynamicData.Tests/Utilities/ReactiveTestCompatibility.cs b/src/DynamicData.Tests/Utilities/ReactiveTestCompatibility.cs new file mode 100644 index 000000000..a337f0efc --- /dev/null +++ b/src/DynamicData.Tests/Utilities/ReactiveTestCompatibility.cs @@ -0,0 +1,165 @@ +using System.Diagnostics; + +namespace DynamicData.Tests.Utilities; + +public enum NotificationKind +{ + OnNext, + OnError, + OnCompleted +} + +public sealed class Notification +{ + private Notification(NotificationKind kind, T? value = default, Exception? exception = null) + { + Kind = kind; + Value = value!; + Exception = exception; + } + + public NotificationKind Kind { get; } + + public T Value { get; } + + public Exception? Exception { get; } + + public static Notification CreateOnNext(T value) => new(NotificationKind.OnNext, value); + + public static Notification CreateOnError(Exception error) => new(NotificationKind.OnError, exception: error); + + public static Notification CreateOnCompleted() => new(NotificationKind.OnCompleted); +} + +public static class Notification +{ + public static Notification CreateOnNext(T value) => Notification.CreateOnNext(value); + + public static Notification CreateOnError(Exception error) => Notification.CreateOnError(error); + + public static Notification CreateOnCompleted() => Notification.CreateOnCompleted(); +} + +public readonly record struct Recorded +{ + public Recorded(long time, T value) + { + Time = time; + Value = value; + } + + public long Time { get; } + + public T Value { get; } +} + +public sealed class NewThreadScheduler : ISequencer +{ + public DateTimeOffset Now => ThreadPoolScheduler.Instance.Now; + + public long Timestamp => ThreadPoolScheduler.Instance.Timestamp; + + public void Schedule(IWorkItem item) => ThreadPoolScheduler.Instance.Schedule(item); + + public void Schedule(IWorkItem item, long dueTimestamp) => ThreadPoolScheduler.Instance.Schedule(item, dueTimestamp); +} + +public sealed class TestScheduler : ISequencer +{ + private readonly List _queue = []; + private long _clockTicks; + private long? _originClockTicks; + private long _timestamp; + private long _nextId; + + public DateTimeOffset Now => new(_clockTicks, TimeSpan.Zero); + + public long Timestamp + { + get + { + _originClockTicks ??= _clockTicks; + return _timestamp; + } + } + + public void Schedule(IWorkItem item) => Schedule(item, _timestamp); + + public void Schedule(IWorkItem item, long dueTimestamp) + { + _queue.Add(new ScheduledItem(dueTimestamp, _nextId++, item)); + _queue.Sort(static (left, right) => + { + var due = left.DueTimestamp.CompareTo(right.DueTimestamp); + return due != 0 ? due : left.Id.CompareTo(right.Id); + }); + } + + public void AdvanceBy(long ticks) + { + if (ticks < 0) + { + throw new ArgumentOutOfRangeException(nameof(ticks)); + } + + AdvanceTo(_clockTicks + ticks); + } + + public void AdvanceTo(long ticks) + { + if (ticks < _clockTicks) + { + return; + } + + _originClockTicks ??= _queue.Count == 0 ? ticks : _clockTicks; + var targetTimestamp = ToTimestamp(ticks); + while (_queue.Count != 0 && _queue[0].DueTimestamp <= targetTimestamp) + { + var next = _queue[0]; + _queue.RemoveAt(0); + _timestamp = next.DueTimestamp; + _clockTicks = ToClockTicks(_timestamp); + next.Item.Execute(); + } + + _clockTicks = ticks; + _timestamp = targetTimestamp; + } + + public void Start() + { + while (_queue.Count != 0) + { + var next = _queue[0]; + AdvanceTo(ToClockTicks(next.DueTimestamp)); + } + } + + private long ToTimestamp(long ticks) + { + _originClockTicks ??= ticks; + var elapsedTicks = ticks - _originClockTicks.Value; + if (elapsedTicks <= 0) + { + return 0; + } + + var value = elapsedTicks / (double)TimeSpan.TicksPerSecond * Stopwatch.Frequency; + return value >= long.MaxValue ? long.MaxValue : (long)value; + } + + private long ToClockTicks(long timestamp) + { + var originClockTicks = _originClockTicks ?? 0; + if (timestamp <= 0) + { + return originClockTicks; + } + + var value = timestamp / (double)Stopwatch.Frequency * TimeSpan.TicksPerSecond; + return value >= long.MaxValue - originClockTicks ? long.MaxValue : originClockTicks + (long)value; + } + + private sealed record ScheduledItem(long DueTimestamp, long Id, IWorkItem Item); +} diff --git a/src/DynamicData.Tests/Utilities/RecordingObserverBase.cs b/src/DynamicData.Tests/Utilities/RecordingObserverBase.cs index 4c7ce3a0e..4e62e06eb 100644 --- a/src/DynamicData.Tests/Utilities/RecordingObserverBase.cs +++ b/src/DynamicData.Tests/Utilities/RecordingObserverBase.cs @@ -1,11 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Reactive.Concurrency; -using System.Reactive; -using System.Threading.Tasks; - -using Microsoft.Reactive.Testing; - namespace DynamicData.Tests.Utilities; // Using a custom implementing of IObserver<> to bypass normal RX safeguards, allowing invalid behaviors to be potentially tested for. diff --git a/src/DynamicData.Tests/Utilities/SelectManyExtensions.cs b/src/DynamicData.Tests/Utilities/SelectManyExtensions.cs index 61c5b3445..ebeb037fe 100644 --- a/src/DynamicData.Tests/Utilities/SelectManyExtensions.cs +++ b/src/DynamicData.Tests/Utilities/SelectManyExtensions.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace DynamicData.Tests.Utilities; +namespace DynamicData.Tests.Utilities; /// /// see http://www.superstarcoders.com/blogs/posts/recursive-select-in-c-sharp-and-linq.aspx diff --git a/src/DynamicData.Tests/Utilities/StressAddRemoveExtensions.cs b/src/DynamicData.Tests/Utilities/StressAddRemoveExtensions.cs index afe5f47d5..40ad4a30a 100644 --- a/src/DynamicData.Tests/Utilities/StressAddRemoveExtensions.cs +++ b/src/DynamicData.Tests/Utilities/StressAddRemoveExtensions.cs @@ -1,8 +1,3 @@ -using System; -using System.Linq; -using System.Reactive.Concurrency; -using System.Reactive.Linq; - namespace DynamicData.Tests.Utilities; internal static class StressAddRemoveExtensions @@ -12,7 +7,7 @@ public static IObservable StressAddRemove(this IObservable item where T : notnull => items.Do(i => onAdd(i, state)) .SelectMany(item => getRemoveTimeout?.Invoke(item) is TimeSpan ts - ? Observable.Timer(ts, scheduler ?? DefaultScheduler.Instance) + ? Observable.Timer(ts, scheduler ?? Scheduler.Default) .Do(_ => onRemove(item, state)) .Select(_ => item) : Observable.Return(item)); diff --git a/src/DynamicData.Tests/Utilities/TestSourceCache.cs b/src/DynamicData.Tests/Utilities/TestSourceCache.cs index bdd440a1c..62a89948b 100644 --- a/src/DynamicData.Tests/Utilities/TestSourceCache.cs +++ b/src/DynamicData.Tests/Utilities/TestSourceCache.cs @@ -1,11 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -using DynamicData.Kernel; - namespace DynamicData.Tests.Utilities; public sealed class TestSourceCache @@ -14,8 +6,8 @@ public sealed class TestSourceCache where TKey : notnull { private readonly IObservable _countChanged; - private readonly BehaviorSubject _error; - private readonly BehaviorSubject _hasCompleted; + private readonly StateSignal _error; + private readonly StateSignal _hasCompleted; private readonly SourceCache _source; public TestSourceCache(Func keySelector) diff --git a/src/DynamicData.Tests/Utilities/TestSourceList.cs b/src/DynamicData.Tests/Utilities/TestSourceList.cs index 6f3a1e2c2..3b496ccb0 100644 --- a/src/DynamicData.Tests/Utilities/TestSourceList.cs +++ b/src/DynamicData.Tests/Utilities/TestSourceList.cs @@ -1,10 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; - namespace DynamicData.Tests.Utilities; public sealed class TestSourceList @@ -12,10 +5,10 @@ public sealed class TestSourceList where T : notnull { private readonly IObservable _countChanged; - private readonly BehaviorSubject _error; - private readonly BehaviorSubject _hasCompleted; - private readonly Subject> _refreshRequested; - private readonly Subject> _refreshRequestedPreview; + private readonly StateSignal _error; + private readonly StateSignal _hasCompleted; + private readonly Signal> _refreshRequested; + private readonly Signal> _refreshRequestedPreview; private readonly SourceList _source; public TestSourceList() diff --git a/src/DynamicData.Tests/Utilities/UnsynchronizedNotificationException.cs b/src/DynamicData.Tests/Utilities/UnsynchronizedNotificationException.cs index 4994e2b35..3af2c35a7 100644 --- a/src/DynamicData.Tests/Utilities/UnsynchronizedNotificationException.cs +++ b/src/DynamicData.Tests/Utilities/UnsynchronizedNotificationException.cs @@ -1,6 +1,3 @@ -using System; -using System.Reactive; - namespace DynamicData.Tests.Utilities; public class UnsynchronizedNotificationException diff --git a/src/DynamicData.Tests/Utilities/ValueRecordingObserver.cs b/src/DynamicData.Tests/Utilities/ValueRecordingObserver.cs index 51c53964d..ffdbd3d75 100644 --- a/src/DynamicData.Tests/Utilities/ValueRecordingObserver.cs +++ b/src/DynamicData.Tests/Utilities/ValueRecordingObserver.cs @@ -1,6 +1,3 @@ -using System.Collections.Generic; -using System.Reactive.Concurrency; - namespace DynamicData.Tests.Utilities; public sealed class ValueRecordingObserver diff --git a/src/DynamicData.sln b/src/DynamicData.sln index e5ce72ac8..fca444658 100644 --- a/src/DynamicData.sln +++ b/src/DynamicData.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.1.11312.151 d18.0 +VisualStudioVersion = 18.1.11312.151 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DynamicData", "DynamicData\DynamicData.csproj", "{FE903921-6C55-40E3-9A16-4127ECACC12C}" EndProject @@ -24,6 +24,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DynamicData.Tests", "Dynami EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DynamicData.Benchmarks", "DynamicData.Benchmarks\DynamicData.Benchmarks.csproj", "{42566F48-05FC-483E-8B2F-D0EA4F28E870}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicData.Reactive", "DynamicData.Reactive\DynamicData.Reactive.csproj", "{B95120A9-0815-41A0-93B0-D6D8FCE02487}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -80,6 +82,22 @@ Global {42566F48-05FC-483E-8B2F-D0EA4F28E870}.Release|x64.Build.0 = Release|Any CPU {42566F48-05FC-483E-8B2F-D0EA4F28E870}.Release|x86.ActiveCfg = Release|Any CPU {42566F48-05FC-483E-8B2F-D0EA4F28E870}.Release|x86.Build.0 = Release|Any CPU + {B95120A9-0815-41A0-93B0-D6D8FCE02487}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B95120A9-0815-41A0-93B0-D6D8FCE02487}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B95120A9-0815-41A0-93B0-D6D8FCE02487}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {B95120A9-0815-41A0-93B0-D6D8FCE02487}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {B95120A9-0815-41A0-93B0-D6D8FCE02487}.Debug|x64.ActiveCfg = Debug|Any CPU + {B95120A9-0815-41A0-93B0-D6D8FCE02487}.Debug|x64.Build.0 = Debug|Any CPU + {B95120A9-0815-41A0-93B0-D6D8FCE02487}.Debug|x86.ActiveCfg = Debug|Any CPU + {B95120A9-0815-41A0-93B0-D6D8FCE02487}.Debug|x86.Build.0 = Debug|Any CPU + {B95120A9-0815-41A0-93B0-D6D8FCE02487}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B95120A9-0815-41A0-93B0-D6D8FCE02487}.Release|Any CPU.Build.0 = Release|Any CPU + {B95120A9-0815-41A0-93B0-D6D8FCE02487}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {B95120A9-0815-41A0-93B0-D6D8FCE02487}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {B95120A9-0815-41A0-93B0-D6D8FCE02487}.Release|x64.ActiveCfg = Release|Any CPU + {B95120A9-0815-41A0-93B0-D6D8FCE02487}.Release|x64.Build.0 = Release|Any CPU + {B95120A9-0815-41A0-93B0-D6D8FCE02487}.Release|x86.ActiveCfg = Release|Any CPU + {B95120A9-0815-41A0-93B0-D6D8FCE02487}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/DynamicData/Aggregation/AggregateEnumerator.cs b/src/DynamicData/Aggregation/AggregateEnumerator.cs index be9f93264..86ec50d72 100644 --- a/src/DynamicData/Aggregation/AggregateEnumerator.cs +++ b/src/DynamicData/Aggregation/AggregateEnumerator.cs @@ -1,16 +1,26 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Collections; -using System.Diagnostics.CodeAnalysis; -using DynamicData.Cache; +namespace DynamicData.Reactive.Aggregation; +#else namespace DynamicData.Aggregation; +#endif +/// +/// Provides members for the AggregateEnumerator class. +/// +/// The type of the T value. +/// The source value. internal sealed class AggregateEnumerator(IChangeSet source) : IAggregateChangeSet where T : notnull { + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. public IEnumerator> GetEnumerator() { foreach (var change in source) @@ -50,16 +60,33 @@ public IEnumerator> GetEnumerator() } } + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } +/// +/// Provides members for the AggregateEnumerator class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Same name, different generics.")] internal sealed class AggregateEnumerator(IChangeSet source) : IAggregateChangeSet where TObject : notnull where TKey : notnull { + /// + /// The _source field. + /// private readonly ChangeSet _source = source.ToConcreteType(); + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. public IEnumerator> GetEnumerator() { foreach (var change in _source) @@ -85,5 +112,9 @@ public IEnumerator> GetEnumerator() } } + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/DynamicData/Aggregation/AggregateItem.cs b/src/DynamicData/Aggregation/AggregateItem.cs index cef641f87..4da4fb37f 100644 --- a/src/DynamicData/Aggregation/AggregateItem.cs +++ b/src/DynamicData/Aggregation/AggregateItem.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Aggregation; +#else namespace DynamicData.Aggregation; +#endif /// /// An object representing added and removed items in a continuous aggregation stream. @@ -11,32 +16,12 @@ namespace DynamicData.Aggregation; /// /// Initializes a new instance of the struct. /// -/// The type. -/// The item. -public readonly struct AggregateItem(AggregateType type, TObject item) : IEquatable> +/// The type. +/// The item. +public readonly record struct AggregateItem(AggregateType Type, TObject Item) { - /// - /// Gets the type. - /// - public AggregateType Type { get; } = type; - - /// - /// Gets the item. - /// - public TObject Item { get; } = item; - - public static bool operator ==(in AggregateItem left, in AggregateItem right) => left.Equals(right); - - public static bool operator !=(in AggregateItem left, in AggregateItem right) => !(left == right); - - /// - public override bool Equals(object? obj) => obj is AggregateItem aggItem && Equals(aggItem); - - /// - public bool Equals(AggregateItem other) => - Type == other.Type && EqualityComparer.Default.Equals(Item, other.Item); - /// + /// The result of the operation. public override int GetHashCode() { var hashCode = -1719135621; diff --git a/src/DynamicData/Aggregation/AggregateType.cs b/src/DynamicData/Aggregation/AggregateType.cs index 776d18949..e277184d1 100644 --- a/src/DynamicData/Aggregation/AggregateType.cs +++ b/src/DynamicData/Aggregation/AggregateType.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Aggregation; +#else namespace DynamicData.Aggregation; +#endif /// /// The type of aggregation. diff --git a/src/DynamicData/Aggregation/AggregationEx.cs b/src/DynamicData/Aggregation/AggregationEx.cs index c90e35520..0d7de5395 100644 --- a/src/DynamicData/Aggregation/AggregationEx.cs +++ b/src/DynamicData/Aggregation/AggregationEx.cs @@ -1,11 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Aggregation; +#else namespace DynamicData.Aggregation; +#endif /// /// Aggregation extensions. @@ -23,7 +25,7 @@ public static IObservable> ForAggregation (IAggregateChangeSet)new AggregateEnumerator(changeSet)); } @@ -36,7 +38,7 @@ public static IObservable> ForAggregation> ForAggregation(this IObservable> source) where TObject : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Select(changeSet => (IAggregateChangeSet)new AggregateEnumerator(changeSet)); } @@ -48,8 +50,13 @@ public static IObservable> ForAggregation( /// The source. /// The invalidate. /// An observable which emits the value. - public static IObservable InvalidateWhen(this IObservable source, IObservable invalidate) => - invalidate.StartWith(Unit.Default).Select(_ => source).Switch().DistinctUntilChanged(); + public static IObservable InvalidateWhen(this IObservable source, IObservable invalidate) + { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(invalidate); + + return invalidate.StartWith(Unit.Default).Select(_ => source).Switch().DistinctUntilChanged(); + } /// /// Used to invalidate an aggregating stream. Used when there has been an inline change. @@ -59,8 +66,13 @@ public static IObservable InvalidateWhen(this IObservable source, IObse /// The source. /// The invalidate. /// An observable which emits the value. - public static IObservable InvalidateWhen(this IObservable source, IObservable invalidate) => - invalidate.StartWith(default(TTrigger)).Select(_ => source).Switch().DistinctUntilChanged(); + public static IObservable InvalidateWhen(this IObservable source, IObservable invalidate) + { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(invalidate); + + return invalidate.StartWith(default(TTrigger)).Select(_ => source).Switch().DistinctUntilChanged(); + } /// /// Applies an accumulator when items are added to and removed from specified stream, @@ -108,10 +120,10 @@ internal static IObservable Accumulate(this IOb /// An observable with the accumulated value. internal static IObservable Accumulate(this IObservable> source, TResult seed, Func accessor, Func addAction, Func removeAction) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - accessor.ThrowArgumentNullExceptionIfNull(nameof(accessor)); - addAction.ThrowArgumentNullExceptionIfNull(nameof(addAction)); - removeAction.ThrowArgumentNullExceptionIfNull(nameof(removeAction)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(accessor); + ArgumentExceptionHelper.ThrowIfNull(addAction); + ArgumentExceptionHelper.ThrowIfNull(removeAction); return source.Scan(seed, (state, changes) => changes.Aggregate(state, (current, aggregateItem) => diff --git a/src/DynamicData/Aggregation/Avg.cs b/src/DynamicData/Aggregation/Avg.cs index 85bb3b160..2dd2a61a6 100644 --- a/src/DynamicData/Aggregation/Avg.cs +++ b/src/DynamicData/Aggregation/Avg.cs @@ -1,12 +1,18 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -namespace DynamicData.Aggregation; +namespace DynamicData.Reactive.Aggregation; +#else -internal readonly struct Avg(int count, TValue sum) -{ - public int Count { get; } = count; +namespace DynamicData.Aggregation; +#endif - public TValue Sum { get; } = sum; -} +/// +/// Represents the Avg record. +/// +/// The type of the TValue value. +/// The Count value. +/// The Sum value. +internal readonly record struct Avg(int Count, TValue Sum); diff --git a/src/DynamicData/Aggregation/AvgEx.cs b/src/DynamicData/Aggregation/AvgEx.cs index f042bba71..9a6b3284d 100644 --- a/src/DynamicData/Aggregation/AvgEx.cs +++ b/src/DynamicData/Aggregation/AvgEx.cs @@ -1,10 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.Aggregation; +#else namespace DynamicData.Aggregation; +#endif /// /// Average extensions. @@ -301,7 +304,11 @@ public static IObservable Avg(this IObservable> source, /// /// An observable of averages. /// - public static IObservable Avg(this IObservable> source, Func valueSelector, int emptyValue = 0) => source.AvgCalc(valueSelector, emptyValue, (current, item) => new Avg(current.Count + 1, current.Sum + item), (current, item) => new Avg(current.Count - 1, current.Sum - item), values => values.Sum / (double)values.Count); + public static IObservable Avg(this IObservable> source, Func valueSelector, int emptyValue = 0) + { + ArgumentExceptionHelper.ThrowIfNull(source); + return source.AvgCalc(valueSelector, emptyValue, (current, item) => new Avg(current.Count + 1, current.Sum + item), (current, item) => new Avg(current.Count - 1, current.Sum - item), values => values.Sum / (double)values.Count); + } /// /// Continuous calculation of the average of the underlying data source. @@ -323,7 +330,11 @@ public static IObservable Avg(this IObservable> source, /// The value selector. /// The empty value. /// An observable of averages as a double value. - public static IObservable Avg(this IObservable> source, Func valueSelector, long emptyValue = 0) => source.AvgCalc(valueSelector, emptyValue, (current, item) => new Avg(current.Count + 1, current.Sum + item), (current, item) => new Avg(current.Count - 1, current.Sum - item), values => values.Sum / (double)values.Count); + public static IObservable Avg(this IObservable> source, Func valueSelector, long emptyValue = 0) + { + ArgumentExceptionHelper.ThrowIfNull(source); + return source.AvgCalc(valueSelector, emptyValue, (current, item) => new Avg(current.Count + 1, current.Sum + item), (current, item) => new Avg(current.Count - 1, current.Sum - item), values => values.Sum / (double)values.Count); + } /// /// Continuous calculation of the average of the underlying data source. @@ -347,7 +358,11 @@ public static IObservable Avg(this IObservable> source, /// /// An observable of averages. /// - public static IObservable Avg(this IObservable> source, Func valueSelector, double emptyValue = 0) => source.AvgCalc(valueSelector, emptyValue, (current, item) => new Avg(current.Count + 1, current.Sum + item), (current, item) => new Avg(current.Count - 1, current.Sum - item), values => values.Sum / (double)values.Count); + public static IObservable Avg(this IObservable> source, Func valueSelector, double emptyValue = 0) + { + ArgumentExceptionHelper.ThrowIfNull(source); + return source.AvgCalc(valueSelector, emptyValue, (current, item) => new Avg(current.Count + 1, current.Sum + item), (current, item) => new Avg(current.Count - 1, current.Sum - item), values => values.Sum / (double)values.Count); + } /// /// Continuous calculation of the average of the underlying data source. @@ -371,7 +386,11 @@ public static IObservable Avg(this IObservable> source, /// /// An observable of averages. /// - public static IObservable Avg(this IObservable> source, Func valueSelector, decimal emptyValue = 0) => source.AvgCalc(valueSelector, emptyValue, (current, item) => new Avg(current.Count + 1, current.Sum + item), (current, item) => new Avg(current.Count - 1, current.Sum - item), values => values.Sum / values.Count); + public static IObservable Avg(this IObservable> source, Func valueSelector, decimal emptyValue = 0) + { + ArgumentExceptionHelper.ThrowIfNull(source); + return source.AvgCalc(valueSelector, emptyValue, (current, item) => new Avg(current.Count + 1, current.Sum + item), (current, item) => new Avg(current.Count - 1, current.Sum - item), values => values.Sum / values.Count); + } /// /// Averages the specified value selector. @@ -393,7 +412,11 @@ public static IObservable Avg(this IObservable> source, /// /// An observable of averages. /// - public static IObservable Avg(this IObservable> source, Func valueSelector, float emptyValue = 0) => source.AvgCalc(valueSelector, emptyValue, (current, item) => new Avg(current.Count + 1, current.Sum + item), (current, item) => new Avg(current.Count - 1, current.Sum - item), values => values.Sum / values.Count); + public static IObservable Avg(this IObservable> source, Func valueSelector, float emptyValue = 0) + { + ArgumentExceptionHelper.ThrowIfNull(source); + return source.AvgCalc(valueSelector, emptyValue, (current, item) => new Avg(current.Count + 1, current.Sum + item), (current, item) => new Avg(current.Count - 1, current.Sum - item), values => values.Sum / values.Count); + } /// /// Continuous calculation of the average of the underlying data source. @@ -407,13 +430,26 @@ public static IObservable Avg(this IObservable> source, /// public static IObservable Avg(this IObservable> source, Func valueSelector, float emptyValue = 0) => source.Avg(t => valueSelector(t).GetValueOrDefault(), emptyValue); + /// + /// Executes the AvgCalc operation. + /// + /// The type of the TObject value. + /// The type of the TValue value. + /// The type of the TResult value. + /// The source value. + /// The valueSelector value. + /// The fallbackValue value. + /// The addAction value. + /// The removeAction value. + /// The resultAction value. + /// The result of the operation. private static IObservable AvgCalc(this IObservable> source, Func valueSelector, TResult fallbackValue, Func, TValue, Avg> addAction, Func, TValue, Avg> removeAction, Func, TResult> resultAction) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - valueSelector.ThrowArgumentNullExceptionIfNull(nameof(valueSelector)); - addAction.ThrowArgumentNullExceptionIfNull(nameof(addAction)); - removeAction.ThrowArgumentNullExceptionIfNull(nameof(removeAction)); - resultAction.ThrowArgumentNullExceptionIfNull(nameof(resultAction)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); + ArgumentExceptionHelper.ThrowIfNull(addAction); + ArgumentExceptionHelper.ThrowIfNull(removeAction); + ArgumentExceptionHelper.ThrowIfNull(resultAction); return source.Scan(default(Avg), (state, changes) => changes.Aggregate(state, (current, aggregateItem) => diff --git a/src/DynamicData/Aggregation/CountEx.cs b/src/DynamicData/Aggregation/CountEx.cs index 46a1f298b..a3bbb35d4 100644 --- a/src/DynamicData/Aggregation/CountEx.cs +++ b/src/DynamicData/Aggregation/CountEx.cs @@ -1,10 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.Aggregation; +#else namespace DynamicData.Aggregation; +#endif /// /// Count extensions. @@ -37,7 +40,11 @@ public static IObservable Count(this IObservableThe type of the object. /// The source. /// An observable which emits the count. - public static IObservable Count(this IObservable> source) => source.Accumulate(0, _ => 1, (current, increment) => current + increment, (current, increment) => current - increment); + public static IObservable Count(this IObservable> source) + { + ArgumentExceptionHelper.ThrowIfNull(source); + return source.Accumulate(0, _ => 1, (current, increment) => current + increment, (current, increment) => current - increment); + } /// /// Counts the total number of items in the underlying data source. diff --git a/src/DynamicData/Aggregation/IAggregateChangeSet.cs b/src/DynamicData/Aggregation/IAggregateChangeSet.cs index bd0b72e43..62720cc9d 100644 --- a/src/DynamicData/Aggregation/IAggregateChangeSet.cs +++ b/src/DynamicData/Aggregation/IAggregateChangeSet.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Aggregation; +#else namespace DynamicData.Aggregation; +#endif /// /// A change set which has been shaped for rapid online aggregations. diff --git a/src/DynamicData/Aggregation/MaxEx.cs b/src/DynamicData/Aggregation/MaxEx.cs index beb4d6e10..d51a2ada4 100644 --- a/src/DynamicData/Aggregation/MaxEx.cs +++ b/src/DynamicData/Aggregation/MaxEx.cs @@ -1,20 +1,32 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.Aggregation; +#else namespace DynamicData.Aggregation; +#endif /// /// Maximum and minimum value extensions. /// public static class MaxEx { - private enum MaxOrMin +/// +/// Defines values for the MaxOrMin enumeration. +/// +private enum MaxOrMin { + /// + /// The Max value. + /// Max, + /// + /// The Min value. + /// Min } @@ -31,7 +43,13 @@ private enum MaxOrMin /// public static IObservable Maximum(this IObservable> source, Func valueSelector, TResult emptyValue = default) where TObject : notnull - where TResult : struct, IComparable => source.ToChangesAndCollection().Calculate(valueSelector, MaxOrMin.Max, emptyValue); + where TResult : struct, IComparable + { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); + + return source.ToChangesAndCollection().Calculate(valueSelector, MaxOrMin.Max, emptyValue); + } /// /// Continually calculates the maximum value from the underlying data source. @@ -48,7 +66,13 @@ public static IObservable Maximum(this IObservable Maximum(this IObservable> source, Func valueSelector, TResult emptyValue = default) where TObject : notnull where TKey : notnull - where TResult : struct, IComparable => source.ToChangesAndCollection().Calculate(valueSelector, MaxOrMin.Max, emptyValue); + where TResult : struct, IComparable + { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); + + return source.ToChangesAndCollection().Calculate(valueSelector, MaxOrMin.Max, emptyValue); + } /// /// Continually calculates the minimum value from the underlying data source. @@ -61,7 +85,13 @@ public static IObservable Maximum(this IObserva /// A distinct observable of the minimums item. public static IObservable Minimum(this IObservable> source, Func valueSelector, TResult emptyValue = default) where TObject : notnull - where TResult : struct, IComparable => source.ToChangesAndCollection().Calculate(valueSelector, MaxOrMin.Min, emptyValue); + where TResult : struct, IComparable + { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); + + return source.ToChangesAndCollection().Calculate(valueSelector, MaxOrMin.Min, emptyValue); + } /// /// Continually calculates the minimum value from the underlying data source. @@ -78,13 +108,29 @@ public static IObservable Minimum(this IObservable Minimum(this IObservable> source, Func valueSelector, TResult emptyValue = default) where TObject : notnull where TKey : notnull - where TResult : struct, IComparable => source.ToChangesAndCollection().Calculate(valueSelector, MaxOrMin.Min, emptyValue); + where TResult : struct, IComparable + { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); + return source.ToChangesAndCollection().Calculate(valueSelector, MaxOrMin.Min, emptyValue); + } + + /// + /// Executes the Calculate operation. + /// + /// The type of the TObject value. + /// The type of the TResult value. + /// The source value. + /// The valueSelector value. + /// The maxOrMin value. + /// The emptyValue value. + /// The result of the operation. private static IObservable Calculate(this IObservable> source, Func valueSelector, MaxOrMin maxOrMin, TResult emptyValue = default) where TResult : struct, IComparable { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - valueSelector.ThrowArgumentNullExceptionIfNull(nameof(valueSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); return source.Scan( default(TResult?), @@ -143,11 +189,18 @@ private static IObservable Calculate(this IObservable }).Select(t => t ?? emptyValue).DistinctUntilChanged(); } + /// + /// Executes the ToChangesAndCollection operation. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The result of the operation. private static IObservable> ToChangesAndCollection(this IObservable> source) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Publish( shared => @@ -158,10 +211,16 @@ private static IObservable> ToChangesAndCollection }); } + /// + /// Executes the ToChangesAndCollection operation. + /// + /// The type of the TObject value. + /// The source value. + /// The result of the operation. private static IObservable> ToChangesAndCollection(this IObservable> source) where TObject : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Publish( shared => @@ -172,10 +231,22 @@ private static IObservable> ToChangesAndCollection }); } - private sealed class ChangesAndCollection(IAggregateChangeSet changes, IReadOnlyCollection collection) +/// +/// Provides members for the ChangesAndCollection class. +/// +/// The type of the T value. +/// The changes value. +/// The collection value. +private sealed class ChangesAndCollection(IAggregateChangeSet changes, IReadOnlyCollection collection) { + /// + /// Gets the Changes value. + /// public IAggregateChangeSet Changes { get; } = changes; + /// + /// Gets the Collection value. + /// public IReadOnlyCollection Collection { get; } = collection; } } diff --git a/src/DynamicData/Aggregation/StdDev.cs b/src/DynamicData/Aggregation/StdDev.cs index 81932c8d9..37b4e8706 100644 --- a/src/DynamicData/Aggregation/StdDev.cs +++ b/src/DynamicData/Aggregation/StdDev.cs @@ -1,14 +1,19 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -namespace DynamicData.Aggregation; - -internal readonly struct StdDev(int count, TValue sumOfItems, TValue sumOfSquares) -{ - public int Count { get; } = count; +namespace DynamicData.Reactive.Aggregation; +#else - public TValue SumOfItems { get; } = sumOfItems; +namespace DynamicData.Aggregation; +#endif - public TValue SumOfSquares { get; } = sumOfSquares; -} +/// +/// Represents the StdDev record. +/// +/// The type of the TValue value. +/// The Count value. +/// The SumOfItems value. +/// The SumOfSquares value. +internal readonly record struct StdDev(int Count, TValue SumOfItems, TValue SumOfSquares); diff --git a/src/DynamicData/Aggregation/StdDevEx.cs b/src/DynamicData/Aggregation/StdDevEx.cs index 30599f9ec..6c281af48 100644 --- a/src/DynamicData/Aggregation/StdDevEx.cs +++ b/src/DynamicData/Aggregation/StdDevEx.cs @@ -1,10 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.Aggregation; +#else namespace DynamicData.Aggregation; +#endif /// /// Extensions for calculating standard deviation. @@ -139,7 +142,11 @@ public static IObservable StdDev(this IObservableThe value selector. /// The fallback value. /// An observable which emits the standard deviation value. - public static IObservable StdDev(this IObservable> source, Func valueSelector, int fallbackValue = 0) => source.StdDevCalc(t => (long)valueSelector(t), fallbackValue, (current, item) => new StdDev(current.Count + 1, current.SumOfItems + item, current.SumOfSquares + (item * item)), (current, item) => new StdDev(current.Count - 1, current.SumOfItems - item, current.SumOfSquares - (item * item)), values => Math.Sqrt(values.SumOfSquares - ((values.SumOfItems * values.SumOfItems) / values.Count)) * (1.0d / (values.Count - 1))); + public static IObservable StdDev(this IObservable> source, Func valueSelector, int fallbackValue = 0) + { + ArgumentExceptionHelper.ThrowIfNull(source); + return source.StdDevCalc(t => (long)valueSelector(t), fallbackValue, (current, item) => new StdDev(current.Count + 1, current.SumOfItems + item, current.SumOfSquares + (item * item)), (current, item) => new StdDev(current.Count - 1, current.SumOfItems - item, current.SumOfSquares - (item * item)), values => Math.Sqrt(values.SumOfSquares - ((values.SumOfItems * values.SumOfItems) / values.Count)) * (1.0d / (values.Count - 1))); + } /// /// Continual computation of the standard deviation of the values in the underlying data source. @@ -149,8 +156,11 @@ public static IObservable StdDev(this IObservableThe value selector. /// The fallback value. /// An observable which emits the standard deviation value. - public static IObservable StdDev(this IObservable> source, Func valueSelector, long fallbackValue = 0) => - source.StdDevCalc(valueSelector, fallbackValue, (current, item) => new StdDev(current.Count + 1, current.SumOfItems + item, current.SumOfSquares + (item * item)), (current, item) => new StdDev(current.Count - 1, current.SumOfItems - item, current.SumOfSquares - (item * item)), values => Math.Sqrt(values.SumOfSquares - ((values.SumOfItems * values.SumOfItems) / values.Count)) * (1.0d / (values.Count - 1))); + public static IObservable StdDev(this IObservable> source, Func valueSelector, long fallbackValue = 0) + { + ArgumentExceptionHelper.ThrowIfNull(source); + return source.StdDevCalc(valueSelector, fallbackValue, (current, item) => new StdDev(current.Count + 1, current.SumOfItems + item, current.SumOfSquares + (item * item)), (current, item) => new StdDev(current.Count - 1, current.SumOfItems - item, current.SumOfSquares - (item * item)), values => Math.Sqrt(values.SumOfSquares - ((values.SumOfItems * values.SumOfItems) / values.Count)) * (1.0d / (values.Count - 1))); + } /// /// Continual computation of the standard deviation of the values in the underlying data source. @@ -160,8 +170,11 @@ public static IObservable StdDev(this IObservableThe value selector. /// The fallback value. /// An observable which emits the standard deviation value. - public static IObservable StdDev(this IObservable> source, Func valueSelector, decimal fallbackValue = 0M) => - source.StdDevCalc(valueSelector, fallbackValue, (current, item) => new StdDev(current.Count + 1, current.SumOfItems + item, current.SumOfSquares + (item * item)), (current, item) => new StdDev(current.Count - 1, current.SumOfItems - item, current.SumOfSquares - (item * item)), values => Sqrt(values.SumOfSquares - ((values.SumOfItems * values.SumOfItems) / values.Count)) * (1.0M / (values.Count - 1))); + public static IObservable StdDev(this IObservable> source, Func valueSelector, decimal fallbackValue = 0M) + { + ArgumentExceptionHelper.ThrowIfNull(source); + return source.StdDevCalc(valueSelector, fallbackValue, (current, item) => new StdDev(current.Count + 1, current.SumOfItems + item, current.SumOfSquares + (item * item)), (current, item) => new StdDev(current.Count - 1, current.SumOfItems - item, current.SumOfSquares - (item * item)), values => Sqrt(values.SumOfSquares - ((values.SumOfItems * values.SumOfItems) / values.Count)) * (1.0M / (values.Count - 1))); + } /// /// Continual computation of the standard deviation of the values in the underlying data source. @@ -171,7 +184,11 @@ public static IObservable StdDev(this IObservableThe value selector. /// The fallback value. /// An observable which emits the standard deviation value. - public static IObservable StdDev(this IObservable> source, Func valueSelector, double fallbackValue = 0) => source.StdDevCalc(valueSelector, fallbackValue, (current, item) => new StdDev(current.Count + 1, current.SumOfItems + item, current.SumOfSquares + (item * item)), (current, item) => new StdDev(current.Count - 1, current.SumOfItems - item, current.SumOfSquares - (item * item)), values => Math.Sqrt(values.SumOfSquares - ((values.SumOfItems * values.SumOfItems) / values.Count)) * (1.0d / (values.Count - 1))); + public static IObservable StdDev(this IObservable> source, Func valueSelector, double fallbackValue = 0) + { + ArgumentExceptionHelper.ThrowIfNull(source); + return source.StdDevCalc(valueSelector, fallbackValue, (current, item) => new StdDev(current.Count + 1, current.SumOfItems + item, current.SumOfSquares + (item * item)), (current, item) => new StdDev(current.Count - 1, current.SumOfItems - item, current.SumOfSquares - (item * item)), values => Math.Sqrt(values.SumOfSquares - ((values.SumOfItems * values.SumOfItems) / values.Count)) * (1.0d / (values.Count - 1))); + } /// /// Continual computation of the standard deviation of the values in the underlying data source. @@ -181,21 +198,44 @@ public static IObservable StdDev(this IObservableThe value selector. /// The fallback value. /// An observable which emits the standard deviation value. - public static IObservable StdDev(this IObservable> source, Func valueSelector, float fallbackValue = 0) => source.StdDevCalc(valueSelector, fallbackValue, (current, item) => new StdDev(current.Count + 1, current.SumOfItems + item, current.SumOfSquares + (item * item)), (current, item) => new StdDev(current.Count - 1, current.SumOfItems - item, current.SumOfSquares - (item * item)), values => Math.Sqrt(values.SumOfSquares - ((values.SumOfItems * values.SumOfItems) / values.Count)) * (1.0d / (values.Count - 1))); + public static IObservable StdDev(this IObservable> source, Func valueSelector, float fallbackValue = 0) + { + ArgumentExceptionHelper.ThrowIfNull(source); + return source.StdDevCalc(valueSelector, fallbackValue, (current, item) => new StdDev(current.Count + 1, current.SumOfItems + item, current.SumOfSquares + (item * item)), (current, item) => new StdDev(current.Count - 1, current.SumOfItems - item, current.SumOfSquares - (item * item)), values => Math.Sqrt(values.SumOfSquares - ((values.SumOfItems * values.SumOfItems) / values.Count)) * (1.0d / (values.Count - 1))); + } + /// + /// Executes the StdDevCalc operation. + /// + /// The type of the TObject value. + /// The type of the TValue value. + /// The type of the TResult value. + /// The source value. + /// The valueSelector value. + /// The fallbackValue value. + /// The addAction value. + /// The removeAction value. + /// The resultAction value. + /// The result of the operation. private static IObservable StdDevCalc(this IObservable> source, Func valueSelector, TResult fallbackValue, Func, TValue, StdDev> addAction, Func, TValue, StdDev> removeAction, Func, TResult> resultAction) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - valueSelector.ThrowArgumentNullExceptionIfNull(nameof(valueSelector)); - addAction.ThrowArgumentNullExceptionIfNull(nameof(addAction)); - removeAction.ThrowArgumentNullExceptionIfNull(nameof(removeAction)); - resultAction.ThrowArgumentNullExceptionIfNull(nameof(resultAction)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); + ArgumentExceptionHelper.ThrowIfNull(addAction); + ArgumentExceptionHelper.ThrowIfNull(removeAction); + ArgumentExceptionHelper.ThrowIfNull(resultAction); return source.Scan(default(StdDev), (state, changes) => changes.Aggregate(state, (current, aggregateItem) => aggregateItem.Type == AggregateType.Add ? addAction(current, valueSelector(aggregateItem.Item)) : removeAction(current, valueSelector(aggregateItem.Item)))).Select(values => values.Count < 2 ? fallbackValue : resultAction(values)); } + /// + /// Executes the Sqrt operation. + /// + /// The x value. + /// The epsilon value. + /// The result of the operation. private static decimal Sqrt(decimal x, decimal epsilon = 0.0M) { if (x < 0) diff --git a/src/DynamicData/Aggregation/SumEx.cs b/src/DynamicData/Aggregation/SumEx.cs index 6d3f59a9a..a476f2078 100644 --- a/src/DynamicData/Aggregation/SumEx.cs +++ b/src/DynamicData/Aggregation/SumEx.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Aggregation; +#else namespace DynamicData.Aggregation; +#endif /// /// Aggregation extensions. @@ -238,8 +243,8 @@ public static IObservable Sum(this IObservable> source, /// An observable which emits the summed value. public static IObservable Sum(this IObservable> source, Func valueSelector) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - valueSelector.ThrowArgumentNullExceptionIfNull(nameof(valueSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); return source.Accumulate(0, valueSelector, (current, value) => current + value, (current, value) => current - value); } @@ -251,7 +256,11 @@ public static IObservable Sum(this IObservable> s /// The source. /// The value selector. /// An observable which emits the summed value. - public static IObservable Sum(this IObservable> source, Func valueSelector) => source.Accumulate(0, t => valueSelector(t).GetValueOrDefault(), (current, value) => current + value, (current, value) => current - value); + public static IObservable Sum(this IObservable> source, Func valueSelector) + { + ArgumentExceptionHelper.ThrowIfNull(source); + return source.Accumulate(0, t => valueSelector(t).GetValueOrDefault(), (current, value) => current + value, (current, value) => current - value); + } /// /// Continual computes the sum of values matching the value selector. @@ -262,8 +271,8 @@ public static IObservable Sum(this IObservable> s /// An observable which emits the summed value. public static IObservable Sum(this IObservable> source, Func valueSelector) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - valueSelector.ThrowArgumentNullExceptionIfNull(nameof(valueSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); return source.Accumulate(0, valueSelector, (current, value) => current + value, (current, value) => current - value); } @@ -277,8 +286,8 @@ public static IObservable Sum(this IObservable> /// An observable which emits the summed value. public static IObservable Sum(this IObservable> source, Func valueSelector) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - valueSelector.ThrowArgumentNullExceptionIfNull(nameof(valueSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); return source.Accumulate(0L, t => valueSelector(t).ValueOr(0), (current, value) => current + value, (current, value) => current - value); } @@ -292,8 +301,8 @@ public static IObservable Sum(this IObservable> /// An observable which emits the summed value. public static IObservable Sum(this IObservable> source, Func valueSelector) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - valueSelector.ThrowArgumentNullExceptionIfNull(nameof(valueSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); return source.Accumulate(0, valueSelector, (current, value) => current + value, (current, value) => current - value); } @@ -307,8 +316,8 @@ public static IObservable Sum(this IObservable /// An observable which emits the summed value. public static IObservable Sum(this IObservable> source, Func valueSelector) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - valueSelector.ThrowArgumentNullExceptionIfNull(nameof(valueSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); return source.Accumulate(0D, t => valueSelector(t).ValueOr(0), (current, value) => current + value, (current, value) => current - value); } @@ -322,8 +331,8 @@ public static IObservable Sum(this IObservable /// An observable which emits the summed value. public static IObservable Sum(this IObservable> source, Func valueSelector) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - valueSelector.ThrowArgumentNullExceptionIfNull(nameof(valueSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); return source.Accumulate(0, valueSelector, (current, value) => current + value, (current, value) => current - value); } @@ -337,8 +346,8 @@ public static IObservable Sum(this IObservableAn observable which emits the summed value. public static IObservable Sum(this IObservable> source, Func valueSelector) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - valueSelector.ThrowArgumentNullExceptionIfNull(nameof(valueSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); return source.Accumulate(0M, t => valueSelector(t).ValueOr(0), (current, value) => current + value, (current, value) => current - value); } @@ -352,8 +361,8 @@ public static IObservable Sum(this IObservableAn observable which emits the summed value. public static IObservable Sum(this IObservable> source, Func valueSelector) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - valueSelector.ThrowArgumentNullExceptionIfNull(nameof(valueSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); return source.Accumulate(0, valueSelector, (current, value) => current + value, (current, value) => current - value); } @@ -367,8 +376,8 @@ public static IObservable Sum(this IObservable> /// An observable which emits the summed value. public static IObservable Sum(this IObservable> source, Func valueSelector) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - valueSelector.ThrowArgumentNullExceptionIfNull(nameof(valueSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); return source.Accumulate(0F, t => valueSelector(t).ValueOr(0), (current, value) => current + value, (current, value) => current - value); } diff --git a/src/DynamicData/Alias/ObservableCacheAlias.cs b/src/DynamicData/Alias/ObservableCacheAlias.cs index 12b0b3ca8..74b0d82c7 100644 --- a/src/DynamicData/Alias/ObservableCacheAlias.cs +++ b/src/DynamicData/Alias/ObservableCacheAlias.cs @@ -1,10 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; +namespace DynamicData.Reactive.Alias; +#else namespace DynamicData.Alias; +#endif /// /// Observable cache alias names. @@ -31,9 +34,9 @@ public static IObservable> Select> Select> SelectSafe> SelectSafe> SelectSafe, TKey>> SelectTree> Where(this I where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Filter(filter); } @@ -286,8 +289,8 @@ public static IObservable> Where(this I where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - predicateChanged.ThrowArgumentNullExceptionIfNull(nameof(predicateChanged)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(predicateChanged); return source.Filter(predicateChanged); } @@ -305,9 +308,9 @@ public static IObservable> Where(this I where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - predicateChanged.ThrowArgumentNullExceptionIfNull(nameof(predicateChanged)); - reapplyFilter.ThrowArgumentNullExceptionIfNull(nameof(reapplyFilter)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(predicateChanged); + ArgumentExceptionHelper.ThrowIfNull(reapplyFilter); return source.Filter(predicateChanged, reapplyFilter); } diff --git a/src/DynamicData/Alias/ObservableListAlias.cs b/src/DynamicData/Alias/ObservableListAlias.cs index ca7d9eb7b..ce8a98be1 100644 --- a/src/DynamicData/Alias/ObservableListAlias.cs +++ b/src/DynamicData/Alias/ObservableListAlias.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Alias; +#else namespace DynamicData.Alias; +#endif /// /// Observable cache alias names. @@ -26,8 +31,8 @@ public static IObservable> Select> SelectMany> SelectMany> Where(this IObservable> source, Func predicate) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - predicate.ThrowArgumentNullExceptionIfNull(nameof(predicate)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(predicate); return source.Filter(predicate); } @@ -81,8 +86,8 @@ public static IObservable> Where(this IObservable public static IObservable> Where(this IObservable> source, IObservable> predicate) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - predicate.ThrowArgumentNullExceptionIfNull(nameof(predicate)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(predicate); return source.Filter(predicate); } diff --git a/src/DynamicData/Attributes.cs b/src/DynamicData/Attributes.cs index dfb6a9e85..0cbef76bc 100644 --- a/src/DynamicData/Attributes.cs +++ b/src/DynamicData/Attributes.cs @@ -2,8 +2,7 @@ // 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.Runtime.CompilerServices; - [assembly: InternalsVisibleTo("DynamicData.Tests")] +[assembly: InternalsVisibleTo("DynamicData.Benchmarks")] [assembly: InternalsVisibleTo("DynamicData.ReactiveUI")] -[assembly: InternalsVisibleTo("DynamicData.Profile")] \ No newline at end of file +[assembly: InternalsVisibleTo("DynamicData.Profile")] diff --git a/src/DynamicData/Binding/AbstractNotifyPropertyChanged.cs b/src/DynamicData/Binding/AbstractNotifyPropertyChanged.cs index e24b07843..df56b0f1f 100644 --- a/src/DynamicData/Binding/AbstractNotifyPropertyChanged.cs +++ b/src/DynamicData/Binding/AbstractNotifyPropertyChanged.cs @@ -1,13 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Reactive.Disposables; -using System.Runtime.CompilerServices; +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// Base class for implementing notify property changes. diff --git a/src/DynamicData/Binding/BindPaged.cs b/src/DynamicData/Binding/BindPaged.cs index 1cbe39d35..8a1a51fad 100644 --- a/src/DynamicData/Binding/BindPaged.cs +++ b/src/DynamicData/Binding/BindPaged.cs @@ -1,19 +1,27 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Diagnostics.CodeAnalysis; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; - +#endif /* * Binding for the result of the SortAndPage operator * * (Direct lift from BindVirtualized). */ + +/// +/// Provides members for the BindPaged class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. +/// The targetList value. +/// The options value. internal sealed class BindPaged<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( IObservable>> source, IList targetList, @@ -21,10 +29,19 @@ internal sealed class BindPaged<[DynamicallyAccessedMembers(DynamicallyAccessedM where TObject : notnull where TKey : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => options is null ? UseContextSortOptions() : UseProvidedOptions(options.Value); + /// + /// Executes the UseProvidedOptions operation. + /// + /// The sortAndBindOptions value. + /// The result of the operation. private IObservable> UseProvidedOptions(SortAndBindOptions sortAndBindOptions) => source.Publish(changes => { @@ -35,6 +52,10 @@ private IObservable> UseProvidedOptions(SortAndBindOpt return changes.SortAndBind(targetList, comparedChanged, sortAndBindOptions); }); + /// + /// Executes the UseContextSortOptions operation. + /// + /// The result of the operation. private IObservable> UseContextSortOptions() => Observable.Create>(observer => { @@ -44,8 +65,8 @@ private IObservable> UseContextSortOptions() => // I tried to make this work without subjects but had issues // making the comparedChanged observable to fire. Probably a deadlock - var changesSubject = new Subject>(); - var comparerSubject = new ReplaySubject>(1); + var changesSubject = new Signal>(); + var comparerSubject = new ReplaySignal>(1); // once we have the initial values, publish as normal. var subsequent = shared diff --git a/src/DynamicData/Binding/BindVirtualized.cs b/src/DynamicData/Binding/BindVirtualized.cs index 37a3e1739..7ebe9f5d6 100644 --- a/src/DynamicData/Binding/BindVirtualized.cs +++ b/src/DynamicData/Binding/BindVirtualized.cs @@ -1,17 +1,25 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Diagnostics.CodeAnalysis; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; - +#endif /* * Binding for the result of the SortAndVirtualize operator */ + +/// +/// Provides members for the BindVirtualized class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. +/// The targetList value. +/// The options value. internal sealed class BindVirtualized<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( IObservable>> source, IList targetList, @@ -19,10 +27,19 @@ internal sealed class BindVirtualized<[DynamicallyAccessedMembers(DynamicallyAcc where TObject : notnull where TKey : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => options is null ? UseVirtualSortOptions() : UseProvidedOptions(options.Value); + /// + /// Executes the UseProvidedOptions operation. + /// + /// The sortAndBindOptions value. + /// The result of the operation. private IObservable> UseProvidedOptions(SortAndBindOptions sortAndBindOptions) => source.Publish(changes => { @@ -33,6 +50,10 @@ private IObservable> UseProvidedOptions(SortAndBindOpt return changes.SortAndBind(targetList, comparedChanged, sortAndBindOptions); }); + /// + /// Executes the UseVirtualSortOptions operation. + /// + /// The result of the operation. private IObservable> UseVirtualSortOptions() => Observable.Create>(observer => { @@ -42,8 +63,8 @@ private IObservable> UseVirtualSortOptions() => // I tried to make this work without subjects but had issues // making the comparedChanged observable to fire. Probably a deadlock - var changesSubject = new Subject>(); - var comparerSubject = new ReplaySubject>(1); + var changesSubject = new Signal>(); + var comparerSubject = new ReplaySignal>(1); // once we have the initial values, publish as normal. var subsequent = shared diff --git a/src/DynamicData/Binding/BindingListAdaptor.cs b/src/DynamicData/Binding/BindingListAdaptor.cs index 06cc41aff..cfbb66e61 100644 --- a/src/DynamicData/Binding/BindingListAdaptor.cs +++ b/src/DynamicData/Binding/BindingListAdaptor.cs @@ -1,14 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. #if SUPPORTS_BINDINGLIST -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using DynamicData.Cache; +#if REACTIVE_SHIM +using DynamicData.Reactive.Cache.Internal; +#else using DynamicData.Cache.Internal; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// Adaptor to reflect a change set into a binding list. @@ -22,13 +28,21 @@ namespace DynamicData.Binding; public class BindingListAdaptor<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(BindingList list, int refreshThreshold = BindingOptions.DefaultResetThreshold) : IChangeSetAdaptor where T : notnull { + /// + /// The _list field. + /// private readonly BindingList _list = list ?? throw new ArgumentNullException(nameof(list)); + + /// + /// The _loaded field. + /// private bool _loaded; /// + /// The changes value. public void Adapt(IChangeSet changes) { - changes.ThrowArgumentNullExceptionIfNull(nameof(changes)); + ArgumentExceptionHelper.ThrowIfNull(changes); if (changes.TotalChanges - changes.Refreshes > refreshThreshold || !_loaded) { @@ -60,15 +74,26 @@ public class BindingListAdaptor<[DynamicallyAccessedMembers(DynamicallyAccessedM where TObject : notnull where TKey : notnull { + /// + /// The _cache field. + /// private readonly Cache _cache = new(); + /// + /// The _list field. + /// private readonly BindingList _list = list ?? throw new ArgumentNullException(nameof(list)); + + /// + /// The _loaded field. + /// private bool _loaded; /// + /// The changes value. public void Adapt(IChangeSet changes) { - changes.ThrowArgumentNullExceptionIfNull(nameof(changes)); + ArgumentExceptionHelper.ThrowIfNull(changes); _cache.Clone(changes); if (changes.Count - changes.Refreshes > refreshThreshold || !_loaded) @@ -86,6 +111,11 @@ public void Adapt(IChangeSet changes) } } + /// + /// Executes the DoUpdate operation. + /// + /// The changes value. + /// The list value. private static void DoUpdate(IChangeSet changes, BindingList list) { foreach (var update in changes.ToConcreteType()) diff --git a/src/DynamicData/Binding/BindingListEventsSuspender.cs b/src/DynamicData/Binding/BindingListEventsSuspender.cs index 256d08fbe..1b061ab10 100644 --- a/src/DynamicData/Binding/BindingListEventsSuspender.cs +++ b/src/DynamicData/Binding/BindingListEventsSuspender.cs @@ -1,17 +1,29 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Reactive.Disposables; +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif +/// +/// Provides members for the BindingListEventsSuspender class. +/// +/// The type of the T value. internal sealed class BindingListEventsSuspender<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T> : IDisposable { + /// + /// The _cleanUp field. + /// private readonly IDisposable _cleanUp; + /// + /// Initializes a new instance of the class. + /// + /// The list value. public BindingListEventsSuspender(BindingList list) { list.RaiseListChangedEvents = false; @@ -24,5 +36,8 @@ public BindingListEventsSuspender(BindingList list) }); } + /// + /// Executes the Dispose operation. + /// public void Dispose() => _cleanUp.Dispose(); } diff --git a/src/DynamicData/Binding/BindingListEx.cs b/src/DynamicData/Binding/BindingListEx.cs index 1b5804bba..0031aef96 100644 --- a/src/DynamicData/Binding/BindingListEx.cs +++ b/src/DynamicData/Binding/BindingListEx.cs @@ -1,14 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Reactive; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// Extensions to convert an binding list into a dynamic stream. @@ -34,7 +33,7 @@ public static IObservable> ObserveCollectionC public static IObservable> ToObservableChangeSet<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this BindingList source) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return ToObservableChangeSet, T>(source); } @@ -55,8 +54,8 @@ public static IObservable> ObserveCollectionC where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - keySelector.ThrowArgumentNullExceptionIfNull(nameof(keySelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(keySelector); return ToObservableChangeSet, TObject>(source).AddKey(keySelector); } @@ -74,7 +73,7 @@ public static IObservable> ToObservableChangeSet(t where TCollection : IBindingList, IEnumerable where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return Observable.Create>( observer => @@ -133,12 +132,18 @@ public static IObservable> ToObservableChangeSet(t }); } + /// + /// Executes the Clone operation. + /// + /// The type of the T value. + /// The source value. + /// The changes value. internal static void Clone<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this BindingList source, IEnumerable> changes) where T : notnull { // ** Copied from ListEx for binding list specific changes - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - changes.ThrowArgumentNullExceptionIfNull(nameof(changes)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(changes); foreach (var item in changes) { @@ -146,6 +151,13 @@ public static IObservable> ToObservableChangeSet(t } } + /// + /// Executes the Clone operation. + /// + /// The type of the T value. + /// The source value. + /// The item value. + /// The equalityComparer value. private static void Clone<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this BindingList source, Change item, IEqualityComparer equalityComparer) where T : notnull { diff --git a/src/DynamicData/Binding/BindingOptions.cs b/src/DynamicData/Binding/BindingOptions.cs index eb2d30934..3a316863b 100644 --- a/src/DynamicData/Binding/BindingOptions.cs +++ b/src/DynamicData/Binding/BindingOptions.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// System wide default values for binding operators. diff --git a/src/DynamicData/Binding/ExpressionBuilder.cs b/src/DynamicData/Binding/ExpressionBuilder.cs index 47b241c96..707bb01e8 100644 --- a/src/DynamicData/Binding/ExpressionBuilder.cs +++ b/src/DynamicData/Binding/ExpressionBuilder.cs @@ -1,17 +1,29 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.ComponentModel; using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Linq; using System.Reflection; +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif +/// +/// Provides members for the ExpressionBuilder class. +/// internal static class ExpressionBuilder { + /// + /// Executes the GetMembers operation. + /// + /// The type of the TObject value. + /// The type of the TProperty value. + /// The source value. + /// The result of the operation. public static IEnumerable GetMembers(this Expression> source) { var memberExpression = source.Body as MemberExpression; @@ -22,6 +34,11 @@ public static IEnumerable GetMembers(this } } + /// + /// Executes the CreatePropertyChangedFactory operation. + /// + /// The source value. + /// The result of the operation. internal static Func> CreatePropertyChangedFactory(this Expression source) { if ((source is not MemberExpression { Member: PropertyInfo property }) @@ -37,6 +54,11 @@ internal static Func> CreatePropertyChangedFactory(thi .Select(static _ => Unit.Default); } + /// + /// Executes the CreateInvoker operation. + /// + /// The source value. + /// The result of the operation. internal static Func CreateInvoker(this Expression source) { switch (source) @@ -69,6 +91,13 @@ internal static Func> CreatePropertyChangedFactory(thi } } + /// + /// Executes the GetMember operation. + /// + /// The type of the TObject value. + /// The type of the TProperty value. + /// The expression value. + /// The result of the operation. internal static MemberInfo GetMember(this Expression> expression) { if (expression is null) @@ -79,6 +108,13 @@ internal static MemberInfo GetMember(this Expression + /// Executes the SplitIntoSteps operation. + /// + /// The type of the TObject value. + /// The type of the TProperty value. + /// The expression value. + /// The result of the operation. internal static IEnumerable SplitIntoSteps(this Expression> expression) { var currentStep = expression.Body; @@ -105,6 +141,13 @@ internal static IEnumerable SplitIntoSteps(this } } + /// + /// Executes the GetProperty operation. + /// + /// The type of the TObject value. + /// The type of the TProperty value. + /// The expression value. + /// The result of the operation. internal static PropertyInfo GetProperty(this Expression> expression) { if (expression.GetMember() is not PropertyInfo property) @@ -115,6 +158,11 @@ internal static PropertyInfo GetProperty(this Expression + /// Executes the GetProperty operation. + /// + /// The expression value. + /// The result of the operation. internal static PropertyInfo GetProperty(this MemberExpression expression) { if (expression.Member is not PropertyInfo property) @@ -125,6 +173,13 @@ internal static PropertyInfo GetProperty(this MemberExpression expression) return property; } + /// + /// Executes the ToCacheKey operation. + /// + /// The type of the TObject value. + /// The type of the TProperty value. + /// The expression value. + /// The result of the operation. internal static string ToCacheKey(this Expression> expression) where TObject : INotifyPropertyChanged { @@ -145,6 +200,11 @@ internal static string ToCacheKey(this Expression + /// Executes the GetMemberInfo operation. + /// + /// The lambda value. + /// The result of the operation. private static MemberInfo GetMemberInfo(LambdaExpression lambda) { if (lambda is null) diff --git a/src/DynamicData/Binding/IEvaluateAware.cs b/src/DynamicData/Binding/IEvaluateAware.cs index 6f4ce4206..06a5fa4e9 100644 --- a/src/DynamicData/Binding/IEvaluateAware.cs +++ b/src/DynamicData/Binding/IEvaluateAware.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// Implement on an object and use in conjunction with InvokeEvaluate operator diff --git a/src/DynamicData/Binding/IIndexAware.cs b/src/DynamicData/Binding/IIndexAware.cs index dc3a8d825..fd731f8ae 100644 --- a/src/DynamicData/Binding/IIndexAware.cs +++ b/src/DynamicData/Binding/IIndexAware.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// Implement on an object and use in conjunction with UpdateIndex operator diff --git a/src/DynamicData/Binding/INotifyCollectionChangedSuspender.cs b/src/DynamicData/Binding/INotifyCollectionChangedSuspender.cs index d0a75cc81..1eaab65ff 100644 --- a/src/DynamicData/Binding/INotifyCollectionChangedSuspender.cs +++ b/src/DynamicData/Binding/INotifyCollectionChangedSuspender.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// Represents an observable collection where collection changed and count notifications can be suspended. diff --git a/src/DynamicData/Binding/IObservableCollection.cs b/src/DynamicData/Binding/IObservableCollection.cs index 4358877b5..bfb5d69a8 100644 --- a/src/DynamicData/Binding/IObservableCollection.cs +++ b/src/DynamicData/Binding/IObservableCollection.cs @@ -3,9 +3,13 @@ // See the LICENSE file in the project root for full license information. using System.Collections.Specialized; -using System.ComponentModel; +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// An override of observable collection which allows the suspension of notifications. diff --git a/src/DynamicData/Binding/IObservableCollectionAdaptor.cs b/src/DynamicData/Binding/IObservableCollectionAdaptor.cs index a8f2d420b..0f6d29d2b 100644 --- a/src/DynamicData/Binding/IObservableCollectionAdaptor.cs +++ b/src/DynamicData/Binding/IObservableCollectionAdaptor.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// Represents an adaptor which is used to update observable collection from diff --git a/src/DynamicData/Binding/IObservableListEx.cs b/src/DynamicData/Binding/IObservableListEx.cs index cf7d3a37a..55cc77b5f 100644 --- a/src/DynamicData/Binding/IObservableListEx.cs +++ b/src/DynamicData/Binding/IObservableListEx.cs @@ -1,21 +1,23 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Collections.ObjectModel; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// -/// Extensions to convert a dynamic stream out to an . +/// Extensions to convert a dynamic stream out to an IObservableList<T>. /// public static class IObservableListEx { /// - /// Binds the results to the specified . Unlike - /// binding to a which loses the - /// ability to refresh items, binding to an . + /// Binds the results to the specified IObservableList<T>. Unlike + /// binding to a ReadOnlyObservableCollection<T> which loses the + /// ability to refresh items, binding to an IObservableList<T>. /// allows for refresh changes to be preserved and keeps the list read-only. /// /// The type of the object. @@ -26,10 +28,7 @@ public static class IObservableListEx public static IObservable> BindToObservableList(this IObservable> source, out IObservableList observableList) where TObject : notnull { - if (source is null) - { - throw new ArgumentNullException(nameof(source)); - } + ArgumentExceptionHelper.ThrowIfNull(source); // Load our source list with the change set. // Each change set we need to convert to remove the key. @@ -43,9 +42,9 @@ public static IObservable> BindToObservableList(thi } /// - /// Binds the results to the specified . Unlike - /// binding to a which loses the - /// ability to refresh items, binding to an . + /// Binds the results to the specified IObservableList<T>. Unlike + /// binding to a ReadOnlyObservableCollection<T> which loses the + /// ability to refresh items, binding to an IObservableList<T>. /// allows for refresh changes to be preserved and keeps the list read-only. /// /// The type of the object. @@ -58,10 +57,7 @@ public static IObservable> BindToObservableList> BindToObservableList - /// Binds the results to the specified . Unlike - /// binding to a which loses the - /// ability to refresh items, binding to an . + /// Binds the results to the specified IObservableList<T>. Unlike + /// binding to a ReadOnlyObservableCollection<T> which loses the + /// ability to refresh items, binding to an IObservableList<T>. /// allows for refresh changes to be preserved and keeps the list read-only. /// /// The type of the object. @@ -90,10 +86,7 @@ public static IObservable> BindToObservableList< where TObject : notnull where TKey : notnull { - if (source is null) - { - throw new ArgumentNullException(nameof(source)); - } + ArgumentExceptionHelper.ThrowIfNull(source); // Load our source list with the change set. // Each change set we need to convert to remove the key. @@ -135,14 +128,14 @@ public static IObservable> BindToObservableList< } /// - /// Converts a to + /// Converts a IChangeSet<TObject, TKey> to IChangeSet<TObject> /// which allows for binding a cache to a list. /// /// The type of the object. /// The type of the key. /// The source change set. /// The list needed to support refresh. - /// The down casted . + /// The down casted IChangeSet<TObject>. private static ChangeSet RemoveKey(this IChangeSet changeSetWithKey, IExtendedList list) where TObject : notnull where TKey : notnull diff --git a/src/DynamicData/Binding/ISortedObservableCollectionAdaptor.cs b/src/DynamicData/Binding/ISortedObservableCollectionAdaptor.cs index 4188c4ef0..168360394 100644 --- a/src/DynamicData/Binding/ISortedObservableCollectionAdaptor.cs +++ b/src/DynamicData/Binding/ISortedObservableCollectionAdaptor.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// Represents an adaptor which is used to update observable collection from diff --git a/src/DynamicData/Binding/NotifyPropertyChangedEx.cs b/src/DynamicData/Binding/NotifyPropertyChangedEx.cs index 3c3ac0924..3e9def4f2 100644 --- a/src/DynamicData/Binding/NotifyPropertyChangedEx.cs +++ b/src/DynamicData/Binding/NotifyPropertyChangedEx.cs @@ -2,11 +2,14 @@ // 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.ComponentModel; using System.Linq.Expressions; -using System.Reactive.Linq; +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// Property changes notification. @@ -23,9 +26,9 @@ public static class NotifyPropertyChangedEx public static IObservable WhenAnyPropertyChanged(this TObject source, params string[] propertiesToMonitor) where TObject : INotifyPropertyChanged { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); - return Observable.FromEventPattern(handler => source.PropertyChanged += handler, handler => source.PropertyChanged -= handler).Where(x => propertiesToMonitor.Length == 0 || propertiesToMonitor.Contains(x.EventArgs.PropertyName!)).Select(_ => source); + return Observable.FromEventPattern(handler => source.PropertyChanged += handler, handler => source.PropertyChanged -= handler).Where(x => propertiesToMonitor.Length == 0 || propertiesToMonitor.Contains(x.EventArgs.PropertyName!)).Select(_ => source); } /// @@ -45,9 +48,9 @@ public static class NotifyPropertyChangedEx public static IObservable WhenChanged(this TObject source, Expression> p1, Func resultSelector, Func? p1Fallback = null) where TObject : INotifyPropertyChanged { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - p1.ThrowArgumentNullExceptionIfNull(nameof(p1)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(p1); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return source.WhenChanged(p1, true, p1Fallback).Select(v => resultSelector(source, v)); } @@ -72,10 +75,10 @@ public static class NotifyPropertyChangedEx public static IObservable WhenChanged(this TObject source, Expression> p1, Expression> p2, Func resultSelector, Func? p1Fallback = null, Func? p2Fallback = null) where TObject : INotifyPropertyChanged { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - p1.ThrowArgumentNullExceptionIfNull(nameof(p1)); - p2.ThrowArgumentNullExceptionIfNull(nameof(p2)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(p1); + ArgumentExceptionHelper.ThrowIfNull(p2); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return source.WhenChanged(p1, true, p1Fallback).CombineLatest(source.WhenChanged(p2, true, p2Fallback), (v1, v2) => resultSelector(source, v1, v2)); } @@ -103,11 +106,11 @@ public static class NotifyPropertyChangedEx public static IObservable WhenChanged(this TObject source, Expression> p1, Expression> p2, Expression> p3, Func resultSelector, Func? p1Fallback = null, Func? p2Fallback = null, Func? p3Fallback = null) where TObject : INotifyPropertyChanged { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - p1.ThrowArgumentNullExceptionIfNull(nameof(p1)); - p2.ThrowArgumentNullExceptionIfNull(nameof(p2)); - p3.ThrowArgumentNullExceptionIfNull(nameof(p3)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(p1); + ArgumentExceptionHelper.ThrowIfNull(p2); + ArgumentExceptionHelper.ThrowIfNull(p3); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return source.WhenChanged(p1, true, p1Fallback).CombineLatest(source.WhenChanged(p2, true, p2Fallback), source.WhenChanged(p3, true, p3Fallback), (v1, v2, v3) => resultSelector(source, v1, v2, v3)); } @@ -138,12 +141,12 @@ public static class NotifyPropertyChangedEx public static IObservable WhenChanged(this TObject source, Expression> p1, Expression> p2, Expression> p3, Expression> p4, Func resultSelector, Func? p1Fallback = null, Func? p2Fallback = null, Func? p3Fallback = null, Func? p4Fallback = null) where TObject : INotifyPropertyChanged { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - p1.ThrowArgumentNullExceptionIfNull(nameof(p1)); - p2.ThrowArgumentNullExceptionIfNull(nameof(p2)); - p3.ThrowArgumentNullExceptionIfNull(nameof(p3)); - p4.ThrowArgumentNullExceptionIfNull(nameof(p4)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(p1); + ArgumentExceptionHelper.ThrowIfNull(p2); + ArgumentExceptionHelper.ThrowIfNull(p3); + ArgumentExceptionHelper.ThrowIfNull(p4); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return source.WhenChanged(p1, true, p1Fallback).CombineLatest(source.WhenChanged(p2, true, p2Fallback), source.WhenChanged(p3, true, p3Fallback), source.WhenChanged(p4, true, p4Fallback), (v1, v2, v3, v4) => resultSelector(source, v1, v2, v3, v4)); } @@ -177,13 +180,13 @@ public static class NotifyPropertyChangedEx public static IObservable WhenChanged(this TObject source, Expression> p1, Expression> p2, Expression> p3, Expression> p4, Expression> p5, Func resultSelector, Func? p1Fallback = null, Func? p2Fallback = null, Func? p3Fallback = null, Func? p4Fallback = null, Func? p5Fallback = null) where TObject : INotifyPropertyChanged { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - p1.ThrowArgumentNullExceptionIfNull(nameof(p1)); - p2.ThrowArgumentNullExceptionIfNull(nameof(p2)); - p3.ThrowArgumentNullExceptionIfNull(nameof(p3)); - p4.ThrowArgumentNullExceptionIfNull(nameof(p4)); - p5.ThrowArgumentNullExceptionIfNull(nameof(p5)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(p1); + ArgumentExceptionHelper.ThrowIfNull(p2); + ArgumentExceptionHelper.ThrowIfNull(p3); + ArgumentExceptionHelper.ThrowIfNull(p4); + ArgumentExceptionHelper.ThrowIfNull(p5); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return source.WhenChanged(p1, true, p1Fallback).CombineLatest(source.WhenChanged(p2, true, p2Fallback), source.WhenChanged(p3, true, p3Fallback), source.WhenChanged(p4, true, p4Fallback), source.WhenChanged(p5, true, p5Fallback), (v1, v2, v3, v4, v5) => resultSelector(source, v1, v2, v3, v4, v5)); } @@ -220,14 +223,14 @@ public static class NotifyPropertyChangedEx public static IObservable WhenChanged(this TObject source, Expression> p1, Expression> p2, Expression> p3, Expression> p4, Expression> p5, Expression> p6, Func resultSelector, Func? p1Fallback = null, Func? p2Fallback = null, Func? p3Fallback = null, Func? p4Fallback = null, Func? p5Fallback = null, Func? p6Fallback = null) where TObject : INotifyPropertyChanged { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - p1.ThrowArgumentNullExceptionIfNull(nameof(p1)); - p2.ThrowArgumentNullExceptionIfNull(nameof(p2)); - p3.ThrowArgumentNullExceptionIfNull(nameof(p3)); - p4.ThrowArgumentNullExceptionIfNull(nameof(p4)); - p5.ThrowArgumentNullExceptionIfNull(nameof(p5)); - p6.ThrowArgumentNullExceptionIfNull(nameof(p6)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(p1); + ArgumentExceptionHelper.ThrowIfNull(p2); + ArgumentExceptionHelper.ThrowIfNull(p3); + ArgumentExceptionHelper.ThrowIfNull(p4); + ArgumentExceptionHelper.ThrowIfNull(p5); + ArgumentExceptionHelper.ThrowIfNull(p6); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return source.WhenChanged(p1, true, p1Fallback).CombineLatest(source.WhenChanged(p2, true, p2Fallback), source.WhenChanged(p3, true, p3Fallback), source.WhenChanged(p4, true, p4Fallback), source.WhenChanged(p5, true, p5Fallback), source.WhenChanged(p6, true, p6Fallback), (v1, v2, v3, v4, v5, v6) => resultSelector(source, v1, v2, v3, v4, v5, v6)); } @@ -248,8 +251,8 @@ public static class NotifyPropertyChangedEx public static IObservable> WhenPropertyChanged(this TObject source, Expression> propertyAccessor, bool notifyOnInitialValue = true, Func? fallbackValue = null) where TObject : INotifyPropertyChanged { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - propertyAccessor.ThrowArgumentNullExceptionIfNull(nameof(propertyAccessor)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(propertyAccessor); var cache = ObservablePropertyFactoryCache.Instance.GetFactory(propertyAccessor); return cache.Create(source, notifyOnInitialValue).Where(pv => !pv.UnobtainableValue || (pv.UnobtainableValue && fallbackValue is not null)); @@ -270,12 +273,19 @@ public static IObservable> WhenPropertyChanged public static IObservable WhenValueChanged(this TObject source, Expression> propertyAccessor, bool notifyOnInitialValue = true, Func? fallbackValue = null) where TObject : INotifyPropertyChanged { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - propertyAccessor.ThrowArgumentNullExceptionIfNull(nameof(propertyAccessor)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(propertyAccessor); return source.WhenChanged(propertyAccessor, notifyOnInitialValue, fallbackValue); } + /// + /// Executes the GetFactory operation. + /// + /// The type of the TObject value. + /// The type of the TProperty value. + /// The expression value. + /// The result of the operation. internal static Func>> GetFactory(this Expression> expression) where TObject : INotifyPropertyChanged { @@ -283,6 +293,16 @@ internal static Func factory.Create(t, initial); } + /// + /// Executes the WhenChanged operation. + /// + /// The type of the TObject value. + /// The type of the TProperty value. + /// The source value. + /// The expression value. + /// The notifyInitial value. + /// The fallbackValue value. + /// The result of the operation. internal static IObservable WhenChanged(this TObject source, Expression> expression, bool notifyInitial = true, Func? fallbackValue = null) where TObject : INotifyPropertyChanged { diff --git a/src/DynamicData/Binding/Observable.cs b/src/DynamicData/Binding/Observable.cs deleted file mode 100644 index 3e439d160..000000000 --- a/src/DynamicData/Binding/Observable.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. -// 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.Reactive.Linq; - -namespace DynamicData.Binding; - -internal static class Observable -{ - public static IObservable Default { get; } = Observable.Return(default); - - public static IObservable Empty { get; } = Observable.Empty(); - - public static IObservable Never { get; } = Observable.Never(); -} diff --git a/src/DynamicData/Binding/ObservableCollectionAdaptor.cs b/src/DynamicData/Binding/ObservableCollectionAdaptor.cs index 4715aba25..a80e6acfe 100644 --- a/src/DynamicData/Binding/ObservableCollectionAdaptor.cs +++ b/src/DynamicData/Binding/ObservableCollectionAdaptor.cs @@ -1,12 +1,20 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Diagnostics.CodeAnalysis; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// Adaptor to reflect a change set into an observable list. @@ -19,7 +27,7 @@ namespace DynamicData.Binding; /// The number of changes before a Reset event is used. /// Use replace instead of remove / add for updates. /// Should a reset be fired for a first time load.This option is due to historic reasons where a reset would be fired for the first time load regardless of the number of changes. -/// collection. +/// collection. public class ObservableCollectionAdaptor(IObservableCollection collection, int refreshThreshold, #pragma warning disable CS9113 // Parameter is unread. bool allowReplace = true, @@ -28,7 +36,14 @@ public class ObservableCollectionAdaptor(IObservableCollection collection, where T : notnull { + /// + /// The _collection field. + /// private readonly IObservableCollection _collection = collection ?? throw new ArgumentNullException(nameof(collection)); + + /// + /// The _loaded field. + /// private bool _loaded; /// @@ -56,7 +71,7 @@ public ObservableCollectionAdaptor(IObservableCollection collection, BindingO /// The changes. public void Adapt(IChangeSet changes) { - changes.ThrowArgumentNullExceptionIfNull(nameof(changes)); + ArgumentExceptionHelper.ThrowIfNull(changes); if (changes.TotalChanges - changes.Refreshes > refreshThreshold || (!_loaded && resetOnFirstTimeLoad)) { @@ -91,7 +106,14 @@ public class ObservableCollectionAdaptor(int refreshThreshold = 2 where TObject : notnull where TKey : notnull { + /// + /// The _cache field. + /// private readonly Cache _cache = new(); + + /// + /// The _loaded field. + /// private bool _loaded; /// @@ -110,8 +132,8 @@ public ObservableCollectionAdaptor(BindingOptions options) /// The collection. public void Adapt(IChangeSet changes, IObservableCollection collection) { - changes.ThrowArgumentNullExceptionIfNull(nameof(changes)); - collection.ThrowArgumentNullExceptionIfNull(nameof(collection)); + ArgumentExceptionHelper.ThrowIfNull(changes); + ArgumentExceptionHelper.ThrowIfNull(collection); _cache.Clone(changes); @@ -132,6 +154,11 @@ public void Adapt(IChangeSet changes, IObservableCollection + /// Executes the DoUpdate operation. + /// + /// The changes value. + /// The list value. private void DoUpdate(IChangeSet changes, IObservableCollection list) { foreach (var change in changes.ToConcreteType()) diff --git a/src/DynamicData/Binding/ObservableCollectionEx.cs b/src/DynamicData/Binding/ObservableCollectionEx.cs index 9b181ddeb..c30a68039 100644 --- a/src/DynamicData/Binding/ObservableCollectionEx.cs +++ b/src/DynamicData/Binding/ObservableCollectionEx.cs @@ -2,12 +2,14 @@ // 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.Collections.ObjectModel; using System.Collections.Specialized; -using System.Reactive; -using System.Reactive.Linq; +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// Extensions to convert an observable collection into a dynamic stream. @@ -32,7 +34,7 @@ public static class ObservableCollectionEx public static IObservable> ToObservableChangeSet(this ObservableCollection source) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return ToObservableChangeSet, T>(source); } @@ -53,8 +55,8 @@ public static IObservable> ToObservableChangeSet, TObject>(source).AddKey(keySelector); } @@ -70,7 +72,7 @@ public static IObservable> ToObservableChangeSet> ToObservableChangeSet(this ReadOnlyObservableCollection source) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return ToObservableChangeSet, T>(source); } @@ -91,8 +93,8 @@ public static IObservable> ToObservableChangeSet, TObject>(source).AddKey(keySelector); } @@ -106,12 +108,12 @@ public static IObservable> ToObservableChangeSetThe source. /// An observable that emits the change set. /// source. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Roslynator", "RCS1146:Use conditional access.", Justification = "net 7.0 has error when conditional access is used.")] + [SuppressMessage("Roslynator", "RCS1146:Use conditional access.", Justification = "net 7.0 has error when conditional access is used.")] public static IObservable> ToObservableChangeSet(this TCollection source) where TCollection : INotifyCollectionChanged, IEnumerable where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return Observable.Create>( observer => diff --git a/src/DynamicData/Binding/ObservableCollectionExtended.cs b/src/DynamicData/Binding/ObservableCollectionExtended.cs index f99d9b9b4..306600d4c 100644 --- a/src/DynamicData/Binding/ObservableCollectionExtended.cs +++ b/src/DynamicData/Binding/ObservableCollectionExtended.cs @@ -2,12 +2,14 @@ // 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.Collections.ObjectModel; using System.Collections.Specialized; -using System.ComponentModel; -using System.Reactive.Disposables; +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// An override of observable collection which allows the suspension of notifications. @@ -15,8 +17,14 @@ namespace DynamicData.Binding; /// The type of the item. public class ObservableCollectionExtended : ObservableCollection, IObservableCollection, IExtendedList { + /// + /// The _suspendCount field. + /// private bool _suspendCount; + /// + /// The _suspendNotifications field. + /// private bool _suspendNotifications; /// @@ -51,7 +59,7 @@ public ObservableCollectionExtended(IEnumerable collection) /// is null. public void AddRange(IEnumerable collection) { - collection.ThrowArgumentNullExceptionIfNull(nameof(collection)); + ArgumentExceptionHelper.ThrowIfNull(collection); foreach (var item in collection) { @@ -60,7 +68,7 @@ public void AddRange(IEnumerable collection) } /// - /// Inserts the elements of a collection into the at the specified index. + /// Inserts the elements of a collection into the ObservableCollectionExtended<T> at the specified index. /// /// Inserts the items at the specified index. /// The zero-based index at which the new elements should be inserted. @@ -68,7 +76,7 @@ public void AddRange(IEnumerable collection) /// is less than 0.-or- is greater than Count. public void InsertRange(IEnumerable collection, int index) { - collection.ThrowArgumentNullExceptionIfNull(nameof(collection)); + ArgumentExceptionHelper.ThrowIfNull(collection); foreach (var item in collection) { @@ -82,7 +90,7 @@ public void InsertRange(IEnumerable collection, int index) /// The items. public void Load(IEnumerable items) { - items.ThrowArgumentNullExceptionIfNull(nameof(items)); + ArgumentExceptionHelper.ThrowIfNull(items); CheckReentrancy(); Clear(); @@ -94,9 +102,9 @@ public void Load(IEnumerable items) } /// - /// Removes a range of elements from the . + /// Removes a range of elements from the ObservableCollectionExtended<T>. /// - /// The zero-based starting index of the range of elements to remove.The number of elements to remove. is less than 0.-or- is less than 0. and do not denote a valid range of elements in the . + /// The zero-based starting index of the range of elements to remove.The number of elements to remove. is less than 0.-or- is less than 0. and do not denote a valid range of elements in the List<T>. public void RemoveRange(int index, int count) { for (var i = 0; i < count; i++) @@ -164,7 +172,7 @@ protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) /// The instance containing the event data. protected override void OnPropertyChanged(PropertyChangedEventArgs e) { - e.ThrowArgumentNullExceptionIfNull(nameof(e)); + ArgumentExceptionHelper.ThrowIfNull(e); if (_suspendCount && e.PropertyName == "Count") { diff --git a/src/DynamicData/Binding/ObservablePropertyFactory.cs b/src/DynamicData/Binding/ObservablePropertyFactory.cs index 4dd3fbd68..66392e36b 100644 --- a/src/DynamicData/Binding/ObservablePropertyFactory.cs +++ b/src/DynamicData/Binding/ObservablePropertyFactory.cs @@ -1,23 +1,34 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.Generic; -using System.ComponentModel; using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; +#if REACTIVE_SHIM -using DynamicData.Internal; +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif +/// +/// Provides members for the ObservablePropertyFactory class. +/// +/// The type of the TObject value. +/// The type of the TProperty value. internal sealed class ObservablePropertyFactory where TObject : INotifyPropertyChanged { + /// + /// The _factory field. + /// private readonly Func>> _factory; + /// + /// Initializes a new instance of the class. + /// + /// The valueAccessor value. + /// The chain value. public ObservablePropertyFactory(Func valueAccessor, ObservablePropertyPart[] chain) { // chain is leaf-first (output of SplitIntoSteps). Reverse once to root-to-leaf order. @@ -26,6 +37,10 @@ public ObservablePropertyFactory(Func valueAccessor, Observa observer => new DeepChainSubscription(observer, source, rootToLeaf, valueAccessor, notifyInitial)); } + /// + /// Initializes a new instance of the class. + /// + /// The expression value. public ObservablePropertyFactory(Expression> expression) { // Shallow form: single property, no chain. Used when depth == 1. Skips SharedDeliveryQueue @@ -37,8 +52,13 @@ public ObservablePropertyFactory(Expression> expression observer => new SinglePropertySubscription(observer, source, memberName, accessor, notifyInitial)); } + /// + /// Executes the Create operation. + /// + /// The source value. + /// The notifyInitial value. + /// The result of the operation. public IObservable> Create(TObject source, bool notifyInitial) => _factory(source, notifyInitial); - // Single-property subscription. Attaches a direct PropertyChanged handler and forwards every // event through a DeliveryQueue. Used for x => x.Prop (depth == 1) where SharedDeliveryQueue // and Observable.FromEventPattern would be needless overhead on the hot path. @@ -47,13 +67,40 @@ public ObservablePropertyFactory(Expression> expression // is no equality dedup at the subscribe seam: a same-valued PropertyChanged firing in the // subscribe window is a legitimate event and must be delivered. The "never drop events" // contract takes precedence over avoiding a benign duplicate. - private sealed class SinglePropertySubscription : IDisposable + +/// +/// Provides members for the SinglePropertySubscription class. +/// +private sealed class SinglePropertySubscription : IDisposable { + /// + /// The _source field. + /// private readonly TObject _source; + + /// + /// The _memberName field. + /// private readonly string _memberName; + + /// + /// The _accessor field. + /// private readonly Func _accessor; + + /// + /// The _queue field. + /// private readonly DeliveryQueue> _queue; + /// + /// Initializes a new instance of the class. + /// + /// The observer value. + /// The source value. + /// The memberName value. + /// The accessor value. + /// The notifyInitial value. public SinglePropertySubscription( IObserver> observer, TObject source, @@ -75,12 +122,20 @@ public SinglePropertySubscription( } } + /// + /// Executes the Dispose operation. + /// public void Dispose() { _source.PropertyChanged -= OnPropertyChanged; _queue.Dispose(); } + /// + /// Executes the OnPropertyChanged operation. + /// + /// The sender value. + /// The args value. private void OnPropertyChanged(object? sender, PropertyChangedEventArgs args) { if (args.PropertyName == _memberName) @@ -88,12 +143,15 @@ private void OnPropertyChanged(object? sender, PropertyChangedEventArgs args) EmitCurrent(); } } - // Reads the current property value and forwards it through the queue. The accessor is // user code and may throw; that exception routes to OnError. The downstream OnNext call // is NOT wrapped: per the Rx contract, if the user observer throws, the exception // propagates back to whoever invoked the PropertyChanged setter, matching what a plain - // Subject.OnNext would do. + // Signal.OnNext would do. + + /// + /// Executes the EmitCurrent operation. + /// private void EmitCurrent() { PropertyValue value; @@ -110,7 +168,6 @@ private void EmitCurrent() _queue.OnNext(value); } } - // Deep-chain subscription. Encapsulates the SharedDeliveryQueue + sub-queues + per-level // SerialDisposable slots so fields are assigned in well-defined order and the signal sub-queue // never needs a forward null bootstrap. @@ -137,25 +194,75 @@ private void EmitCurrent() // notifyInitial only controls whether ProcessSignal emits the current chain value during // the InitialSetupSignal pass. There is no equality dedup at the subscribe seam: every // chain event is delivered. - private sealed class DeepChainSubscription : IDisposable + +/// +/// Provides members for the DeepChainSubscription class. +/// +private sealed class DeepChainSubscription : IDisposable { // Sentinel signal value enqueued during subscribe to perform the initial chain setup // from inside the SharedDeliveryQueue drainer. + + /// + /// The InitialSetupSignal field. + /// private const int InitialSetupSignal = -1; + /// + /// The _source field. + /// private readonly TObject _source; + + /// + /// The _rootToLeaf field. + /// private readonly ObservablePropertyPart[] _rootToLeaf; + + /// + /// The _valueAccessor field. + /// private readonly Func _valueAccessor; + + /// + /// The _notifyInitial field. + /// private readonly bool _notifyInitial; + + /// + /// The _sharedQueue field. + /// private readonly SharedDeliveryQueue _sharedQueue; + + /// + /// The _userSub field. + /// private readonly DeliverySubQueue> _userSub; + + /// + /// The _signalSub field. + /// private readonly DeliverySubQueue _signalSub; - private readonly SerialDisposable[] _levelSlots; + /// + /// The _levelSlots field. + /// + private readonly SerialDisposable[] _levelSlots; // Pre-allocated per-level notifier callbacks. Indexed by level. ResubscribeFrom reuses // these instead of allocating a fresh closure per re-walk. + + /// + /// The _levelCallbacks field. + /// private readonly Action[] _levelCallbacks; + /// + /// Initializes a new instance of the class. + /// + /// The observer value. + /// The source value. + /// The rootToLeaf value. + /// The valueAccessor value. + /// The notifyInitial value. public DeepChainSubscription( IObserver> observer, TObject source, @@ -194,6 +301,9 @@ public DeepChainSubscription( _signalSub.OnNext(InitialSetupSignal); } + /// + /// Executes the Dispose operation. + /// public void Dispose() { foreach (var slot in _levelSlots) @@ -206,12 +316,16 @@ public void Dispose() _sharedQueue.Dispose(); } + /// + /// Executes the ProcessSignal operation. + /// + /// The level value. private void ProcessSignal(int level) { // Drainer thread. The chain walk (Invoker / notifier Factory / ReadCurrent's accessor) // is user code and may throw; those exceptions route to OnError. The downstream // OnNext call is NOT wrapped: per the Rx contract, if the user observer throws, the - // exception propagates back through the drainer, matching what a plain Subject + // exception propagates back through the drainer, matching what a plain Signal // would do. // // The two cases (initial setup vs level-fire) collapse to: @@ -239,6 +353,10 @@ private void ProcessSignal(int level) _userSub.OnNext(value); } + /// + /// Executes the ResubscribeFrom operation. + /// + /// The startLevel value. private void ResubscribeFrom(int startLevel) { var depth = _rootToLeaf.Length; @@ -276,8 +394,12 @@ private void ResubscribeFrom(int startLevel) value = _rootToLeaf[i].Invoker(value); } } - // Root-to-leaf chain walk. Stops at null and returns an unobtainable PropertyValue. + + /// + /// Executes the ReadCurrent operation. + /// + /// The result of the operation. private PropertyValue ReadCurrent() { object? value = _source; diff --git a/src/DynamicData/Binding/ObservablePropertyFactoryCache.cs b/src/DynamicData/Binding/ObservablePropertyFactoryCache.cs index c0e67dd3d..ddfa8afb9 100644 --- a/src/DynamicData/Binding/ObservablePropertyFactoryCache.cs +++ b/src/DynamicData/Binding/ObservablePropertyFactoryCache.cs @@ -3,21 +3,44 @@ // See the LICENSE file in the project root for full license information. using System.Collections.Concurrent; -using System.ComponentModel; using System.Linq.Expressions; +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif +/// +/// Provides members for the ObservablePropertyFactoryCache class. +/// internal sealed class ObservablePropertyFactoryCache { + /// + /// The Instance field. + /// public static readonly ObservablePropertyFactoryCache Instance = new(); + /// + /// The _factories field. + /// private readonly ConcurrentDictionary _factories = new(); + /// + /// Initializes a new instance of the class. + /// private ObservablePropertyFactoryCache() { } + /// + /// Executes the GetFactory operation. + /// + /// The type of the TObject value. + /// The type of the TProperty value. + /// The expression value. + /// The result of the operation. public ObservablePropertyFactory GetFactory(Expression> expression) where TObject : INotifyPropertyChanged { diff --git a/src/DynamicData/Binding/ObservablePropertyPart.cs b/src/DynamicData/Binding/ObservablePropertyPart.cs index 9b4b1ab09..ca7e7b142 100644 --- a/src/DynamicData/Binding/ObservablePropertyPart.cs +++ b/src/DynamicData/Binding/ObservablePropertyPart.cs @@ -4,14 +4,28 @@ using System.Diagnostics; using System.Linq.Expressions; -using System.Reactive; +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif +/// +/// Provides members for the ObservablePropertyPart class. +/// +/// The expression value. [DebuggerDisplay("ObservablePropertyPart<{" + nameof(expression) + "}>")] internal sealed class ObservablePropertyPart(Expression expression) { + /// + /// Gets the Invoker value. + /// public Func Invoker { get; } = expression.CreateInvoker(); + /// + /// Gets the Factory value. + /// public Func> Factory { get; } = expression.CreatePropertyChangedFactory(); } diff --git a/src/DynamicData/Binding/PropertyValue.cs b/src/DynamicData/Binding/PropertyValue.cs index 1f782386f..cff3040ad 100644 --- a/src/DynamicData/Binding/PropertyValue.cs +++ b/src/DynamicData/Binding/PropertyValue.cs @@ -1,15 +1,23 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// Container holding sender and latest property value. /// +/// Gets the Sender. +/// Gets latest observed value. +/// Gets a value indicating whether flag to indicated that the value was unobtainable when observing a deeply nested struct. /// The type of the object. /// The type of the value. -public sealed class PropertyValue : IEquatable> +public sealed record PropertyValue(TObject Sender, TValue? Value, bool UnobtainableValue) { /// /// Initializes a new instance of the class. @@ -17,83 +25,21 @@ public sealed class PropertyValue : IEquatableThe sender. /// The value. public PropertyValue(TObject sender, TValue value) + : this(sender, value, default) { - Sender = sender; - Value = value; - } - - internal PropertyValue(TObject sender) - { - Sender = sender; - UnobtainableValue = true; - Value = default; } /// - /// Gets the Sender. - /// - public TObject Sender { get; } - - /// - /// Gets latest observed value. - /// - public TValue? Value { get; } - - /// - /// Gets a value indicating whether flag to indicated that the value was unobtainable when observing a deeply nested struct. - /// - internal bool UnobtainableValue { get; } - - /// - /// Implements the operator ==. - /// - /// The left. - /// The right. - /// - /// The result of the operator. - /// - public static bool operator ==(PropertyValue? left, PropertyValue? right) => Equals(left, right); - - /// - /// Implements the operator !=. + /// Initializes a new instance of the class. /// - /// The left. - /// The right. - /// - /// The result of the operator. - /// - public static bool operator !=(PropertyValue? left, PropertyValue? right) => !Equals(left, right); - - /// - public bool Equals(PropertyValue? other) + /// The sender value. + internal PropertyValue(TObject sender) + : this(sender, default, true) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.Value is null && Value is null) - { - return true; - } - - if (other.Value is null || Value is null) - { - return false; - } - - return EqualityComparer.Default.Equals(Sender, other.Sender) && EqualityComparer.Default.Equals(Value, other.Value); } /// - public override bool Equals(object? obj) => obj is PropertyValue propertyValue && Equals(propertyValue); - - /// + /// The result of the operation. public override int GetHashCode() { unchecked @@ -103,5 +49,6 @@ public override int GetHashCode() } /// + /// The result of the operation. public override string ToString() => $"{Sender} ({Value})"; } diff --git a/src/DynamicData/Binding/SortAndBind.cs b/src/DynamicData/Binding/SortAndBind.cs index 0bb5fe208..a38c0e8a6 100644 --- a/src/DynamicData/Binding/SortAndBind.cs +++ b/src/DynamicData/Binding/SortAndBind.cs @@ -1,16 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif +#if REACTIVE_SHIM -namespace DynamicData.Binding; +namespace DynamicData.Reactive.Binding; +#else +namespace DynamicData.Binding; +#endif /* * A much more optimised bind where the sort forms part of the binding. * @@ -18,15 +22,35 @@ namespace DynamicData.Binding; * collection upon every change in order that the sorted list could be transmitted to the bind operator. * */ + +/// +/// Provides members for the SortAndBind class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal sealed class SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey> where TObject : notnull where TKey : notnull { // NB: Either comparer or comparerChanged will be used, but not both. + /// + /// The _cache field. + /// private readonly Cache _cache = new(); + + /// + /// The _sorted field. + /// private readonly IObservable> _sorted; + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The comparer value. + /// The options value. + /// The target value. public SortAndBind(IObservable> source, IComparer comparer, SortAndBindOptions options, @@ -51,6 +75,13 @@ public SortAndBind(IObservable> source, }); } + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The comparerChanged value. + /// The options value. + /// The target value. public SortAndBind(IObservable> source, IObservable> comparerChanged, SortAndBindOptions options, @@ -92,14 +123,28 @@ public SortAndBind(IObservable> source, return new CompositeDisposable(latestComparer, subscriber, queue); }); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => _sorted; - internal sealed class SortApplicator( +/// +/// Provides members for the SortApplicator class. +/// +/// The cache value. +/// The target value. +/// The comparer value. +/// The options value. +internal sealed class SortApplicator( Cache cache, IList target, IComparer comparer, SortAndBindOptions options) { + /// + /// Executes the ApplySort operation. + /// public void ApplySort() { if (cache.Count == 0) return; @@ -109,8 +154,13 @@ public void ApplySort() Reset(sorted, fireReset); } - // apply sorting as a side effect of the observable stream. + + /// + /// Executes the ProcessChanges operation. + /// + /// The changeSet value. + /// The isFirstTimeLoad value. public void ProcessChanges(IChangeSet changeSet, bool isFirstTimeLoad) { var forceReset = isFirstTimeLoad && options.ResetOnFirstTimeLoad; @@ -134,6 +184,11 @@ public void ProcessChanges(IChangeSet changeSet, bool isFirstTime } } + /// + /// Executes the Reset operation. + /// + /// The sorted value. + /// The fireReset value. private void Reset(IEnumerable sorted, bool fireReset) { if (fireReset && target is ObservableCollectionExtended observableCollectionExtended) @@ -162,6 +217,10 @@ private void Reset(IEnumerable sorted, bool fireReset) } } + /// + /// Executes the ApplyChanges operation. + /// + /// The changes value. private void ApplyChanges(IChangeSet changes) { // iterate through collection, find sorted position and apply changes @@ -248,9 +307,19 @@ private void ApplyChanges(IChangeSet changes) } } + /// + /// Executes the GetCurrentPosition operation. + /// + /// The item value. + /// The result of the operation. private int GetCurrentPosition(TObject item) => target.GetCurrentPosition(item, comparer, options.UseBinarySearch); + /// + /// Executes the GetInsertPosition operation. + /// + /// The item value. + /// The result of the operation. private int GetInsertPosition(TObject item) => target.GetInsertPosition(item, comparer, options.UseBinarySearch); } diff --git a/src/DynamicData/Binding/SortAndBindOptions.cs b/src/DynamicData/Binding/SortAndBindOptions.cs index bf93e8afe..5d8e7b835 100644 --- a/src/DynamicData/Binding/SortAndBindOptions.cs +++ b/src/DynamicData/Binding/SortAndBindOptions.cs @@ -1,10 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Concurrency; +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// Options for the sort and bind operator. diff --git a/src/DynamicData/Binding/SortDirection.cs b/src/DynamicData/Binding/SortDirection.cs index 84313ab99..e2aa23c5f 100644 --- a/src/DynamicData/Binding/SortDirection.cs +++ b/src/DynamicData/Binding/SortDirection.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// Sort direction. diff --git a/src/DynamicData/Binding/SortExpression.cs b/src/DynamicData/Binding/SortExpression.cs index 9ff365ad2..0c9376ce5 100644 --- a/src/DynamicData/Binding/SortExpression.cs +++ b/src/DynamicData/Binding/SortExpression.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// A value expression with sort direction. diff --git a/src/DynamicData/Binding/SortExpressionComparer.cs b/src/DynamicData/Binding/SortExpressionComparer.cs index 14c7f94c4..09550ed8a 100644 --- a/src/DynamicData/Binding/SortExpressionComparer.cs +++ b/src/DynamicData/Binding/SortExpressionComparer.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// Generic sort expression to help create inline sorting for the .Sort(IComparer comparer) operator. @@ -25,6 +30,9 @@ public class SortExpressionComparer : List>, IComparer public static SortExpressionComparer Descending(Func expression) => [new(expression, SortDirection.Descending)]; /// + /// The x value. + /// The y value. + /// The result of the operation. public int Compare(T? x, T? y) { foreach (var item in this) diff --git a/src/DynamicData/Binding/SortedBindingListAdaptor.cs b/src/DynamicData/Binding/SortedBindingListAdaptor.cs index 81956791a..5fc07ee8c 100644 --- a/src/DynamicData/Binding/SortedBindingListAdaptor.cs +++ b/src/DynamicData/Binding/SortedBindingListAdaptor.cs @@ -1,12 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. #if SUPPORTS_BINDINGLIST -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; - +#if REACTIVE_SHIM +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// Represents an adaptor which is used to update a binding list from @@ -23,12 +24,16 @@ public class SortedBindingListAdaptor<[DynamicallyAccessedMembers(DynamicallyAcc where TObject : notnull where TKey : notnull { + /// + /// The _list field. + /// private readonly BindingList _list = list ?? throw new ArgumentNullException(nameof(list)); /// + /// The changes value. public void Adapt(ISortedChangeSet changes) { - changes.ThrowArgumentNullExceptionIfNull(nameof(changes)); + ArgumentExceptionHelper.ThrowIfNull(changes); switch (changes.SortedItems.SortReason) { @@ -68,6 +73,10 @@ public void Adapt(ISortedChangeSet changes) } } + /// + /// Executes the DoUpdate operation. + /// + /// The changes value. private void DoUpdate(ISortedChangeSet changes) { foreach (var change in changes) diff --git a/src/DynamicData/Binding/SortedObservableCollectionAdaptor.cs b/src/DynamicData/Binding/SortedObservableCollectionAdaptor.cs index 21c4c3e0c..a763afb16 100644 --- a/src/DynamicData/Binding/SortedObservableCollectionAdaptor.cs +++ b/src/DynamicData/Binding/SortedObservableCollectionAdaptor.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Binding; +#else namespace DynamicData.Binding; +#endif /// /// Represents an adaptor which is used to update observable collection from @@ -44,8 +49,8 @@ public SortedObservableCollectionAdaptor(BindingOptions options) /// The collection. public void Adapt(ISortedChangeSet changes, IObservableCollection collection) { - changes.ThrowArgumentNullExceptionIfNull(nameof(changes)); - collection.ThrowArgumentNullExceptionIfNull(nameof(collection)); + ArgumentExceptionHelper.ThrowIfNull(changes); + ArgumentExceptionHelper.ThrowIfNull(collection); switch (changes.SortedItems.SortReason) { @@ -116,6 +121,11 @@ public void Adapt(ISortedChangeSet changes, IObservableCollection } } + /// + /// Executes the DoUpdate operation. + /// + /// The updates value. + /// The list value. private void DoUpdate(ISortedChangeSet updates, IObservableCollection list) { foreach (var update in updates) diff --git a/src/DynamicData/Cache/CacheChangeSetEx.cs b/src/DynamicData/Cache/CacheChangeSetEx.cs index 2d03df512..eb7d14c07 100644 --- a/src/DynamicData/Cache/CacheChangeSetEx.cs +++ b/src/DynamicData/Cache/CacheChangeSetEx.cs @@ -1,9 +1,17 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache; +#else namespace DynamicData.Cache; +#endif +/// +/// Provides members for the CacheChangeSetEx class. +/// internal static class CacheChangeSetEx { /// @@ -12,7 +20,7 @@ internal static class CacheChangeSetEx /// This extension is a crazy hack to cast to the concrete change set which means we no longer allocate /// as change set now inherits from List which has allocation free enumerations. /// - /// IChangeSet will be removed in a future version and instead will be used directly. + /// IChangeSet will be removed in a future version and instead ChangeSet<TObject, TKey> will be used directly. /// In the mean time I am banking that no-one has implemented a custom change set - personally I think it is very unlikely. /// /// ChangeSet Object Type. @@ -44,8 +52,8 @@ public static IChangeSet Transform new Change(change.Reason, change.Key, transformer(change.Current), change.Previous.Convert(transformer), change.CurrentIndex, change.PreviousIndex)); diff --git a/src/DynamicData/Cache/Change.cs b/src/DynamicData/Cache/Change.cs index 9593871b3..59b3cfda8 100644 --- a/src/DynamicData/Cache/Change.cs +++ b/src/DynamicData/Cache/Change.cs @@ -1,9 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Container to describe a single change to a cache. @@ -22,7 +26,7 @@ namespace DynamicData; /// The current. /// The index. public Change(ChangeReason reason, TKey key, TObject current, int index = -1) - : this(reason, key, current, Optional.None(), index) + : this(reason, key, current, ReactiveUI.Primitives.Optional.None, index) { } @@ -53,7 +57,7 @@ public Change(TKey key, TObject current, int currentIndex, int previousIndex) } Current = current; - Previous = Optional.None(); + Previous = ReactiveUI.Primitives.Optional.None; Key = key; Reason = ChangeReason.Moved; CurrentIndex = currentIndex; @@ -74,7 +78,7 @@ public Change(TKey key, TObject current, int currentIndex, int previousIndex) /// or /// For , must supply previous value. /// - public Change(ChangeReason reason, TKey key, TObject current, in Optional previous, int currentIndex = -1, int previousIndex = -1) + public Change(ChangeReason reason, TKey key, TObject current, in ReactiveUI.Primitives.Optional previous, int currentIndex = -1, int previousIndex = -1) : this() { Current = current; @@ -119,7 +123,7 @@ public Change(ChangeReason reason, TKey key, TObject current, in OptionalGets the item from before the change. /// This is only when is . /// - public Optional Previous { get; } + public ReactiveUI.Primitives.Optional Previous { get; } /// /// Gets the previous index. @@ -144,9 +148,13 @@ public Change(ChangeReason reason, TKey key, TObject current, in Optional left, in Change right) => !left.Equals(right); /// + /// The other value. + /// The result of the operation. public bool Equals(Change other) => EqualityComparer.Default.Equals(Key, other.Key) && Reason == other.Reason && EqualityComparer.Default.Equals(Current, other.Current) && CurrentIndex == other.CurrentIndex && Previous.Equals(other.Previous) && PreviousIndex == other.PreviousIndex; /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) { if (obj is null) @@ -158,6 +166,7 @@ public override bool Equals(object? obj) } /// + /// The result of the operation. public override int GetHashCode() { unchecked @@ -173,5 +182,6 @@ public override int GetHashCode() } /// + /// The result of the operation. public override string ToString() => $"{Reason}, Key: {Key}, Current: {Current}, Previous: {Previous}"; } diff --git a/src/DynamicData/Cache/ChangeAwareCache.cs b/src/DynamicData/Cache/ChangeAwareCache.cs index 3230a9946..3964e1321 100644 --- a/src/DynamicData/Cache/ChangeAwareCache.cs +++ b/src/DynamicData/Cache/ChangeAwareCache.cs @@ -1,24 +1,33 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. -using DynamicData.Cache; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A cache which captures all changes which are made to it. These changes are recorded until CaptureChanges() at which point thw changes are cleared. /// Used for creating custom operators. /// -/// +/// ICache<TObject, TKey> /// The value of the cache. /// The key of the cache. public sealed class ChangeAwareCache : ICache where TObject : notnull where TKey : notnull { + /// + /// The _data field. + /// private readonly Dictionary _data; + + /// + /// The _changes field. + /// private ChangeSet _changes; /// @@ -46,7 +55,9 @@ public ChangeAwareCache(int capacity) /// Data to populate the cache with. public ChangeAwareCache(Dictionary data) { - _data = data ?? throw new ArgumentNullException(nameof(data)); + ArgumentExceptionHelper.ThrowIfNull(data); + + _data = data; _changes = []; } @@ -62,6 +73,10 @@ public ChangeAwareCache(Dictionary data) /// public IEnumerable> KeyValues => _data; + /// + /// Executes the GetDictionary operation. + /// + /// The result of the operation. internal Dictionary GetDictionary() => _data; /// @@ -76,6 +91,8 @@ public void Add(TObject item, TKey key) } /// + /// The item value. + /// The key value. public void AddOrUpdate(TObject item, TKey key) { _changes.Add(_data.TryGetValue(key, out var existingItem) ? new Change(ChangeReason.Update, key, item, existingItem) : new Change(ChangeReason.Add, key, item)); @@ -87,7 +104,7 @@ public void AddOrUpdate(TObject item, TKey key) /// Create a change set from recorded changes and clears known changes. /// /// A change set with the key/value changes. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0301:Simplify collection initialization", Justification = "This would result in differing operation")] + [SuppressMessage("Style", "IDE0301:Simplify collection initialization", Justification = "This would result in differing operation")] public ChangeSet CaptureChanges() { if (_changes.Count == 0) @@ -109,9 +126,10 @@ public void Clear() } /// + /// The changes value. public void Clone(IChangeSet changes) { - changes.ThrowArgumentNullExceptionIfNull(nameof(changes)); + ArgumentExceptionHelper.ThrowIfNull(changes); foreach (var change in changes.ToConcreteType()) { @@ -138,7 +156,9 @@ public void Clone(IChangeSet changes) } /// - public Optional Lookup(TKey key) => _data.Lookup(key); + /// The key value. + /// The result of the operation. + public ReactiveUI.Primitives.Optional Lookup(TKey key) => _data.Lookup(key); /// /// Raises an evaluate change for the specified keys. @@ -146,7 +166,7 @@ public void Clone(IChangeSet changes) /// The keys to refresh. public void Refresh(IEnumerable keys) { - keys.ThrowArgumentNullExceptionIfNull(nameof(keys)); + ArgumentExceptionHelper.ThrowIfNull(keys); if (keys is IList list) { @@ -187,7 +207,7 @@ public void Refresh(TKey key) /// The keys. public void Remove(IEnumerable keys) { - keys.ThrowArgumentNullExceptionIfNull(nameof(keys)); + ArgumentExceptionHelper.ThrowIfNull(keys); if (keys is IList list) { @@ -206,6 +226,7 @@ public void Remove(IEnumerable keys) } /// + /// The key value. public void Remove(TKey key) { if (_data.TryGetValue(key, out var existingItem)) diff --git a/src/DynamicData/Cache/ChangeReason.cs b/src/DynamicData/Cache/ChangeReason.cs index 899295daa..aee9a5632 100644 --- a/src/DynamicData/Cache/ChangeReason.cs +++ b/src/DynamicData/Cache/ChangeReason.cs @@ -1,9 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// The reason for an individual change. diff --git a/src/DynamicData/Cache/ChangeSet.cs b/src/DynamicData/Cache/ChangeSet.cs index 01c65f5f9..1e7d9e91f 100644 --- a/src/DynamicData/Cache/ChangeSet.cs +++ b/src/DynamicData/Cache/ChangeSet.cs @@ -1,9 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A collection of changes with some arbitrary additional context. @@ -98,5 +102,6 @@ public ChangeSet(int capacity) public int Updates => this.Count(c => c.Reason == ChangeReason.Update); /// + /// The result of the operation. public override string ToString() => $"ChangeSet<{typeof(TObject).Name}.{typeof(TKey).Name}>. Count={Count}"; } diff --git a/src/DynamicData/Cache/DistinctChangeSet.cs b/src/DynamicData/Cache/DistinctChangeSet.cs index 0edd202a9..e863bc104 100644 --- a/src/DynamicData/Cache/DistinctChangeSet.cs +++ b/src/DynamicData/Cache/DistinctChangeSet.cs @@ -1,22 +1,41 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif +/// +/// Provides members for the DistinctChangeSet class. +/// +/// The type of the T value. internal sealed class DistinctChangeSet : ChangeSet, IDistinctChangeSet where T : notnull { + /// + /// Initializes a new instance of the class. + /// + /// The items value. public DistinctChangeSet(IEnumerable> items) : base(items) { } + /// + /// Initializes a new instance of the class. + /// public DistinctChangeSet() { } + /// + /// Initializes a new instance of the class. + /// + /// The capacity value. public DistinctChangeSet(int capacity) : base(capacity) { diff --git a/src/DynamicData/Cache/GroupChangeSet.cs b/src/DynamicData/Cache/GroupChangeSet.cs index 43127e584..034841998 100644 --- a/src/DynamicData/Cache/GroupChangeSet.cs +++ b/src/DynamicData/Cache/GroupChangeSet.cs @@ -1,22 +1,42 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif +/// +/// Provides members for the GroupChangeSet class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TGroupKey value. internal sealed class GroupChangeSet : ChangeSet, TGroupKey>, IGroupChangeSet where TObject : notnull where TKey : notnull where TGroupKey : notnull { + /// + /// The Empty field. + /// public static new readonly IGroupChangeSet Empty = new GroupChangeSet(); + /// + /// Initializes a new instance of the class. + /// + /// The items value. public GroupChangeSet(IEnumerable, TGroupKey>> items) : base(items) { } + /// + /// Initializes a new instance of the class. + /// private GroupChangeSet() { } diff --git a/src/DynamicData/Cache/ICache.cs b/src/DynamicData/Cache/ICache.cs index b7335b4c3..42d2c8e96 100644 --- a/src/DynamicData/Cache/ICache.cs +++ b/src/DynamicData/Cache/ICache.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A cache which captures all changes which are made to it. These changes are recorded until CaptureChanges() at which point thw changes are cleared. @@ -11,7 +15,7 @@ namespace DynamicData; /// /// The type of the object. /// The type of the key. -/// +/// IQuery<TObject, TKey> public interface ICache : IQuery where TObject : notnull where TKey : notnull diff --git a/src/DynamicData/Cache/ICacheUpdater.cs b/src/DynamicData/Cache/ICacheUpdater.cs index aaeefc725..0ecdef96a 100644 --- a/src/DynamicData/Cache/ICacheUpdater.cs +++ b/src/DynamicData/Cache/ICacheUpdater.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Api for updating an intermediate cache. diff --git a/src/DynamicData/Cache/IChangeSet.cs b/src/DynamicData/Cache/IChangeSet.cs index 59301f394..45487fe74 100644 --- a/src/DynamicData/Cache/IChangeSet.cs +++ b/src/DynamicData/Cache/IChangeSet.cs @@ -1,9 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A collection of changes with some arbitrary additional context. diff --git a/src/DynamicData/Cache/IChangeSetAdaptor.cs b/src/DynamicData/Cache/IChangeSetAdaptor.cs index 454395795..de974a3da 100644 --- a/src/DynamicData/Cache/IChangeSetAdaptor.cs +++ b/src/DynamicData/Cache/IChangeSetAdaptor.cs @@ -1,9 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A simple adaptor to inject side effects into a change set observable. diff --git a/src/DynamicData/Cache/IConnectableCache.cs b/src/DynamicData/Cache/IConnectableCache.cs index 7663c0e83..4165fe54e 100644 --- a/src/DynamicData/Cache/IConnectableCache.cs +++ b/src/DynamicData/Cache/IConnectableCache.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for full license information. // ReSharper disable CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A cache for observing and querying in memory data. diff --git a/src/DynamicData/Cache/IDistinctChangeSet.cs b/src/DynamicData/Cache/IDistinctChangeSet.cs index 1410ab6d8..1a2d27e0a 100644 --- a/src/DynamicData/Cache/IDistinctChangeSet.cs +++ b/src/DynamicData/Cache/IDistinctChangeSet.cs @@ -1,9 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A collection of distinct value updates. diff --git a/src/DynamicData/Cache/IGroup.cs b/src/DynamicData/Cache/IGroup.cs index 431b79c13..5974fc985 100644 --- a/src/DynamicData/Cache/IGroup.cs +++ b/src/DynamicData/Cache/IGroup.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for full license information. // ReSharper disable CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// An update stream which has been grouped by a common key. diff --git a/src/DynamicData/Cache/IGroupChangeSet.cs b/src/DynamicData/Cache/IGroupChangeSet.cs index e9a9d1f29..5375666c1 100644 --- a/src/DynamicData/Cache/IGroupChangeSet.cs +++ b/src/DynamicData/Cache/IGroupChangeSet.cs @@ -1,9 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A grouped change set. diff --git a/src/DynamicData/Cache/IGrouping.cs b/src/DynamicData/Cache/IGrouping.cs index b951ff715..4a73f282a 100644 --- a/src/DynamicData/Cache/IGrouping.cs +++ b/src/DynamicData/Cache/IGrouping.cs @@ -1,9 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Represents a group which provides an update after any value within the group changes. @@ -50,5 +54,5 @@ public interface IGrouping /// /// The key. /// The value that is looked up. - Optional Lookup(TKey key); + ReactiveUI.Primitives.Optional Lookup(TKey key); } diff --git a/src/DynamicData/Cache/IImmutableGroupChangeSet.cs b/src/DynamicData/Cache/IImmutableGroupChangeSet.cs index 7cfdc789c..52f9c8738 100644 --- a/src/DynamicData/Cache/IImmutableGroupChangeSet.cs +++ b/src/DynamicData/Cache/IImmutableGroupChangeSet.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A grouped update collection. diff --git a/src/DynamicData/Cache/IIntermediateCache.cs b/src/DynamicData/Cache/IIntermediateCache.cs index 7ca604be0..47ce7c1d4 100644 --- a/src/DynamicData/Cache/IIntermediateCache.cs +++ b/src/DynamicData/Cache/IIntermediateCache.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// An observable cache which exposes an update API. diff --git a/src/DynamicData/Cache/IKey.cs b/src/DynamicData/Cache/IKey.cs index eb7b2e351..6d027c60b 100644 --- a/src/DynamicData/Cache/IKey.cs +++ b/src/DynamicData/Cache/IKey.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Represents the key of an object. diff --git a/src/DynamicData/Cache/IKeyValue.cs b/src/DynamicData/Cache/IKeyValue.cs index 9782c0001..a2158f5d4 100644 --- a/src/DynamicData/Cache/IKeyValue.cs +++ b/src/DynamicData/Cache/IKeyValue.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A keyed value. diff --git a/src/DynamicData/Cache/IKeyValueCollection.cs b/src/DynamicData/Cache/IKeyValueCollection.cs index 41314fc55..f8e6afa4d 100644 --- a/src/DynamicData/Cache/IKeyValueCollection.cs +++ b/src/DynamicData/Cache/IKeyValueCollection.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A key collection which contains sorting information. diff --git a/src/DynamicData/Cache/IObservableCache.cs b/src/DynamicData/Cache/IObservableCache.cs index 8897962a6..68193c804 100644 --- a/src/DynamicData/Cache/IObservableCache.cs +++ b/src/DynamicData/Cache/IObservableCache.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A cache for observing and querying in memory data. With additional data access operators. @@ -42,5 +46,5 @@ public interface IObservableCache : IConnectableCache /// The key. /// An optional with the looked up value. - Optional Lookup(TKey key); + ReactiveUI.Primitives.Optional Lookup(TKey key); } diff --git a/src/DynamicData/Cache/IPageRequest.cs b/src/DynamicData/Cache/IPageRequest.cs index 4d5b06956..b2a4d6d0d 100644 --- a/src/DynamicData/Cache/IPageRequest.cs +++ b/src/DynamicData/Cache/IPageRequest.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Represents a new page request. diff --git a/src/DynamicData/Cache/IPageResponse.cs b/src/DynamicData/Cache/IPageResponse.cs index c92011c3d..78e4cfaa3 100644 --- a/src/DynamicData/Cache/IPageResponse.cs +++ b/src/DynamicData/Cache/IPageResponse.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Operators; +#else namespace DynamicData.Operators; +#endif /// /// Response from the pagination operator. diff --git a/src/DynamicData/Cache/IPagedChangeSet.cs b/src/DynamicData/Cache/IPagedChangeSet.cs index 4fd88a798..7001c593e 100644 --- a/src/DynamicData/Cache/IPagedChangeSet.cs +++ b/src/DynamicData/Cache/IPagedChangeSet.cs @@ -1,11 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Operators; +#else using DynamicData.Operators; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A paged update collection. diff --git a/src/DynamicData/Cache/IQuery.cs b/src/DynamicData/Cache/IQuery.cs index 4f071c272..c14a069c0 100644 --- a/src/DynamicData/Cache/IQuery.cs +++ b/src/DynamicData/Cache/IQuery.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Exposes internal cache state to enable querying. @@ -44,5 +48,5 @@ public interface IQuery /// /// The key. /// The looked up value. - Optional Lookup(TKey key); + ReactiveUI.Primitives.Optional Lookup(TKey key); } diff --git a/src/DynamicData/Cache/ISortedChangeSet.cs b/src/DynamicData/Cache/ISortedChangeSet.cs index 8379f4992..682564be1 100644 --- a/src/DynamicData/Cache/ISortedChangeSet.cs +++ b/src/DynamicData/Cache/ISortedChangeSet.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// An update collection as per the system convention additionally providing a sorted set of the underling state. diff --git a/src/DynamicData/Cache/ISortedChangeSetAdaptor.cs b/src/DynamicData/Cache/ISortedChangeSetAdaptor.cs index 0e904cf22..bbdb108e7 100644 --- a/src/DynamicData/Cache/ISortedChangeSetAdaptor.cs +++ b/src/DynamicData/Cache/ISortedChangeSetAdaptor.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A simple adaptor to inject side effects into a sorted change set observable. diff --git a/src/DynamicData/Cache/ISourceCache.cs b/src/DynamicData/Cache/ISourceCache.cs index a24d693ed..011c9a4d7 100644 --- a/src/DynamicData/Cache/ISourceCache.cs +++ b/src/DynamicData/Cache/ISourceCache.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// An observable cache which exposes an update API. Used at the root diff --git a/src/DynamicData/Cache/ISourceUpdater.cs b/src/DynamicData/Cache/ISourceUpdater.cs index 10eb71f22..7e0e7383c 100644 --- a/src/DynamicData/Cache/ISourceUpdater.cs +++ b/src/DynamicData/Cache/ISourceUpdater.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// API for updating a source cache. diff --git a/src/DynamicData/Cache/IVirtualChangeSet.cs b/src/DynamicData/Cache/IVirtualChangeSet.cs index 4c86cbce7..d02c85929 100644 --- a/src/DynamicData/Cache/IVirtualChangeSet.cs +++ b/src/DynamicData/Cache/IVirtualChangeSet.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Represents a subset of data reduced by a defined set of parameters. diff --git a/src/DynamicData/Cache/IVirtualRequest.cs b/src/DynamicData/Cache/IVirtualRequest.cs index cdbee1ee5..e47c05c58 100644 --- a/src/DynamicData/Cache/IVirtualRequest.cs +++ b/src/DynamicData/Cache/IVirtualRequest.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A request to virtualise a stream. diff --git a/src/DynamicData/Cache/IVirtualResponse.cs b/src/DynamicData/Cache/IVirtualResponse.cs index 616e2fb11..422f7af77 100644 --- a/src/DynamicData/Cache/IVirtualResponse.cs +++ b/src/DynamicData/Cache/IVirtualResponse.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Defines values used to virtualise the result set. diff --git a/src/DynamicData/Cache/IndexedItem.cs b/src/DynamicData/Cache/IndexedItem.cs index 7b5c77511..39da83232 100644 --- a/src/DynamicData/Cache/IndexedItem.cs +++ b/src/DynamicData/Cache/IndexedItem.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// An item with it's index. @@ -34,9 +38,13 @@ public sealed class IndexedItem(TObject value, TKey key, int inde public TObject Value { get; } = value; /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) => obj is IndexedItem indexedKey && Equals(indexedKey); /// + /// The other value. + /// The result of the operation. public bool Equals(IndexedItem? other) { if (other is null) @@ -48,6 +56,7 @@ public bool Equals(IndexedItem? other) } /// + /// The result of the operation. public override int GetHashCode() { unchecked @@ -60,5 +69,6 @@ public override int GetHashCode() } /// + /// The result of the operation. public override string ToString() => $"Value: {Value}, Key: {Key}, CurrentIndex: {Index}"; } diff --git a/src/DynamicData/Cache/IntermediateCache.cs b/src/DynamicData/Cache/IntermediateCache.cs index 4494449a5..26bf009a8 100644 --- a/src/DynamicData/Cache/IntermediateCache.cs +++ b/src/DynamicData/Cache/IntermediateCache.cs @@ -3,10 +3,18 @@ // See the LICENSE file in the project root for full license information. using System.Diagnostics; +#if REACTIVE_SHIM +using DynamicData.Reactive.Binding; +#else using DynamicData.Binding; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Cache designed to be used for custom operator construction. It requires no key to be specified @@ -19,6 +27,9 @@ public sealed class IntermediateCache : IIntermediateCache + /// The _innerCache field. + /// private readonly ObservableCache _innerCache; /// @@ -28,7 +39,7 @@ public sealed class IntermediateCache : IIntermediateCachesource. public IntermediateCache(IObservable> source) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); _innerCache = new ObservableCache(source); } @@ -54,6 +65,9 @@ public IntermediateCache(IObservable> source) public IReadOnlyDictionary KeyValues => _innerCache.KeyValues; /// + /// The predicate value. + /// The suppressEmptyChangeSets value. + /// The result of the operation. public IObservable> Connect(Func? predicate = null, bool suppressEmptyChangeSets = true) => _innerCache.Connect(predicate, suppressEmptyChangeSets); @@ -61,23 +75,37 @@ public IObservable> Connect(Func? predi public void Dispose() => _innerCache.Dispose(); /// + /// The updateAction value. public void Edit(Action> updateAction) => _innerCache.UpdateFromIntermediate(updateAction); /// - public Optional Lookup(TKey key) => _innerCache.Lookup(key); + /// The key value. + /// The result of the operation. + public ReactiveUI.Primitives.Optional Lookup(TKey key) => _innerCache.Lookup(key); /// + /// The predicate value. + /// The result of the operation. public IObservable> Preview(Func? predicate = null) => _innerCache.Preview(predicate); /// + /// The key value. + /// The result of the operation. public IObservable> Watch(TKey key) => _innerCache.Watch(key); /// + /// The result of the operation. public IDisposable SuspendCount() => _innerCache.SuspendCount(); /// + /// The result of the operation. public IDisposable SuspendNotifications() => _innerCache.SuspendNotifications(); + /// + /// Executes the GetInitialUpdates operation. + /// + /// The filter value. + /// The result of the operation. internal IChangeSet GetInitialUpdates(Func? filter = null) => _innerCache.GetInitialUpdates(filter); } diff --git a/src/DynamicData/Cache/Internal/AbstractFilter.cs b/src/DynamicData/Cache/Internal/AbstractFilter.cs index 5e86dd997..dc397e4b7 100644 --- a/src/DynamicData/Cache/Internal/AbstractFilter.cs +++ b/src/DynamicData/Cache/Internal/AbstractFilter.cs @@ -1,29 +1,56 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the AbstractFilter class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal abstract class AbstractFilter : IFilter where TObject : notnull where TKey : notnull { + /// + /// The _cache field. + /// private readonly ChangeAwareCache _cache; + /// + /// Initializes a new instance of the class. + /// + /// The cache value. + /// The filter value. protected AbstractFilter(ChangeAwareCache cache, Func? filter) { - _cache = cache ?? throw new ArgumentNullException(nameof(cache)); + ArgumentExceptionHelper.ThrowIfNull(cache); + _cache = cache; Filter = filter ?? (_ => true); } + /// + /// Gets the Filter value. + /// public Func Filter { get; } + /// + /// Executes the Refresh operation. + /// + /// The items value. + /// The result of the operation. public IChangeSet Refresh(IEnumerable> items) { // this is an internal method only so we can be sure there are no duplicate keys in the result // (therefore safe to parallelise) - Optional> Factory(KeyValuePair kv) + ReactiveUI.Primitives.Optional> Factory(KeyValuePair kv) { var existing = _cache.Lookup(kv.Key); var matches = Filter(kv.Value); @@ -40,7 +67,7 @@ Optional> Factory(KeyValuePair kv) return new Change(ChangeReason.Remove, kv.Key, kv.Value, existing); } - return Optional.None>(); + return ReactiveUI.Primitives.Optional>.None; } var result = Refresh(items, Factory); @@ -49,16 +76,37 @@ Optional> Factory(KeyValuePair kv) return _cache.CaptureChanges(); } + /// + /// Executes the Update operation. + /// + /// The updates value. + /// The result of the operation. public IChangeSet Update(IChangeSet updates) { var withFilter = GetChangesWithFilter(updates.ToConcreteType()); return ProcessResult(withFilter); } + /// + /// Executes the GetChangesWithFilter operation. + /// + /// The updates value. + /// The result of the operation. protected abstract IEnumerable GetChangesWithFilter(ChangeSet updates); - protected abstract IEnumerable> Refresh(IEnumerable> items, Func, Optional>> factory); + /// + /// Executes the Refresh operation. + /// + /// The items value. + /// The factory value. + /// The result of the operation. + protected abstract IEnumerable> Refresh(IEnumerable> items, Func, ReactiveUI.Primitives.Optional>> factory); + /// + /// Executes the ProcessResult operation. + /// + /// The source value. + /// The result of the operation. private ChangeSet ProcessResult(IEnumerable source) { // Have to process one item at a time as an item can be included multiple @@ -126,16 +174,22 @@ private ChangeSet ProcessResult(IEnumerable sou return _cache.CaptureChanges(); } - /// - /// Initializes a new instance of the struct. - /// Initializes a new instance of the class. - /// - /// If the filter is a match. - /// The change. - protected readonly struct UpdateWithFilter(bool isMatch, Change change) +/// +/// Initializes a new instance of the struct. +/// Initializes a new instance of the class. +/// +/// If the filter is a match. +/// The change. +protected readonly struct UpdateWithFilter(bool isMatch, Change change) { + /// + /// Gets the Change value. + /// public Change Change { get; } = change; + /// + /// Gets the IsMatch value. + /// public bool IsMatch { get; } = isMatch; } } diff --git a/src/DynamicData/Cache/Internal/AnonymousObservableCache.cs b/src/DynamicData/Cache/Internal/AnonymousObservableCache.cs index be5e8d189..4e5dc7640 100644 --- a/src/DynamicData/Cache/Internal/AnonymousObservableCache.cs +++ b/src/DynamicData/Cache/Internal/AnonymousObservableCache.cs @@ -3,53 +3,117 @@ // See the LICENSE file in the project root for full license information. using System.Diagnostics; -using System.Reactive.Disposables; +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the AnonymousObservableCache class. +/// +/// The type of the TObject value. +/// The type of the TKey value. [DebuggerDisplay("AnonymousObservableCache<{typeof(TObject).Name}, {typeof(TKey).Name}> ({Count} Items)")] internal sealed class AnonymousObservableCache : IObservableCache where TObject : notnull where TKey : notnull { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed through _cleanUp")] + /// + /// The _cache field. + /// + [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed through _cleanUp")] private readonly IObservableCache _cache; + + /// + /// The _cleanUp field. + /// private readonly IDisposable _cleanUp; + /// + /// Initializes a new instance of the class. + /// + /// The source value. public AnonymousObservableCache(IObservable> source) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); _cache = new ObservableCache(source); _cleanUp = _cache; } + /// + /// Initializes a new instance of the class. + /// + /// The cache value. public AnonymousObservableCache(IObservableCache cache) { - _cache = cache ?? throw new ArgumentNullException(nameof(cache)); + ArgumentExceptionHelper.ThrowIfNull(cache); + _cache = cache; _cleanUp = Disposable.Empty; } + /// + /// Gets the Count value. + /// public int Count => _cache.Count; + /// + /// Gets the CountChanged value. + /// public IObservable CountChanged => _cache.CountChanged; + /// + /// Gets the Items value. + /// public IReadOnlyList Items => _cache.Items; + /// + /// Gets the Keys value. + /// public IReadOnlyList Keys => _cache.Keys; + /// + /// Gets the KeyValues value. + /// public IReadOnlyDictionary KeyValues => _cache.KeyValues; + /// + /// Executes the Connect operation. + /// + /// The predicate value. + /// The suppressEmptyChangeSets value. + /// The result of the operation. public IObservable> Connect(Func? predicate = null, bool suppressEmptyChangeSets = true) => _cache.Connect(predicate, suppressEmptyChangeSets); - public Optional Lookup(TKey key) => _cache.Lookup(key); - + /// + /// Executes the Lookup operation. + /// + /// The key value. + /// The result of the operation. + public ReactiveUI.Primitives.Optional Lookup(TKey key) => _cache.Lookup(key); + + /// + /// Executes the Preview operation. + /// + /// The predicate value. + /// The result of the operation. public IObservable> Preview(Func? predicate = null) => _cache.Preview(predicate); + /// + /// Executes the Watch operation. + /// + /// The key value. + /// The result of the operation. public IObservable> Watch(TKey key) => _cache.Watch(key); + /// + /// Executes the Dispose operation. + /// public void Dispose() => _cleanUp.Dispose(); } diff --git a/src/DynamicData/Cache/Internal/AnonymousQuery.cs b/src/DynamicData/Cache/Internal/AnonymousQuery.cs index f83fd2018..292cabffe 100644 --- a/src/DynamicData/Cache/Internal/AnonymousQuery.cs +++ b/src/DynamicData/Cache/Internal/AnonymousQuery.cs @@ -1,22 +1,53 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the AnonymousQuery class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The cache value. internal sealed class AnonymousQuery(Cache cache) : IQuery where TObject : notnull where TKey : notnull { + /// + /// The _cache field. + /// private readonly Cache _cache = cache.Clone(); + /// + /// Gets the Count value. + /// public int Count => _cache.Count; + /// + /// Gets the Items value. + /// public IEnumerable Items => _cache.Items; + /// + /// Gets the Keys value. + /// public IEnumerable Keys => _cache.Keys; + /// + /// Gets the KeyValues value. + /// public IEnumerable> KeyValues => _cache.KeyValues; - public Optional Lookup(TKey key) => _cache.Lookup(key); + /// + /// Executes the Lookup operation. + /// + /// The key value. + /// The result of the operation. + public ReactiveUI.Primitives.Optional Lookup(TKey key) => _cache.Lookup(key); } diff --git a/src/DynamicData/Cache/Internal/AsyncDisposeMany.cs b/src/DynamicData/Cache/Internal/AsyncDisposeMany.cs index 8699e1c58..3281ce4a6 100644 --- a/src/DynamicData/Cache/Internal/AsyncDisposeMany.cs +++ b/src/DynamicData/Cache/Internal/AsyncDisposeMany.cs @@ -1,75 +1,83 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; - +#endif #if SUPPORTS_ASYNC_DISPOSABLE + +/// +/// Provides members for the AsyncDisposeMany class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal static class AsyncDisposeMany where TObject : notnull where TKey : notnull { + /// + /// Executes the Create operation. + /// + /// The source value. + /// The disposalsCompletedAccessor value. + /// The result of the operation. public static IObservable> Create( IObservable> source, Action> disposalsCompletedAccessor) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - disposalsCompletedAccessor.ThrowArgumentNullExceptionIfNull(nameof(disposalsCompletedAccessor)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(disposalsCompletedAccessor); return Observable .Create>(downstreamObserver => { + var gate = new Lock(); var itemsByKey = new Dictionary(); - - var disposals = new Subject>(); - var disposalsCompleted = disposals - .Merge() - .IgnoreElements() - .Concat(Observable.Return(Unit.Default)) - .Publish() - .AutoConnect(0); + var disposalsCompleted = new ReplaySignal(1); + var pendingAsyncDisposals = 0; + var teardownStarted = false; + var teardownCompleted = false; + var disposalsFinalized = false; disposalsCompletedAccessor.Invoke(disposalsCompleted); - var sourceSubscription = source - .SynchronizeSafe() - .SubscribeSafe( - onNext: upstreamChanges => - { - downstreamObserver.OnNext(upstreamChanges); + var sourceSubscription = PrimitivesLinqExtensions.SubscribeSafe( + source.SynchronizeSafe(), + onNext: upstreamChanges => + { + downstreamObserver.OnNext(upstreamChanges); - foreach (var change in upstreamChanges.ToConcreteType()) + foreach (var change in upstreamChanges.ToConcreteType()) + { + switch (change.Reason) { - switch (change.Reason) - { - case ChangeReason.Update: - if (change.Previous.HasValue && !EqualityComparer.Default.Equals(change.Current, change.Previous.Value)) - TryDisposeItem(change.Previous.Value); - break; - - case ChangeReason.Remove: - TryDisposeItem(change.Current); - break; - } + case ChangeReason.Update: + if (change.Previous.HasValue && !EqualityComparer.Default.Equals(change.Current, change.Previous.Value)) + TryDisposeItem(change.Previous.Value); + break; + + case ChangeReason.Remove: + TryDisposeItem(change.Current); + break; } + } - itemsByKey.Clone(upstreamChanges); - }, - onError: error => - { - downstreamObserver.OnError(error); - TearDown(); - }, - onCompleted: () => - { - downstreamObserver.OnCompleted(); - TearDown(); - }); + itemsByKey.Clone(upstreamChanges); + }, + onError: error => + { + downstreamObserver.OnError(error); + TearDown(); + }, + onCompleted: () => + { + downstreamObserver.OnCompleted(); + TearDown(); + }); return Disposable.Create(() => { @@ -79,20 +87,37 @@ public static IObservable> Create( void TearDown() { - if (disposals.HasObservers) + TObject[] items; + + lock (gate) { - try - { - foreach (var item in itemsByKey.Values) - TryDisposeItem(item); - disposals.OnCompleted(); - itemsByKey.Clear(); - } - catch (Exception error) - { - disposals.OnError(error); - } + if (teardownStarted) + return; + + teardownStarted = true; + items = [.. itemsByKey.Values]; + itemsByKey.Clear(); + } + + try + { + foreach (var item in items) + TryDisposeItem(item); + } + catch (Exception error) + { + TryFailDisposals(error); } + + var publishCompleted = false; + lock (gate) + { + teardownCompleted = true; + publishCompleted = TryMarkDisposalsCompleted(); + } + + if (publishCompleted) + PublishDisposalsCompleted(); } void TryDisposeItem(TObject item) @@ -100,9 +125,85 @@ void TryDisposeItem(TObject item) if (item is IDisposable disposable) disposable.Dispose(); else if (item is IAsyncDisposable asyncDisposable) - disposals.OnNext(Observable.FromAsync(() => asyncDisposable.DisposeAsync().AsTask())); + TrackAsyncDisposal(asyncDisposable.DisposeAsync().AsTask()); + } + + void TrackAsyncDisposal(Task disposalTask) + { + lock (gate) + { + ++pendingAsyncDisposals; + } + + if (disposalTask.IsCompleted) + { + CompleteAsyncDisposal(disposalTask); + return; + } + + _ = disposalTask.ContinueWith( + static (completedTask, state) => ((Action)state!).Invoke(completedTask), + (Action)CompleteAsyncDisposal, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + } + + void CompleteAsyncDisposal(Task disposalTask) + { + if (disposalTask.IsFaulted) + { + TryFailDisposals(disposalTask.Exception.InnerExceptions.Count == 1 + ? disposalTask.Exception.InnerException! + : disposalTask.Exception); + } + else if (disposalTask.IsCanceled) + { + TryFailDisposals(new TaskCanceledException(disposalTask)); + } + + var publishCompleted = false; + lock (gate) + { + --pendingAsyncDisposals; + publishCompleted = TryMarkDisposalsCompleted(); + } + + if (publishCompleted) + PublishDisposalsCompleted(); + } + + bool TryMarkDisposalsCompleted() + { + if (!teardownCompleted || pendingAsyncDisposals != 0 || disposalsFinalized) + return false; + + disposalsFinalized = true; + return true; + } + + void PublishDisposalsCompleted() + { + disposalsCompleted.OnNext(Unit.Default); + disposalsCompleted.OnCompleted(); + } + + void TryFailDisposals(Exception error) + { + var publishError = false; + lock (gate) + { + if (!disposalsFinalized) + { + disposalsFinalized = true; + publishError = true; + } + } + + if (publishError) + disposalsCompleted.OnError(error); } }); } } -#endif \ No newline at end of file +#endif diff --git a/src/DynamicData/Cache/Internal/AutoRefresh.cs b/src/DynamicData/Cache/Internal/AutoRefresh.cs index ee81fe581..59f94fa37 100644 --- a/src/DynamicData/Cache/Internal/AutoRefresh.cs +++ b/src/DynamicData/Cache/Internal/AutoRefresh.cs @@ -1,23 +1,47 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the AutoRefresh class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TAny value. +/// The source value. +/// The reEvaluator value. +/// The buffer value. +/// The scheduler value. internal sealed class AutoRefresh(IObservable> source, Func> reEvaluator, TimeSpan? buffer = null, IScheduler? scheduler = null) where TObject : notnull where TKey : notnull { + /// + /// The _reEvaluator field. + /// private readonly Func> _reEvaluator = reEvaluator ?? throw new ArgumentNullException(nameof(reEvaluator)); + /// + /// The _scheduler field. + /// private readonly IScheduler _scheduler = scheduler ?? GlobalConfig.DefaultScheduler; + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { diff --git a/src/DynamicData/Cache/Internal/BatchIf.cs b/src/DynamicData/Cache/Internal/BatchIf.cs index 76af20a61..e4853d6f8 100644 --- a/src/DynamicData/Cache/Internal/BatchIf.cs +++ b/src/DynamicData/Cache/Internal/BatchIf.cs @@ -1,24 +1,48 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; - +#endif + +/// +/// Provides members for the BatchIf class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. +/// The pauseIfTrueSelector value. +/// The timeOut value. +/// The initialPauseState value. +/// The intervalTimer value. +/// The scheduler value. internal sealed class BatchIf(IObservable> source, IObservable pauseIfTrueSelector, TimeSpan? timeOut, bool initialPauseState = false, IObservable? intervalTimer = null, IScheduler? scheduler = null) where TObject : notnull where TKey : notnull { + /// + /// The _pauseIfTrueSelector field. + /// private readonly IObservable _pauseIfTrueSelector = pauseIfTrueSelector ?? throw new ArgumentNullException(nameof(pauseIfTrueSelector)); + /// + /// The _scheduler field. + /// private readonly IScheduler _scheduler = scheduler ?? GlobalConfig.DefaultScheduler; + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { diff --git a/src/DynamicData/Cache/Internal/Cache.cs b/src/DynamicData/Cache/Internal/Cache.cs index 0a882ad88..d5415dc28 100644 --- a/src/DynamicData/Cache/Internal/Cache.cs +++ b/src/DynamicData/Cache/Internal/Cache.cs @@ -1,42 +1,94 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Diagnostics; +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the Cache class. +/// +/// The type of the TObject value. +/// The type of the TKey value. [DebuggerDisplay("Cache<{typeof(TObject).Name}, {typeof(TKey).Name}> ({Count} Items)")] internal sealed class Cache : ICache where TObject : notnull where TKey : notnull { + /// + /// The Empty field. + /// public static readonly Cache Empty = new(); + /// + /// The _data field. + /// private readonly Dictionary _data; + /// + /// Initializes a new instance of the class. + /// + /// The capacity value. public Cache(int capacity = -1) => _data = capacity > 1 ? new Dictionary(capacity) : []; + /// + /// Initializes a new instance of the class. + /// + /// The data value. public Cache(Dictionary data) => _data = data; + /// + /// Gets the Count value. + /// public int Count => _data.Count; + /// + /// Gets the Items value. + /// public IEnumerable Items => _data.Values; + /// + /// Gets the Keys value. + /// public IEnumerable Keys => _data.Keys; + /// + /// Gets the KeyValues value. + /// public IEnumerable> KeyValues => _data; + /// + /// Executes the AddOrUpdate operation. + /// + /// The item value. + /// The key value. public void AddOrUpdate(TObject item, TKey key) => _data[key] = item; + /// + /// Executes the Clear operation. + /// public void Clear() => _data.Clear(); + /// + /// Executes the Clone operation. + /// + /// The result of the operation. public Cache Clone() => new(new Dictionary(_data)); + /// + /// Executes the Clone operation. + /// + /// The changes value. public void Clone(IChangeSet changes) { - changes.ThrowArgumentNullExceptionIfNull(nameof(changes)); + ArgumentExceptionHelper.ThrowIfNull(changes); foreach (var item in changes.ToConcreteType()) { @@ -57,7 +109,12 @@ public void Clone(IChangeSet changes) } } - public Optional Lookup(TKey key) => _data.Lookup(key); + /// + /// Executes the Lookup operation. + /// + /// The key value. + /// The result of the operation. + public ReactiveUI.Primitives.Optional Lookup(TKey key) => _data.Lookup(key); /// /// Sends a signal for operators to recalculate it's state. @@ -82,6 +139,10 @@ public void Refresh(TKey key) { } + /// + /// Executes the Remove operation. + /// + /// The keys value. public void Remove(IEnumerable keys) { if (keys is IList list) @@ -100,5 +161,9 @@ public void Remove(IEnumerable keys) } } + /// + /// Executes the Remove operation. + /// + /// The key value. public void Remove(TKey key) => _data.Remove(key); } diff --git a/src/DynamicData/Cache/Internal/CacheEx.cs b/src/DynamicData/Cache/Internal/CacheEx.cs index 8250f168e..a00adc1dd 100644 --- a/src/DynamicData/Cache/Internal/CacheEx.cs +++ b/src/DynamicData/Cache/Internal/CacheEx.cs @@ -1,11 +1,26 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the CacheEx class. +/// internal static class CacheEx { + /// + /// Executes the Clone operation. + /// + /// The type of the TKey value. + /// The type of the TObject value. + /// The source value. + /// The changes value. public static void Clone(this IDictionary source, IChangeSet changes) where TKey : notnull where TObject : notnull @@ -26,6 +41,14 @@ public static void Clone(this IDictionary source, } } + /// + /// Executes the GetInitialUpdates operation. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The filter value. + /// The result of the operation. public static IChangeSet GetInitialUpdates(this ChangeAwareCache source, Func? filter = null) where TObject : notnull where TKey : notnull diff --git a/src/DynamicData/Cache/Internal/CacheUpdater.cs b/src/DynamicData/Cache/Internal/CacheUpdater.cs index 8eb6eadf8..9ac68e160 100644 --- a/src/DynamicData/Cache/Internal/CacheUpdater.cs +++ b/src/DynamicData/Cache/Internal/CacheUpdater.cs @@ -1,42 +1,84 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the CacheUpdater class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal sealed class CacheUpdater : ISourceUpdater where TObject : notnull where TKey : notnull { + /// + /// The _cache field. + /// private readonly ICache _cache; + /// + /// The _keySelector field. + /// private readonly Func? _keySelector; + /// + /// Initializes a new instance of the class. + /// + /// The cache value. + /// The keySelector value. public CacheUpdater(ICache cache, Func? keySelector = null) { _cache = cache ?? throw new ArgumentNullException(nameof(cache)); _keySelector = keySelector; } + /// + /// Initializes a new instance of the class. + /// + /// The data value. + /// The keySelector value. public CacheUpdater(Dictionary data, Func? keySelector = null) { - data.ThrowArgumentNullExceptionIfNull(nameof(data)); + ArgumentExceptionHelper.ThrowIfNull(data); _cache = new Cache(data); _keySelector = keySelector; } + /// + /// Gets the Count value. + /// public int Count => _cache.Count; + /// + /// Gets the Items value. + /// public IEnumerable Items => _cache.Items; + /// + /// Gets the Keys value. + /// public IEnumerable Keys => _cache.Keys; + /// + /// Gets the KeyValues value. + /// public IEnumerable> KeyValues => _cache.KeyValues; + /// + /// Executes the AddOrUpdate operation. + /// + /// The items value. public void AddOrUpdate(IEnumerable items) { - items.ThrowArgumentNullExceptionIfNull(nameof(items)); + ArgumentExceptionHelper.ThrowIfNull(items); if (_keySelector is null) { @@ -60,14 +102,15 @@ public void AddOrUpdate(IEnumerable items) } } + /// + /// Executes the AddOrUpdate operation. + /// + /// The items value. + /// The comparer value. public void AddOrUpdate(IEnumerable items, IEqualityComparer comparer) { - items.ThrowArgumentNullExceptionIfNull(nameof(items)); - - if (comparer is null) - { - throw new ArgumentNullException(nameof(comparer)); - } + ArgumentExceptionHelper.ThrowIfNull(items); + ArgumentExceptionHelper.ThrowIfNull(comparer); if (_keySelector is null) { @@ -111,6 +154,10 @@ void AddOrUpdateImpl(TObject item) } } + /// + /// Executes the AddOrUpdate operation. + /// + /// The item value. public void AddOrUpdate(TObject item) { if (_keySelector is null) @@ -122,6 +169,11 @@ public void AddOrUpdate(TObject item) _cache.AddOrUpdate(item, key); } + /// + /// Executes the AddOrUpdate operation. + /// + /// The item value. + /// The comparer value. public void AddOrUpdate(TObject item, IEqualityComparer comparer) { if (_keySelector is null) @@ -145,6 +197,10 @@ public void AddOrUpdate(TObject item, IEqualityComparer comparer) _cache.AddOrUpdate(item, key); } + /// + /// Executes the AddOrUpdate operation. + /// + /// The itemsPairs value. public void AddOrUpdate(IEnumerable> itemsPairs) { if (itemsPairs is IList> list) @@ -164,14 +220,35 @@ public void AddOrUpdate(IEnumerable> itemsPairs) } } + /// + /// Executes the AddOrUpdate operation. + /// + /// The item value. public void AddOrUpdate(KeyValuePair item) => _cache.AddOrUpdate(item.Value, item.Key); + /// + /// Executes the AddOrUpdate operation. + /// + /// The item value. + /// The key value. public void AddOrUpdate(TObject item, TKey key) => _cache.AddOrUpdate(item, key); + /// + /// Executes the Clear operation. + /// public void Clear() => _cache.Clear(); + /// + /// Executes the Clone operation. + /// + /// The changes value. public void Clone(IChangeSet changes) => _cache.Clone(changes); + /// + /// Executes the GetKey operation. + /// + /// The item value. + /// The result of the operation. public TKey GetKey(TObject item) { if (_keySelector is null) @@ -182,6 +259,11 @@ public TKey GetKey(TObject item) return _keySelector(item); } + /// + /// Executes the GetKeyValues operation. + /// + /// The items value. + /// The result of the operation. public IEnumerable> GetKeyValues(IEnumerable items) { if (_keySelector is null) @@ -192,21 +274,35 @@ public IEnumerable> GetKeyValues(IEnumerable new KeyValuePair(_keySelector(t), t)); } + /// + /// Executes the Load operation. + /// + /// The items value. public void Load(IEnumerable items) { - items.ThrowArgumentNullExceptionIfNull(nameof(items)); + ArgumentExceptionHelper.ThrowIfNull(items); Clear(); AddOrUpdate(items); } - public Optional Lookup(TKey key) + /// + /// Executes the Lookup operation. + /// + /// The key value. + /// The result of the operation. + public ReactiveUI.Primitives.Optional Lookup(TKey key) { var item = _cache.Lookup(key); - return item.HasValue ? item.Value : Optional.None(); + return item.HasValue ? item.Value : ReactiveUI.Primitives.Optional.None; } - public Optional Lookup(TObject item) + /// + /// Executes the Lookup operation. + /// + /// The item value. + /// The result of the operation. + public ReactiveUI.Primitives.Optional Lookup(TObject item) { if (_keySelector is null) { @@ -217,11 +313,18 @@ public Optional Lookup(TObject item) return Lookup(key); } + /// + /// Executes the Refresh operation. + /// public void Refresh() => _cache.Refresh(); + /// + /// Executes the Refresh operation. + /// + /// The items value. public void Refresh(IEnumerable items) { - items.ThrowArgumentNullExceptionIfNull(nameof(items)); + ArgumentExceptionHelper.ThrowIfNull(items); if (items is IList list) { @@ -240,9 +343,13 @@ public void Refresh(IEnumerable items) } } + /// + /// Executes the Refresh operation. + /// + /// The keys value. public void Refresh(IEnumerable keys) { - keys.ThrowArgumentNullExceptionIfNull(nameof(keys)); + ArgumentExceptionHelper.ThrowIfNull(keys); if (keys is IList list) { @@ -261,6 +368,10 @@ public void Refresh(IEnumerable keys) } } + /// + /// Executes the Refresh operation. + /// + /// The item value. public void Refresh(TObject item) { if (_keySelector is null) @@ -272,11 +383,19 @@ public void Refresh(TObject item) _cache.Refresh(key); } + /// + /// Executes the Refresh operation. + /// + /// The key value. public void Refresh(TKey key) => _cache.Refresh(key); + /// + /// Executes the Remove operation. + /// + /// The items value. public void Remove(IEnumerable items) { - items.ThrowArgumentNullExceptionIfNull(nameof(items)); + ArgumentExceptionHelper.ThrowIfNull(items); if (items is IList list) { @@ -295,9 +414,13 @@ public void Remove(IEnumerable items) } } + /// + /// Executes the Remove operation. + /// + /// The keys value. public void Remove(IEnumerable keys) { - keys.ThrowArgumentNullExceptionIfNull(nameof(keys)); + ArgumentExceptionHelper.ThrowIfNull(keys); if (keys is IList list) { @@ -316,6 +439,10 @@ public void Remove(IEnumerable keys) } } + /// + /// Executes the Remove operation. + /// + /// The item value. public void Remove(TObject item) { if (_keySelector is null) @@ -327,11 +454,19 @@ public void Remove(TObject item) _cache.Remove(key); } + /// + /// Executes the Remove operation. + /// + /// The key value. public void Remove(TKey key) => _cache.Remove(key); + /// + /// Executes the Remove operation. + /// + /// The items value. public void Remove(IEnumerable> items) { - items.ThrowArgumentNullExceptionIfNull(nameof(items)); + ArgumentExceptionHelper.ThrowIfNull(items); if (items is IList list) { @@ -350,16 +485,32 @@ public void Remove(IEnumerable> items) } } + /// + /// Executes the Remove operation. + /// + /// The item value. public void Remove(KeyValuePair item) => Remove(item.Key); + /// + /// Executes the RemoveKey operation. + /// + /// The key value. public void RemoveKey(TKey key) => Remove(key); + /// + /// Executes the RemoveKeys operation. + /// + /// The keys value. public void RemoveKeys(IEnumerable keys) { - keys.ThrowArgumentNullExceptionIfNull(nameof(keys)); + ArgumentExceptionHelper.ThrowIfNull(keys); _cache.Remove(keys); } + /// + /// Executes the Update operation. + /// + /// The changes value. public void Update(IChangeSet changes) => _cache.Clone(changes); } diff --git a/src/DynamicData/Cache/Internal/Cast.cs b/src/DynamicData/Cache/Internal/Cast.cs index 02f62ebb8..bb8462854 100644 --- a/src/DynamicData/Cache/Internal/Cast.cs +++ b/src/DynamicData/Cache/Internal/Cast.cs @@ -1,20 +1,41 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the Cast class. +/// +/// The type of the TSource value. +/// The type of the TKey value. +/// The type of the TDestination value. +/// The source value. +/// The converter value. internal sealed class Cast(IObservable> source, Func converter) where TSource : notnull where TKey : notnull where TDestination : notnull { + /// + /// The _converter field. + /// private readonly Func _converter = converter ?? throw new ArgumentNullException(nameof(converter)); + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => _source.Select( changes => { diff --git a/src/DynamicData/Cache/Internal/ChangeSetCache.cs b/src/DynamicData/Cache/Internal/ChangeSetCache.cs index d63a93bea..c19443a7c 100644 --- a/src/DynamicData/Cache/Internal/ChangeSetCache.cs +++ b/src/DynamicData/Cache/Internal/ChangeSetCache.cs @@ -1,10 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif /// /// Wraps an Observable ChangeSet while maintaining a copy of the aggregated changes. @@ -15,10 +18,20 @@ internal sealed class ChangeSetCache where TObject : notnull where TKey : notnull { + /// + /// Initializes a new instance of the class. + /// + /// The source value. public ChangeSetCache(IObservable> source) => Source = source.Do(Cache.Clone); + /// + /// Gets the Cache value. + /// public Cache Cache { get; } = new(); + /// + /// Gets the Source value. + /// public IObservable> Source { get; } } diff --git a/src/DynamicData/Cache/Internal/ChangeSetMergeTracker.cs b/src/DynamicData/Cache/Internal/ChangeSetMergeTracker.cs index ae973094e..cd30a46ed 100644 --- a/src/DynamicData/Cache/Internal/ChangeSetMergeTracker.cs +++ b/src/DynamicData/Cache/Internal/ChangeSetMergeTracker.cs @@ -1,19 +1,51 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -namespace DynamicData.Cache.Internal; +namespace DynamicData.Reactive.Cache.Internal; +#else +namespace DynamicData.Cache.Internal; +#endif + +/// +/// Provides members for the ChangeSetMergeTracker class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The selectCaches value. +/// The comparer value. +/// The equalityComparer value. internal sealed class ChangeSetMergeTracker(Func>> selectCaches, IComparer? comparer, IEqualityComparer? equalityComparer) where TObject : notnull where TKey : notnull { + /// + /// The _resultCache field. + /// private readonly ChangeAwareCache _resultCache = new(); + + /// + /// The _equalityComparer field. + /// private readonly IEqualityComparer _equalityComparer = equalityComparer ?? EqualityComparer.Default; + + /// + /// The _hasCompleted field. + /// private bool _hasCompleted; + /// + /// Executes the MarkComplete operation. + /// public void MarkComplete() => _hasCompleted = true; + /// + /// Executes the RemoveItems operation. + /// + /// The items value. + /// The observer value. public void RemoveItems(IEnumerable> items, IObserver>? observer = null) { var sourceCaches = selectCaches().ToArray(); @@ -41,6 +73,11 @@ public void RemoveItems(IEnumerable> items, IObserve } } + /// + /// Executes the RefreshItems operation. + /// + /// The keys value. + /// The observer value. public void RefreshItems(IEnumerable keys, IObserver>? observer = null) { var sourceCaches = selectCaches().ToArray(); @@ -68,6 +105,11 @@ public void RefreshItems(IEnumerable keys, IObserver + /// Executes the ProcessChangeSet operation. + /// + /// The changes value. + /// The observer value. public void ProcessChangeSet(IChangeSet changes, IObserver>? observer = null) { var sourceCaches = selectCaches().ToArray(); @@ -100,6 +142,10 @@ public void ProcessChangeSet(IChangeSet changes, IObserver + /// Executes the EmitChanges operation. + /// + /// The observer value. public void EmitChanges(IObserver> observer) { var changeSet = _resultCache.CaptureChanges(); @@ -114,6 +160,11 @@ public void EmitChanges(IObserver> observer) } } + /// + /// Executes the OnItemAdded operation. + /// + /// The item value. + /// The key value. private void OnItemAdded(TObject item, TKey key) { var cached = _resultCache.Lookup(key); @@ -129,6 +180,12 @@ private void OnItemAdded(TObject item, TKey key) } } + /// + /// Executes the OnItemRemoved operation. + /// + /// The sourceCaches value. + /// The item value. + /// The key value. private void OnItemRemoved(ChangeSetCache[] sourceCaches, TObject item, TKey key) { var cached = _resultCache.Lookup(key); @@ -141,7 +198,14 @@ private void OnItemRemoved(ChangeSetCache[] sourceCaches, TObject } } - private void OnItemUpdated(ChangeSetCache[] sources, TObject item, TKey key, in Optional prev) + /// + /// Executes the OnItemUpdated operation. + /// + /// The sources value. + /// The item value. + /// The key value. + /// The prev value. + private void OnItemUpdated(ChangeSetCache[] sources, TObject item, TKey key, in ReactiveUI.Primitives.Optional prev) { var cached = _resultCache.Lookup(key); @@ -185,6 +249,12 @@ private void OnItemUpdated(ChangeSetCache[] sources, TObject item } } + /// + /// Executes the OnItemRefreshed operation. + /// + /// The sources value. + /// The item value. + /// The key value. private void OnItemRefreshed(ChangeSetCache[] sources, TObject item, TKey key) { var cached = _resultCache.Lookup(key); @@ -205,6 +275,11 @@ private void OnItemRefreshed(ChangeSetCache[] sources, TObject it } } + /// + /// Executes the ForceEvaluate operation. + /// + /// The sources value. + /// The key value. private void ForceEvaluate(ChangeSetCache[] sources, TKey key) { var cached = _resultCache.Lookup(key); @@ -219,7 +294,14 @@ private void ForceEvaluate(ChangeSetCache[] sources, TKey key) UpdateToBestValue(sources, key, cached); } - private bool UpdateToBestValue(ChangeSetCache[] sources, TKey key, in Optional current) + /// + /// Executes the UpdateToBestValue operation. + /// + /// The sources value. + /// The key value. + /// The current value. + /// The result of the operation. + private bool UpdateToBestValue(ChangeSetCache[] sources, TKey key, in ReactiveUI.Primitives.Optional current) { // Determine which value should be the one seen downstream var candidate = LookupBestValue(sources, key); @@ -248,11 +330,17 @@ private bool UpdateToBestValue(ChangeSetCache[] sources, TKey key return true; } - private Optional LookupBestValue(ChangeSetCache[] sources, TKey key) + /// + /// Executes the LookupBestValue operation. + /// + /// The sources value. + /// The key value. + /// The result of the operation. + private ReactiveUI.Primitives.Optional LookupBestValue(ChangeSetCache[] sources, TKey key) { if (sources.Length == 0) { - return Optional.None(); + return ReactiveUI.Primitives.Optional.None; } var values = sources.Select(s => s.Cache.Lookup(key)).Where(opt => opt.HasValue); @@ -265,10 +353,22 @@ private Optional LookupBestValue(ChangeSetCache[] source return values.FirstOrDefault(); } + /// + /// Executes the CheckEquality operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. private bool CheckEquality(TObject left, TObject right) => _equalityComparer.Equals(left, right); - // Return true if candidate should replace current as the observed downstream value + + /// + /// Executes the ShouldReplace operation. + /// + /// The candidate value. + /// The current value. + /// The result of the operation. private bool ShouldReplace(TObject candidate, TObject current) => comparer?.Compare(candidate, current) < 0; } diff --git a/src/DynamicData/Cache/Internal/CombineOperator.cs b/src/DynamicData/Cache/Internal/CombineOperator.cs index 9bba3dd38..e382a7ab8 100644 --- a/src/DynamicData/Cache/Internal/CombineOperator.cs +++ b/src/DynamicData/Cache/Internal/CombineOperator.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif /// /// How the multiple streams are combinedL. diff --git a/src/DynamicData/Cache/Internal/Combiner.cs b/src/DynamicData/Cache/Internal/Combiner.cs index c7734512d..7d1391cb9 100644 --- a/src/DynamicData/Cache/Internal/Combiner.cs +++ b/src/DynamicData/Cache/Internal/Combiner.cs @@ -1,28 +1,45 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif /// /// Combines multiple caches using logical operators. /// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type value. +/// The updatedCallback value. internal sealed class Combiner(CombineOperator type, Action> updatedCallback) where TObject : notnull where TKey : notnull { + /// + /// The _combinedCache field. + /// private readonly ChangeAwareCache _combinedCache = new(); -#if NET9_0_OR_GREATER + /// + /// The _locker field. + /// private readonly Lock _locker = new(); -#else - private readonly object _locker = new(); -#endif + /// + /// The _sourceCaches field. + /// private readonly IList> _sourceCaches = []; + /// + /// Executes the Subscribe operation. + /// + /// The source value. + /// The result of the operation. public IDisposable Subscribe(IObservable>[] source) { // subscribe @@ -42,6 +59,11 @@ public IDisposable Subscribe(IObservable>[] source) return disposable; } + /// + /// Executes the MatchesConstraint operation. + /// + /// The key value. + /// The result of the operation. private bool MatchesConstraint(TKey key) { switch (type) @@ -73,6 +95,11 @@ private bool MatchesConstraint(TKey key) } } + /// + /// Executes the Update operation. + /// + /// The cache value. + /// The updates value. private void Update(Cache cache, IChangeSet updates) { ChangeSet notifications; @@ -92,6 +119,11 @@ private void Update(Cache cache, IChangeSet update } } + /// + /// Executes the UpdateCombined operation. + /// + /// The updates value. + /// The result of the operation. private ChangeSet UpdateCombined(IChangeSet updates) { // child caches have been updated before we reached this point. diff --git a/src/DynamicData/Cache/Internal/DeferUntilLoaded.cs b/src/DynamicData/Cache/Internal/DeferUntilLoaded.cs index 0402b7b5f..96b281085 100644 --- a/src/DynamicData/Cache/Internal/DeferUntilLoaded.cs +++ b/src/DynamicData/Cache/Internal/DeferUntilLoaded.cs @@ -1,25 +1,48 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the DeferUntilLoaded class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal sealed class DeferUntilLoaded where TObject : notnull where TKey : notnull { + /// + /// The _result field. + /// private readonly IObservable> _result; + /// + /// Initializes a new instance of the class. + /// + /// The source value. public DeferUntilLoaded(IObservableCache source) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); _result = source.CountChanged.Where(count => count != 0).Take(1).Select(_ => new ChangeSet()).Concat(source.Connect()).NotEmpty(); } + /// + /// Initializes a new instance of the class. + /// + /// The source value. public DeferUntilLoaded(IObservable> source) => _result = source.MonitorStatus().Where(status => status == ConnectionStatus.Loaded).Take(1).Select(_ => new ChangeSet()).Concat(source).NotEmpty(); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => _result; } diff --git a/src/DynamicData/Cache/Internal/DictionaryExtensions.cs b/src/DynamicData/Cache/Internal/DictionaryExtensions.cs index d8d9f27ea..1cacfd769 100644 --- a/src/DynamicData/Cache/Internal/DictionaryExtensions.cs +++ b/src/DynamicData/Cache/Internal/DictionaryExtensions.cs @@ -1,11 +1,27 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the DictionaryExtensions class. +/// internal static class DictionaryExtensions { + /// + /// Executes the GetOrEmpty operation. + /// + /// The type of the TDictKey value. + /// The type of the T value. + /// The dict value. + /// The key value. + /// The result of the operation. internal static IEnumerable GetOrEmpty(this IDictionary> dict, TDictKey key) => dict.TryGetValue(key, out var value) ? value : []; } diff --git a/src/DynamicData/Cache/Internal/DisposeMany.cs b/src/DynamicData/Cache/Internal/DisposeMany.cs index fe97f5c0e..ed18ea5ef 100644 --- a/src/DynamicData/Cache/Internal/DisposeMany.cs +++ b/src/DynamicData/Cache/Internal/DisposeMany.cs @@ -1,19 +1,33 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; - +#endif + +/// +/// Provides members for the DisposeMany class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. internal sealed class DisposeMany(IObservable> source) where TObject : notnull where TKey : notnull { + /// + /// The _source field. + /// private readonly IObservable> _source = source; + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>(observer => { diff --git a/src/DynamicData/Cache/Internal/DistinctCalculator.cs b/src/DynamicData/Cache/Internal/DistinctCalculator.cs index 934f17ac3..df7e67da0 100644 --- a/src/DynamicData/Cache/Internal/DistinctCalculator.cs +++ b/src/DynamicData/Cache/Internal/DistinctCalculator.cs @@ -1,25 +1,58 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; - +#endif + +/// +/// Provides members for the DistinctCalculator class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TValue value. +/// The source value. +/// The valueSelector value. internal sealed class DistinctCalculator(IObservable> source, Func valueSelector) where TObject : notnull where TKey : notnull where TValue : notnull { + /// + /// The _itemCache field. + /// private readonly Dictionary _itemCache = []; + /// + /// The _keyCounters field. + /// private readonly Dictionary _keyCounters = []; + + /// + /// The _valueCounters field. + /// private readonly Dictionary _valueCounters = []; + /// + /// The _valueSelector field. + /// private readonly Func _valueSelector = valueSelector ?? throw new ArgumentNullException(nameof(valueSelector)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => source.Select(Calculate).Where(updates => updates.Count != 0); + /// + /// Executes the Calculate operation. + /// + /// The changes value. + /// The result of the operation. private DistinctChangeSet Calculate(IChangeSet changes) { var result = new DistinctChangeSet(); diff --git a/src/DynamicData/Cache/Internal/DynamicCombiner.cs b/src/DynamicData/Cache/Internal/DynamicCombiner.cs index 41c7e3a8d..aae48656f 100644 --- a/src/DynamicData/Cache/Internal/DynamicCombiner.cs +++ b/src/DynamicData/Cache/Internal/DynamicCombiner.cs @@ -1,18 +1,34 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; - +#endif + +/// +/// Provides members for the DynamicCombiner class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. +/// The type value. internal sealed class DynamicCombiner(IObservableList>> source, CombineOperator type) where TObject : notnull where TKey : notnull { + /// + /// The _source field. + /// private readonly IObservableList>> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -82,6 +98,12 @@ public IObservable> Run() => Observable.Create + /// Executes the MatchesConstraint operation. + /// + /// The sources value. + /// The key value. + /// The result of the operation. private bool MatchesConstraint(MergeContainer[] sources, TKey key) { if (sources.Length == 0) @@ -118,6 +140,12 @@ private bool MatchesConstraint(MergeContainer[] sources, TKey key) } } + /// + /// Executes the ProcessChanges operation. + /// + /// The target value. + /// The sourceLists value. + /// The items value. private void ProcessChanges(ChangeAwareCache target, MergeContainer[] sourceLists, IEnumerable> items) { // check whether the item should be removed from the list (or in the case of And, added) @@ -138,6 +166,13 @@ private void ProcessChanges(ChangeAwareCache target, MergeContain } } + /// + /// Executes the ProcessItem operation. + /// + /// The target value. + /// The sourceLists value. + /// The item value. + /// The key value. private void ProcessItem(ChangeAwareCache target, MergeContainer[] sourceLists, TObject item, TKey key) { var cached = target.Lookup(key); @@ -160,6 +195,12 @@ private void ProcessItem(ChangeAwareCache target, MergeContainer[ } } + /// + /// Executes the UpdateResultList operation. + /// + /// The target value. + /// The sourceLists value. + /// The changes value. private void UpdateResultList(ChangeAwareCache target, MergeContainer[] sourceLists, IChangeSet changes) { foreach (var change in changes.ToConcreteType()) @@ -168,14 +209,31 @@ private void UpdateResultList(ChangeAwareCache target, MergeConta } } - private sealed class MergeContainer +/// +/// Provides members for the MergeContainer class. +/// +private sealed class MergeContainer { + /// + /// Initializes a new instance of the class. + /// + /// The source value. public MergeContainer(IObservable> source) => Source = source.Do(Clone); + /// + /// Gets the Cache value. + /// public Cache Cache { get; } = new(); + /// + /// Gets the Source value. + /// public IObservable> Source { get; } + /// + /// Executes the Clone operation. + /// + /// The changes value. private void Clone(IChangeSet changes) => Cache.Clone(changes); } } diff --git a/src/DynamicData/Cache/Internal/DynamicGrouper.cs b/src/DynamicData/Cache/Internal/DynamicGrouper.cs index cb4880193..99f0ad204 100644 --- a/src/DynamicData/Cache/Internal/DynamicGrouper.cs +++ b/src/DynamicData/Cache/Internal/DynamicGrouper.cs @@ -1,23 +1,60 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Diagnostics; -using System.Reactive.Disposables; +#if REACTIVE_SHIM -namespace DynamicData.Cache.Internal; +namespace DynamicData.Reactive.Cache.Internal; +#else +namespace DynamicData.Cache.Internal; +#endif + +/// +/// Provides members for the DynamicGrouper class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TGroupKey value. +/// The groupSelector value. internal sealed class DynamicGrouper(Func? groupSelector = null) : IDisposable where TObject : notnull where TKey : notnull where TGroupKey : notnull { + /// + /// The _groupCache field. + /// private readonly ChangeAwareCache, TGroupKey> _groupCache = new(); + + /// + /// The _groupKeys field. + /// private readonly Dictionary _groupKeys = []; + + /// + /// The _emptyGroups field. + /// private readonly HashSet> _emptyGroups = []; + + /// + /// The _suspendTracker field. + /// private readonly SuspendTracker _suspendTracker = new(); + + /// + /// The _groupSelector field. + /// private Func? _groupSelector = groupSelector; + /// + /// Executes the AddOrUpdate operation. + /// + /// The key value. + /// The groupKey value. + /// The item value. + /// The observer value. public void AddOrUpdate(TKey key, TGroupKey groupKey, TObject item, IObserver>? observer = null) { // If not emitting the changes, then suspend the notifications @@ -30,6 +67,11 @@ public void AddOrUpdate(TKey key, TGroupKey groupKey, TObject item, IObserver + /// Executes the ProcessChangeSet operation. + /// + /// The changeSet value. + /// The observer value. public void ProcessChangeSet(IChangeSet changeSet, IObserver>? observer = null) { var suspendTracker = (observer, changeSet.Count) switch @@ -53,8 +95,17 @@ public void ProcessChangeSet(IChangeSet changeSet, IObserver + /// Executes the ProcessChange operation. + /// + /// The change value. public void ProcessChange(Change change) => ProcessChange(change, _suspendTracker); + /// + /// Executes the ProcessChange operation. + /// + /// The change value. + /// The suspendTracker value. private void ProcessChange(Change change, SuspendTracker? suspendTracker) { switch (change.Reason) @@ -84,9 +135,13 @@ private void ProcessChange(Change change, SuspendTracker? suspend break; } } - // Re-evaluate the GroupSelector for each item and apply the changes so that each group only emits a single changset // Perform all the adds/removes for each group in a single step + + /// + /// Executes the RegroupAll operation. + /// + /// The observer value. public void RegroupAll(IObserver> observer) { if (_groupSelector == null) @@ -119,12 +174,23 @@ public void RegroupAll(IObserver> obse EmitChanges(observer); } + /// + /// Executes the SetGroupSelector operation. + /// + /// The groupSelector value. + /// The observer value. public void SetGroupSelector(Func groupSelector, IObserver> observer) { _groupSelector = groupSelector; RegroupAll(observer); } + /// + /// Executes the Initialize operation. + /// + /// The initialValues value. + /// The groupSelector value. + /// The observer value. public void Initialize(IEnumerable> initialValues, Func groupSelector, IObserver> observer) { if (_groupSelector != null) @@ -143,6 +209,10 @@ public void Initialize(IEnumerable> initialValues, F EmitChanges(observer); } + /// + /// Executes the EmitChanges operation. + /// + /// The observer value. public void EmitChanges(IObserver> observer) { // Verify logic doesn't capture any non-empty groups @@ -173,13 +243,22 @@ public void EmitChanges(IObserver> obs } } + /// + /// Executes the Dispose operation. + /// public void Dispose() { _suspendTracker.Dispose(); _groupCache.Items.ForEach(group => (group as ManagedGroup)?.Dispose()); } - private static void PerformGroupRefresh(TKey key, in Optional> optionalGroup, SuspendTracker? suspendTracker = null) + /// + /// Executes the PerformGroupRefresh operation. + /// + /// The key value. + /// The optionalGroup value. + /// The suspendTracker value. + private static void PerformGroupRefresh(TKey key, in ReactiveUI.Primitives.Optional> optionalGroup, SuspendTracker? suspendTracker = null) { if (optionalGroup.HasValue) { @@ -192,12 +271,27 @@ private static void PerformGroupRefresh(TKey key, in Optional> LookupGroup(TKey key) => + /// + /// Executes the LookupGroup operation. + /// + /// The key value. + /// The result of the operation. + private ReactiveUI.Primitives.Optional> LookupGroup(TKey key) => _groupKeys.Lookup(key).Convert(LookupGroup); - private Optional> LookupGroup(TGroupKey groupKey) => + /// + /// Executes the LookupGroup operation. + /// + /// The groupKey value. + /// The result of the operation. + private ReactiveUI.Primitives.Optional> LookupGroup(TGroupKey groupKey) => _groupCache.Lookup(groupKey).Convert(static grp => (grp as ManagedGroup)!); + /// + /// Executes the GetOrAddGroup operation. + /// + /// The groupKey value. + /// The result of the operation. private ManagedGroup GetOrAddGroup(TGroupKey groupKey) => LookupGroup(groupKey).ValueOr(() => { @@ -206,6 +300,13 @@ private ManagedGroup GetOrAddGroup(TGroupKey groupKey) return newGroup; }); + /// + /// Executes the PerformAddOrUpdate operation. + /// + /// The key value. + /// The groupKey value. + /// The item value. + /// The suspendTracker value. private void PerformAddOrUpdate(TKey key, TGroupKey groupKey, TObject item, SuspendTracker? suspendTracker = null) { // See if this item already has been grouped @@ -236,6 +337,13 @@ private void PerformAddOrUpdate(TKey key, TGroupKey groupKey, TObject item, Susp PerformGroupAddOrUpdate(key, groupKey, item, suspendTracker); } + /// + /// Executes the PerformGroupAddOrUpdate operation. + /// + /// The key value. + /// The groupKey value. + /// The item value. + /// The suspendTracker value. private void PerformGroupAddOrUpdate(TKey key, TGroupKey groupKey, TObject item, SuspendTracker? suspendTracker = null) { var group = GetOrAddGroup(groupKey); @@ -247,9 +355,21 @@ private void PerformGroupAddOrUpdate(TKey key, TGroupKey groupKey, TObject item, _emptyGroups.Remove(group); } + /// + /// Executes the PerformRefresh operation. + /// + /// The key value. + /// The suspendTracker value. private void PerformRefresh(TKey key, SuspendTracker? suspendTracker = null) => PerformGroupRefresh(key, LookupGroup(key), suspendTracker); - // When the GroupKey is available, check then and move the group if it changed + + /// + /// Executes the PerformRefresh operation. + /// + /// The key value. + /// The newGroupKey value. + /// The item value. + /// The suspendTracker value. private void PerformRefresh(TKey key, TGroupKey newGroupKey, TObject item, SuspendTracker? suspendTracker = null) { if (_groupKeys.TryGetValue(key, out var groupKey)) @@ -273,6 +393,11 @@ private void PerformRefresh(TKey key, TGroupKey newGroupKey, TObject item, Suspe } } + /// + /// Executes the PerformRemove operation. + /// + /// The key value. + /// The suspendTracker value. private void PerformRemove(TKey key, SuspendTracker? suspendTracker = null) { if (_groupKeys.TryGetValue(key, out var groupKey)) @@ -286,6 +411,12 @@ private void PerformRemove(TKey key, SuspendTracker? suspendTracker = null) } } + /// + /// Executes the PerformRemove operation. + /// + /// The key value. + /// The groupKey value. + /// The suspendTracker value. private void PerformRemove(TKey key, TGroupKey groupKey, SuspendTracker? suspendTracker = null) { var optionalGroup = LookupGroup(groupKey); @@ -307,18 +438,40 @@ private void PerformRemove(TKey key, TGroupKey groupKey, SuspendTracker? suspend Debug.Fail("Should not receive a Remove Event for an unknown Group Key"); } } - // Without the new group key, all that can be done is remove the old value // Consumer of the Grouper is resonsible for Adding the New Value. + + /// + /// Executes the PerformUpdate operation. + /// + /// The key value. + /// The suspendTracker value. private void PerformUpdate(TKey key, SuspendTracker? suspendTracker = null) => PerformRemove(key, suspendTracker); - private sealed class SuspendTracker : IDisposable +/// +/// Provides members for the SuspendTracker class. +/// +private sealed class SuspendTracker : IDisposable { + /// + /// The _trackedKeys field. + /// private readonly HashSet _trackedKeys = []; + + /// + /// The _disposables field. + /// private CompositeDisposable _disposables = []; + /// + /// Gets the HasItems value. + /// public bool HasItems => _disposables.Count > 0; + /// + /// Executes the Add operation. + /// + /// The managedGroup value. public void Add(ManagedGroup managedGroup) { if (_trackedKeys.Add(managedGroup.Key)) @@ -327,6 +480,9 @@ public void Add(ManagedGroup managedGroup) } } + /// + /// Executes the Reset operation. + /// public void Reset() { if (_disposables.Count > 0) @@ -337,6 +493,9 @@ public void Reset() } } + /// + /// Executes the Dispose operation. + /// public void Dispose() => _disposables.Dispose(); } } diff --git a/src/DynamicData/Cache/Internal/EditDiff.cs b/src/DynamicData/Cache/Internal/EditDiff.cs index ba6679d03..ca56e0a20 100644 --- a/src/DynamicData/Cache/Internal/EditDiff.cs +++ b/src/DynamicData/Cache/Internal/EditDiff.cs @@ -1,19 +1,44 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -namespace DynamicData.Cache.Internal; +namespace DynamicData.Reactive.Cache.Internal; +#else +namespace DynamicData.Cache.Internal; +#endif + +/// +/// Provides members for the EditDiff class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. +/// The areEqual value. internal sealed class EditDiff(ISourceCache source, Func areEqual) where TObject : notnull where TKey : notnull { + /// + /// The _areEqual field. + /// private readonly Func _areEqual = areEqual ?? throw new ArgumentNullException(nameof(areEqual)); + /// + /// The _keyComparer field. + /// private readonly IEqualityComparer> _keyComparer = new KeyComparer(); + /// + /// The _source field. + /// private readonly ISourceCache _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Edit operation. + /// + /// The items value. public void Edit(IEnumerable items) => _source.Edit( innerCache => { diff --git a/src/DynamicData/Cache/Internal/EditDiffChangeSet.cs b/src/DynamicData/Cache/Internal/EditDiffChangeSet.cs index b363a9505..bc054cf08 100644 --- a/src/DynamicData/Cache/Internal/EditDiffChangeSet.cs +++ b/src/DynamicData/Cache/Internal/EditDiffChangeSet.cs @@ -1,19 +1,45 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the EditDiffChangeSet class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. +/// The keySelector value. +/// The equalityComparer value. internal sealed class EditDiffChangeSet(IObservable> source, Func keySelector, IEqualityComparer? equalityComparer) where TObject : notnull where TKey : notnull { + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// The _equalityComparer field. + /// private readonly IEqualityComparer _equalityComparer = equalityComparer ?? EqualityComparer.Default; + /// + /// The _keySelector field. + /// private readonly Func _keySelector = keySelector ?? throw new ArgumentNullException(nameof(keySelector)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => ObservableChangeSet.Create( cache => _source.Subscribe(items => cache.EditDiff(items, _equalityComparer), () => cache.Dispose()), diff --git a/src/DynamicData/Cache/Internal/EditDiffChangeSetOptional.cs b/src/DynamicData/Cache/Internal/EditDiffChangeSetOptional.cs index f65df1b96..3f7734c41 100644 --- a/src/DynamicData/Cache/Internal/EditDiffChangeSetOptional.cs +++ b/src/DynamicData/Cache/Internal/EditDiffChangeSetOptional.cs @@ -1,24 +1,48 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif -internal sealed class EditDiffChangeSetOptional(IObservable> source, Func keySelector, IEqualityComparer? equalityComparer) +/// +/// Provides members for the EditDiffChangeSetOptional class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. +/// The keySelector value. +/// The equalityComparer value. +internal sealed class EditDiffChangeSetOptional(IObservable> source, Func keySelector, IEqualityComparer? equalityComparer) where TObject : notnull where TKey : notnull { - private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// The _source field. + /// + private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// The _equalityComparer field. + /// private readonly IEqualityComparer _equalityComparer = equalityComparer ?? EqualityComparer.Default; + /// + /// The _keySelector field. + /// private readonly Func _keySelector = keySelector ?? throw new ArgumentNullException(nameof(keySelector)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>(observer => { - var previous = Optional.None(); + var previous = ReactiveUI.Primitives.Optional.None; return _source.Synchronize().Subscribe( nextValue => @@ -47,6 +71,12 @@ public IObservable> Run() => Observable.Create + /// Executes the CreateUpdateChanges operation. + /// + /// The prev value. + /// The curr value. + /// The result of the operation. private Change[] CreateUpdateChanges(in ValueContainer prev, in ValueContainer curr) { if (EqualityComparer.Default.Equals(prev.Key, curr.Key)) @@ -68,10 +98,21 @@ private Change[] CreateUpdateChanges(in ValueContainer prev, in V ]; } - private readonly struct ValueContainer(TObject obj, TKey key) +/// +/// Represents the ValueContainer value. +/// +/// The obj value. +/// The key value. +private readonly struct ValueContainer(TObject obj, TKey key) { + /// + /// Gets the Object value. + /// public TObject Object { get; } = obj; + /// + /// Gets the Key value. + /// public TKey Key { get; } = key; } } diff --git a/src/DynamicData/Cache/Internal/ExpirableItem.cs b/src/DynamicData/Cache/Internal/ExpirableItem.cs index ab02ac3d1..11cdf6052 100644 --- a/src/DynamicData/Cache/Internal/ExpirableItem.cs +++ b/src/DynamicData/Cache/Internal/ExpirableItem.cs @@ -1,30 +1,27 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -namespace DynamicData.Cache.Internal; +namespace DynamicData.Reactive.Cache.Internal; +#else -internal readonly struct ExpirableItem(TObject value, TKey key, DateTime dateTime, long index = 0) : IEquatable> +namespace DynamicData.Cache.Internal; +#endif + +/// +/// Represents the ExpirableItem record. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The Value value. +/// The Key value. +/// The ExpireAt value. +/// The Index value. +internal readonly record struct ExpirableItem(TObject Value, TKey Key, DateTime ExpireAt, long Index = 0) { - public TObject Value { get; } = value; - - public TKey Key { get; } = key; - - public DateTime ExpireAt { get; } = dateTime; - - public long Index { get; } = index; - - public static bool operator ==(in ExpirableItem left, in ExpirableItem right) => left.Equals(right); - - public static bool operator !=(in ExpirableItem left, in ExpirableItem right) => !left.Equals(right); - - /// - public bool Equals(ExpirableItem other) => EqualityComparer.Default.Equals(Key, other.Key) && ExpireAt.Equals(other.ExpireAt); - - /// - public override bool Equals(object? obj) => obj is ExpirableItem expItem && Equals(expItem); - /// + /// The result of the operation. public override int GetHashCode() { unchecked @@ -33,5 +30,9 @@ public override int GetHashCode() } } + /// + /// Executes the ToString operation. + /// + /// The result of the operation. public override string ToString() => $"Key: {Key}, Expire At: {ExpireAt}"; } diff --git a/src/DynamicData/Cache/Internal/ExpireAfter.ForSource.cs b/src/DynamicData/Cache/Internal/ExpireAfter.ForSource.cs index 60bcd9de2..4ac684ec3 100644 --- a/src/DynamicData/Cache/Internal/ExpireAfter.ForSource.cs +++ b/src/DynamicData/Cache/Internal/ExpireAfter.ForSource.cs @@ -1,29 +1,44 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; - -using DynamicData.Internal; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the ExpireAfter class. +/// internal static partial class ExpireAfter { - public static class ForSource +/// +/// Provides members for the ForSource class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +public static class ForSource where TObject : notnull where TKey : notnull { + /// + /// Executes the Create operation. + /// + /// The source value. + /// The timeSelector value. + /// The pollingInterval value. + /// The scheduler value. + /// The result of the operation. public static IObservable>> Create( ISourceCache source, Func timeSelector, TimeSpan? pollingInterval = null, IScheduler? scheduler = null) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - timeSelector.ThrowArgumentNullExceptionIfNull(nameof(timeSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(timeSelector); return Observable.Create>>(observer => (pollingInterval is { } pollingIntervalValue) ? new PollingSubscription( @@ -39,22 +54,74 @@ public static IObservable>> Create( timeSelector: timeSelector)); } - private abstract class SubscriptionBase +/// +/// Provides members for the SubscriptionBase class. +/// +private abstract class SubscriptionBase : IDisposable { + /// + /// The _expirationDueTimesByKey field. + /// private readonly Dictionary _expirationDueTimesByKey; + + /// + /// The _observer field. + /// private readonly IObserver>> _observer; + + /// + /// The _onEditingSource field. + /// private readonly Action> _onEditingSource; + + /// + /// The _proposedExpirationsQueue field. + /// private readonly List _proposedExpirationsQueue; + + /// + /// The _removedItemsBuffer field. + /// private readonly List> _removedItemsBuffer; + + /// + /// The _scheduler field. + /// private readonly IScheduler _scheduler; + + /// + /// The _source field. + /// private readonly ISourceCache _source; + + /// + /// The _sourceSubscription field. + /// private readonly IDisposable _sourceSubscription; + + /// + /// The _timeSelector field. + /// private readonly Func _timeSelector; + /// + /// The _hasSourceCompleted field. + /// private bool _hasSourceCompleted; + + /// + /// The _nextScheduledManagement field. + /// private ScheduledManagement? _nextScheduledManagement; + /// + /// Initializes a new instance of the class. + /// + /// The observer value. + /// The scheduler value. + /// The source value. + /// The timeSelector value. protected SubscriptionBase( IObserver>> observer, IScheduler? scheduler, @@ -73,17 +140,20 @@ protected SubscriptionBase( _proposedExpirationsQueue = []; _removedItemsBuffer = []; - _sourceSubscription = source - .Connect() - // It's important to set this flag outside the context of a lock, because it'll be read outside of lock as well. - .Finally(() => _hasSourceCompleted = true) - .Synchronize(SynchronizationGate) - .SubscribeSafe( - onNext: OnSourceNext, - onError: OnSourceError, - onCompleted: OnSourceCompleted); + _sourceSubscription = PrimitivesLinqExtensions.SubscribeSafe( + source + .Connect() + // It's important to set this flag outside the context of a lock, because it'll be read outside of lock as well. + .Finally(() => _hasSourceCompleted = true) + .Synchronize(SynchronizationGate), + OnSourceNext, + OnSourceError, + OnSourceCompleted); } + /// + /// Executes the Dispose operation. + /// public void Dispose() { lock (SynchronizationGate) @@ -94,28 +164,53 @@ public void Dispose() } } + /// + /// Gets the Scheduler value. + /// protected IScheduler Scheduler => _scheduler; - // Instead of using a dedicated _synchronizationGate object, we can save an allocation by using any object that is never exposed to public consumers. + + /// + /// Gets the SynchronizationGate value. + /// protected object SynchronizationGate => _expirationDueTimesByKey; + /// + /// Executes the GetNextManagementDueTime operation. + /// + /// The result of the operation. protected abstract DateTimeOffset? GetNextManagementDueTime(); + /// + /// Executes the GetNextProposedExpirationDueTime operation. + /// + /// The result of the operation. protected DateTimeOffset? GetNextProposedExpirationDueTime() => _proposedExpirationsQueue.Count is 0 ? null : _proposedExpirationsQueue[0].DueTime; + /// + /// Executes the OnExpirationsManaged operation. + /// + /// The dueTime value. protected abstract void OnExpirationsManaged(DateTimeOffset dueTime); + /// + /// Executes the ClearExpiration operation. + /// + /// The key value. private void ClearExpiration(TKey key) // This is what puts the "proposed" in _proposedExpirationsQueue. // Finding the position of the item to remove from the queue would be O(log n), at best, // so just leave it and flush it later during normal processing of the queue. => _expirationDueTimesByKey.Remove(key); + /// + /// Executes the ManageExpirations operation. + /// private void ManageExpirations() { // This check is needed, to make sure we don't try and call .Edit() on a disposed _source, @@ -132,6 +227,10 @@ private void ManageExpirations() _source.Edit(_onEditingSource); } + /// + /// Executes the OnEditingSource operation. + /// + /// The updater value. private void OnEditingSource(ISourceUpdater updater) { lock (SynchronizationGate) @@ -193,6 +292,9 @@ private void OnEditingSource(ISourceUpdater updater) } } + /// + /// Executes the OnExpirationsChanged operation. + /// private void OnExpirationsChanged() { // Clear out any expirations at the front of the queue that are no longer valid. @@ -233,6 +335,9 @@ private void OnExpirationsChanged() } } + /// + /// Executes the OnSourceCompleted operation. + /// private void OnSourceCompleted() { // If the source completes, we can no longer remove items from it, so any pending expirations are moot. @@ -241,6 +346,10 @@ private void OnSourceCompleted() _observer.OnCompleted(); } + /// + /// Executes the OnSourceError operation. + /// + /// The error value. private void OnSourceError(Exception error) { TryCancelNextScheduledManagement(); @@ -248,6 +357,10 @@ private void OnSourceError(Exception error) _observer.OnError(error); } + /// + /// Executes the OnSourceNext operation. + /// + /// The changes value. private void OnSourceNext(IChangeSet changes) { try @@ -305,12 +418,21 @@ private void OnSourceNext(IChangeSet changes) } } + /// + /// Executes the TryCancelNextScheduledManagement operation. + /// private void TryCancelNextScheduledManagement() { _nextScheduledManagement?.Cancellation.Dispose(); _nextScheduledManagement = null; } + /// + /// Executes the TrySetExpiration operation. + /// + /// The key value. + /// The dueTime value. + /// The result of the operation. private bool TrySetExpiration( TKey key, DateTimeOffset dueTime) @@ -342,22 +464,47 @@ private bool TrySetExpiration( return true; } - private readonly struct ProposedExpiration +/// +/// Represents the ProposedExpiration value. +/// +private readonly struct ProposedExpiration { + /// + /// Gets or sets the DueTime value. + /// public required DateTimeOffset DueTime { get; init; } + /// + /// Gets or sets the Key value. + /// public required TKey Key { get; init; } } - private readonly struct ScheduledManagement +/// +/// Represents the ScheduledManagement value. +/// +private readonly struct ScheduledManagement { + /// + /// Gets or sets the Cancellation value. + /// public required IDisposable Cancellation { get; init; } + /// + /// Gets or sets the DueTime value. + /// public required DateTimeOffset DueTime { get; init; } } } - private sealed class OnDemandSubscription( +/// +/// Provides members for the OnDemandSubscription class. +/// +/// The observer value. +/// The scheduler value. +/// The source value. +/// The timeSelector value. +private sealed class OnDemandSubscription( IObserver>> observer, IScheduler? scheduler, ISourceCache source, @@ -368,21 +515,46 @@ private sealed class OnDemandSubscription( source, timeSelector) { + /// + /// Executes the GetNextManagementDueTime operation. + /// + /// The result of the operation. protected override DateTimeOffset? GetNextManagementDueTime() => GetNextProposedExpirationDueTime(); + /// + /// Executes the OnExpirationsManaged operation. + /// + /// The dueTime value. protected override void OnExpirationsManaged(DateTimeOffset dueTime) { } } - private sealed class PollingSubscription +/// +/// Provides members for the PollingSubscription class. +/// +private sealed class PollingSubscription : SubscriptionBase { + /// + /// The _pollingInterval field. + /// private readonly TimeSpan _pollingInterval; + /// + /// The _lastManagementDueTime field. + /// private DateTimeOffset _lastManagementDueTime; + /// + /// Initializes a new instance of the class. + /// + /// The observer value. + /// The pollingInterval value. + /// The scheduler value. + /// The source value. + /// The timeSelector value. public PollingSubscription( IObserver>> observer, TimeSpan pollingInterval, @@ -400,6 +572,10 @@ public PollingSubscription( _lastManagementDueTime = Scheduler.Now; } + /// + /// Executes the GetNextManagementDueTime operation. + /// + /// The result of the operation. protected override DateTimeOffset? GetNextManagementDueTime() { var now = Scheduler.Now; @@ -411,6 +587,10 @@ public PollingSubscription( : now; } + /// + /// Executes the OnExpirationsManaged operation. + /// + /// The dueTime value. protected override void OnExpirationsManaged(DateTimeOffset dueTime) => _lastManagementDueTime = dueTime; } diff --git a/src/DynamicData/Cache/Internal/ExpireAfter.ForStream.cs b/src/DynamicData/Cache/Internal/ExpireAfter.ForStream.cs index d128ada8f..bfa548c8c 100644 --- a/src/DynamicData/Cache/Internal/ExpireAfter.ForStream.cs +++ b/src/DynamicData/Cache/Internal/ExpireAfter.ForStream.cs @@ -1,29 +1,44 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; - -using DynamicData.Internal; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the ExpireAfter class. +/// internal static partial class ExpireAfter { - public static class ForStream +/// +/// Provides members for the ForStream class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +public static class ForStream where TObject : notnull where TKey : notnull { + /// + /// Executes the Create operation. + /// + /// The source value. + /// The timeSelector value. + /// The pollingInterval value. + /// The scheduler value. + /// The result of the operation. public static IObservable> Create( IObservable> source, Func timeSelector, TimeSpan? pollingInterval = null, IScheduler? scheduler = null) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - timeSelector.ThrowArgumentNullExceptionIfNull(nameof(timeSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(timeSelector); return Observable.Create>(observer => (pollingInterval is { } pollingIntervalValue) ? new PollingSubscription( @@ -39,20 +54,64 @@ public static IObservable> Create( timeSelector: timeSelector)); } - private abstract class SubscriptionBase +/// +/// Provides members for the SubscriptionBase class. +/// +private abstract class SubscriptionBase : IDisposable { + /// + /// The _expirationDueTimesByKey field. + /// private readonly Dictionary _expirationDueTimesByKey; + + /// + /// The _itemsCache field. + /// private readonly ChangeAwareCache _itemsCache; + + /// + /// The _observer field. + /// private readonly IObserver> _observer; + + /// + /// The _proposedExpirationsQueue field. + /// private readonly List _proposedExpirationsQueue; + + /// + /// The _scheduler field. + /// private readonly IScheduler _scheduler; + + /// + /// The _sourceSubscription field. + /// private readonly IDisposable _sourceSubscription; + + /// + /// The _timeSelector field. + /// private readonly Func _timeSelector; + /// + /// The _hasSourceCompleted field. + /// private bool _hasSourceCompleted; + + /// + /// The _nextScheduledManagement field. + /// private ScheduledManagement? _nextScheduledManagement; + /// + /// Initializes a new instance of the class. + /// + /// The observer value. + /// The scheduler value. + /// The source value. + /// The timeSelector value. protected SubscriptionBase( IObserver> observer, IScheduler? scheduler, @@ -68,14 +127,16 @@ protected SubscriptionBase( _itemsCache = new(); _proposedExpirationsQueue = []; - _sourceSubscription = source - .Synchronize(SynchronizationGate) - .SubscribeSafe( - onNext: OnSourceNext, - onError: OnSourceError, - onCompleted: OnSourceCompleted); + _sourceSubscription = PrimitivesLinqExtensions.SubscribeSafe( + source.Synchronize(SynchronizationGate), + OnSourceNext, + OnSourceError, + OnSourceCompleted); } + /// + /// Executes the Dispose operation. + /// public void Dispose() { lock (SynchronizationGate) @@ -86,28 +147,53 @@ public void Dispose() } } + /// + /// Gets the Scheduler value. + /// protected IScheduler Scheduler => _scheduler; - // Instead of using a dedicated _synchronizationGate object, we can save an allocation by using any object that is never exposed to public consumers. + + /// + /// Gets the SynchronizationGate value. + /// protected object SynchronizationGate => _expirationDueTimesByKey; + /// + /// Executes the GetNextManagementDueTime operation. + /// + /// The result of the operation. protected abstract DateTimeOffset? GetNextManagementDueTime(); + /// + /// Executes the GetNextProposedExpirationDueTime operation. + /// + /// The result of the operation. protected DateTimeOffset? GetNextProposedExpirationDueTime() => _proposedExpirationsQueue.Count is 0 ? null : _proposedExpirationsQueue[0].DueTime; + /// + /// Executes the OnExpirationsManaged operation. + /// + /// The dueTime value. protected abstract void OnExpirationsManaged(DateTimeOffset dueTime); + /// + /// Executes the ClearExpiration operation. + /// + /// The key value. private void ClearExpiration(TKey key) // This is what puts the "proposed" in _proposedExpirationsQueue. // Finding the position of the item to remove from the queue would be O(log n), at best, // so just leave it and flush it later during normal processing of the queue. => _expirationDueTimesByKey.Remove(key); + /// + /// Executes the ManageExpirations operation. + /// private void ManageExpirations() { lock (SynchronizationGate) @@ -154,6 +240,9 @@ private void ManageExpirations() } } + /// + /// Executes the OnExpirationsChanged operation. + /// private void OnExpirationsChanged() { // Clear out any expirations at the front of the queue that are no longer valid. @@ -204,6 +293,9 @@ private void OnExpirationsChanged() } } + /// + /// Executes the OnSourceCompleted operation. + /// private void OnSourceCompleted() { _hasSourceCompleted = true; @@ -217,6 +309,10 @@ private void OnSourceCompleted() } } + /// + /// Executes the OnSourceError operation. + /// + /// The error value. private void OnSourceError(Exception error) { TryCancelNextScheduledManagement(); @@ -224,6 +320,10 @@ private void OnSourceError(Exception error) _observer.OnError(error); } + /// + /// Executes the OnSourceNext operation. + /// + /// The upstreamChanges value. private void OnSourceNext(IChangeSet upstreamChanges) { try @@ -294,12 +394,21 @@ private void OnSourceNext(IChangeSet upstreamChanges) } } + /// + /// Executes the TryCancelNextScheduledManagement operation. + /// private void TryCancelNextScheduledManagement() { _nextScheduledManagement?.Cancellation.Dispose(); _nextScheduledManagement = null; } + /// + /// Executes the TrySetExpiration operation. + /// + /// The key value. + /// The dueTime value. + /// The result of the operation. private bool TrySetExpiration( TKey key, DateTimeOffset dueTime) @@ -331,22 +440,47 @@ private bool TrySetExpiration( return true; } - private readonly struct ProposedExpiration +/// +/// Represents the ProposedExpiration value. +/// +private readonly struct ProposedExpiration { + /// + /// Gets or sets the DueTime value. + /// public required DateTimeOffset DueTime { get; init; } + /// + /// Gets or sets the Key value. + /// public required TKey Key { get; init; } } - private readonly struct ScheduledManagement +/// +/// Represents the ScheduledManagement value. +/// +private readonly struct ScheduledManagement { + /// + /// Gets or sets the Cancellation value. + /// public required IDisposable Cancellation { get; init; } + /// + /// Gets or sets the DueTime value. + /// public required DateTimeOffset DueTime { get; init; } } } - private sealed class OnDemandSubscription( +/// +/// Provides members for the OnDemandSubscription class. +/// +/// The observer value. +/// The scheduler value. +/// The source value. +/// The timeSelector value. +private sealed class OnDemandSubscription( IObserver> observer, IScheduler? scheduler, IObservable> source, @@ -357,21 +491,46 @@ private sealed class OnDemandSubscription( source, timeSelector) { + /// + /// Executes the GetNextManagementDueTime operation. + /// + /// The result of the operation. protected override DateTimeOffset? GetNextManagementDueTime() => GetNextProposedExpirationDueTime(); + /// + /// Executes the OnExpirationsManaged operation. + /// + /// The dueTime value. protected override void OnExpirationsManaged(DateTimeOffset dueTime) { } } - private sealed class PollingSubscription +/// +/// Provides members for the PollingSubscription class. +/// +private sealed class PollingSubscription : SubscriptionBase { + /// + /// The _pollingInterval field. + /// private readonly TimeSpan _pollingInterval; + /// + /// The _lastManagementDueTime field. + /// private DateTimeOffset _lastManagementDueTime; + /// + /// Initializes a new instance of the class. + /// + /// The observer value. + /// The pollingInterval value. + /// The scheduler value. + /// The source value. + /// The timeSelector value. public PollingSubscription( IObserver> observer, TimeSpan pollingInterval, @@ -389,6 +548,10 @@ public PollingSubscription( _lastManagementDueTime = Scheduler.Now; } + /// + /// Executes the GetNextManagementDueTime operation. + /// + /// The result of the operation. protected override DateTimeOffset? GetNextManagementDueTime() { var now = Scheduler.Now; @@ -400,6 +563,10 @@ public PollingSubscription( : now; } + /// + /// Executes the OnExpirationsManaged operation. + /// + /// The dueTime value. protected override void OnExpirationsManaged(DateTimeOffset dueTime) => _lastManagementDueTime = dueTime; } diff --git a/src/DynamicData/Cache/Internal/Filter.Dynamic.cs b/src/DynamicData/Cache/Internal/Filter.Dynamic.cs index 350ae272b..87b98aef8 100644 --- a/src/DynamicData/Cache/Internal/Filter.Dynamic.cs +++ b/src/DynamicData/Cache/Internal/Filter.Dynamic.cs @@ -1,19 +1,38 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; -using System.Reactive.Linq; -using DynamicData.Internal; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the Filter class. +/// internal static partial class Filter { - public static class Dynamic +/// +/// Provides members for the Dynamic class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TState value. +public static class Dynamic where TObject : notnull where TKey : notnull { + /// + /// Executes the Create operation. + /// + /// The source value. + /// The predicateState value. + /// The predicate value. + /// The reapplyFilter value. + /// The suppressEmptyChangeSets value. + /// The result of the operation. public static IObservable> Create( IObservable> source, IObservable predicateState, @@ -21,10 +40,10 @@ public static IObservable> Create( IObservable reapplyFilter, bool suppressEmptyChangeSets) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - predicateState.ThrowArgumentNullExceptionIfNull(nameof(predicateState)); - predicate.ThrowArgumentNullExceptionIfNull(nameof(predicate)); - reapplyFilter.ThrowArgumentNullExceptionIfNull(nameof(reapplyFilter)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(predicateState); + ArgumentExceptionHelper.ThrowIfNull(predicate); + ArgumentExceptionHelper.ThrowIfNull(reapplyFilter); return Observable.Create>(downstreamObserver => new Subscription( downstreamObserver: downstreamObserver, @@ -35,30 +54,101 @@ public static IObservable> Create( suppressEmptyChangeSets: suppressEmptyChangeSets)); } - private sealed class Subscription +/// +/// Provides members for the Subscription class. +/// +private sealed class Subscription : IDisposable { + /// + /// The _downstreamChangesBuffer field. + /// private readonly List> _downstreamChangesBuffer; + + /// + /// The _downstreamObserver field. + /// private readonly IObserver> _downstreamObserver; + + /// + /// The _itemStatesByKey field. + /// private readonly Dictionary _itemStatesByKey; + + /// + /// The _predicate field. + /// private readonly Func _predicate; + + /// + /// The _predicateStateSubscription field. + /// private readonly IDisposable? _predicateStateSubscription; + + /// + /// The _reapplyFilterSubscription field. + /// private readonly IDisposable? _reapplyFilterSubscription; + + /// + /// The _sourceSubscription field. + /// private readonly IDisposable? _sourceSubscription; + + /// + /// The _suppressEmptyChangeSets field. + /// private readonly bool _suppressEmptyChangeSets; -#if NET9_0_OR_GREATER + /// + /// The _downstreamGate field. + /// private readonly Lock _downstreamGate = new(); + + /// + /// The _upstreamGate field. + /// private readonly Lock _upstreamGate = new(); -#endif + /// + /// The _hasInitialized field. + /// private bool _hasInitialized; + + /// + /// The _hasPredicateStateCompleted field. + /// private bool _hasPredicateStateCompleted; + + /// + /// The _hasReapplyFilterCompleted field. + /// private bool _hasReapplyFilterCompleted; + + /// + /// The _hasSourceCompleted field. + /// private bool _hasSourceCompleted; + + /// + /// The _isLatestPredicateStateValid field. + /// private bool _isLatestPredicateStateValid; + + /// + /// The _latestPredicateState field. + /// private TState _latestPredicateState; + /// + /// Initializes a new instance of the class. + /// + /// The downstreamObserver value. + /// The predicate value. + /// The predicateState value. + /// The reapplyFilter value. + /// The source value. + /// The suppressEmptyChangeSets value. public Subscription( IObserver> downstreamObserver, Func predicate, @@ -80,23 +170,22 @@ public Subscription( using var @lock = SwappableLock.CreateAndEnter(UpstreamSynchronizationGate); - _predicateStateSubscription = predicateState - .SubscribeSafe( - onNext: OnPredicateStateNext, - onError: onError, - onCompleted: OnPredicateStateCompleted); + _predicateStateSubscription = predicateState.SubscribeSafe(Observer.Create( + onNext: OnPredicateStateNext, + onError: onError, + onCompleted: OnPredicateStateCompleted)); - _reapplyFilterSubscription = reapplyFilter - .SubscribeSafe( - onNext: OnReapplyFilterNext, - onError: onError, - onCompleted: OnReapplyFilterCompleted); + _reapplyFilterSubscription = PrimitivesLinqExtensions.SubscribeSafe( + reapplyFilter, + OnReapplyFilterNext, + onError, + OnReapplyFilterCompleted); - _sourceSubscription = source - .SubscribeSafe( - onNext: OnSourceNext, - onError: onError, - onCompleted: OnSourceCompleted); + _sourceSubscription = PrimitivesLinqExtensions.SubscribeSafe( + source, + OnSourceNext, + onError, + OnSourceCompleted); _hasInitialized = true; @@ -114,6 +203,9 @@ public Subscription( } } + /// + /// Executes the Dispose operation. + /// public void Dispose() { _predicateStateSubscription?.Dispose(); @@ -121,20 +213,22 @@ public void Dispose() _sourceSubscription?.Dispose(); } -#if NET9_0_OR_GREATER + /// + /// Gets the DownstreamSynchronizationGate value. + /// private Lock DownstreamSynchronizationGate => _downstreamGate; + /// + /// Gets the UpstreamSynchronizationGate value. + /// private Lock UpstreamSynchronizationGate => _upstreamGate; -#else - private object DownstreamSynchronizationGate - => _downstreamChangesBuffer; - - private object UpstreamSynchronizationGate - => _itemStatesByKey; -#endif + /// + /// Executes the AssembleDownstreamChanges operation. + /// + /// The result of the operation. private ChangeSet AssembleDownstreamChanges() { if (_downstreamChangesBuffer.Count is 0) @@ -146,6 +240,10 @@ private ChangeSet AssembleDownstreamChanges() return downstreamChanges; } + /// + /// Executes the OnError operation. + /// + /// The error value. private void OnError(Exception error) { using var @lock = SwappableLock.CreateAndEnter(UpstreamSynchronizationGate); @@ -158,6 +256,9 @@ private void OnError(Exception error) _downstreamObserver.OnError(error); } + /// + /// Executes the OnPredicateStateCompleted operation. + /// private void OnPredicateStateCompleted() { using var @lock = SwappableLock.CreateAndEnter(UpstreamSynchronizationGate); @@ -176,6 +277,10 @@ private void OnPredicateStateCompleted() } } + /// + /// Executes the OnPredicateStateNext operation. + /// + /// The predicateState value. private void OnPredicateStateNext(TState predicateState) { using var @lock = SwappableLock.CreateAndEnter(UpstreamSynchronizationGate); @@ -194,6 +299,9 @@ private void OnPredicateStateNext(TState predicateState) } } + /// + /// Executes the OnReapplyFilterCompleted operation. + /// private void OnReapplyFilterCompleted() { using var @lock = SwappableLock.CreateAndEnter(UpstreamSynchronizationGate); @@ -209,6 +317,10 @@ private void OnReapplyFilterCompleted() } } + /// + /// Executes the OnReapplyFilterNext operation. + /// + /// The value value. private void OnReapplyFilterNext(Unit value) { using var @lock = SwappableLock.CreateAndEnter(UpstreamSynchronizationGate); @@ -225,6 +337,9 @@ private void OnReapplyFilterNext(Unit value) } } + /// + /// Executes the OnSourceCompleted operation. + /// private void OnSourceCompleted() { using var @lock = SwappableLock.CreateAndEnter(UpstreamSynchronizationGate); @@ -244,6 +359,10 @@ private void OnSourceCompleted() } } + /// + /// Executes the OnSourceNext operation. + /// + /// The upstreamChanges value. private void OnSourceNext(IChangeSet upstreamChanges) { using var @lock = SwappableLock.CreateAndEnter(UpstreamSynchronizationGate); @@ -376,6 +495,10 @@ private void OnSourceNext(IChangeSet upstreamChanges) } } + /// + /// Executes the ReFilter operation. + /// + /// The predicateState value. private void ReFilter(TState predicateState) { #if SUPPORTS_DICTIONARY_MUTATION_DURING_ENUMERATION @@ -412,10 +535,19 @@ private void ReFilter(TState predicateState) } } - private readonly struct ItemState +/// +/// Represents the ItemState value. +/// +private readonly struct ItemState { + /// + /// Gets or sets the IsIncluded value. + /// public required bool IsIncluded { get; init; } + /// + /// Gets or sets the Item value. + /// public required TObject Item { get; init; } } } diff --git a/src/DynamicData/Cache/Internal/Filter.Static.cs b/src/DynamicData/Cache/Internal/Filter.Static.cs index e53a479bc..b7e6a3885 100644 --- a/src/DynamicData/Cache/Internal/Filter.Static.cs +++ b/src/DynamicData/Cache/Internal/Filter.Static.cs @@ -1,24 +1,42 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the Filter class. +/// internal static partial class Filter { - public static class Static +/// +/// Provides members for the Static class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +public static class Static where TObject : notnull where TKey : notnull { + /// + /// Executes the Create operation. + /// + /// The source value. + /// The filter value. + /// The suppressEmptyChangeSets value. + /// The result of the operation. public static IObservable> Create( IObservable> source, Func filter, bool suppressEmptyChangeSets) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - filter.ThrowArgumentNullExceptionIfNull(nameof(filter)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(filter); return Observable.Create>(downstreamObserver => { diff --git a/src/DynamicData/Cache/Internal/FilterEx.cs b/src/DynamicData/Cache/Internal/FilterEx.cs index f21c493e9..77798b0f5 100644 --- a/src/DynamicData/Cache/Internal/FilterEx.cs +++ b/src/DynamicData/Cache/Internal/FilterEx.cs @@ -1,11 +1,27 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the FilterEx class. +/// internal static class FilterEx { + /// + /// Executes the FilterChanges operation. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The cache value. + /// The changes value. + /// The predicate value. public static void FilterChanges(this ChangeAwareCache cache, IChangeSet changes, Func predicate) where TObject : notnull where TKey : notnull @@ -70,6 +86,15 @@ public static void FilterChanges(this ChangeAwareCache + /// Executes the RefreshFilteredFrom operation. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The filtered value. + /// The allData value. + /// The predicate value. + /// The result of the operation. public static IChangeSet RefreshFilteredFrom(this ChangeAwareCache filtered, Cache allData, Func predicate) where TObject : notnull where TKey : notnull diff --git a/src/DynamicData/Cache/Internal/FilterImmutable.cs b/src/DynamicData/Cache/Internal/FilterImmutable.cs index 67f6ec02d..337fc33cf 100644 --- a/src/DynamicData/Cache/Internal/FilterImmutable.cs +++ b/src/DynamicData/Cache/Internal/FilterImmutable.cs @@ -1,20 +1,44 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the FilterImmutable class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal sealed class FilterImmutable where TObject : notnull where TKey : notnull { + /// + /// The _onNextInvoker field. + /// private readonly Action>, IChangeSet> _onNextInvoker; + + /// + /// The _predicate field. + /// private readonly Func _predicate; + + /// + /// The _source field. + /// private readonly IObservable> _source; + /// + /// Initializes a new instance of the class. + /// + /// The predicate value. + /// The source value. + /// The suppressEmptyChangeSets value. public FilterImmutable( Func predicate, IObservable> source, @@ -35,6 +59,10 @@ public FilterImmutable( : (observer, changes) => observer.OnNext(changes); } + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>(observer => _source .SubscribeSafe(Observer.Create>( diff --git a/src/DynamicData/Cache/Internal/FilterOnObservable.cs b/src/DynamicData/Cache/Internal/FilterOnObservable.cs index d444d9ac4..9bb18d922 100644 --- a/src/DynamicData/Cache/Internal/FilterOnObservable.cs +++ b/src/DynamicData/Cache/Internal/FilterOnObservable.cs @@ -1,19 +1,41 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Concurrency; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the FilterOnObservable class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. +/// The filterFactory value. +/// The buffer value. +/// The scheduler value. internal sealed class FilterOnObservable(IObservable> source, Func> filterFactory, TimeSpan? buffer = null, IScheduler? scheduler = null) where TObject : notnull where TKey : notnull { + /// + /// The _filterFactory field. + /// private readonly Func> _filterFactory = filterFactory ?? throw new ArgumentNullException(nameof(filterFactory)); + + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => _source .Transform((val, key) => new FilterProxy(val, _filterFactory(val, key))) @@ -21,12 +43,26 @@ public IObservable> Run() => .Filter(proxy => proxy.PassesFilter) .TransformImmutable(proxy => proxy.Value); - private sealed class FilterProxy(TObject obj, IObservable observable) +/// +/// Provides members for the FilterProxy class. +/// +/// The obj value. +/// The observable value. +private sealed class FilterProxy(TObject obj, IObservable observable) { + /// + /// Gets the Value value. + /// public TObject Value { get; } = obj; + /// + /// Gets or sets the PassesFilter value. + /// public bool PassesFilter { get; private set; } + /// + /// Gets the FilterObservable value. + /// public IObservable FilterObservable => observable.DistinctUntilChanged().Do(filterValue => PassesFilter = filterValue); } } diff --git a/src/DynamicData/Cache/Internal/FilteredIndexCalculator.cs b/src/DynamicData/Cache/Internal/FilteredIndexCalculator.cs index c8ed437e0..14b439e8b 100644 --- a/src/DynamicData/Cache/Internal/FilteredIndexCalculator.cs +++ b/src/DynamicData/Cache/Internal/FilteredIndexCalculator.cs @@ -1,13 +1,30 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the FilteredIndexCalculator class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal static class FilteredIndexCalculator where TObject : notnull where TKey : notnull { + /// + /// Executes the Calculate operation. + /// + /// The currentItems value. + /// The previousItems value. + /// The sourceUpdates value. + /// The result of the operation. public static IList> Calculate(IKeyValueCollection currentItems, IKeyValueCollection previousItems, IChangeSet? sourceUpdates) { if (currentItems.SortReason == SortReason.ComparerChanged || currentItems.SortReason == SortReason.InitialLoad) @@ -130,6 +147,13 @@ public static IList> Calculate(IKeyValueCollection + /// Executes the GetInsertPositionLinear operation. + /// + /// The list value. + /// The item value. + /// The comparer value. + /// The result of the operation. private static int GetInsertPositionLinear(List> list, KeyValuePair item, IComparer> comparer) { for (var i = 0; i < list.Count; i++) diff --git a/src/DynamicData/Cache/Internal/FinallySafe.cs b/src/DynamicData/Cache/Internal/FinallySafe.cs index d4cb04523..91cb497d7 100644 --- a/src/DynamicData/Cache/Internal/FinallySafe.cs +++ b/src/DynamicData/Cache/Internal/FinallySafe.cs @@ -1,18 +1,36 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the FinallySafe class. +/// +/// The type of the T value. +/// The source value. +/// The finallyAction value. internal sealed class FinallySafe(IObservable source, Action finallyAction) { + /// + /// The _finallyAction field. + /// private readonly Action _finallyAction = finallyAction ?? throw new ArgumentNullException(nameof(finallyAction)); + /// + /// The _source field. + /// private readonly IObservable _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable Run() => Observable.Create( o => { diff --git a/src/DynamicData/Cache/Internal/FullJoin.cs b/src/DynamicData/Cache/Internal/FullJoin.cs index 46bdb663f..5be021403 100644 --- a/src/DynamicData/Cache/Internal/FullJoin.cs +++ b/src/DynamicData/Cache/Internal/FullJoin.cs @@ -1,27 +1,57 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; - -internal sealed class FullJoin(IObservable> left, IObservable> right, Func rightKeySelector, Func, Optional, TDestination> resultSelector) +#endif + +/// +/// Provides members for the FullJoin class. +/// +/// The type of the TLeft value. +/// The type of the TLeftKey value. +/// The type of the TRight value. +/// The type of the TRightKey value. +/// The type of the TDestination value. +/// The left value. +/// The right value. +/// The rightKeySelector value. +/// The resultSelector value. +internal sealed class FullJoin(IObservable> left, IObservable> right, Func rightKeySelector, Func, ReactiveUI.Primitives.Optional, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull where TRight : notnull where TRightKey : notnull where TDestination : notnull { + /// + /// The _left field. + /// private readonly IObservable> _left = left ?? throw new ArgumentNullException(nameof(left)); - private readonly Func, Optional, TDestination> _resultSelector = resultSelector ?? throw new ArgumentNullException(nameof(resultSelector)); + /// + /// The _resultSelector field. + /// + private readonly Func, ReactiveUI.Primitives.Optional, TDestination> _resultSelector = resultSelector ?? throw new ArgumentNullException(nameof(resultSelector)); + /// + /// The _right field. + /// private readonly IObservable> _right = right ?? throw new ArgumentNullException(nameof(right)); + /// + /// The _rightKeySelector field. + /// private readonly Func _rightKeySelector = rightKeySelector ?? throw new ArgumentNullException(nameof(rightKeySelector)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -59,7 +89,7 @@ public IObservable> Run() => Observable.Creat else { // update with no left value - joinedCache.AddOrUpdate(_resultSelector(change.Key, Optional.None, rightLookup), change.Key); + joinedCache.AddOrUpdate(_resultSelector(change.Key, ReactiveUI.Primitives.Optional.None, rightLookup), change.Key); } break; @@ -101,7 +131,7 @@ public IObservable> Run() => Observable.Creat else { // update with no right value - joinedCache.AddOrUpdate(_resultSelector(change.Key, left, Optional.None), change.Key); + joinedCache.AddOrUpdate(_resultSelector(change.Key, left, ReactiveUI.Primitives.Optional.None), change.Key); } } diff --git a/src/DynamicData/Cache/Internal/FullJoinMany.cs b/src/DynamicData/Cache/Internal/FullJoinMany.cs index 28ae5a008..02eca05bd 100644 --- a/src/DynamicData/Cache/Internal/FullJoinMany.cs +++ b/src/DynamicData/Cache/Internal/FullJoinMany.cs @@ -1,24 +1,57 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif -internal sealed class FullJoinMany(IObservable> left, IObservable> right, Func rightKeySelector, Func, IGrouping, TDestination> resultSelector) +/// +/// Provides members for the FullJoinMany class. +/// +/// The type of the TLeft value. +/// The type of the TLeftKey value. +/// The type of the TRight value. +/// The type of the TRightKey value. +/// The type of the TDestination value. +/// The left value. +/// The right value. +/// The rightKeySelector value. +/// The resultSelector value. +internal sealed class FullJoinMany(IObservable> left, IObservable> right, Func rightKeySelector, Func, IGrouping, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull where TRight : notnull where TRightKey : notnull where TDestination : notnull { + /// + /// The _left field. + /// private readonly IObservable> _left = left ?? throw new ArgumentNullException(nameof(left)); - private readonly Func, IGrouping, TDestination> _resultSelector = resultSelector ?? throw new ArgumentNullException(nameof(resultSelector)); + /// + /// The _resultSelector field. + /// + private readonly Func, IGrouping, TDestination> _resultSelector = resultSelector ?? throw new ArgumentNullException(nameof(resultSelector)); + /// + /// The _right field. + /// private readonly IObservable> _right = right ?? throw new ArgumentNullException(nameof(right)); + /// + /// The _rightKeySelector field. + /// private readonly Func _rightKeySelector = rightKeySelector ?? throw new ArgumentNullException(nameof(rightKeySelector)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() { var emptyCache = Cache.Empty; diff --git a/src/DynamicData/Cache/Internal/GroupOn.cs b/src/DynamicData/Cache/Internal/GroupOn.cs index fd0936fe7..3e8e6d62f 100644 --- a/src/DynamicData/Cache/Internal/GroupOn.cs +++ b/src/DynamicData/Cache/Internal/GroupOn.cs @@ -1,24 +1,47 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; - +#endif + +/// +/// Provides members for the GroupOn class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TGroupKey value. +/// The source value. +/// The groupSelectorKey value. +/// The regrouper value. internal sealed class GroupOn(IObservable> source, Func groupSelectorKey, IObservable? regrouper) where TObject : notnull where TKey : notnull where TGroupKey : notnull { + /// + /// The _groupSelectorKey field. + /// private readonly Func _groupSelectorKey = groupSelectorKey ?? throw new ArgumentNullException(nameof(groupSelectorKey)); + /// + /// The _regrouper field. + /// private readonly IObservable _regrouper = regrouper ?? Observable.Never(); + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -38,11 +61,26 @@ public IObservable> Run() => Observabl return new CompositeDisposable(connected, disposer, subscriber, queue); }); - private sealed class Grouper(Func groupSelectorKey) +/// +/// Provides members for the Grouper class. +/// +/// The groupSelectorKey value. +private sealed class Grouper(Func groupSelectorKey) { + /// + /// The _groupCache field. + /// private readonly Dictionary> _groupCache = []; + + /// + /// The _itemCache field. + /// private readonly Dictionary _itemCache = []; + /// + /// Executes the Regroup operation. + /// + /// The result of the operation. public IGroupChangeSet Regroup() { // re-evaluate all items in the group @@ -50,8 +88,18 @@ public IGroupChangeSet Regroup() return HandleUpdates(new ChangeSet(items), true); } + /// + /// Executes the Update operation. + /// + /// The updates value. + /// The result of the operation. public IGroupChangeSet Update(IChangeSet updates) => HandleUpdates(updates); + /// + /// Executes the GetCache operation. + /// + /// The key value. + /// The result of the operation. private (ManagedGroup group, bool wasCreated) GetCache(TGroupKey key) { var cache = _groupCache.Lookup(key); @@ -65,6 +113,12 @@ public IGroupChangeSet Regroup() return (newcache, true); } + /// + /// Executes the HandleUpdates operation. + /// + /// The changes value. + /// The isRegrouping value. + /// The result of the operation. private GroupChangeSet HandleUpdates(IEnumerable> changes, bool isRegrouping = false) { var result = new List, TGroupKey>>(); @@ -216,34 +270,95 @@ private GroupChangeSet HandleUpdates(IEnumerable(result); } - private readonly struct ChangeWithGroup(Change change, Func keySelector) : IEquatable +/// +/// Represents the ChangeWithGroup value. +/// +/// The change value. +/// The keySelector value. +private readonly struct ChangeWithGroup(Change change, Func keySelector) : IEquatable { + /// + /// Gets the Item value. + /// public TObject Item { get; } = change.Current; + /// + /// Gets the Key value. + /// public TKey Key { get; } = change.Key; + /// + /// Gets the GroupKey value. + /// public TGroupKey GroupKey { get; } = keySelector(change.Current); + /// + /// Gets the Reason value. + /// public ChangeReason Reason { get; } = change.Reason; + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator ==(in ChangeWithGroup left, in ChangeWithGroup right) => left.Equals(right); + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator !=(in ChangeWithGroup left, in ChangeWithGroup right) => !left.Equals(right); + /// + /// Executes the Equals operation. + /// + /// The other value. + /// The result of the operation. public bool Equals(ChangeWithGroup other) => EqualityComparer.Default.Equals(Key, other.Key); + /// + /// Executes the Equals operation. + /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) => obj is ChangeWithGroup group && Equals(group); + /// + /// Executes the GetHashCode operation. + /// + /// The result of the operation. public override int GetHashCode() => Key.GetHashCode(); + /// + /// Executes the ToString operation. + /// + /// The result of the operation. public override string ToString() => $"Key: {Key}, GroupKey: {GroupKey}, Item: {Item}"; } - private sealed class SuspendTracker : IDisposable +/// +/// Provides members for the SuspendTracker class. +/// +private sealed class SuspendTracker : IDisposable { + /// + /// The _trackedKeys field. + /// private readonly HashSet _trackedKeys = []; + + /// + /// The _disposables field. + /// private readonly CompositeDisposable _disposables = []; + /// + /// Executes the Add operation. + /// + /// The managedGroup value. public void Add(ManagedGroup managedGroup) { if (_trackedKeys.Add(managedGroup.Key)) @@ -252,6 +367,9 @@ public void Add(ManagedGroup managedGroup) } } + /// + /// Executes the Dispose operation. + /// public void Dispose() => _disposables.Dispose(); } } diff --git a/src/DynamicData/Cache/Internal/GroupOnDynamic.cs b/src/DynamicData/Cache/Internal/GroupOnDynamic.cs index 0e11bc594..cb35dcc6f 100644 --- a/src/DynamicData/Cache/Internal/GroupOnDynamic.cs +++ b/src/DynamicData/Cache/Internal/GroupOnDynamic.cs @@ -1,18 +1,32 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the GroupOnDynamic class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TGroupKey value. +/// The source value. +/// The selectGroupObservable value. +/// The regrouper value. internal sealed class GroupOnDynamic(IObservable> source, IObservable> selectGroupObservable, IObservable? regrouper = null) where TObject : notnull where TKey : notnull where TGroupKey : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>(observer => { var dynamicGrouper = new DynamicGrouper(); @@ -27,54 +41,55 @@ public IObservable> Run() => Observabl // The first value from the Group Selector should update the Grouper with all the values seen so far // Then indicate a selector has been found. Subsequent values should just update the group selector. - var subGroupSelector = sharedGroupSelector - .SubscribeSafe( - onNext: groupSelector => + var subGroupSelector = PrimitivesLinqExtensions.SubscribeSafe( + sharedGroupSelector, + onNext: groupSelector => + { + if (hasSelector) { - if (hasSelector) - { - dynamicGrouper.SetGroupSelector(groupSelector, observer); - } - else - { - dynamicGrouper.Initialize(notGrouped.KeyValues, groupSelector, observer); - hasSelector = true; - } - }, - onError: observer.OnError); + dynamicGrouper.SetGroupSelector(groupSelector, observer); + } + else + { + dynamicGrouper.Initialize(notGrouped.KeyValues, groupSelector, observer); + hasSelector = true; + } + }, + onError: observer.OnError); // Ignore values until a selector has been provided // Then re-evaluate all the groupings each time it fires - var subRegrouper = sharedRegrouper - .SubscribeSafe( - onNext: _ => + var subRegrouper = PrimitivesLinqExtensions.SubscribeSafe( + sharedRegrouper, + onNext: _ => + { + if (hasSelector) { - if (hasSelector) - { - dynamicGrouper.RegroupAll(observer); - } - }, - onError: observer.OnError); + dynamicGrouper.RegroupAll(observer); + } + }, + onError: observer.OnError); - var subChanges = sharedSource - .SubscribeSafe( - onNext: changeSet => + var subChanges = PrimitivesLinqExtensions.SubscribeSafe( + sharedSource, + onNext: changeSet => + { + if (hasSelector) + { + dynamicGrouper.ProcessChangeSet(changeSet, observer); + } + else { - if (hasSelector) - { - dynamicGrouper.ProcessChangeSet(changeSet, observer); - } - else - { - notGrouped.Clone(changeSet); - } - }, - onError: observer.OnError); + notGrouped.Clone(changeSet); + } + }, + onError: observer.OnError); // Create an observable that completes when all 3 inputs complete so the downstream can be completed as well - var subOnComplete = Observable.Merge(sharedSource.ToUnit(), sharedGroupSelector.ToUnit(), sharedRegrouper) - .IgnoreElements() - .SubscribeSafe(observer.OnError, observer.OnCompleted); + var subOnComplete = PrimitivesLinqExtensions.SubscribeSafe( + Observable.Merge(sharedSource.ToUnit(), sharedGroupSelector.ToUnit(), sharedRegrouper).IgnoreElements(), + observer.OnError, + observer.OnCompleted); return new CompositeDisposable( sharedGroupSelector.Connect(), diff --git a/src/DynamicData/Cache/Internal/GroupOnImmutable.cs b/src/DynamicData/Cache/Internal/GroupOnImmutable.cs index ba33db031..763e04f9c 100644 --- a/src/DynamicData/Cache/Internal/GroupOnImmutable.cs +++ b/src/DynamicData/Cache/Internal/GroupOnImmutable.cs @@ -1,24 +1,47 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; - +#endif + +/// +/// Provides members for the GroupOnImmutable class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TGroupKey value. +/// The source value. +/// The groupSelectorKey value. +/// The regrouper value. internal sealed class GroupOnImmutable(IObservable> source, Func groupSelectorKey, IObservable? regrouper) where TObject : notnull where TKey : notnull where TGroupKey : notnull { + /// + /// The _groupSelectorKey field. + /// private readonly Func _groupSelectorKey = groupSelectorKey ?? throw new ArgumentNullException(nameof(groupSelectorKey)); + /// + /// The _regrouper field. + /// private readonly IObservable _regrouper = regrouper ?? Observable.Never(); + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -32,11 +55,26 @@ public IObservable> Run() => return new CompositeDisposable(groups.Merge(regroup).SubscribeSafe(observer), queue); }); - private sealed class Grouper(Func groupSelectorKey) +/// +/// Provides members for the Grouper class. +/// +/// The groupSelectorKey value. +private sealed class Grouper(Func groupSelectorKey) { + /// + /// The _allGroupings field. + /// private readonly Dictionary _allGroupings = []; + + /// + /// The _itemCache field. + /// private readonly Dictionary _itemCache = []; + /// + /// Executes the Regroup operation. + /// + /// The result of the operation. public IImmutableGroupChangeSet Regroup() { // re-evaluate all items in the group @@ -44,18 +82,45 @@ public IImmutableGroupChangeSet Regroup() return HandleUpdates(new ChangeSet(items)); } + /// + /// Executes the Update operation. + /// + /// The updates value. + /// The result of the operation. public IImmutableGroupChangeSet Update(IChangeSet updates) => HandleUpdates(updates); + /// + /// Executes the CreateMissingKeyException operation. + /// + /// The reason value. + /// The key value. + /// The result of the operation. private static MissingKeyException CreateMissingKeyException(ChangeReason reason, TKey key) { var message = $"{key} is missing from previous group on {reason}.{Environment.NewLine}Object type {typeof(TObject)}, Key type {typeof(TKey)}, Group key type {typeof(TGroupKey)}"; return new MissingKeyException(message); } + /// + /// Executes the GetGroupState operation. + /// + /// The grouping value. + /// The result of the operation. private static ImmutableGroup GetGroupState(GroupCache grouping) => new(grouping.Key, grouping.Cache); + /// + /// Executes the GetGroupState operation. + /// + /// The key value. + /// The cache value. + /// The result of the operation. private static ImmutableGroup GetGroupState(TGroupKey key, ICache cache) => new(key, cache); + /// + /// Executes the CreateChangeSet operation. + /// + /// The initialGroupState value. + /// The result of the operation. private ImmutableGroupChangeSet CreateChangeSet(IDictionary> initialGroupState) { var result = new List, TGroupKey>>(); @@ -78,7 +143,7 @@ private ImmutableGroupChangeSet CreateChangeSet(IDicti } else { - var previousState = Optional.Some(initialGroup.Value); + var previousState = ReactiveUI.Primitives.Optional.Some(initialGroup.Value); result.Add(new Change, TGroupKey>(ChangeReason.Update, key, currentState, previousState)); } } @@ -87,6 +152,11 @@ private ImmutableGroupChangeSet CreateChangeSet(IDicti return new ImmutableGroupChangeSet(result); } + /// + /// Executes the GetCache operation. + /// + /// The key value. + /// The result of the operation. private Tuple GetCache(TGroupKey key) { var cache = _allGroupings.Lookup(key); @@ -100,6 +170,11 @@ private Tuple GetCache(TGroupKey key) return Tuple.Create(newcache, true); } + /// + /// Executes the HandleUpdates operation. + /// + /// The changes value. + /// The result of the operation. private ImmutableGroupChangeSet HandleUpdates(IEnumerable> changes) { // need to keep track of effected groups to calculate correct notifications @@ -201,6 +276,12 @@ private ImmutableGroupChangeSet HandleUpdates(IEnumera return CreateChangeSet(initialStateOfGroups); } + /// + /// Executes the RemoveFromOldGroup operation. + /// + /// The groupState value. + /// The groupKey value. + /// The currentKey value. private void RemoveFromOldGroup(Dictionary> groupState, TGroupKey groupKey, TKey currentKey) => _allGroupings.Lookup(groupKey).IfHasValue( g => @@ -213,35 +294,92 @@ private void RemoveFromOldGroup(Dictionary change, Func keySelector) : IEquatable +/// +/// Represents the ChangeWithGroup value. +/// +/// The change value. +/// The keySelector value. +private readonly struct ChangeWithGroup(Change change, Func keySelector) : IEquatable { + /// + /// Gets the Item value. + /// public TObject Item { get; } = change.Current; + /// + /// Gets the Key value. + /// public TKey Key { get; } = change.Key; + /// + /// Gets the GroupKey value. + /// public TGroupKey GroupKey { get; } = keySelector(change.Current); + /// + /// Gets the Reason value. + /// public ChangeReason Reason { get; } = change.Reason; + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator ==(in ChangeWithGroup left, in ChangeWithGroup right) => left.Equals(right); + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator !=(in ChangeWithGroup left, in ChangeWithGroup right) => !left.Equals(right); + /// + /// Executes the Equals operation. + /// + /// The other value. + /// The result of the operation. public bool Equals(ChangeWithGroup other) => Key.Equals(other.Key); + /// + /// Executes the Equals operation. + /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) => obj is ChangeWithGroup changeGroup && Equals(changeGroup); + /// + /// Executes the GetHashCode operation. + /// + /// The result of the operation. public override int GetHashCode() => Key.GetHashCode(); + /// + /// Executes the ToString operation. + /// + /// The result of the operation. public override string ToString() => $"Key: {Key}, GroupKey: {GroupKey}, Item: {Item}"; } - private sealed class GroupCache(TGroupKey key) +/// +/// Provides members for the GroupCache class. +/// +/// The key value. +private sealed class GroupCache(TGroupKey key) { + /// + /// Gets the Cache value. + /// public Cache Cache { get; } = new Cache(); + /// + /// Gets the Key value. + /// public TGroupKey Key { get; } = key; } } diff --git a/src/DynamicData/Cache/Internal/GroupOnObservable.cs b/src/DynamicData/Cache/Internal/GroupOnObservable.cs index 0555004e1..9d71fbde5 100644 --- a/src/DynamicData/Cache/Internal/GroupOnObservable.cs +++ b/src/DynamicData/Cache/Internal/GroupOnObservable.cs @@ -1,26 +1,56 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; -using DynamicData.Internal; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the GroupOnObservable class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TGroupKey value. +/// The source value. +/// The selectGroup value. internal sealed class GroupOnObservable(IObservable> source, Func> selectGroup) where TObject : notnull where TKey : notnull where TGroupKey : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>(observer => new Subscription(source, selectGroup, observer)); - // Maintains state for a single subscription - private sealed class Subscription : CacheParentSubscription> + +/// +/// Provides members for the Subscription class. +/// +private sealed class Subscription : CacheParentSubscription> { + /// + /// The _grouper field. + /// private readonly DynamicGrouper _grouper = new(); + + /// + /// The _selectGroup field. + /// private readonly Func> _selectGroup; + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The selectGroup value. + /// The observer value. public Subscription(IObservable> source, Func> selectGroup, IObserver> observer) : base(observer) { @@ -28,6 +58,10 @@ public Subscription(IObservable> source, Func + /// Executes the ParentOnNext operation. + /// + /// The changes value. protected override void ParentOnNext(IChangeSet changes) { // Process all the changes at once to preserve the changeset order @@ -51,18 +85,36 @@ protected override void ParentOnNext(IChangeSet changes) } } + /// + /// Executes the ChildOnNext operation. + /// + /// The tuple value. + /// The parentKey value. protected override void ChildOnNext((TGroupKey, TObject) tuple, TKey parentKey) => _grouper.AddOrUpdate(parentKey, tuple.Item1, tuple.Item2); + /// + /// Executes the EmitChanges operation. + /// + /// The observer value. protected override void EmitChanges(IObserver> observer) => _grouper.EmitChanges(observer); + /// + /// Executes the Dispose operation. + /// + /// The disposing value. protected override void Dispose(bool disposing) { _grouper.Dispose(); base.Dispose(disposing); } + /// + /// Executes the AddGroupSubscription operation. + /// + /// The obj value. + /// The key value. private void AddGroupSubscription(TObject obj, TKey key) => AddChildSubscription(MakeChildObservable(_selectGroup(obj, key).DistinctUntilChanged().Select(groupKey => (groupKey, obj))), key); } diff --git a/src/DynamicData/Cache/Internal/GroupOnProperty.cs b/src/DynamicData/Cache/Internal/GroupOnProperty.cs index 8fa06893b..975c70200 100644 --- a/src/DynamicData/Cache/Internal/GroupOnProperty.cs +++ b/src/DynamicData/Cache/Internal/GroupOnProperty.cs @@ -1,22 +1,45 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.ComponentModel; using System.Linq.Expressions; -using System.Reactive.Concurrency; -using System.Reactive.Linq; +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the GroupOnProperty class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TGroup value. +/// The source value. +/// The groupSelectorKey value. +/// The throttle value. +/// The scheduler value. internal sealed class GroupOnProperty(IObservable> source, Expression> groupSelectorKey, TimeSpan? throttle = null, IScheduler? scheduler = null) where TObject : INotifyPropertyChanged where TKey : notnull where TGroup : notnull { + /// + /// The _groupSelector field. + /// private readonly Func _groupSelector = groupSelectorKey.Compile(); + + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => _source.Publish( shared => { diff --git a/src/DynamicData/Cache/Internal/GroupOnPropertyWithImmutableState.cs b/src/DynamicData/Cache/Internal/GroupOnPropertyWithImmutableState.cs index bc3755a44..8d3273142 100644 --- a/src/DynamicData/Cache/Internal/GroupOnPropertyWithImmutableState.cs +++ b/src/DynamicData/Cache/Internal/GroupOnPropertyWithImmutableState.cs @@ -1,24 +1,50 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.ComponentModel; using System.Linq.Expressions; -using System.Reactive.Concurrency; -using System.Reactive.Linq; +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the GroupOnPropertyWithImmutableState class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TGroup value. +/// The source value. +/// The groupSelectorKey value. +/// The throttle value. +/// The scheduler value. internal sealed class GroupOnPropertyWithImmutableState(IObservable> source, Expression> groupSelectorKey, TimeSpan? throttle = null, IScheduler? scheduler = null) where TObject : INotifyPropertyChanged where TKey : notnull where TGroup : notnull { + /// + /// The _groupSelector field. + /// private readonly Func _groupSelector = groupSelectorKey.Compile(); + + /// + /// The _scheduler field. + /// private readonly IScheduler _scheduler = scheduler ?? GlobalConfig.DefaultScheduler; + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => _source.Publish( shared => { diff --git a/src/DynamicData/Cache/Internal/IFilter.cs b/src/DynamicData/Cache/Internal/IFilter.cs index 6301a0cfa..f7bb17058 100644 --- a/src/DynamicData/Cache/Internal/IFilter.cs +++ b/src/DynamicData/Cache/Internal/IFilter.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif /// /// Provides a filter. diff --git a/src/DynamicData/Cache/Internal/IKeySelector.cs b/src/DynamicData/Cache/Internal/IKeySelector.cs index 71c6e5aa9..d09206c72 100644 --- a/src/DynamicData/Cache/Internal/IKeySelector.cs +++ b/src/DynamicData/Cache/Internal/IKeySelector.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif /// /// Selects a key from a item. diff --git a/src/DynamicData/Cache/Internal/ImmutableGroup.cs b/src/DynamicData/Cache/Internal/ImmutableGroup.cs index 82ffaae3d..3ec11d10b 100644 --- a/src/DynamicData/Cache/Internal/ImmutableGroup.cs +++ b/src/DynamicData/Cache/Internal/ImmutableGroup.cs @@ -1,16 +1,35 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -namespace DynamicData.Cache.Internal; +namespace DynamicData.Reactive.Cache.Internal; +#else +namespace DynamicData.Cache.Internal; +#endif + +/// +/// Provides members for the ImmutableGroup class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TGroupKey value. internal sealed class ImmutableGroup : IGrouping, IEquatable> where TObject : notnull where TKey : notnull where TGroupKey : notnull { + /// + /// The _cache field. + /// private readonly Cache _cache; + /// + /// Initializes a new instance of the class. + /// + /// The key value. + /// The cache value. internal ImmutableGroup(TGroupKey key, ICache cache) { Key = key; @@ -18,20 +37,52 @@ internal ImmutableGroup(TGroupKey key, ICache cache) cache.KeyValues.ForEach(kvp => _cache.AddOrUpdate(kvp.Value, kvp.Key)); } + /// + /// Gets the Count value. + /// public int Count => _cache.Count; + /// + /// Gets the Items value. + /// public IEnumerable Items => _cache.Items; + /// + /// Gets the Key value. + /// public TGroupKey Key { get; } + /// + /// Gets the Keys value. + /// public IEnumerable Keys => _cache.Keys; + /// + /// Gets the KeyValues value. + /// public IEnumerable> KeyValues => _cache.KeyValues; + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator ==(ImmutableGroup left, ImmutableGroup right) => Equals(left, right); + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator !=(ImmutableGroup left, ImmutableGroup right) => !Equals(left, right); + /// + /// Executes the Equals operation. + /// + /// The other value. + /// The result of the operation. public bool Equals(ImmutableGroup? other) { if (ReferenceEquals(this, other)) @@ -42,11 +93,29 @@ public bool Equals(ImmutableGroup? other) return other is not null && EqualityComparer.Default.Equals(Key, other.Key); } + /// + /// Executes the Equals operation. + /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) => obj is ImmutableGroup value && Equals(value); + /// + /// Executes the GetHashCode operation. + /// + /// The result of the operation. public override int GetHashCode() => EqualityComparer.Default.GetHashCode(Key); - public Optional Lookup(TKey key) => _cache.Lookup(key); - + /// + /// Executes the Lookup operation. + /// + /// The key value. + /// The result of the operation. + public ReactiveUI.Primitives.Optional Lookup(TKey key) => _cache.Lookup(key); + + /// + /// Executes the ToString operation. + /// + /// The result of the operation. public override string ToString() => $"Grouping for: {Key} ({Count} items)"; } diff --git a/src/DynamicData/Cache/Internal/ImmutableGroupChangeSet.cs b/src/DynamicData/Cache/Internal/ImmutableGroupChangeSet.cs index 81ebbc290..57af027ab 100644 --- a/src/DynamicData/Cache/Internal/ImmutableGroupChangeSet.cs +++ b/src/DynamicData/Cache/Internal/ImmutableGroupChangeSet.cs @@ -1,21 +1,42 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the ImmutableGroupChangeSet class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TGroupKey value. internal sealed class ImmutableGroupChangeSet : ChangeSet, TGroupKey>, IImmutableGroupChangeSet where TObject : notnull where TKey : notnull where TGroupKey : notnull { + /// + /// The Empty field. + /// public static new readonly IImmutableGroupChangeSet Empty = new ImmutableGroupChangeSet(); + /// + /// Initializes a new instance of the class. + /// + /// The items value. public ImmutableGroupChangeSet(IEnumerable, TGroupKey>> items) : base(items) { } + /// + /// Initializes a new instance of the class. + /// private ImmutableGroupChangeSet() { } diff --git a/src/DynamicData/Cache/Internal/IndexAndNode.cs b/src/DynamicData/Cache/Internal/IndexAndNode.cs index d80b66fd9..1da0dc3ab 100644 --- a/src/DynamicData/Cache/Internal/IndexAndNode.cs +++ b/src/DynamicData/Cache/Internal/IndexAndNode.cs @@ -1,20 +1,45 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Diagnostics.CodeAnalysis; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the IndexAndNode class. +/// internal static class IndexAndNode { + /// + /// Executes the Create operation. + /// + /// The type of the TNodeValue value. + /// The index value. + /// The value value. + /// The result of the operation. public static IndexAndNode Create(int index, LinkedListNode value) => new(index, value); } +/// +/// Provides members for the IndexAndNode class. +/// +/// The type of the TNodeValue value. +/// The index value. +/// The node value. [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Same class name, different generics.")] internal sealed class IndexAndNode(int index, LinkedListNode node) { + /// + /// Gets the Index value. + /// public int Index { get; } = index; + /// + /// Gets the Node value. + /// public LinkedListNode Node { get; } = node; } diff --git a/src/DynamicData/Cache/Internal/IndexCalculator.cs b/src/DynamicData/Cache/Internal/IndexCalculator.cs index 709245408..622023a01 100644 --- a/src/DynamicData/Cache/Internal/IndexCalculator.cs +++ b/src/DynamicData/Cache/Internal/IndexCalculator.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif /// /// Calculates a sequential change set. @@ -16,14 +21,25 @@ namespace DynamicData.Cache.Internal; /// /// The comparer to use. /// Selected indexing optimisations. +/// The type of the TObject value. +/// The type of the TKey value. internal sealed class IndexCalculator(KeyValueComparer comparer, SortOptimisations optimisations) where TObject : notnull where TKey : notnull { + /// + /// The _comparer field. + /// private KeyValueComparer _comparer = comparer; + /// + /// Gets the Comparer value. + /// public IComparer> Comparer => _comparer; + /// + /// Gets the List value. + /// public List> List { get; private set; } = []; /// @@ -127,6 +143,11 @@ public IChangeSet Calculate(IChangeSet changes) return new ChangeSet(result); } + /// + /// Executes the ChangeComparer operation. + /// + /// The comparer value. + /// The result of the operation. public IChangeSet ChangeComparer(KeyValueComparer comparer) { _comparer = comparer; @@ -147,6 +168,10 @@ public IChangeSet Load(ChangeAwareCache cache) return new ChangeSet(initialItems); } + /// + /// Executes the Reorder operation. + /// + /// The result of the operation. public IChangeSet Reorder() { var result = new List>(); @@ -189,6 +214,11 @@ public IChangeSet Reorder() /// The cache. public void Reset(ChangeAwareCache cache) => List = [.. cache.KeyValues.OrderBy(kv => kv, _comparer)]; + /// + /// Executes the GetCurrentPosition operation. + /// + /// The item value. + /// The result of the operation. private int GetCurrentPosition(KeyValuePair item) { int index; @@ -215,6 +245,11 @@ private int GetCurrentPosition(KeyValuePair item) return index; } + /// + /// Executes the GetInsertPositionBinary operation. + /// + /// The item value. + /// The result of the operation. private int GetInsertPositionBinary(KeyValuePair item) { var index = List.BinarySearch(item, _comparer); @@ -232,6 +267,12 @@ private int GetInsertPositionBinary(KeyValuePair item) return ~index; } + /// + /// Executes the GetInsertPositionLinear operation. + /// + /// The list value. + /// The item value. + /// The result of the operation. private int GetInsertPositionLinear(List> list, KeyValuePair item) { for (var i = 0; i < list.Count; i++) diff --git a/src/DynamicData/Cache/Internal/InnerJoin.cs b/src/DynamicData/Cache/Internal/InnerJoin.cs index e27c565c6..018dd9ae2 100644 --- a/src/DynamicData/Cache/Internal/InnerJoin.cs +++ b/src/DynamicData/Cache/Internal/InnerJoin.cs @@ -1,12 +1,26 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; - +#endif + +/// +/// Provides members for the InnerJoin class. +/// +/// The type of the TLeft value. +/// The type of the TLeftKey value. +/// The type of the TRight value. +/// The type of the TRightKey value. +/// The type of the TDestination value. +/// The left value. +/// The right value. +/// The rightKeySelector value. +/// The resultSelector value. internal sealed class InnerJoin(IObservable> left, IObservable> right, Func rightKeySelector, Func<(TLeftKey leftKey, TRightKey rightKey), TLeft, TRight, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull @@ -14,14 +28,30 @@ internal sealed class InnerJoin + /// The _left field. + /// private readonly IObservable> _left = left ?? throw new ArgumentNullException(nameof(left)); + /// + /// The _resultSelector field. + /// private readonly Func<(TLeftKey leftKey, TRightKey rightKey), TLeft, TRight, TDestination> _resultSelector = resultSelector ?? throw new ArgumentNullException(nameof(resultSelector)); + /// + /// The _right field. + /// private readonly IObservable> _right = right ?? throw new ArgumentNullException(nameof(right)); + /// + /// The _rightKeySelector field. + /// private readonly Func _rightKeySelector = rightKeySelector ?? throw new ArgumentNullException(nameof(rightKeySelector)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { diff --git a/src/DynamicData/Cache/Internal/InnerJoinMany.cs b/src/DynamicData/Cache/Internal/InnerJoinMany.cs index dcec95d86..a52998888 100644 --- a/src/DynamicData/Cache/Internal/InnerJoinMany.cs +++ b/src/DynamicData/Cache/Internal/InnerJoinMany.cs @@ -1,9 +1,26 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the InnerJoinMany class. +/// +/// The type of the TLeft value. +/// The type of the TLeftKey value. +/// The type of the TRight value. +/// The type of the TRightKey value. +/// The type of the TDestination value. +/// The left value. +/// The right value. +/// The rightKeySelector value. +/// The resultSelector value. internal sealed class InnerJoinMany(IObservable> left, IObservable> right, Func rightKeySelector, Func, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull @@ -11,14 +28,30 @@ internal sealed class InnerJoinMany + /// The _left field. + /// private readonly IObservable> _left = left ?? throw new ArgumentNullException(nameof(left)); + /// + /// The _resultSelector field. + /// private readonly Func, TDestination> _resultSelector = resultSelector ?? throw new ArgumentNullException(nameof(resultSelector)); + /// + /// The _right field. + /// private readonly IObservable> _right = right ?? throw new ArgumentNullException(nameof(right)); + /// + /// The _rightKeySelector field. + /// private readonly Func _rightKeySelector = rightKeySelector ?? throw new ArgumentNullException(nameof(rightKeySelector)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() { var rightGrouped = _right.GroupWithImmutableState(_rightKeySelector); diff --git a/src/DynamicData/Cache/Internal/KeyComparer.cs b/src/DynamicData/Cache/Internal/KeyComparer.cs index 6cc831558..2b76cbf83 100644 --- a/src/DynamicData/Cache/Internal/KeyComparer.cs +++ b/src/DynamicData/Cache/Internal/KeyComparer.cs @@ -1,12 +1,33 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the KeyComparer class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal sealed class KeyComparer : IEqualityComparer> { + /// + /// Executes the Equals operation. + /// + /// The x value. + /// The y value. + /// The result of the operation. public bool Equals(KeyValuePair x, KeyValuePair y) => x.Key?.Equals(y.Key) ?? false; + /// + /// Executes the GetHashCode operation. + /// + /// The obj value. + /// The result of the operation. public int GetHashCode(KeyValuePair obj) => obj.Key is null ? 0 : obj.Key.GetHashCode(); } diff --git a/src/DynamicData/Cache/Internal/KeySelector.cs b/src/DynamicData/Cache/Internal/KeySelector.cs index 87127dfcb..b77f876b7 100644 --- a/src/DynamicData/Cache/Internal/KeySelector.cs +++ b/src/DynamicData/Cache/Internal/KeySelector.cs @@ -1,18 +1,38 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Diagnostics.CodeAnalysis; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the KeySelector class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The keySelector value. internal sealed class KeySelector(Func keySelector) : IKeySelector { + /// + /// The _keySelector field. + /// private readonly Func _keySelector = keySelector ?? throw new ArgumentNullException(nameof(keySelector)); + /// + /// Gets the Type value. + /// [SuppressMessage("Design", "CA1822: Member can be static", Justification = "Backwards compatibilty")] public Type Type => typeof(TObject); + /// + /// Executes the GetKey operation. + /// + /// The item value. + /// The result of the operation. public TKey GetKey(TObject item) { try diff --git a/src/DynamicData/Cache/Internal/KeySelectorException.cs b/src/DynamicData/Cache/Internal/KeySelectorException.cs index 5a5fe737b..d539f5e58 100644 --- a/src/DynamicData/Cache/Internal/KeySelectorException.cs +++ b/src/DynamicData/Cache/Internal/KeySelectorException.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif /// /// An exception that happens when there is a problem with the key selector. diff --git a/src/DynamicData/Cache/Internal/KeyValueCollection.cs b/src/DynamicData/Cache/Internal/KeyValueCollection.cs index d743073f5..4e2b8c72c 100644 --- a/src/DynamicData/Cache/Internal/KeyValueCollection.cs +++ b/src/DynamicData/Cache/Internal/KeyValueCollection.cs @@ -1,15 +1,33 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Collections; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the KeyValueCollection class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal sealed class KeyValueCollection : IKeyValueCollection { + /// + /// The _items field. + /// private readonly IReadOnlyCollection> _items; + /// + /// Initializes a new instance of the class. + /// + /// The items value. + /// The comparer value. + /// The sortReason value. + /// The optimisations value. public KeyValueCollection(IReadOnlyCollection> items, IComparer> comparer, SortReason sortReason, SortOptimisations optimisations) { _items = items ?? throw new ArgumentNullException(nameof(items)); @@ -18,6 +36,9 @@ public KeyValueCollection(IReadOnlyCollection> items Optimisations = optimisations; } + /// + /// Initializes a new instance of the class. + /// public KeyValueCollection() { Optimisations = SortOptimisations.None; @@ -33,15 +54,36 @@ public KeyValueCollection() /// public IComparer> Comparer { get; } + /// + /// Gets the Count value. + /// public int Count => _items.Count; + /// + /// Gets the Optimisations value. + /// public SortOptimisations Optimisations { get; } + /// + /// Gets the SortReason value. + /// public SortReason SortReason { get; } + /// + /// Gets or sets the indexed value. + /// + /// The index value. public KeyValuePair this[int index] => _items.ElementAt(index); + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. public IEnumerator> GetEnumerator() => _items.GetEnumerator(); + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/DynamicData/Cache/Internal/KeyValueComparer.cs b/src/DynamicData/Cache/Internal/KeyValueComparer.cs index b9f3face6..cc3a96d4f 100644 --- a/src/DynamicData/Cache/Internal/KeyValueComparer.cs +++ b/src/DynamicData/Cache/Internal/KeyValueComparer.cs @@ -1,11 +1,28 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -namespace DynamicData.Cache.Internal; +namespace DynamicData.Reactive.Cache.Internal; +#else +namespace DynamicData.Cache.Internal; +#endif + +/// +/// Provides members for the KeyValueComparer class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The comparer value. internal sealed class KeyValueComparer(IComparer? comparer = null) : IComparer> { + /// + /// Executes the Compare operation. + /// + /// The x value. + /// The y value. + /// The result of the operation. public int Compare(KeyValuePair x, KeyValuePair y) { if (comparer is not null) diff --git a/src/DynamicData/Cache/Internal/LeftJoin.cs b/src/DynamicData/Cache/Internal/LeftJoin.cs index f0709cb75..aebe2ea78 100644 --- a/src/DynamicData/Cache/Internal/LeftJoin.cs +++ b/src/DynamicData/Cache/Internal/LeftJoin.cs @@ -1,27 +1,57 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; - -internal sealed class LeftJoin(IObservable> left, IObservable> right, Func rightKeySelector, Func, TDestination> resultSelector) +#endif + +/// +/// Provides members for the LeftJoin class. +/// +/// The type of the TLeft value. +/// The type of the TLeftKey value. +/// The type of the TRight value. +/// The type of the TRightKey value. +/// The type of the TDestination value. +/// The left value. +/// The right value. +/// The rightKeySelector value. +/// The resultSelector value. +internal sealed class LeftJoin(IObservable> left, IObservable> right, Func rightKeySelector, Func, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull where TRight : notnull where TRightKey : notnull where TDestination : notnull { + /// + /// The _left field. + /// private readonly IObservable> _left = left ?? throw new ArgumentNullException(nameof(left)); - private readonly Func, TDestination> _resultSelector = resultSelector ?? throw new ArgumentNullException(nameof(resultSelector)); + /// + /// The _resultSelector field. + /// + private readonly Func, TDestination> _resultSelector = resultSelector ?? throw new ArgumentNullException(nameof(resultSelector)); + /// + /// The _right field. + /// private readonly IObservable> _right = right ?? throw new ArgumentNullException(nameof(right)); + /// + /// The _rightKeySelector field. + /// private readonly Func _rightKeySelector = rightKeySelector ?? throw new ArgumentNullException(nameof(rightKeySelector)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -92,7 +122,7 @@ public IObservable> Run() => Observable.Creat { var priorLeft = leftCache.Lookup(priorForeignKey); if (priorLeft.HasValue) - joined.AddOrUpdate(_resultSelector(priorForeignKey, priorLeft.Value, Optional.None), priorForeignKey); + joined.AddOrUpdate(_resultSelector(priorForeignKey, priorLeft.Value, ReactiveUI.Primitives.Optional.None), priorForeignKey); } if (left.HasValue) @@ -105,7 +135,7 @@ public IObservable> Run() => Observable.Creat case ChangeReason.Remove: if (left.HasValue) - joined.AddOrUpdate(_resultSelector(foreignKey, left.Value, Optional.None), foreignKey); + joined.AddOrUpdate(_resultSelector(foreignKey, left.Value, ReactiveUI.Primitives.Optional.None), foreignKey); rightForeignKeysByKey.Remove(change.Key); @@ -118,7 +148,7 @@ public IObservable> Run() => Observable.Creat { var priorLeft = leftCache.Lookup(priorForeignKey); if (priorLeft.HasValue) - joined.AddOrUpdate(_resultSelector(priorForeignKey, priorLeft.Value, Optional.None), priorForeignKey); + joined.AddOrUpdate(_resultSelector(priorForeignKey, priorLeft.Value, ReactiveUI.Primitives.Optional.None), priorForeignKey); if (left.HasValue) joined.AddOrUpdate(_resultSelector(foreignKey, left.Value, right), foreignKey); diff --git a/src/DynamicData/Cache/Internal/LeftJoinMany.cs b/src/DynamicData/Cache/Internal/LeftJoinMany.cs index 8ce0b1f06..d9df78498 100644 --- a/src/DynamicData/Cache/Internal/LeftJoinMany.cs +++ b/src/DynamicData/Cache/Internal/LeftJoinMany.cs @@ -1,9 +1,26 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the LeftJoinMany class. +/// +/// The type of the TLeft value. +/// The type of the TLeftKey value. +/// The type of the TRight value. +/// The type of the TRightKey value. +/// The type of the TDestination value. +/// The left value. +/// The right value. +/// The rightKeySelector value. +/// The resultSelector value. internal sealed class LeftJoinMany(IObservable> left, IObservable> right, Func rightKeySelector, Func, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull @@ -11,14 +28,30 @@ internal sealed class LeftJoinMany + /// The _left field. + /// private readonly IObservable> _left = left ?? throw new ArgumentNullException(nameof(left)); + /// + /// The _resultSelector field. + /// private readonly Func, TDestination> _resultSelector = resultSelector ?? throw new ArgumentNullException(nameof(resultSelector)); + /// + /// The _right field. + /// private readonly IObservable> _right = right ?? throw new ArgumentNullException(nameof(right)); + /// + /// The _rightKeySelector field. + /// private readonly Func _rightKeySelector = rightKeySelector ?? throw new ArgumentNullException(nameof(rightKeySelector)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() { var emptyCache = Cache.Empty; diff --git a/src/DynamicData/Cache/Internal/LockFreeObservableCache.cs b/src/DynamicData/Cache/Internal/LockFreeObservableCache.cs index be539b82c..a67a6fed2 100644 --- a/src/DynamicData/Cache/Internal/LockFreeObservableCache.cs +++ b/src/DynamicData/Cache/Internal/LockFreeObservableCache.cs @@ -1,13 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Diagnostics; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif /// /// An observable cache which exposes an update API. Used at the root @@ -20,19 +22,37 @@ public sealed class LockFreeObservableCache : IObservableCache> _changes = new(); + /// + /// The _changes field. + /// + [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed with _cleanUp")] + private readonly Signal> _changes = new(); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed with _cleanUp")] - private readonly Subject> _changesPreview = new(); + /// + /// The _changesPreview field. + /// + [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed with _cleanUp")] + private readonly Signal> _changesPreview = new(); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed with _cleanUp")] - private readonly Subject _countChanged = new(); + /// + /// The _countChanged field. + /// + [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed with _cleanUp")] + private readonly Signal _countChanged = new(); + /// + /// The _cleanUp field. + /// private readonly IDisposable _cleanUp; + /// + /// The _innerCache field. + /// private readonly ChangeAwareCache _innerCache = new(); + /// + /// The _updater field. + /// private readonly ICacheUpdater _updater; /// @@ -41,6 +61,8 @@ public sealed class LockFreeObservableCache : IObservableCacheThe source. public LockFreeObservableCache(IObservable> source) { + ArgumentExceptionHelper.ThrowIfNull(source); + _updater = new CacheUpdater(_innerCache); var loader = source.Select( @@ -91,6 +113,9 @@ public LockFreeObservableCache() public IReadOnlyDictionary KeyValues => new Dictionary(_innerCache.GetDictionary()); /// + /// The predicate value. + /// The suppressEmptyChangeSets value. + /// The result of the operation. public IObservable> Connect(Func? predicate = null, bool suppressEmptyChangeSets = true) => Observable.Defer( () => { @@ -118,10 +143,7 @@ public IObservable> Connect(Func? predi /// The edit action. public void Edit(Action> editAction) { - if (editAction is null) - { - throw new ArgumentNullException(nameof(editAction)); - } + ArgumentExceptionHelper.ThrowIfNull(editAction); editAction(_updater); _changes.OnNext(_innerCache.CaptureChanges()); @@ -135,9 +157,11 @@ public void Edit(Action> editAction) /// /// Fast indexed lookup. /// - public Optional Lookup(TKey key) => _innerCache.Lookup(key); + public ReactiveUI.Primitives.Optional Lookup(TKey key) => _innerCache.Lookup(key); /// + /// The predicate value. + /// The result of the operation. public IObservable> Preview(Func? predicate = null) => predicate is null ? _changesPreview : _changesPreview.Filter(predicate); /// diff --git a/src/DynamicData/Cache/Internal/ManagedGroup.cs b/src/DynamicData/Cache/Internal/ManagedGroup.cs index 02d3583fa..47a7a6817 100644 --- a/src/DynamicData/Cache/Internal/ManagedGroup.cs +++ b/src/DynamicData/Cache/Internal/ManagedGroup.cs @@ -1,23 +1,54 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the ManagedGroup class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TGroupKey value. +/// The groupKey value. internal sealed class ManagedGroup(TGroupKey groupKey) : IGroup, IDisposable where TObject : notnull where TKey : notnull { + /// + /// The _cache field. + /// private readonly IntermediateCache _cache = new(); + /// + /// Gets the Cache value. + /// public IObservableCache Cache => _cache; + /// + /// Gets the Key value. + /// public TGroupKey Key { get; } = groupKey; + /// + /// Gets the Count value. + /// internal int Count => _cache.Count; + /// + /// Executes the Dispose operation. + /// public void Dispose() => _cache.Dispose(); + /// + /// Executes the SuspendNotifications operation. + /// + /// The result of the operation. public IDisposable SuspendNotifications() => _cache.SuspendNotifications(); /// @@ -58,9 +89,22 @@ public override bool Equals(object? obj) /// public override string ToString() => $"Group: {Key}"; + /// + /// Executes the GetInitialUpdates operation. + /// + /// The result of the operation. internal IChangeSet GetInitialUpdates() => _cache.GetInitialUpdates(); + /// + /// Executes the Update operation. + /// + /// The updateAction value. internal void Update(Action> updateAction) => _cache.Edit(updateAction); + /// + /// Executes the Equals operation. + /// + /// The other value. + /// The result of the operation. private bool Equals(ManagedGroup other) => EqualityComparer.Default.Equals(Key, other.Key); } diff --git a/src/DynamicData/Cache/Internal/MergeChangeSets.cs b/src/DynamicData/Cache/Internal/MergeChangeSets.cs index ebf4b22f1..674847daa 100644 --- a/src/DynamicData/Cache/Internal/MergeChangeSets.cs +++ b/src/DynamicData/Cache/Internal/MergeChangeSets.cs @@ -1,25 +1,43 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif /// /// Operator that is similiar to Merge but intelligently handles Cache ChangeSets. /// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. +/// The equalityComparer value. +/// The comparer value. internal sealed class MergeChangeSets(IObservable>> source, IEqualityComparer? equalityComparer, IComparer? comparer) where TObject : notnull where TKey : notnull { + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The equalityComparer value. + /// The comparer value. + /// The completable value. + /// The scheduler value. public MergeChangeSets(IEnumerable>> source, IEqualityComparer? equalityComparer, IComparer? comparer, bool completable, IScheduler? scheduler = null) : this(CreateObservable(source, completable, scheduler), equalityComparer, comparer) { } + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -30,25 +48,47 @@ public IObservable> Run() => Observable.Create(() => cache.Items, comparer, equalityComparer); // Create a ChangeSet of Caches, synchronize, update the local copy, and merge the sub-observables together. - return new CompositeDisposable(CreateContainerObservable(source, queue) - .SynchronizeSafe(queue) - .Do(cache.Clone) - .MergeMany(mc => mc.Source.Do(static _ => { }, observer.OnError)) - .SubscribeSafe( + return new CompositeDisposable( + PrimitivesLinqExtensions.SubscribeSafe( + CreateContainerObservable(source, queue) + .SynchronizeSafe(queue) + .Do(cache.Clone) + .MergeMany(mc => mc.Source.Do(static _ => { }, observer.OnError)), changes => changeTracker.ProcessChangeSet(changes, observer), observer.OnError, - observer.OnCompleted), queue); + observer.OnCompleted), + queue); }); - // Can optimize for the Add case because that's the only one that applies + + /// + /// Executes the CreateChange operation. + /// + /// The source value. + /// The index value. + /// The queue value. + /// The result of the operation. private static Change, int> CreateChange(IObservable> source, int index, SharedDeliveryQueue queue) => new(ChangeReason.Add, index, new ChangeSetCache(source.IgnoreSameReferenceUpdate().SynchronizeSafe(queue))); - // Create a ChangeSet Observable that produces ChangeSets with a single Add event for each new sub-observable + + /// + /// Executes the CreateContainerObservable operation. + /// + /// The source value. + /// The queue value. + /// The result of the operation. private static IObservable, int>> CreateContainerObservable(IObservable>> source, SharedDeliveryQueue queue) => source.Select((src, index) => new ChangeSet, int>(new[] { CreateChange(src, index, queue) })); - // Create a ChangeSet Observable with a single event that adds all the values in the enum (and then completes, maybe) + + /// + /// Executes the CreateObservable operation. + /// + /// The source value. + /// The completable value. + /// The scheduler value. + /// The result of the operation. private static IObservable>> CreateObservable(IEnumerable>> source, bool completable, IScheduler? scheduler = null) { var obs = (scheduler != null) ? source.ToObservable(scheduler) : source.ToObservable(); diff --git a/src/DynamicData/Cache/Internal/MergeMany.cs b/src/DynamicData/Cache/Internal/MergeMany.cs index fe2eecc75..cd4f34bbf 100644 --- a/src/DynamicData/Cache/Internal/MergeMany.cs +++ b/src/DynamicData/Cache/Internal/MergeMany.cs @@ -1,35 +1,62 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the MergeMany class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TDestination value. internal sealed class MergeMany where TObject : notnull where TKey : notnull { + /// + /// The _observableSelector field. + /// private readonly Func> _observableSelector; + /// + /// The _source field. + /// private readonly IObservable> _source; + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The observableSelector value. public MergeMany(IObservable> source, Func> observableSelector) { _source = source ?? throw new ArgumentNullException(nameof(source)); _observableSelector = observableSelector ?? throw new ArgumentNullException(nameof(observableSelector)); } + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The observableSelector value. public MergeMany(IObservable> source, Func> observableSelector) { - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); _source = source ?? throw new ArgumentNullException(nameof(source)); _observableSelector = (t, _) => observableSelector(t); } + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable Run() => Observable.Create( observer => { @@ -51,6 +78,11 @@ public IObservable Run() => Observable.Create( .Subscribe(static _ => { }, observer.OnError)); }); + /// + /// Executes the CheckCompleted operation. + /// + /// The counter value. + /// The queue value. private static void CheckCompleted(StrongBox counter, DeliveryQueue queue) { if (Interlocked.Decrement(ref counter.Value) == 0 && !queue.IsTerminated) diff --git a/src/DynamicData/Cache/Internal/MergeManyCacheChangeSets.cs b/src/DynamicData/Cache/Internal/MergeManyCacheChangeSets.cs index 551cc8bbf..c67107933 100644 --- a/src/DynamicData/Cache/Internal/MergeManyCacheChangeSets.cs +++ b/src/DynamicData/Cache/Internal/MergeManyCacheChangeSets.cs @@ -1,30 +1,62 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; -using DynamicData.Internal; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif /// /// Operator that is similiar to MergeMany but intelligently handles Cache ChangeSets. /// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TDestination value. +/// The type of the TDestinationKey value. +/// The source value. +/// The changeSetSelector value. +/// The equalityComparer value. +/// The comparer value. internal sealed class MergeManyCacheChangeSets(IObservable> source, Func>> changeSetSelector, IEqualityComparer? equalityComparer, IComparer? comparer) where TObject : notnull where TKey : notnull where TDestination : notnull where TDestinationKey : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => new Subscription(source, changeSetSelector, observer, equalityComparer, comparer)); - // Maintains state for a single subscription - private sealed class Subscription : CacheParentSubscription, TKey, IChangeSet, IChangeSet> + +/// +/// Provides members for the Subscription class. +/// +private sealed class Subscription : CacheParentSubscription, TKey, IChangeSet, IChangeSet> { + /// + /// The _cache field. + /// private readonly Cache, TKey> _cache = new(); + + /// + /// The _changeSetMergeTracker field. + /// private readonly ChangeSetMergeTracker _changeSetMergeTracker; + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The changeSetSelector value. + /// The observer value. + /// The equalityComparer value. + /// The comparer value. public Subscription( IObservable> source, Func>> changeSetSelector, @@ -40,7 +72,11 @@ public Subscription( new ChangeSetCache(MakeChildObservable(changeSetSelector(obj, key).IgnoreSameReferenceUpdate())))); } - protected override void ParentOnNext(IChangeSet, TKey> changes) + /// + /// Executes the ParentOnNext operation. + /// + /// The changes value. + protected override void ParentOnNext(IChangeSet, TKey> changes) { // Process all the changes at once to preserve the changeset order foreach (var change in changes.ToConcreteType()) @@ -61,16 +97,25 @@ protected override void ParentOnNext(IChangeSet + /// Executes the ChildOnNext operation. + /// + /// The changes value. + /// The parentKey value. protected override void ChildOnNext(IChangeSet changes, TKey parentKey) => _changeSetMergeTracker.ProcessChangeSet(changes, null); + /// + /// Executes the EmitChanges operation. + /// + /// The observer value. protected override void EmitChanges(IObserver> observer) => _changeSetMergeTracker.EmitChanges(observer); } diff --git a/src/DynamicData/Cache/Internal/MergeManyCacheChangeSetsSourceCompare.cs b/src/DynamicData/Cache/Internal/MergeManyCacheChangeSetsSourceCompare.cs index a49ad8223..65d5fea48 100644 --- a/src/DynamicData/Cache/Internal/MergeManyCacheChangeSetsSourceCompare.cs +++ b/src/DynamicData/Cache/Internal/MergeManyCacheChangeSetsSourceCompare.cs @@ -1,39 +1,87 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; -using DynamicData.Internal; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif /// /// Alternate version of MergeManyCacheChangeSets that uses a Comparer of the source, not the destination type /// So that items from the most important source go into the resulting changeset. /// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TDestination value. +/// The type of the TDestinationKey value. +/// The source value. +/// The selector value. +/// The parentCompare value. +/// The equalityComparer value. +/// The childCompare value. +/// The reevalOnRefresh value. internal sealed class MergeManyCacheChangeSetsSourceCompare(IObservable> source, Func>> selector, IComparer parentCompare, IEqualityComparer? equalityComparer, IComparer? childCompare, bool reevalOnRefresh = false) where TObject : notnull where TKey : notnull where TDestination : notnull where TDestinationKey : notnull { + /// + /// The _changeSetSelector field. + /// private readonly Func>> _changeSetSelector = (obj, key) => selector(obj, key).Transform(dest => new ParentChildEntry(obj, dest)); + /// + /// The _comparer field. + /// private readonly IComparer _comparer = (childCompare is null) ? new ParentOnlyCompare(parentCompare) : new ParentChildCompare(parentCompare, childCompare); + /// + /// The _equalityComparer field. + /// private readonly IEqualityComparer? _equalityComparer = (equalityComparer != null) ? new ParentChildEqualityCompare(equalityComparer) : null; + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>(observer => new Subscription(source, _changeSetSelector, observer, _comparer, _equalityComparer, reevalOnRefresh)) .TransformImmutable(entry => entry.Child); - // Maintains state for a single subscription - private sealed class Subscription : CacheParentSubscription, TKey, IChangeSet, IChangeSet> + +/// +/// Provides members for the Subscription class. +/// +private sealed class Subscription : CacheParentSubscription, TKey, IChangeSet, IChangeSet> { + /// + /// The _cache field. + /// private readonly Cache, TKey> _cache = new(); + + /// + /// The _changeSetMergeTracker field. + /// private readonly ChangeSetMergeTracker _changeSetMergeTracker; + + /// + /// The _reevalOnRefresh field. + /// private readonly bool _reevalOnRefresh; + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The changeSetSelector value. + /// The observer value. + /// The comparer value. + /// The equalityComparer value. + /// The reevalOnRefresh value. public Subscription( IObservable> source, Func>> changeSetSelector, @@ -51,6 +99,10 @@ public Subscription( new ChangeSetCache(MakeChildObservable(changeSetSelector(obj, key).IgnoreSameReferenceUpdate())))); } + /// + /// Executes the ParentOnNext operation. + /// + /// The changes value. protected override void ParentOnNext(IChangeSet, TKey> changes) { // Process all the changes at once to preserve the changeset order @@ -86,17 +138,42 @@ protected override void ParentOnNext(IChangeSet + /// Executes the ChildOnNext operation. + /// + /// The changes value. + /// The parentKey value. protected override void ChildOnNext(IChangeSet changes, TKey parentKey) => _changeSetMergeTracker.ProcessChangeSet(changes, null); + /// + /// Executes the EmitChanges operation. + /// + /// The observer value. protected override void EmitChanges(IObserver> observer) => _changeSetMergeTracker.EmitChanges(observer); } + /// + /// Represents the ParentChildEntry record. + /// + /// The Parent value. + /// The Child value. private sealed record ParentChildEntry(TObject Parent, TDestination Child); - private sealed class ParentChildCompare(IComparer comparerParent, IComparer comparerChild) : Comparer +/// +/// Provides members for the ParentChildCompare class. +/// +/// The comparerParent value. +/// The comparerChild value. +private sealed class ParentChildCompare(IComparer comparerParent, IComparer comparerChild) : Comparer { + /// + /// Executes the Compare operation. + /// + /// The x value. + /// The y value. + /// The result of the operation. public override int Compare(ParentChildEntry? x, ParentChildEntry? y) => (x, y) switch { (not null, not null) => comparerParent.Compare(x.Parent, y.Parent) switch @@ -110,8 +187,18 @@ private sealed class ParentChildCompare(IComparer comparerParent, IComp }; } - private sealed class ParentOnlyCompare(IComparer comparer) : Comparer +/// +/// Provides members for the ParentOnlyCompare class. +/// +/// The comparer value. +private sealed class ParentOnlyCompare(IComparer comparer) : Comparer { + /// + /// Executes the Compare operation. + /// + /// The x value. + /// The y value. + /// The result of the operation. public override int Compare(ParentChildEntry? x, ParentChildEntry? y) => (x, y) switch { (not null, not null) => comparer.Compare(x.Parent, y.Parent), @@ -121,8 +208,18 @@ private sealed class ParentOnlyCompare(IComparer comparer) : Comparer

comparer) : EqualityComparer +///

+/// Provides members for the ParentChildEqualityCompare class. +/// +/// The comparer value. +private sealed class ParentChildEqualityCompare(IEqualityComparer comparer) : EqualityComparer { + /// + /// Executes the Equals operation. + /// + /// The x value. + /// The y value. + /// The result of the operation. public override bool Equals(ParentChildEntry? x, ParentChildEntry? y) => (x, y) switch { (not null, not null) => comparer.Equals(x.Child, y.Child), @@ -130,6 +227,11 @@ private sealed class ParentChildEqualityCompare(IEqualityComparer _ => false, }; + /// + /// Executes the GetHashCode operation. + /// + /// The obj value. + /// The result of the operation. public override int GetHashCode(ParentChildEntry obj) => comparer.GetHashCode(obj.Child); } } diff --git a/src/DynamicData/Cache/Internal/MergeManyItems.cs b/src/DynamicData/Cache/Internal/MergeManyItems.cs index 26b85614b..c6bd5e001 100644 --- a/src/DynamicData/Cache/Internal/MergeManyItems.cs +++ b/src/DynamicData/Cache/Internal/MergeManyItems.cs @@ -1,35 +1,65 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the MergeManyItems class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TDestination value. internal sealed class MergeManyItems where TObject : notnull where TKey : notnull { + /// + /// The _observableSelector field. + /// private readonly Func> _observableSelector; + /// + /// The _source field. + /// private readonly IObservable> _source; + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The observableSelector value. public MergeManyItems(IObservable> source, Func> observableSelector) { - _source = source ?? throw new ArgumentNullException(nameof(source)); - _observableSelector = observableSelector ?? throw new ArgumentNullException(nameof(observableSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); + + _source = source; + _observableSelector = observableSelector; } + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The observableSelector value. public MergeManyItems(IObservable> source, Func> observableSelector) { - if (observableSelector is null) - { - throw new ArgumentNullException(nameof(observableSelector)); - } + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); - _source = source ?? throw new ArgumentNullException(nameof(source)); + _source = source; _observableSelector = (t, _) => observableSelector(t); } + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>(observer => _source.SubscribeMany((t, v) => _observableSelector(t, v).Select(z => new ItemWithValue(t, z)).SubscribeSafe(observer)).Subscribe()); } diff --git a/src/DynamicData/Cache/Internal/MergeManyListChangeSets.cs b/src/DynamicData/Cache/Internal/MergeManyListChangeSets.cs index bd7b18f4d..0ba24b020 100644 --- a/src/DynamicData/Cache/Internal/MergeManyListChangeSets.cs +++ b/src/DynamicData/Cache/Internal/MergeManyListChangeSets.cs @@ -1,29 +1,60 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else -using System.Reactive.Linq; -using DynamicData.Internal; using DynamicData.List.Internal; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif /// /// Operator that is similiar to MergeMany but intelligently handles List ChangeSets. /// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TDestination value. +/// The source value. +/// The selector value. +/// The equalityComparer value. internal sealed class MergeManyListChangeSets(IObservable> source, Func>> selector, IEqualityComparer? equalityComparer) where TObject : notnull where TKey : notnull where TDestination : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => new Subscription(source, selector, observer, equalityComparer)); - // Maintains state for a single subscription - private sealed class Subscription : CacheParentSubscription, TKey, IChangeSet, IChangeSet> + +/// +/// Provides members for the Subscription class. +/// +private sealed class Subscription : CacheParentSubscription, TKey, IChangeSet, IChangeSet> { + /// + /// The _changeSetMergeTracker field. + /// private readonly ChangeSetMergeTracker _changeSetMergeTracker = new(); + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The selector value. + /// The observer value. + /// The equalityComparer value. public Subscription( IObservable> source, Func>> selector, @@ -36,6 +67,10 @@ public Subscription( new ClonedListChangeSet(MakeChildObservable(selector(obj, key).RemoveIndex()), equalityComparer))); } + /// + /// Executes the ParentOnNext operation. + /// + /// The changes value. protected override void ParentOnNext(IChangeSet, TKey> changes) { // Process all the changes at once to preserve the changeset order @@ -55,16 +90,25 @@ protected override void ParentOnNext(IChangeSet + /// Executes the ChildOnNext operation. + ///
+ /// The child value. + /// The parentKey value. protected override void ChildOnNext(IChangeSet child, TKey parentKey) => _changeSetMergeTracker.ProcessChangeSet(child, null); + /// + /// Executes the EmitChanges operation. + /// + /// The observer value. protected override void EmitChanges(IObserver> observer) => _changeSetMergeTracker.EmitChanges(observer); } diff --git a/src/DynamicData/Cache/Internal/ObservableWithValue.cs b/src/DynamicData/Cache/Internal/ObservableWithValue.cs index 51ae8ac9d..a651e2e85 100644 --- a/src/DynamicData/Cache/Internal/ObservableWithValue.cs +++ b/src/DynamicData/Cache/Internal/ObservableWithValue.cs @@ -1,23 +1,45 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the ObservableWithValue class. +/// +/// The type of the TObject value. +/// The type of the TValue value. internal sealed class ObservableWithValue where TValue : notnull { + /// + /// Initializes a new instance of the class. + /// + /// The item value. + /// The source value. public ObservableWithValue(TObject item, IObservable source) { Item = item; Observable = source.Do(value => LatestValue = value); } + /// + /// Gets the Item value. + /// public TObject Item { get; } - public Optional LatestValue { get; private set; } = Optional.None; + /// + /// Gets the LatestValue value. + /// + public ReactiveUI.Primitives.Optional LatestValue { get; private set; } = ReactiveUI.Primitives.Optional.None; + /// + /// Gets the Observable value. + /// public IObservable Observable { get; } } diff --git a/src/DynamicData/Cache/Internal/OfType.cs b/src/DynamicData/Cache/Internal/OfType.cs index 17546116d..6cf00d286 100644 --- a/src/DynamicData/Cache/Internal/OfType.cs +++ b/src/DynamicData/Cache/Internal/OfType.cs @@ -1,67 +1,81 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; -using DynamicData.Internal; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the OfType class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TDestination value. +/// The source value. +/// The suppressEmptyChangeSets value. internal sealed class OfType(IObservable> source, bool suppressEmptyChangeSets) where TObject : notnull where TKey : notnull where TDestination : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => - Observable.Create>(observer => source - .SubscribeSafe( - onNext: upstreamChanges => - { - var downstreamChanges = new ChangeSet(capacity: upstreamChanges.Count); + Observable.Create>(observer => PrimitivesLinqExtensions.SubscribeSafe( + source, + onNext: upstreamChanges => + { + var downstreamChanges = new ChangeSet(capacity: upstreamChanges.Count); - try + try + { + foreach (var change in upstreamChanges.ToConcreteType()) { - foreach (var change in upstreamChanges.ToConcreteType()) - { - // Don't propagate moves at all, since we don't preserve indexes. - if (change.Reason is ChangeReason.Moved) - continue; - - Change? transformedChange = (change.Reason, change.Current) switch - { - // Update when Current is the right type, but the Previous was not (Add) - (ChangeReason.Update, TDestination addDestination) when change.Previous.Value is not TDestination => - new(ChangeReason.Add, change.Key, addDestination), + // Don't propagate moves at all, since we don't preserve indexes. + if (change.Reason is ChangeReason.Moved) + continue; - // Update when Current is not the right type, but the Previous was (Remove) - (ChangeReason.Update, not TDestination) when change.Previous.Value is TDestination removeDestination => - new(ChangeReason.Remove, change.Key, removeDestination), + Change? transformedChange = (change.Reason, change.Current) switch + { + // Update when Current is the right type, but the Previous was not (Add) + (ChangeReason.Update, TDestination addDestination) when change.Previous.Value is not TDestination => + new(ChangeReason.Add, change.Key, addDestination), - // For any other change reason, if the Current is the right type, forward with converted types - (_, TDestination otherDestination) => - new(change.Reason, change.Key, otherDestination, change.Previous.HasValue && change.Previous.Value is TDestination pd ? Optional.Some(pd) : default), + // Update when Current is not the right type, but the Previous was (Remove) + (ChangeReason.Update, not TDestination) when change.Previous.Value is TDestination removeDestination => + new(ChangeReason.Remove, change.Key, removeDestination), - // Otherwise, don't do anything at all - _ => default, - }; + // For any other change reason, if the Current is the right type, forward with converted types + (_, TDestination otherDestination) => + new(change.Reason, change.Key, otherDestination, change.Previous.HasValue && change.Previous.Value is TDestination pd ? ReactiveUI.Primitives.Optional.Some(pd) : default), - if (transformedChange is { } c) - { - // Do not propagate indexes, we can't guarantee them to be correct, because we aren't caching items. - downstreamChanges.Add(c); - } - } + // Otherwise, don't do anything at all + _ => default, + }; - if (!suppressEmptyChangeSets || downstreamChanges.Count != 0) + if (transformedChange is { } c) { - observer.OnNext(downstreamChanges); + // Do not propagate indexes, we can't guarantee them to be correct, because we aren't caching items. + downstreamChanges.Add(c); } } - catch (Exception error) + + if (!suppressEmptyChangeSets || downstreamChanges.Count != 0) { - observer.OnError(error); + observer.OnNext(downstreamChanges); } - }, - onError: observer.OnError, - onCompleted: observer.OnCompleted)); + } + catch (Exception error) + { + observer.OnError(error); + } + }, + onError: observer.OnError, + onCompleted: observer.OnCompleted)); } diff --git a/src/DynamicData/Cache/Internal/OnBeingRemoved.cs b/src/DynamicData/Cache/Internal/OnBeingRemoved.cs index 092306f3d..5ab4bcd8b 100644 --- a/src/DynamicData/Cache/Internal/OnBeingRemoved.cs +++ b/src/DynamicData/Cache/Internal/OnBeingRemoved.cs @@ -1,19 +1,39 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the OnBeingRemoved class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. +/// The removeAction value. internal sealed class OnBeingRemoved(IObservable> source, Action removeAction) where TObject : notnull where TKey : notnull { + /// + /// The _removeAction field. + /// private readonly Action _removeAction = removeAction ?? throw new ArgumentNullException(nameof(removeAction)); + + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -31,6 +51,11 @@ public IObservable> Run() => Observable.Create + /// Executes the RegisterForRemoval operation. + ///
+ /// The changes value. + /// The cache value. private void RegisterForRemoval(IChangeSet changes, Cache cache) { changes.ForEach( diff --git a/src/DynamicData/Cache/Internal/Page.cs b/src/DynamicData/Cache/Internal/Page.cs index e41595871..ed87ac4f7 100644 --- a/src/DynamicData/Cache/Internal/Page.cs +++ b/src/DynamicData/Cache/Internal/Page.cs @@ -1,16 +1,29 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; - +#endif + +/// +/// Provides members for the Page class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. +/// The pageRequests value. internal sealed class Page(IObservable> source, IObservable pageRequests) where TObject : notnull where TKey : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -25,22 +38,45 @@ public IObservable> Run() => Observable.Create +/// Provides members for the Paginator class. +///
+private sealed class Paginator { + /// + /// The _all field. + /// private IKeyValueCollection _all = new KeyValueCollection(); + /// + /// The _current field. + /// private KeyValueCollection _current = new(); + /// + /// The _isLoaded field. + /// private bool _isLoaded; + /// + /// The _request field. + /// private IPageRequest _request; + /// + /// Initializes a new instance of the class. + /// public Paginator() { _request = PageRequest.Default; _isLoaded = false; } + /// + /// Executes the Paginate operation. + /// + /// The parameters value. + /// The result of the operation. public IPagedChangeSet? Paginate(IPageRequest? parameters) { if (parameters is null || parameters.Page < 0 || parameters.Size < 1) @@ -58,6 +94,11 @@ public Paginator() return Paginate(); } + /// + /// Executes the Update operation. + /// + /// The updates value. + /// The result of the operation. public IPagedChangeSet? Update(ISortedChangeSet updates) { _isLoaded = true; @@ -65,6 +106,10 @@ public Paginator() return Paginate(updates); } + /// + /// Executes the CalculatePages operation. + /// + /// The result of the operation. private int CalculatePages() { if (_request.Size >= _all.Count) @@ -83,6 +128,11 @@ private int CalculatePages() return pages + 1; } + /// + /// Executes the Paginate operation. + /// + /// The updates value. + /// The result of the operation. private PagedChangeSet? Paginate(ISortedChangeSet? updates = null) { if (!_isLoaded) diff --git a/src/DynamicData/Cache/Internal/QueryWhenChanged.cs b/src/DynamicData/Cache/Internal/QueryWhenChanged.cs index 01828e85e..45c96fad8 100644 --- a/src/DynamicData/Cache/Internal/QueryWhenChanged.cs +++ b/src/DynamicData/Cache/Internal/QueryWhenChanged.cs @@ -1,18 +1,35 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the QueryWhenChanged class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TValue value. +/// The source value. +/// The itemChangedTrigger value. internal sealed class QueryWhenChanged(IObservable> source, Func>? itemChangedTrigger = null) where TObject : notnull where TKey : notnull { + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() { if (itemChangedTrigger is null) diff --git a/src/DynamicData/Cache/Internal/ReaderWriter.cs b/src/DynamicData/Cache/Internal/ReaderWriter.cs index bbe56eb14..f9ca51f99 100644 --- a/src/DynamicData/Cache/Internal/ReaderWriter.cs +++ b/src/DynamicData/Cache/Internal/ReaderWriter.cs @@ -1,23 +1,42 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the ReaderWriter class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The keySelector value. internal sealed class ReaderWriter(Func? keySelector = null) where TObject : notnull where TKey : notnull { -#if NET9_0_OR_GREATER + /// + /// The _locker field. + /// private readonly Lock _locker = new(); -#else - private readonly object _locker = new(); -#endif + /// + /// The _activeUpdater field. + /// private CacheUpdater? _activeUpdater; + /// + /// The _data field. + /// private Dictionary _data = []; // could do with priming this on first time load + /// + /// Gets the Count value. + /// public int Count { get @@ -29,6 +48,9 @@ public int Count } } + /// + /// Gets the Items value. + /// public TObject[] Items { get @@ -40,6 +62,9 @@ public TObject[] Items } } + /// + /// Gets the Keys value. + /// public TKey[] Keys { get @@ -51,6 +76,9 @@ public TKey[] Keys } } + /// + /// Gets the KeyValues value. + /// public IReadOnlyDictionary KeyValues { get @@ -62,6 +90,11 @@ public IReadOnlyDictionary KeyValues } } + /// + /// Executes the GetInitialUpdates operation. + /// + /// The filter value. + /// The result of the operation. public ChangeSet GetInitialUpdates(Func? filter = null) { lock (_locker) @@ -87,7 +120,12 @@ public ChangeSet GetInitialUpdates(Func? filter = } } - public Optional Lookup(TKey key) + /// + /// Executes the Lookup operation. + /// + /// The key value. + /// The result of the operation. + public ReactiveUI.Primitives.Optional Lookup(TKey key) { lock (_locker) { @@ -95,27 +133,52 @@ public Optional Lookup(TKey key) } } + /// + /// Executes the Write operation. + /// + /// The changes value. + /// The previewHandler value. + /// The collectChanges value. + /// The result of the operation. public ChangeSet Write(IChangeSet changes, Action>? previewHandler, bool collectChanges) { - changes.ThrowArgumentNullExceptionIfNull(nameof(changes)); + ArgumentExceptionHelper.ThrowIfNull(changes); return DoUpdate(updater => updater.Clone(changes), previewHandler, collectChanges); } + /// + /// Executes the Write operation. + /// + /// The updateAction value. + /// The previewHandler value. + /// The collectChanges value. + /// The result of the operation. public ChangeSet Write(Action> updateAction, Action>? previewHandler, bool collectChanges) { - updateAction.ThrowArgumentNullExceptionIfNull(nameof(updateAction)); + ArgumentExceptionHelper.ThrowIfNull(updateAction); return DoUpdate(updateAction, previewHandler, collectChanges); } + /// + /// Executes the Write operation. + /// + /// The updateAction value. + /// The previewHandler value. + /// The collectChanges value. + /// The result of the operation. public ChangeSet Write(Action> updateAction, Action>? previewHandler, bool collectChanges) { - updateAction.ThrowArgumentNullExceptionIfNull(nameof(updateAction)); + ArgumentExceptionHelper.ThrowIfNull(updateAction); return DoUpdate(updateAction, previewHandler, collectChanges); } + /// + /// Executes the WriteNested operation. + /// + /// The updateAction value. public void WriteNested(Action> updateAction) { lock (_locker) @@ -129,6 +192,13 @@ public void WriteNested(Action> updateAction) } } + /// + /// Executes the DoUpdate operation. + /// + /// The updateAction value. + /// The previewHandler value. + /// The collectChanges value. + /// The result of the operation. private ChangeSet DoUpdate(Action> updateAction, Action>? previewHandler, bool collectChanges) { lock (_locker) diff --git a/src/DynamicData/Cache/Internal/RefCount.cs b/src/DynamicData/Cache/Internal/RefCount.cs index 4c4736391..23786b347 100644 --- a/src/DynamicData/Cache/Internal/RefCount.cs +++ b/src/DynamicData/Cache/Internal/RefCount.cs @@ -1,28 +1,48 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the RefCount class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. internal sealed class RefCount(IObservable> source) where TObject : notnull where TKey : notnull { -#if NET9_0_OR_GREATER + /// + /// The _locker field. + /// private readonly Lock _locker = new(); -#else - private readonly object _locker = new(); -#endif + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// The _cache field. + /// private IObservableCache? _cache; + /// + /// The _refCount field. + /// private int _refCount; + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { diff --git a/src/DynamicData/Cache/Internal/RemoveKeyEnumerator.cs b/src/DynamicData/Cache/Internal/RemoveKeyEnumerator.cs index 716d847d5..2ca548b1b 100644 --- a/src/DynamicData/Cache/Internal/RemoveKeyEnumerator.cs +++ b/src/DynamicData/Cache/Internal/RemoveKeyEnumerator.cs @@ -1,28 +1,36 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Collections; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif -/// Initializes a new instance of the class.Converts a to . +/// Initializes a new instance of the class.Converts a Change<TObject, TKey> to ChangeSet<TObject>. /// The change set with a key. /// /// An optional list, if provided it allows the refresh from a key based cache to find the index for the resulting list based refresh. /// If not provided a refresh will dropdown to a replace which may ultimately result in a remove+add change downstream. /// +/// The type of the TObject value. +/// The type of the TKey value. internal sealed class RemoveKeyEnumerator(IChangeSet source, IExtendedList? list = null) : IEnumerable> where TObject : notnull where TKey : notnull { + /// + /// The _source field. + /// private readonly IChangeSet _source = source ?? throw new ArgumentNullException(nameof(source)); /// /// Returns an enumerator that iterates through the collection. /// /// - /// A that can be used to iterate through the collection. + /// A IEnumerator<T> that can be used to iterate through the collection. /// public IEnumerator> GetEnumerator() { @@ -72,5 +80,9 @@ public IEnumerator> GetEnumerator() } } + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/DynamicData/Cache/Internal/RightJoin.cs b/src/DynamicData/Cache/Internal/RightJoin.cs index 85163f75e..257e43775 100644 --- a/src/DynamicData/Cache/Internal/RightJoin.cs +++ b/src/DynamicData/Cache/Internal/RightJoin.cs @@ -1,27 +1,57 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; - -internal sealed class RightJoin(IObservable> left, IObservable> right, Func rightKeySelector, Func, TRight, TDestination> resultSelector) +#endif + +/// +/// Provides members for the RightJoin class. +/// +/// The type of the TLeft value. +/// The type of the TLeftKey value. +/// The type of the TRight value. +/// The type of the TRightKey value. +/// The type of the TDestination value. +/// The left value. +/// The right value. +/// The rightKeySelector value. +/// The resultSelector value. +internal sealed class RightJoin(IObservable> left, IObservable> right, Func rightKeySelector, Func, TRight, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull where TRight : notnull where TRightKey : notnull where TDestination : notnull { + /// + /// The _left field. + /// private readonly IObservable> _left = left ?? throw new ArgumentNullException(nameof(left)); - private readonly Func, TRight, TDestination> _resultSelector = resultSelector ?? throw new ArgumentNullException(nameof(resultSelector)); + /// + /// The _resultSelector field. + /// + private readonly Func, TRight, TDestination> _resultSelector = resultSelector ?? throw new ArgumentNullException(nameof(resultSelector)); + /// + /// The _right field. + /// private readonly IObservable> _right = right ?? throw new ArgumentNullException(nameof(right)); + /// + /// The _rightKeySelector field. + /// private readonly Func _rightKeySelector = rightKeySelector ?? throw new ArgumentNullException(nameof(rightKeySelector)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -113,7 +143,7 @@ public IObservable> Run() => Observable.Crea case ChangeReason.Remove: if (right.HasValue) { - joinedCache.AddOrUpdate(_resultSelector(right.Value.key, Optional.None, right.Value.item), right.Value.key); + joinedCache.AddOrUpdate(_resultSelector(right.Value.key, ReactiveUI.Primitives.Optional.None, right.Value.item), right.Value.key); } break; diff --git a/src/DynamicData/Cache/Internal/RightJoinMany.cs b/src/DynamicData/Cache/Internal/RightJoinMany.cs index 825d71a90..0bdff2e97 100644 --- a/src/DynamicData/Cache/Internal/RightJoinMany.cs +++ b/src/DynamicData/Cache/Internal/RightJoinMany.cs @@ -1,24 +1,57 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif -internal sealed class RightJoinMany(IObservable> left, IObservable> right, Func rightKeySelector, Func, IGrouping, TDestination> resultSelector) +/// +/// Provides members for the RightJoinMany class. +/// +/// The type of the TLeft value. +/// The type of the TLeftKey value. +/// The type of the TRight value. +/// The type of the TRightKey value. +/// The type of the TDestination value. +/// The left value. +/// The right value. +/// The rightKeySelector value. +/// The resultSelector value. +internal sealed class RightJoinMany(IObservable> left, IObservable> right, Func rightKeySelector, Func, IGrouping, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull where TRight : notnull where TRightKey : notnull where TDestination : notnull { + /// + /// The _left field. + /// private readonly IObservable> _left = left ?? throw new ArgumentNullException(nameof(left)); - private readonly Func, IGrouping, TDestination> _resultSelector = resultSelector ?? throw new ArgumentNullException(nameof(resultSelector)); + /// + /// The _resultSelector field. + /// + private readonly Func, IGrouping, TDestination> _resultSelector = resultSelector ?? throw new ArgumentNullException(nameof(resultSelector)); + /// + /// The _right field. + /// private readonly IObservable> _right = right ?? throw new ArgumentNullException(nameof(right)); + /// + /// The _rightKeySelector field. + /// private readonly Func _rightKeySelector = rightKeySelector ?? throw new ArgumentNullException(nameof(rightKeySelector)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() { var rightGrouped = _right.GroupWithImmutableState(_rightKeySelector); diff --git a/src/DynamicData/Cache/Internal/SizeExpirer.cs b/src/DynamicData/Cache/Internal/SizeExpirer.cs index ede14bced..4a104a10b 100644 --- a/src/DynamicData/Cache/Internal/SizeExpirer.cs +++ b/src/DynamicData/Cache/Internal/SizeExpirer.cs @@ -1,20 +1,38 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the SizeExpirer class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal sealed class SizeExpirer where TObject : notnull where TKey : notnull { + /// + /// The _size field. + /// private readonly int _size; + /// + /// The _source field. + /// private readonly IObservable> _source; + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The size value. public SizeExpirer(IObservable> source, int size) { if (size <= 0) @@ -26,6 +44,10 @@ public SizeExpirer(IObservable> source, int size) _size = size; } + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { diff --git a/src/DynamicData/Cache/Internal/SizeLimiter.cs b/src/DynamicData/Cache/Internal/SizeLimiter.cs index bfd0fa72f..a72935bc7 100644 --- a/src/DynamicData/Cache/Internal/SizeLimiter.cs +++ b/src/DynamicData/Cache/Internal/SizeLimiter.cs @@ -1,15 +1,34 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -namespace DynamicData.Cache.Internal; +namespace DynamicData.Reactive.Cache.Internal; +#else +namespace DynamicData.Cache.Internal; +#endif + +/// +/// Provides members for the SizeLimiter class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The size value. internal sealed class SizeLimiter(int size) where TObject : notnull where TKey : notnull { + /// + /// The _cache field. + /// private readonly ChangeAwareCache, TKey> _cache = new(); + /// + /// Executes the Change operation. + /// + /// The updates value. + /// The result of the operation. public IChangeSet Change(IChangeSet, TKey> updates) { _cache.Clone(updates); @@ -22,11 +41,16 @@ public IChangeSet Change(IChangeSet, } var notifications = _cache.CaptureChanges(); - var changed = notifications.Select(update => new Change(update.Reason, update.Key, update.Current.Value, update.Previous.HasValue ? update.Previous.Value.Value : Optional.None)); + var changed = notifications.Select(update => new Change(update.Reason, update.Key, update.Current.Value, update.Previous.HasValue ? update.Previous.Value.Value : ReactiveUI.Primitives.Optional.None)); return new ChangeSet(changed); } + /// + /// Executes the CloneAndReturnExpiredOnly operation. + /// + /// The updates value. + /// The result of the operation. public KeyValuePair[] CloneAndReturnExpiredOnly(IChangeSet, TKey> updates) { _cache.Clone(updates); diff --git a/src/DynamicData/Cache/Internal/Sort.cs b/src/DynamicData/Cache/Internal/Sort.cs index 11b39035e..e52e84893 100644 --- a/src/DynamicData/Cache/Internal/Sort.cs +++ b/src/DynamicData/Cache/Internal/Sort.cs @@ -1,29 +1,62 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the Sort class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal sealed class Sort where TObject : notnull where TKey : notnull { + /// + /// The _comparer field. + /// private readonly IComparer? _comparer; + /// + /// The _comparerChangedObservable field. + /// private readonly IObservable>? _comparerChangedObservable; + /// + /// The _resetThreshold field. + /// private readonly int _resetThreshold; + /// + /// The _resorter field. + /// private readonly IObservable? _resorter; + /// + /// The _sortOptimisations field. + /// private readonly SortOptimisations _sortOptimisations; + /// + /// The _source field. + /// private readonly IObservable> _source; + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The comparer value. + /// The sortOptimisations value. + /// The comparerChangedObservable value. + /// The resorter value. + /// The resetThreshold value. public Sort(IObservable> source, IComparer? comparer, SortOptimisations sortOptimisations = SortOptimisations.None, IObservable>? comparerChangedObservable = null, IObservable? resorter = null, int resetThreshold = -1) { if (comparer is null && comparerChangedObservable is null) @@ -39,6 +72,10 @@ public Sort(IObservable> source, IComparer? c _resetThreshold = resetThreshold; } + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -60,17 +97,42 @@ public IObservable> Run() => Observable.Create result is not null).Select(x => x!).SubscribeSafe(observer), queue); }); - private sealed class Sorter(SortOptimisations optimisations, IComparer? comparer = null, int resetThreshold = -1) +/// +/// Provides members for the Sorter class. +/// +/// The optimisations value. +/// The comparer value. +/// The resetThreshold value. +private sealed class Sorter(SortOptimisations optimisations, IComparer? comparer = null, int resetThreshold = -1) { + /// + /// The _cache field. + /// private readonly ChangeAwareCache _cache = new(); + + /// + /// The _calculator field. + /// private IndexCalculator? _calculator; + /// + /// The _comparer field. + /// private KeyValueComparer _comparer = new(comparer); + /// + /// The _haveReceivedData field. + /// private bool _haveReceivedData; + /// + /// The _initialised field. + /// private bool _initialised; + /// + /// The _sorted field. + /// private IKeyValueCollection _sorted = new KeyValueCollection(); /// diff --git a/src/DynamicData/Cache/Internal/SortAndPage.cs b/src/DynamicData/Cache/Internal/SortAndPage.cs index 2d612a9b6..fd89676cf 100644 --- a/src/DynamicData/Cache/Internal/SortAndPage.cs +++ b/src/DynamicData/Cache/Internal/SortAndPage.cs @@ -1,24 +1,62 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else -using System.Reactive.Disposables; -using System.Reactive.Linq; using DynamicData.Binding; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the SortAndPage class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal sealed class SortAndPage where TObject : notnull where TKey : notnull { + /// + /// The _keyComparer field. + /// private static readonly KeyComparer _keyComparer = new(); + /// + /// The _source field. + /// private readonly IObservable> _source; + + /// + /// The _comparerChanged field. + /// private readonly IObservable> _comparerChanged; + + /// + /// The _pageRequests field. + /// private readonly IObservable _pageRequests; + + /// + /// The _options field. + /// private readonly SortAndPageOptions _options; + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The comparer value. + /// The virtualRequests value. + /// The options value. public SortAndPage(IObservable> source, IComparer comparer, IObservable virtualRequests, @@ -27,6 +65,13 @@ public SortAndPage(IObservable> source, { } + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The comparerChanged value. + /// The virtualRequests value. + /// The options value. public SortAndPage(IObservable> source, IObservable> comparerChanged, IObservable virtualRequests, @@ -38,8 +83,15 @@ public SortAndPage(IObservable> source, _pageRequests = virtualRequests ?? throw new ArgumentNullException(nameof(virtualRequests)); } + /// + /// The Empty field. + /// private static readonly ChangeSet> Empty = new(0, PageContext.Empty); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable>> Run() => Observable.Create>>( observer => @@ -159,8 +211,16 @@ static int CalculatePages(IPageRequest request, int totalCount) return pages + 1; } }); - // Calculates any changes within the paged range. + + /// + /// Executes the CalculatePageChanges operation. + /// + /// The context value. + /// The currentItems value. + /// The previousItems value. + /// The changes value. + /// The result of the operation. private static ChangeSet> CalculatePageChanges(PageContext context, List> currentItems, List> previousItems, diff --git a/src/DynamicData/Cache/Internal/SortAndVirtualize.cs b/src/DynamicData/Cache/Internal/SortAndVirtualize.cs index 44c6d3dc3..fcbf138c3 100644 --- a/src/DynamicData/Cache/Internal/SortAndVirtualize.cs +++ b/src/DynamicData/Cache/Internal/SortAndVirtualize.cs @@ -1,24 +1,62 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else -using System.Reactive.Disposables; -using System.Reactive.Linq; using DynamicData.Binding; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the SortAndVirtualize class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal sealed class SortAndVirtualize where TObject : notnull where TKey : notnull { + /// + /// The _keyComparer field. + /// private static readonly KeyComparer _keyComparer = new(); + /// + /// The _source field. + /// private readonly IObservable> _source; + + /// + /// The _comparerChanged field. + /// private readonly IObservable> _comparerChanged; + + /// + /// The _virtualRequests field. + /// private readonly IObservable _virtualRequests; + + /// + /// The _options field. + /// private readonly SortAndVirtualizeOptions _options; + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The comparer value. + /// The virtualRequests value. + /// The options value. public SortAndVirtualize(IObservable> source, IComparer comparer, IObservable virtualRequests, @@ -27,6 +65,13 @@ public SortAndVirtualize(IObservable> source, { } + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The comparerChanged value. + /// The virtualRequests value. + /// The options value. public SortAndVirtualize(IObservable> source, IObservable> comparerChanged, IObservable virtualRequests, @@ -38,8 +83,15 @@ public SortAndVirtualize(IObservable> source, _virtualRequests = virtualRequests ?? throw new ArgumentNullException(nameof(virtualRequests)); } + /// + /// The Empty field. + /// private static readonly ChangeSet> Empty = new(0, VirtualContext.Empty); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable>> Run() => Observable.Create>>( observer => @@ -137,8 +189,16 @@ ChangeSet> ApplyVirtualChanges(IChangeSet return virtualChanges; } }); - // Calculates any changes within the virtualized range. + + /// + /// Executes the CalculateVirtualChanges operation. + /// + /// The context value. + /// The currentItems value. + /// The previousItems value. + /// The changes value. + /// The result of the operation. private static ChangeSet> CalculateVirtualChanges(VirtualContext context, List> currentItems, List> previousItems, diff --git a/src/DynamicData/Cache/Internal/SortExtensions.cs b/src/DynamicData/Cache/Internal/SortExtensions.cs index c6275a358..10c0c5a5e 100644 --- a/src/DynamicData/Cache/Internal/SortExtensions.cs +++ b/src/DynamicData/Cache/Internal/SortExtensions.cs @@ -1,13 +1,28 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Collections.ObjectModel; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the SortExtensions class. +/// internal static class SortExtensions { + /// + /// Executes the GetCurrentPosition operation. + /// + /// The type of the TItem value. + /// The source value. + /// The item value. + /// The comparer value. + /// The useBinarySearch value. + /// The result of the operation. public static int GetCurrentPosition(this IList source, TItem item, IComparer comparer, bool useBinarySearch = false) { var index = useBinarySearch ? source.BinarySearch(item, comparer) : source.IndexOf(item); @@ -20,6 +35,15 @@ public static int GetCurrentPosition(this IList source, TItem item return index; } + /// + /// Executes the GetInsertPosition operation. + /// + /// The type of the T value. + /// The source value. + /// The item value. + /// The comparer value. + /// The useBinarySearch value. + /// The result of the operation. public static int GetInsertPosition(this IList source, T item, IComparer comparer, bool useBinarySearch = false) { return useBinarySearch @@ -27,6 +51,14 @@ public static int GetInsertPosition(this IList source, T item, IComparer + /// Executes the GetInsertPositionBinary operation. + /// + /// The type of the TItem value. + /// The list value. + /// The t value. + /// The c value. + /// The result of the operation. public static int GetInsertPositionBinary(this IList list, TItem t, IComparer c) { var index = list.BinarySearch(t, c); @@ -52,6 +84,14 @@ public static int GetInsertPositionBinary(this IList list, TItem t return insertIndex; } + /// + /// Executes the GetInsertPositionLinear operation. + /// + /// The type of the TItem value. + /// The list value. + /// The t value. + /// The c value. + /// The result of the operation. public static int GetInsertPositionLinear(this IList list, TItem t, IComparer c) { for (var i = 0; i < list.Count; i++) @@ -65,6 +105,14 @@ public static int GetInsertPositionLinear(this IList list, TItem t return list.Count; } + /// + /// Executes the Move operation. + /// + /// The type of the TItem value. + /// The list value. + /// The original value. + /// The destination value. + /// The item value. public static void Move(this IList list, int original, int destination, TItem item) { // If the list supports the Move method, use it instead of removing and inserting. diff --git a/src/DynamicData/Cache/Internal/SortedKeyValueApplicator.cs b/src/DynamicData/Cache/Internal/SortedKeyValueApplicator.cs index 4ab732ceb..1424b24ef 100644 --- a/src/DynamicData/Cache/Internal/SortedKeyValueApplicator.cs +++ b/src/DynamicData/Cache/Internal/SortedKeyValueApplicator.cs @@ -1,26 +1,61 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else using DynamicData.Binding; +#endif +#if REACTIVE_SHIM -namespace DynamicData.Cache.Internal; +namespace DynamicData.Reactive.Cache.Internal; +#else +namespace DynamicData.Cache.Internal; +#endif /* * Object which maintains a sorted list of key value pair and produces a change set. * * Used by virtualise and page. */ + +/// +/// Provides members for the SortedKeyValueApplicator class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal sealed class SortedKeyValueApplicator where TObject : notnull where TKey : notnull { + /// + /// The _cache field. + /// private readonly Cache _cache = new(); + + /// + /// The _target field. + /// private readonly List> _target; + + /// + /// The _options field. + /// private readonly SortAndBindOptions _options; + /// + /// The _comparer field. + /// private KeyValueComparer _comparer; + /// + /// Initializes a new instance of the class. + /// + /// The target value. + /// The comparer value. + /// The options value. public SortedKeyValueApplicator(List> target, KeyValueComparer comparer, SortAndBindOptions options) @@ -30,6 +65,10 @@ public SortedKeyValueApplicator(List> target, _comparer = comparer; } + /// + /// Executes the ChangeComparer operation. + /// + /// The comparer value. public void ChangeComparer(KeyValueComparer comparer) { _comparer = comparer; @@ -37,6 +76,10 @@ public void ChangeComparer(KeyValueComparer comparer) _target.Sort(comparer); } + /// + /// Executes the ProcessChanges operation. + /// + /// The changes value. public void ProcessChanges(IChangeSet changes) { _cache.Clone(changes); @@ -53,6 +96,9 @@ public void ProcessChanges(IChangeSet changes) } } + /// + /// Executes the Reset operation. + /// public void Reset() { var sorted = _cache.KeyValues.OrderBy(t => t, _comparer); @@ -60,6 +106,10 @@ public void Reset() _target.AddRange(sorted); } + /// + /// Executes the ApplyChanges operation. + /// + /// The changes value. private void ApplyChanges(IChangeSet changes) { // iterate through collection, find sorted position and apply changes @@ -122,7 +172,17 @@ private void ApplyChanges(IChangeSet changes) } } + /// + /// Executes the GetCurrentPosition operation. + /// + /// The item value. + /// The result of the operation. private int GetCurrentPosition(KeyValuePair item) => _target.GetCurrentPosition(item, _comparer, _options.UseBinarySearch); + /// + /// Executes the GetInsertPosition operation. + /// + /// The item value. + /// The result of the operation. private int GetInsertPosition(KeyValuePair item) => _target.GetInsertPosition(item, _comparer, _options.UseBinarySearch); } diff --git a/src/DynamicData/Cache/Internal/SpecifiedGrouper.cs b/src/DynamicData/Cache/Internal/SpecifiedGrouper.cs index 863f2d6d6..7d8f4e080 100644 --- a/src/DynamicData/Cache/Internal/SpecifiedGrouper.cs +++ b/src/DynamicData/Cache/Internal/SpecifiedGrouper.cs @@ -1,23 +1,47 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the SpecifiedGrouper class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TGroupKey value. +/// The source value. +/// The groupSelector value. +/// The resultGroupSource value. internal sealed class SpecifiedGrouper(IObservable> source, Func groupSelector, IObservable> resultGroupSource) where TObject : notnull where TKey : notnull where TGroupKey : notnull { + /// + /// The _groupSelector field. + /// private readonly Func _groupSelector = groupSelector ?? throw new ArgumentNullException(nameof(groupSelector)); + /// + /// The _resultGroupSource field. + /// private readonly IObservable> _resultGroupSource = resultGroupSource ?? throw new ArgumentNullException(nameof(resultGroupSource)); + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { diff --git a/src/DynamicData/Cache/Internal/StatusMonitor.cs b/src/DynamicData/Cache/Internal/StatusMonitor.cs index 445074b63..19b9d5d26 100644 --- a/src/DynamicData/Cache/Internal/StatusMonitor.cs +++ b/src/DynamicData/Cache/Internal/StatusMonitor.cs @@ -1,19 +1,29 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the StatusMonitor class. +/// +/// The type of the T value. +/// The source value. internal sealed class StatusMonitor(IObservable source) { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable Run() => Observable.Create( observer => { - var statusSubject = new Subject(); + var statusSubject = new Signal(); var status = ConnectionStatus.Pending; void Error(Exception ex) diff --git a/src/DynamicData/Cache/Internal/SubscribeMany.cs b/src/DynamicData/Cache/Internal/SubscribeMany.cs index 78579fbeb..60899da49 100644 --- a/src/DynamicData/Cache/Internal/SubscribeMany.cs +++ b/src/DynamicData/Cache/Internal/SubscribeMany.cs @@ -1,35 +1,61 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the SubscribeMany class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal sealed class SubscribeMany where TObject : notnull where TKey : notnull { + /// + /// The _source field. + /// private readonly IObservable> _source; + /// + /// The _subscriptionFactory field. + /// private readonly Func _subscriptionFactory; + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The subscriptionFactory value. public SubscribeMany(IObservable> source, Func subscriptionFactory) { - subscriptionFactory.ThrowArgumentNullExceptionIfNull(nameof(subscriptionFactory)); + ArgumentExceptionHelper.ThrowIfNull(subscriptionFactory); _source = source ?? throw new ArgumentNullException(nameof(source)); _subscriptionFactory = (t, _) => subscriptionFactory(t); } + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The subscriptionFactory value. public SubscribeMany(IObservable> source, Func subscriptionFactory) { _source = source ?? throw new ArgumentNullException(nameof(source)); _subscriptionFactory = subscriptionFactory ?? throw new ArgumentNullException(nameof(subscriptionFactory)); } + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { diff --git a/src/DynamicData/Cache/Internal/Switch.cs b/src/DynamicData/Cache/Internal/Switch.cs index 766f9f14a..c8581e940 100644 --- a/src/DynamicData/Cache/Internal/Switch.cs +++ b/src/DynamicData/Cache/Internal/Switch.cs @@ -1,19 +1,33 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; - +#endif + +/// +/// Provides members for the Switch class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The sources value. internal sealed class Switch(IObservable>> sources) where TObject : notnull where TKey : notnull { + /// + /// The _sources field. + /// private readonly IObservable>> _sources = sources ?? throw new ArgumentNullException(nameof(sources)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -21,7 +35,7 @@ public IObservable> Run() => Observable.Create(); - var errors = new Subject>(); + var errors = new Signal>(); var populator = Observable.Switch( _sources @@ -31,6 +45,7 @@ public IObservable> Run() => Observable.Create { }, onError: error => errors.OnError(error)) + .Catch, Exception>(static _ => Observable.Empty>()) .PopulateInto(destination); return new CompositeDisposable( diff --git a/src/DynamicData/Cache/Internal/ToObservableChangeSet.cs b/src/DynamicData/Cache/Internal/ToObservableChangeSet.cs index f3634dac3..7b7b9a02d 100644 --- a/src/DynamicData/Cache/Internal/ToObservableChangeSet.cs +++ b/src/DynamicData/Cache/Internal/ToObservableChangeSet.cs @@ -1,19 +1,32 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; - -using DynamicData.Internal; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the ToObservableChangeSet class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal static class ToObservableChangeSet where TObject : notnull where TKey : notnull { + /// + /// Executes the Create operation. + /// + /// The source value. + /// The keySelector value. + /// The expireAfter value. + /// The limitSizeTo value. + /// The scheduler value. + /// The result of the operation. public static IObservable> Create( IObservable source, Func keySelector, @@ -38,6 +51,15 @@ public static IObservable> Create( .SubscribeSafe(downstreamObserver); }); + /// + /// Executes the Create operation. + /// + /// The source value. + /// The keySelector value. + /// The expireAfter value. + /// The limitSizeTo value. + /// The scheduler value. + /// The result of the operation. public static IObservable> Create( IObservable> source, Func keySelector, @@ -52,29 +74,91 @@ public static IObservable> Create( scheduler: scheduler, source: source)); - private sealed class Subscription +/// +/// Provides members for the Subscription class. +/// +private sealed class Subscription : IDisposable { + /// + /// The _downstreamItems field. + /// private readonly ChangeAwareCache _downstreamItems; + + /// + /// The _downstreamObserver field. + /// private readonly IObserver> _downstreamObserver; + + /// + /// The _evictionQueue field. + /// private readonly Queue _evictionQueue; + + /// + /// The _expirationQueue field. + /// private readonly List _expirationQueue; + + /// + /// The _expireAfter field. + /// private readonly Func? _expireAfter; + + /// + /// The _expireAtsByKey field. + /// private readonly Dictionary _expireAtsByKey; + + /// + /// The _keySelector field. + /// private readonly Func _keySelector; + + /// + /// The _limitSizeTo field. + /// private readonly int _limitSizeTo; + + /// + /// The _scheduler field. + /// private readonly IScheduler _scheduler; + + /// + /// The _sourceSubscription field. + /// private readonly IDisposable _sourceSubscription; - #if NET9_0_OR_GREATER + + /// + /// The _synchronizationGate field. + /// private readonly Lock _synchronizationGate; - #else - private readonly object _synchronizationGate; - #endif + /// + /// The _hasInitialized field. + /// private bool _hasInitialized; + + /// + /// The _hasSourceCompleted field. + /// private bool _hasSourceCompleted; + + /// + /// The _scheduledExpiration field. + /// private ScheduledExpiration? _scheduledExpiration; + /// + /// Initializes a new instance of the class. + /// + /// The downstreamObserver value. + /// The expireAfter value. + /// The keySelector value. + /// The limitSizeTo value. + /// The scheduler value. + /// The source value. public Subscription( IObserver> downstreamObserver, Func? expireAfter, @@ -94,7 +178,8 @@ public Subscription( _scheduler = scheduler ?? GlobalConfig.DefaultScheduler; _synchronizationGate = new(); - _sourceSubscription = source.SubscribeSafe( + _sourceSubscription = PrimitivesLinqExtensions.SubscribeSafe( + source, onNext: OnSourceNext, onError: downstreamObserver.OnError, onCompleted: OnSourceCompleted); @@ -108,12 +193,20 @@ public Subscription( } } + /// + /// Executes the Dispose operation. + /// public void Dispose() { _sourceSubscription.Dispose(); _scheduledExpiration?.Cancellation.Dispose(); } + /// + /// Executes the OnScheduledExpirationInvoked operation. + /// + /// The intendedExpiration value. + /// The result of the operation. private IDisposable OnScheduledExpirationInvoked(Expiration intendedExpiration) { try @@ -167,6 +260,10 @@ private IDisposable OnScheduledExpirationInvoked(Expiration intendedExpiration) return Disposable.Empty; } + /// + /// Executes the OnSourceNext operation. + /// + /// The upstreamItems value. private void OnSourceNext(IEnumerable upstreamItems) { try @@ -234,6 +331,9 @@ private void OnSourceNext(IEnumerable upstreamItems) } } + /// + /// Executes the OnSourceCompleted operation. + /// private void OnSourceCompleted() { lock (_synchronizationGate) @@ -243,13 +343,18 @@ private void OnSourceCompleted() TryPublishCompletion(); } } - // This method must NOT be invoked under the umbrella of _synchronizationGate, // as some IScheduler implementations perform locking internally, which can result in deadlocking if we invoke the scheduler within our own lock. // // Additionally, some IScheduler implementations can invoke actions synchronously, // so it's important that scheduler invocation is only performed AFTER downstream changes have been processed. // Otherwise, downstream notifications can end up published out-of-order. + + /// + /// Executes the FinishSchedulingExpiration operation. + /// + /// The unfinishedExpiration value. + /// The scheduler value. private void FinishSchedulingExpiration( ScheduledExpiration unfinishedExpiration, IScheduler scheduler) @@ -269,6 +374,9 @@ private void FinishSchedulingExpiration( return Disposable.Empty; }); + /// + /// Executes the TryPublishCompletion operation. + /// private void TryPublishCompletion() { // There needs to be no possibility of a new changeset being emitted before we can call the stream complete. @@ -276,6 +384,9 @@ private void TryPublishCompletion() _downstreamObserver.OnCompleted(); } + /// + /// Executes the TryPublishDownstreamChanges operation. + /// private void TryPublishDownstreamChanges() { var downstreamChanges = _downstreamItems.CaptureChanges(); @@ -287,6 +398,10 @@ private void TryPublishDownstreamChanges() } } + /// + /// Executes the TryBeginSchedulingExpiration operation. + /// + /// The result of the operation. private ScheduledExpiration? TryBeginSchedulingExpiration() { // If there's no expirations currently queued up, we don't need to schedule anything. @@ -312,20 +427,43 @@ private void TryPublishDownstreamChanges() } } - private readonly struct ScheduledExpiration +/// +/// Represents the ScheduledExpiration value. +/// +private readonly struct ScheduledExpiration { + /// + /// Gets or sets the Cancellation value. + /// public required SingleAssignmentDisposable Cancellation { get; init; } + /// + /// Gets or sets the Expiration value. + /// public required Expiration Expiration { get; init; } } - private readonly record struct Expiration +/// +/// Represents the Expiration record. +/// +private readonly record struct Expiration : IComparable { + /// + /// Gets or sets the ExpireAt value. + /// public required DateTimeOffset ExpireAt { get; init; } + /// + /// Gets or sets the Key value. + /// public required TKey Key { get; init; } + /// + /// Executes the CompareTo operation. + /// + /// The other value. + /// The result of the operation. public int CompareTo(Expiration other) => ExpireAt.CompareTo(other.ExpireAt); } diff --git a/src/DynamicData/Cache/Internal/ToObservableOptional.cs b/src/DynamicData/Cache/Internal/ToObservableOptional.cs index 47efd420c..e8d7078ad 100644 --- a/src/DynamicData/Cache/Internal/ToObservableOptional.cs +++ b/src/DynamicData/Cache/Internal/ToObservableOptional.cs @@ -1,22 +1,48 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the ToObservableOptional class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. +/// The key value. +/// The equalityComparer value. internal sealed class ToObservableOptional(IObservable> source, TKey key, IEqualityComparer? equalityComparer = null) where TObject : notnull where TKey : notnull { + /// + /// The _equalityComparer field. + /// private readonly IEqualityComparer _equalityComparer = equalityComparer ?? EqualityComparer.Default; + + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + + /// + /// The _key field. + /// private readonly TKey _key = key; - public IObservable> Run() => Observable.Create>(observer => + /// + /// Executes the Run operation. + /// + /// The result of the operation. + public IObservable> Run() => Observable.Create>(observer => { - var lastValue = Optional.None(); + var lastValue = ReactiveUI.Primitives.Optional.None; return _source.Subscribe( changes => lastValue = EmitChanges(changes, observer, lastValue), @@ -24,7 +50,14 @@ public IObservable> Run() => Observable.Create EmitChanges(IChangeSet changes, IObserver> observer, Optional lastValue) + /// + /// Executes the EmitChanges operation. + /// + /// The changes value. + /// The observer value. + /// The lastValue value. + /// The result of the operation. + private ReactiveUI.Primitives.Optional EmitChanges(IChangeSet changes, IObserver> observer, ReactiveUI.Primitives.Optional lastValue) { foreach (var change in changes.ToConcreteType()) { @@ -37,8 +70,8 @@ private Optional EmitChanges(IChangeSet changes, IObserv // Remove is None, everything else is the current value var emitValue = change switch { - { Reason: ChangeReason.Remove } => Optional.None(), - _ => Optional.Some(change.Current), + { Reason: ChangeReason.Remove } => ReactiveUI.Primitives.Optional.None, + _ => ReactiveUI.Primitives.Optional.Some(change.Current), }; // Emit the value if it has changed diff --git a/src/DynamicData/Cache/Internal/Transform.cs b/src/DynamicData/Cache/Internal/Transform.cs index 0934d1061..a7202804e 100644 --- a/src/DynamicData/Cache/Internal/Transform.cs +++ b/src/DynamicData/Cache/Internal/Transform.cs @@ -1,18 +1,39 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif -internal sealed class Transform(IObservable> source, Func, TKey, TDestination> transformFactory, Action>? exceptionCallback = null, bool transformOnRefresh = false) +/// +/// Provides members for the Transform class. +/// +/// The type of the TDestination value. +/// The type of the TSource value. +/// The type of the TKey value. +/// The source value. +/// The transformFactory value. +/// The exceptionCallback value. +/// The transformOnRefresh value. +internal sealed class Transform(IObservable> source, Func, TKey, TDestination> transformFactory, Action>? exceptionCallback = null, bool transformOnRefresh = false) where TDestination : notnull where TSource : notnull where TKey : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Defer(RunImpl); + /// + /// Executes the RunImpl operation. + /// + /// The result of the operation. private IObservable> RunImpl() => source.Scan( (ChangeAwareCache?)null, (cache, changes) => diff --git a/src/DynamicData/Cache/Internal/TransformAsync.cs b/src/DynamicData/Cache/Internal/TransformAsync.cs index 73b6f62a9..85a2af653 100644 --- a/src/DynamicData/Cache/Internal/TransformAsync.cs +++ b/src/DynamicData/Cache/Internal/TransformAsync.cs @@ -1,16 +1,29 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Threading.Tasks; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; - +#endif + +/// +/// Provides members for the TransformAsync class. +/// +/// The type of the TDestination value. +/// The type of the TSource value. +/// The type of the TKey value. +/// The source value. +/// The transformFactory value. +/// The exceptionCallback value. +/// The forceTransform value. +/// The maximumConcurrency value. +/// The transformOnRefresh value. internal class TransformAsync( IObservable> source, - Func, TKey, Task> transformFactory, + Func, TKey, Task> transformFactory, Action>? exceptionCallback, IObservable>? forceTransform = null, int? maximumConcurrency = null, @@ -19,6 +32,10 @@ internal class TransformAsync( where TSource : notnull where TKey : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>(observer => { @@ -40,6 +57,12 @@ public IObservable> Run() => return transformer.SubscribeSafe(observer); }); + /// + /// Executes the DoTransform operation. + /// + /// The cache value. + /// The shouldTransform value. + /// The result of the operation. private IObservable> DoTransform(ChangeAwareCache cache, Func shouldTransform) { var toTransform = cache.KeyValues.Where(kvp => shouldTransform(kvp.Value.Source, kvp.Key)).Select(kvp => @@ -51,6 +74,12 @@ private IObservable> DoTransform(ChangeAwareCache .Select(transformed => ProcessUpdates(cache, transformed)); } + /// + /// Executes the DoTransform operation. + /// + /// The cache value. + /// The changes value. + /// The result of the operation. private IObservable> DoTransform( ChangeAwareCache cache, IChangeSet changes) { @@ -60,6 +89,12 @@ private IObservable> DoTransform( .Select(transformed => ProcessUpdates(cache, transformed)); } + /// + /// Executes the ProcessUpdates operation. + /// + /// The cache value. + /// The transformedItems value. + /// The result of the operation. private ChangeSet ProcessUpdates(ChangeAwareCache cache, TransformResult[] transformedItems) { // check for errors and callback if a handler has been specified @@ -105,6 +140,11 @@ private ChangeSet ProcessUpdates(ChangeAwareCache(transformed); } + /// + /// Executes the Transform operation. + /// + /// The change value. + /// The result of the operation. private async Task Transform(Change change) { try @@ -130,15 +170,34 @@ private async Task Transform(Change change) } } - private readonly struct TransformedItemContainer(TSource source, TDestination destination) +/// +/// Represents the TransformedItemContainer value. +/// +/// The source value. +/// The destination value. +private readonly struct TransformedItemContainer(TSource source, TDestination destination) { + /// + /// Gets the Destination value. + /// public TDestination Destination { get; } = destination; + /// + /// Gets the Source value. + /// public TSource Source { get; } = source; } - private sealed class TransformResult +/// +/// Provides members for the TransformResult class. +/// +private sealed class TransformResult { + /// + /// Initializes a new instance of the class. + /// + /// The change value. + /// The container value. public TransformResult(in Change change, in TransformedItemContainer container) { Change = change; @@ -147,14 +206,23 @@ public TransformResult(in Change change, in TransformedItemContai Key = change.Key; } + /// + /// Initializes a new instance of the class. + /// + /// The change value. public TransformResult(in Change change) { Change = change; - Container = Optional.None; + Container = ReactiveUI.Primitives.Optional.None; Success = true; Key = change.Key; } + /// + /// Initializes a new instance of the class. + /// + /// The change value. + /// The error value. public TransformResult(in Change change, Exception error) { Change = change; @@ -163,14 +231,29 @@ public TransformResult(in Change change, Exception error) Key = change.Key; } + /// + /// Gets the Change value. + /// public Change Change { get; } - public Optional Container { get; } + /// + /// Gets the Container value. + /// + public ReactiveUI.Primitives.Optional Container { get; } + /// + /// Gets the Error value. + /// public Exception? Error { get; } + /// + /// Gets the Key value. + /// public TKey Key { get; } + /// + /// Gets the Success value. + /// public bool Success { get; } } } diff --git a/src/DynamicData/Cache/Internal/TransformImmutable.cs b/src/DynamicData/Cache/Internal/TransformImmutable.cs index 6090f034c..38fe3c38c 100644 --- a/src/DynamicData/Cache/Internal/TransformImmutable.cs +++ b/src/DynamicData/Cache/Internal/TransformImmutable.cs @@ -1,20 +1,40 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the TransformImmutable class. +/// +/// The type of the TDestination value. +/// The type of the TSource value. +/// The type of the TKey value. internal sealed class TransformImmutable where TDestination : notnull where TSource : notnull where TKey : notnull { + /// + /// The _source field. + /// private readonly IObservable> _source; + + /// + /// The _transformFactory field. + /// private readonly Func _transformFactory; + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The transformFactory value. public TransformImmutable( IObservable> source, Func transformFactory) @@ -23,6 +43,10 @@ public TransformImmutable( _transformFactory = transformFactory; } + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>(observer => _source .SubscribeSafe(Observer.Create>( @@ -40,7 +64,7 @@ public IObservable> Run() current: _transformFactory.Invoke(change.Current), previous: change.Previous.HasValue ? _transformFactory.Invoke(change.Previous.Value) - : Optional.None(), + : ReactiveUI.Primitives.Optional.None, currentIndex: change.CurrentIndex, previousIndex: change.PreviousIndex)); } diff --git a/src/DynamicData/Cache/Internal/TransformMany.cs b/src/DynamicData/Cache/Internal/TransformMany.cs index f38853c08..25e4b554c 100644 --- a/src/DynamicData/Cache/Internal/TransformMany.cs +++ b/src/DynamicData/Cache/Internal/TransformMany.cs @@ -1,230 +1,327 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. -// 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.Collections; -using System.Collections.ObjectModel; -using System.Reactive.Disposables; -using System.Reactive.Linq; - -using DynamicData.Binding; - -namespace DynamicData.Cache.Internal; - -internal sealed class TransformMany(IObservable> source, Func> manySelector, Func keySelector, Func>>? childChanges = null) - where TDestination : notnull - where TDestinationKey : notnull - where TSource : notnull - where TSourceKey : notnull -{ - public TransformMany(IObservable> source, Func> manySelector, Func keySelector) - : this( - source, - manySelector, - keySelector, - t => Observable.Defer( - () => - { - var subsequentChanges = manySelector(t).ToObservableChangeSet(keySelector); - - if (manySelector(t).Count > 0) - { - return subsequentChanges; - } - - return Observable.Return(ChangeSet.Empty).Concat(subsequentChanges); - })) - { - } - - public TransformMany(IObservable> source, Func> manySelector, Func keySelector) - : this( - source, - manySelector, - keySelector, - t => Observable.Defer( - () => - { - var subsequentChanges = manySelector(t).ToObservableChangeSet(keySelector); - - if (manySelector(t).Count > 0) - { - return subsequentChanges; - } - - return Observable.Return(ChangeSet.Empty).Concat(subsequentChanges); - })) - { - } - - public TransformMany(IObservable> source, Func> manySelector, Func keySelector) - : this( - source, - x => manySelector(x).Items, - keySelector, - t => Observable.Defer( - () => - { - var subsequentChanges = Observable.Create>(o => manySelector(t).Connect().Subscribe(o)); - - if (manySelector(t).Count > 0) - { - return subsequentChanges; - } - - return Observable.Return(ChangeSet.Empty).Concat(subsequentChanges); - })) - { - } - - public IObservable> Run() => childChanges is null ? Create() : CreateWithChangeSet(); - - private IObservable> Create() => source.Transform( - (t, _) => - { - var destination = manySelector(t).Select(m => new DestinationContainer(m, keySelector(m))).ToArray(); - return new ManyContainer(() => destination); - }, - true).Select(changes => new ChangeSet(new DestinationEnumerator(changes))); - - private IObservable> CreateWithChangeSet() - { - if (childChanges is null) - { - throw new InvalidOperationException("The childChanges is null and should not be."); - } - - return Observable.Create>( - observer => - { - var result = new ChangeAwareCache(); - - var transformed = source.Transform( - (t, _) => - { - // Only skip initial for first time Adds where there is initial data records - var locker = InternalEx.NewLock(); - var changes = childChanges(t).Synchronize(locker).Skip(1); - return new ManyContainer( - () => - { - var collection = manySelector(t); - lock (locker) - { - return collection.Select(m => new DestinationContainer(m, keySelector(m))).ToArray(); - } - }, - changes); - }).Publish(); - - var queue = new SharedDeliveryQueue(); - var initial = transformed.SynchronizeSafe(queue).Select(changes => new ChangeSet(new DestinationEnumerator(changes))); - - var subsequent = transformed.MergeMany(x => x.Changes).SynchronizeSafe(queue); - - var allChanges = initial.Merge(subsequent).Select( - changes => - { - result.Clone(changes); - return result.CaptureChanges(); - }); - - return new CompositeDisposable(allChanges.SubscribeSafe(observer), transformed.Connect(), queue); - }); - } - - private sealed class DestinationContainer(TDestination item, TDestinationKey key) - { - public static IEqualityComparer KeyComparer { get; } = new KeyEqualityComparer(); - - public TDestination Item { get; } = item; - - public TDestinationKey Key { get; } = key; - - private sealed class KeyEqualityComparer : IEqualityComparer - { - public bool Equals(DestinationContainer? x, DestinationContainer? y) - { - if (x is null && y is null) - { - return true; - } - - if (x is null || y is null) - { - return false; - } - - return EqualityComparer.Default.Equals(x.Key, y.Key); - } - - public int GetHashCode(DestinationContainer obj) => EqualityComparer.Default.GetHashCode(obj.Key); - } - } - - private sealed class DestinationEnumerator(IChangeSet changes) : IEnumerable> - { - public IEnumerator> GetEnumerator() - { - foreach (var change in changes.ToConcreteType()) - { - switch (change.Reason) - { - case ChangeReason.Add: - case ChangeReason.Remove: - case ChangeReason.Refresh: - { - foreach (var destination in change.Current.Destination) - { - yield return new Change(change.Reason, destination.Key, destination.Item); - } - } - - break; - case ChangeReason.Update: - { - var previousItems = change.Previous.Value.Destination.AsArray(); - var currentItems = change.Current.Destination.AsArray(); - - var removes = previousItems.Except(currentItems, DestinationContainer.KeyComparer); - var adds = currentItems.Except(previousItems, DestinationContainer.KeyComparer); - var updates = currentItems.Intersect(previousItems, DestinationContainer.KeyComparer); - - foreach (var destination in removes) - { - yield return new Change(ChangeReason.Remove, destination.Key, destination.Item); - } - - foreach (var destination in adds) - { - yield return new Change(ChangeReason.Add, destination.Key, destination.Item); - } - - foreach (var destination in updates) - { - var current = currentItems.First(d => d.Key.Equals(destination.Key)); - var previous = previousItems.First(d => d.Key.Equals(destination.Key)); - - // Do not update is items are the same reference - if (!ReferenceEquals(current.Item, previous.Item)) - { - yield return new Change(ChangeReason.Update, destination.Key, current.Item, previous.Item); - } - } - } - - break; - } - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - - private sealed class ManyContainer(Func> initial, IObservable>? changes = null) - { - public IObservable> Changes { get; } = changes ?? Observable.Empty>(); - - public IEnumerable Destination => initial(); - } -} +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Roland Pheasant licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else + +using DynamicData.Binding; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Cache.Internal; +#else + +namespace DynamicData.Cache.Internal; +#endif + +/// +/// Provides members for the TransformMany class. +/// +/// The type of the TDestination value. +/// The type of the TDestinationKey value. +/// The type of the TSource value. +/// The type of the TSourceKey value. +/// The source value. +/// The manySelector value. +/// The keySelector value. +/// The childChanges value. +internal sealed class TransformMany(IObservable> source, Func> manySelector, Func keySelector, Func>>? childChanges = null) + where TDestination : notnull + where TDestinationKey : notnull + where TSource : notnull + where TSourceKey : notnull +{ + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The manySelector value. + /// The keySelector value. + public TransformMany(IObservable> source, Func> manySelector, Func keySelector) + : this( + source, + manySelector, + keySelector, + t => Observable.Defer( + () => + { + var subsequentChanges = manySelector(t).ToObservableChangeSet(keySelector); + + if (manySelector(t).Count > 0) + { + return subsequentChanges; + } + + return Observable.Return(ChangeSet.Empty).Concat(subsequentChanges); + })) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The manySelector value. + /// The keySelector value. + public TransformMany(IObservable> source, Func> manySelector, Func keySelector) + : this( + source, + manySelector, + keySelector, + t => Observable.Defer( + () => + { + var subsequentChanges = manySelector(t).ToObservableChangeSet(keySelector); + + if (manySelector(t).Count > 0) + { + return subsequentChanges; + } + + return Observable.Return(ChangeSet.Empty).Concat(subsequentChanges); + })) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The manySelector value. + /// The keySelector value. + public TransformMany(IObservable> source, Func> manySelector, Func keySelector) + : this( + source, + x => manySelector(x).Items, + keySelector, + t => Observable.Defer( + () => + { + var subsequentChanges = Observable.Create>(o => manySelector(t).Connect().Subscribe(o)); + + if (manySelector(t).Count > 0) + { + return subsequentChanges; + } + + return Observable.Return(ChangeSet.Empty).Concat(subsequentChanges); + })) + { + } + + /// + /// Executes the Run operation. + /// + /// The result of the operation. + public IObservable> Run() => childChanges is null ? Create() : CreateWithChangeSet(); + + /// + /// Executes the Create operation. + /// + /// The result of the operation. + private IObservable> Create() => source.Transform( + (t, _) => + { + var destination = manySelector(t).Select(m => new DestinationContainer(m, keySelector(m))).ToArray(); + return new ManyContainer(() => destination); + }, + true).Select(changes => new ChangeSet(new DestinationEnumerator(changes))); + + /// + /// Executes the CreateWithChangeSet operation. + /// + /// The result of the operation. + private IObservable> CreateWithChangeSet() + { + if (childChanges is null) + { + throw new InvalidOperationException("The childChanges is null and should not be."); + } + + return Observable.Create>( + observer => + { + var result = new ChangeAwareCache(); + + var transformed = source.Transform( + (t, _) => + { + // Only skip initial for first time Adds where there is initial data records + var locker = InternalEx.NewLock(); + var changes = childChanges(t).Synchronize(locker).Skip(1); + return new ManyContainer( + () => + { + var collection = manySelector(t); + lock (locker) + { + return collection.Select(m => new DestinationContainer(m, keySelector(m))).ToArray(); + } + }, + changes); + }).Publish(); + + var queue = new SharedDeliveryQueue(); + var initial = transformed.SynchronizeSafe(queue).Select(changes => new ChangeSet(new DestinationEnumerator(changes))); + + var subsequent = transformed.MergeMany(x => x.Changes).SynchronizeSafe(queue); + + var allChanges = initial.Merge(subsequent).Select( + changes => + { + result.Clone(changes); + return result.CaptureChanges(); + }); + + return new CompositeDisposable(allChanges.SubscribeSafe(observer), transformed.Connect(), queue); + }); + } + +/// +/// Provides members for the DestinationContainer class. +/// +/// The item value. +/// The key value. +private sealed class DestinationContainer(TDestination item, TDestinationKey key) + { + /// + /// Gets the KeyComparer value. + /// + public static IEqualityComparer KeyComparer { get; } = new KeyEqualityComparer(); + + /// + /// Gets the Item value. + /// + public TDestination Item { get; } = item; + + /// + /// Gets the Key value. + /// + public TDestinationKey Key { get; } = key; + +/// +/// Provides members for the KeyEqualityComparer class. +/// +private sealed class KeyEqualityComparer : IEqualityComparer + { + /// + /// Executes the Equals operation. + /// + /// The x value. + /// The y value. + /// The result of the operation. + public bool Equals(DestinationContainer? x, DestinationContainer? y) + { + if (x is null && y is null) + { + return true; + } + + if (x is null || y is null) + { + return false; + } + + return EqualityComparer.Default.Equals(x.Key, y.Key); + } + + /// + /// Executes the GetHashCode operation. + /// + /// The obj value. + /// The result of the operation. + public int GetHashCode(DestinationContainer obj) => EqualityComparer.Default.GetHashCode(obj.Key); + } + } + +/// +/// Provides members for the DestinationEnumerator class. +/// +/// The changes value. +private sealed class DestinationEnumerator(IChangeSet changes) : IEnumerable> + { + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. + public IEnumerator> GetEnumerator() + { + foreach (var change in changes.ToConcreteType()) + { + switch (change.Reason) + { + case ChangeReason.Add: + case ChangeReason.Remove: + case ChangeReason.Refresh: + { + foreach (var destination in change.Current.Destination) + { + yield return new Change(change.Reason, destination.Key, destination.Item); + } + } + + break; + case ChangeReason.Update: + { + var previousItems = change.Previous.Value.Destination.AsArray(); + var currentItems = change.Current.Destination.AsArray(); + + var removes = previousItems.Except(currentItems, DestinationContainer.KeyComparer); + var adds = currentItems.Except(previousItems, DestinationContainer.KeyComparer); + var updates = currentItems.Intersect(previousItems, DestinationContainer.KeyComparer); + + foreach (var destination in removes) + { + yield return new Change(ChangeReason.Remove, destination.Key, destination.Item); + } + + foreach (var destination in adds) + { + yield return new Change(ChangeReason.Add, destination.Key, destination.Item); + } + + foreach (var destination in updates) + { + var current = currentItems.First(d => d.Key.Equals(destination.Key)); + var previous = previousItems.First(d => d.Key.Equals(destination.Key)); + + // Do not update is items are the same reference + if (!ReferenceEquals(current.Item, previous.Item)) + { + yield return new Change(ChangeReason.Update, destination.Key, current.Item, previous.Item); + } + } + } + + break; + } + } + } + + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + +/// +/// Provides members for the ManyContainer class. +/// +/// The initial value. +/// The changes value. +private sealed class ManyContainer(Func> initial, IObservable>? changes = null) + { + /// + /// Gets the Changes value. + /// + public IObservable> Changes { get; } = changes ?? Observable.Empty>(); + + /// + /// Gets the Destination value. + /// + public IEnumerable Destination => initial(); + } +} diff --git a/src/DynamicData/Cache/Internal/TransformManyAsync.cs b/src/DynamicData/Cache/Internal/TransformManyAsync.cs index b202cb307..eafc2504c 100644 --- a/src/DynamicData/Cache/Internal/TransformManyAsync.cs +++ b/src/DynamicData/Cache/Internal/TransformManyAsync.cs @@ -1,27 +1,64 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; -using DynamicData.Internal; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the TransformManyAsync class. +/// +/// The type of the TSource value. +/// The type of the TKey value. +/// The type of the TDestination value. +/// The type of the TDestinationKey value. +/// The source value. +/// The transformer value. +/// The equalityComparer value. +/// The comparer value. +/// The errorHandler value. internal sealed class TransformManyAsync(IObservable> source, Func>>> transformer, IEqualityComparer? equalityComparer, IComparer? comparer, Action>? errorHandler = null) where TSource : notnull where TKey : notnull where TDestination : notnull where TDestinationKey : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => new Subscription(source, transformer, observer, equalityComparer, comparer, errorHandler)); - // Maintains state for a single subscription - private sealed class Subscription : CacheParentSubscription, TKey, IChangeSet, IChangeSet> + +/// +/// Provides members for the Subscription class. +/// +private sealed class Subscription : CacheParentSubscription, TKey, IChangeSet, IChangeSet> { + /// + /// The _cache field. + /// private readonly Cache, TKey> _cache = new(); + + /// + /// The _changeSetMergeTracker field. + /// private readonly ChangeSetMergeTracker _changeSetMergeTracker; + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The transform value. + /// The observer value. + /// The equalityComparer value. + /// The comparer value. + /// The errorHandler value. public Subscription(IObservable> source, Func>>> transform, IObserver> observer, IEqualityComparer? equalityComparer, IComparer? comparer, Action>? errorHandler = null) : base(observer) { @@ -57,6 +94,10 @@ ChangeSetCache SafeTransformer(TSource obj, TKey } } + /// + /// Executes the ParentOnNext operation. + /// + /// The changes value. protected override void ParentOnNext(IChangeSet, TKey> changes) { // Process all the changes at once to preserve the changeset order @@ -85,9 +126,18 @@ protected override void ParentOnNext(IChangeSet + /// Executes the ChildOnNext operation. + ///
+ /// The child value. + /// The parentKey value. protected override void ChildOnNext(IChangeSet child, TKey parentKey) => _changeSetMergeTracker.ProcessChangeSet(child); + /// + /// Executes the EmitChanges operation. + /// + /// The observer value. protected override void EmitChanges(IObserver> observer) => _changeSetMergeTracker.EmitChanges(observer); } diff --git a/src/DynamicData/Cache/Internal/TransformOnObservable.cs b/src/DynamicData/Cache/Internal/TransformOnObservable.cs index b88e4f1c6..210104e31 100644 --- a/src/DynamicData/Cache/Internal/TransformOnObservable.cs +++ b/src/DynamicData/Cache/Internal/TransformOnObservable.cs @@ -1,27 +1,63 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; -using DynamicData.Internal; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the TransformOnObservable class. +/// +/// The type of the TSource value. +/// The type of the TKey value. +/// The type of the TDestination value. +/// The source value. +/// The transform value. +/// The transformOnRefresh value. internal sealed class TransformOnObservable(IObservable> source, Func> transform, bool transformOnRefresh = false) where TSource : notnull where TKey : notnull where TDestination : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>(observer => new Subscription(source, transform, observer, transformOnRefresh)); - // Maintains state for a single subscription - private sealed class Subscription : CacheParentSubscription> + +/// +/// Provides members for the Subscription class. +/// +private sealed class Subscription : CacheParentSubscription> { + /// + /// The _cache field. + /// private readonly ChangeAwareCache _cache = new(); + + /// + /// The _transform field. + /// private readonly Func> _transform; + + /// + /// The _transformOnRefresh field. + /// private readonly bool _transformOnRefresh; + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The transform value. + /// The observer value. + /// The transformOnRefresh value. public Subscription(IObservable> source, Func> transform, IObserver> observer, bool transformOnRefresh) : base(observer) { @@ -30,6 +66,10 @@ public Subscription(IObservable> source, Func + /// Executes the ParentOnNext operation. + ///
+ /// The changes value. protected override void ParentOnNext(IChangeSet changes) { // Process all the changes at once to preserve the changeset order @@ -64,9 +104,18 @@ protected override void ParentOnNext(IChangeSet changes) } } + /// + /// Executes the ChildOnNext operation. + /// + /// The child value. + /// The parentKey value. protected override void ChildOnNext(TDestination child, TKey parentKey) => _cache.AddOrUpdate(child, parentKey); + /// + /// Executes the EmitChanges operation. + /// + /// The observer value. protected override void EmitChanges(IObserver> observer) { var changes = _cache.CaptureChanges(); @@ -76,6 +125,11 @@ protected override void EmitChanges(IObserver> ob } } + /// + /// Executes the AddTransformSubscription operation. + /// + /// The obj value. + /// The key value. private void AddTransformSubscription(TSource obj, TKey key) => AddChildSubscription(MakeChildObservable(_transform(obj, key).DistinctUntilChanged()), key); } diff --git a/src/DynamicData/Cache/Internal/TransformWithForcedTransform.cs b/src/DynamicData/Cache/Internal/TransformWithForcedTransform.cs index c7c95aa66..e056fcf75 100644 --- a/src/DynamicData/Cache/Internal/TransformWithForcedTransform.cs +++ b/src/DynamicData/Cache/Internal/TransformWithForcedTransform.cs @@ -1,17 +1,33 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; - -internal sealed class TransformWithForcedTransform(IObservable> source, Func, TKey, TDestination> transformFactory, IObservable> forceTransform, Action>? exceptionCallback = null) +#endif + +/// +/// Provides members for the TransformWithForcedTransform class. +/// +/// The type of the TDestination value. +/// The type of the TSource value. +/// The type of the TKey value. +/// The source value. +/// The transformFactory value. +/// The forceTransform value. +/// The exceptionCallback value. +internal sealed class TransformWithForcedTransform(IObservable> source, Func, TKey, TDestination> transformFactory, IObservable> forceTransform, Action>? exceptionCallback = null) where TDestination : notnull where TSource : notnull where TKey : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -33,6 +49,12 @@ public IObservable> Run() => Observable.Create + /// Executes the CaptureChanges operation. + ///
+ /// The cache value. + /// The shouldTransform value. + /// The result of the operation. private static IEnumerable> CaptureChanges(Cache cache, Func shouldTransform) => cache.KeyValues.Where(kvp => shouldTransform(kvp.Value, kvp.Key)).Select(kvp => new Change(ChangeReason.Refresh, kvp.Key, kvp.Value)); } diff --git a/src/DynamicData/Cache/Internal/TransformWithInlineUpdate.cs b/src/DynamicData/Cache/Internal/TransformWithInlineUpdate.cs index 322a50eb1..217b38d8f 100644 --- a/src/DynamicData/Cache/Internal/TransformWithInlineUpdate.cs +++ b/src/DynamicData/Cache/Internal/TransformWithInlineUpdate.cs @@ -1,11 +1,25 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the TransformWithInlineUpdate class. +/// +/// The type of the TDestination value. +/// The type of the TSource value. +/// The type of the TKey value. +/// The source value. +/// The transformFactory value. +/// The updateAction value. +/// The exceptionCallback value. +/// The transformOnRefresh value. internal sealed class TransformWithInlineUpdate(IObservable> source, Func transformFactory, Action updateAction, @@ -15,8 +29,16 @@ internal sealed class TransformWithInlineUpdate(IOb where TSource : notnull where TKey : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Defer(RunImpl); + /// + /// Executes the RunImpl operation. + /// + /// The result of the operation. private IObservable> RunImpl() => source.Scan( (ChangeAwareCache?)null, (cache, changes) => @@ -62,6 +84,11 @@ private IObservable> RunImpl() => source.Scan( .Where(x => x is not null) .Select(cache => cache!.CaptureChanges()); + /// + /// Executes the Transform operation. + /// + /// The cache value. + /// The change value. private void Transform(ChangeAwareCache cache, in Change change) { TDestination transformed; @@ -84,6 +111,11 @@ private void Transform(ChangeAwareCache cache, in Change + /// Executes the InlineUpdate operation. + ///
+ /// The cache value. + /// The change value. private void InlineUpdate(ChangeAwareCache cache, Change change) { var previous = cache.Lookup(change.Key) diff --git a/src/DynamicData/Cache/Internal/TreeBuilder.cs b/src/DynamicData/Cache/Internal/TreeBuilder.cs index bf379ef99..9ab405db2 100644 --- a/src/DynamicData/Cache/Internal/TreeBuilder.cs +++ b/src/DynamicData/Cache/Internal/TreeBuilder.cs @@ -1,31 +1,55 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; - +#endif + +/// +/// Provides members for the TreeBuilder class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. +/// The pivotOn value. +/// The predicateChanged value. internal sealed class TreeBuilder(IObservable> source, Func pivotOn, IObservable, bool>>? predicateChanged) where TObject : class where TKey : notnull { + /// + /// The _pivotOn field. + /// private readonly Func _pivotOn = pivotOn ?? throw new ArgumentNullException(nameof(pivotOn)); + /// + /// The _predicateChanged field. + /// private readonly IObservable, bool>> _predicateChanged = predicateChanged ?? Observable.Return(DefaultPredicate); + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Gets the DefaultPredicate value. + /// private static Func, bool> DefaultPredicate => node => node.IsRoot; + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable, TKey>> Run() => Observable.Create, TKey>>( observer => { var queue = new SharedDeliveryQueue(); - var reFilterObservable = new BehaviorSubject(Unit.Default); + var reFilterObservable = new StateSignal(Unit.Default); var allData = _source.SynchronizeSafe(queue).AsObservableCache(); diff --git a/src/DynamicData/Cache/Internal/TrueFor.cs b/src/DynamicData/Cache/Internal/TrueFor.cs index 28e036298..d3447619a 100644 --- a/src/DynamicData/Cache/Internal/TrueFor.cs +++ b/src/DynamicData/Cache/Internal/TrueFor.cs @@ -1,23 +1,47 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the TrueFor class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The type of the TValue value. +/// The source value. +/// The observableSelector value. +/// The collectionMatcher value. internal sealed class TrueFor(IObservable> source, Func> observableSelector, Func>, bool> collectionMatcher) where TObject : notnull where TKey : notnull where TValue : notnull { + /// + /// The _collectionMatcher field. + /// private readonly Func>, bool> _collectionMatcher = collectionMatcher ?? throw new ArgumentNullException(nameof(collectionMatcher)); + /// + /// The _observableSelector field. + /// private readonly Func> _observableSelector = observableSelector ?? throw new ArgumentNullException(nameof(observableSelector)); + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable Run() => Observable.Create(observer => { @@ -28,9 +52,9 @@ public IObservable Run() .Publish(); var subscription = itemsWithValues.MergeMany(item => item.Observable).CombineLatest( - second: itemsWithValues.ToCollection(), + itemsWithValues.ToCollection(), // We don't need to actually look at the changed values, we just need them as a trigger to re-evaluate the matcher method. - resultSelector: (_, itemsWithValues) => _collectionMatcher.Invoke(itemsWithValues)) + (_, itemsWithValues) => _collectionMatcher.Invoke(itemsWithValues)) .DistinctUntilChanged() .SubscribeSafe(observer); diff --git a/src/DynamicData/Cache/Internal/UniquenessEnforcer.cs b/src/DynamicData/Cache/Internal/UniquenessEnforcer.cs index 0c858419c..a5d3d6ef7 100644 --- a/src/DynamicData/Cache/Internal/UniquenessEnforcer.cs +++ b/src/DynamicData/Cache/Internal/UniquenessEnforcer.cs @@ -1,15 +1,28 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; +#endif +/// +/// Provides members for the UniquenessEnforcer class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. internal sealed class UniquenessEnforcer(IObservable> source) where TObject : notnull where TKey : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => /* For refresh, we need to check whether there was a previous add or update in the batch. If not use refresh, diff --git a/src/DynamicData/Cache/Internal/Virtualise.cs b/src/DynamicData/Cache/Internal/Virtualise.cs index 9a5fefbcc..1d750b5df 100644 --- a/src/DynamicData/Cache/Internal/Virtualise.cs +++ b/src/DynamicData/Cache/Internal/Virtualise.cs @@ -1,20 +1,39 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Cache.Internal; +#else namespace DynamicData.Cache.Internal; - +#endif + +/// +/// Provides members for the Virtualise class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. +/// The virtualRequests value. internal sealed class Virtualise(IObservable> source, IObservable virtualRequests) where TObject : notnull where TKey : notnull { + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// The _virtualRequests field. + /// private readonly IObservable _virtualRequests = virtualRequests ?? throw new ArgumentNullException(nameof(virtualRequests)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -26,16 +45,37 @@ public IObservable> Run() => Observable.Create< return new CompositeDisposable(request.Merge(dataChange).Where(updates => updates is not null).SubscribeSafe(observer), queue); }); - private sealed class Virtualiser(VirtualRequest? request = null) +/// +/// Provides members for the Virtualiser class. +/// +/// The request value. +private sealed class Virtualiser(VirtualRequest? request = null) { + /// + /// The _all field. + /// private IKeyValueCollection _all = new KeyValueCollection(); + /// + /// The _current field. + /// private KeyValueCollection _current = new(); + /// + /// The _isLoaded field. + /// private bool _isLoaded; + /// + /// The _parameters field. + /// private IVirtualRequest _parameters = request ?? new VirtualRequest(); + /// + /// Executes the Update operation. + /// + /// The updates value. + /// The result of the operation. public IVirtualChangeSet? Update(ISortedChangeSet updates) { _isLoaded = true; @@ -43,6 +83,11 @@ private sealed class Virtualiser(VirtualRequest? request = null) return Virtualise(updates); } + /// + /// Executes the Virtualise operation. + /// + /// The parameters value. + /// The result of the operation. public IVirtualChangeSet? Virtualise(IVirtualRequest? parameters) { if (parameters is null || parameters.StartIndex < 0 || parameters.Size < 1) @@ -59,6 +104,11 @@ private sealed class Virtualiser(VirtualRequest? request = null) return Virtualise(); } + /// + /// Executes the Virtualise operation. + /// + /// The updates value. + /// The result of the operation. private VirtualChangeSet? Virtualise(ISortedChangeSet? updates = null) { if (!_isLoaded) diff --git a/src/DynamicData/Cache/MissingKeyException.cs b/src/DynamicData/Cache/MissingKeyException.cs index 72b2b2bea..7635165d9 100644 --- a/src/DynamicData/Cache/MissingKeyException.cs +++ b/src/DynamicData/Cache/MissingKeyException.cs @@ -1,9 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Thrown when a key is expected in a cache but not found. diff --git a/src/DynamicData/Cache/Node.cs b/src/DynamicData/Cache/Node.cs index 44918b4c1..5d53c30d8 100644 --- a/src/DynamicData/Cache/Node.cs +++ b/src/DynamicData/Cache/Node.cs @@ -2,10 +2,12 @@ // 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.Reactive.Disposables; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Node describing the relationship between and item and it's ancestors and descendent. @@ -16,11 +18,20 @@ public class Node : IDisposable, IEquatable> where TObject : class where TKey : notnull { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed with _cleanUp")] + /// + /// The _children field. + /// + [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed with _cleanUp")] private readonly SourceCache, TKey> _children = new(n => n.Key); + /// + /// The _cleanUp field. + /// private readonly CompositeDisposable _cleanUp; + /// + /// The _isDisposed field. + /// private bool _isDisposed; /// @@ -39,13 +50,13 @@ public Node(TObject item, TKey key) /// The item. /// The key. /// The parent. - public Node(TObject item, TKey key, in Optional> parent) + public Node(TObject item, TKey key, in ReactiveUI.Primitives.Optional> parent) { Item = item ?? throw new ArgumentNullException(nameof(item)); Key = key; Parent = parent; Children = _children.AsObservableCache(); - _cleanUp = new(Children, _children); + _cleanUp = new CompositeDisposable(Children, _children); } /// @@ -93,7 +104,7 @@ public int Depth /// /// Gets the parent if it has one. /// - public Optional> Parent { get; internal set; } + public ReactiveUI.Primitives.Optional> Parent { get; internal set; } /// /// Determines whether the specified objects are equal. @@ -179,6 +190,10 @@ public override string ToString() return $"{Item}{count}"; } + /// + /// Executes the Update operation. + /// + /// The updateAction value. internal void Update(Action, TKey>> updateAction) => _children.Edit(updateAction); /// diff --git a/src/DynamicData/Cache/ObservableCache.cs b/src/DynamicData/Cache/ObservableCache.cs index 5cbeca988..03d584b9b 100644 --- a/src/DynamicData/Cache/ObservableCache.cs +++ b/src/DynamicData/Cache/ObservableCache.cs @@ -3,51 +3,96 @@ // See the LICENSE file in the project root for full license information. using System.Diagnostics; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using System.Threading; +#if REACTIVE_SHIM +using DynamicData.Reactive.Binding; +#else using DynamicData.Binding; -using DynamicData.Cache; +#endif +#if REACTIVE_SHIM +using DynamicData.Reactive.Cache.Internal; +#else using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif +/// +/// Provides members for the ObservableCache class. +/// +/// The type of the TObject value. +/// The type of the TKey value. [DebuggerDisplay("ObservableCache<{typeof(TObject).Name}, {typeof(TKey).Name}> ({Count} Items)")] internal sealed class ObservableCache : IObservableCache, INotifyCollectionChangedSuspender where TObject : notnull where TKey : notnull { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed with _cleanUp")] - private readonly Subject> _changes = new(); + /// + /// The _changes field. + /// + [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed with _cleanUp")] + private readonly Signal> _changes = new(); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed with _cleanUp")] - private readonly Subject> _changesPreview = new(); + /// + /// The _changesPreview field. + /// + [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed with _cleanUp")] + private readonly Signal> _changesPreview = new(); + /// + /// The _cleanUp field. + /// private readonly IDisposable _cleanUp; - private readonly Lazy> _countChanged = new(() => new Subject()); + /// + /// The _countChanged field. + /// + private readonly Lazy> _countChanged = new(() => new Signal()); + /// + /// The _suspensionTracker field. + /// private readonly Lazy _suspensionTracker; -#if NET9_0_OR_GREATER + /// + /// The _locker field. + /// private readonly Lock _locker = new(); -#else - private readonly object _locker = new(); -#endif + /// + /// The _readerWriter field. + /// private readonly ReaderWriter _readerWriter; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Terminated via NotifyCompleted in _cleanUp")] + /// + /// The _notifications field. + /// + [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Terminated via NotifyCompleted in _cleanUp")] private readonly DeliveryQueue _notifications; + /// + /// The _editLevel field. + /// private int _editLevel; // The level of recursion in editing. + /// + /// The _currentVersion field. + /// private long _currentVersion; // Monotonic counter incremented under lock for each enqueued change notification. + /// + /// The _currentDeliveryVersion field. + /// private long _currentDeliveryVersion; // Version of the item currently being delivered. Set before _changes.OnNext. + /// + /// Initializes a new instance of the class. + /// + /// The source value. public ObservableCache(IObservable> source) { _readerWriter = new ReaderWriter(); @@ -78,6 +123,10 @@ public ObservableCache(IObservable> source) }); } + /// + /// Initializes a new instance of the class. + /// + /// The keySelector value. public ObservableCache(Func? keySelector = null) { _readerWriter = new ReaderWriter(keySelector); @@ -87,8 +136,14 @@ public ObservableCache(Func? keySelector = null) _cleanUp = Disposable.Create(NotifyCompleted); } + /// + /// Gets the Count value. + /// public int Count => _readerWriter.Count; + /// + /// Gets the CountChanged value. + /// public IObservable CountChanged => Observable.Create( observer => @@ -104,12 +159,27 @@ public ObservableCache(Func? keySelector = null) return source.SubscribeSafe(observer); }); + /// + /// Gets the Items value. + /// public IReadOnlyList Items => _readerWriter.Items; + /// + /// Gets the Keys value. + /// public IReadOnlyList Keys => _readerWriter.Keys; + /// + /// Gets the KeyValues value. + /// public IReadOnlyDictionary KeyValues => _readerWriter.KeyValues; + /// + /// Executes the Connect operation. + /// + /// The predicate value. + /// The suppressEmptyChangeSets value. + /// The result of the operation. public IObservable> Connect(Func? predicate = null, bool suppressEmptyChangeSets = true) => Observable.Create>(observer => { @@ -128,12 +198,30 @@ public IObservable> Connect(Func? predi } }); + /// + /// Executes the Dispose operation. + /// public void Dispose() => _cleanUp.Dispose(); - public Optional Lookup(TKey key) => _readerWriter.Lookup(key); + /// + /// Executes the Lookup operation. + /// + /// The key value. + /// The result of the operation. + public ReactiveUI.Primitives.Optional Lookup(TKey key) => _readerWriter.Lookup(key); + /// + /// Executes the Preview operation. + /// + /// The predicate value. + /// The result of the operation. public IObservable> Preview(Func? predicate = null) => predicate is null ? _changesPreview : _changesPreview.Filter(predicate); + /// + /// Executes the Watch operation. + /// + /// The key value. + /// The result of the operation. public IObservable> Watch(TKey key) => Observable.Create>(observer => { @@ -152,6 +240,10 @@ public IObservable> Watch(TKey key) => } }); + /// + /// Executes the SuspendCount operation. + /// + /// The result of the operation. public IDisposable SuspendCount() { lock (_locker) @@ -161,6 +253,10 @@ public IDisposable SuspendCount() } } + /// + /// Executes the SuspendNotifications operation. + /// + /// The result of the operation. public IDisposable SuspendNotifications() { lock (_locker) @@ -170,11 +266,20 @@ public IDisposable SuspendNotifications() } } + /// + /// Executes the GetInitialUpdates operation. + /// + /// The filter value. + /// The result of the operation. internal ChangeSet GetInitialUpdates(Func? filter = null) => _readerWriter.GetInitialUpdates(filter); + /// + /// Executes the UpdateFromIntermediate operation. + /// + /// The updateAction value. internal void UpdateFromIntermediate(Action> updateAction) { - updateAction.ThrowArgumentNullExceptionIfNull(nameof(updateAction)); + ArgumentExceptionHelper.ThrowIfNull(updateAction); using var notifications = _notifications.AcquireLock(); @@ -199,9 +304,13 @@ internal void UpdateFromIntermediate(Action> update } } + /// + /// Executes the UpdateFromSource operation. + /// + /// The updateAction value. internal void UpdateFromSource(Action> updateAction) { - updateAction.ThrowArgumentNullExceptionIfNull(nameof(updateAction)); + ArgumentExceptionHelper.ThrowIfNull(updateAction); using var notifications = _notifications.AcquireLock(); @@ -226,6 +335,12 @@ internal void UpdateFromSource(Action> updateActio } } + /// + /// Executes the CreateConnectObservable operation. + /// + /// The predicate value. + /// The suppressEmptyChangeSets value. + /// The result of the operation. private IObservable> CreateConnectObservable(Func? predicate, bool suppressEmptyChangeSets) => Observable.Create>( observer => @@ -256,6 +371,11 @@ private IObservable> CreateConnectObservable(Func + /// Executes the CreateWatchObservable operation. + /// + /// The key value. + /// The result of the operation. private IObservable> CreateWatchObservable(TKey key) => Observable.Create>( observer => @@ -295,6 +415,7 @@ private IObservable> CreateWatchObservable(TKey key) => /// called by ReaderWriter during a write, between two data swaps, so it MUST /// fire under the lock with the pre-write state visible to subscribers. /// + /// The changes value. private void InvokePreview(ChangeSet changes) { if (changes.Count != 0 && !_notifications.IsTerminated) @@ -303,12 +424,19 @@ private void InvokePreview(ChangeSet changes) } } + /// + /// Executes the NotifyCompleted operation. + /// private void NotifyCompleted() { using var notifications = _notifications.AcquireLock(); notifications.EnqueueCompleted(); } + /// + /// Executes the NotifyError operation. + /// + /// The ex value. private void NotifyError(Exception ex) { using var notifications = _notifications.AcquireLock(); @@ -318,7 +446,7 @@ private void NotifyError(Exception ex) /// /// Delivers a single notification to subscribers. This method is the delivery /// callback for and must never be called directly. - /// It is invoked by the after releasing the + /// It is invoked by the DeliveryQueue<TItem> after releasing the /// lock, which guarantees that no lock is held when subscriber code runs. The /// queue's single-deliverer token ensures this method is never called concurrently, /// preserving the Rx serialization contract across all subjects. @@ -336,26 +464,22 @@ private void ResumeCount() } } + /// + /// Executes the ResumeNotifications operation. + /// private void ResumeNotifications() { - bool emitResume; + using var notifications = _notifications.AcquireLock(); + Debug.Assert(_suspensionTracker.IsValueCreated, "Should not be Resuming Notifications without Suspend Notifications instance"); - using (var notifications = _notifications.AcquireLock()) + var (changes, emitResume) = _suspensionTracker.Value.ResumeNotifications(); + if (changes is not null) { - Debug.Assert(_suspensionTracker.IsValueCreated, "Should not be Resuming Notifications without Suspend Notifications instance"); - - (var changes, emitResume) = _suspensionTracker.Value.ResumeNotifications(); - if (changes is not null) - { - notifications.EnqueueNext(new CacheUpdate(changes, _readerWriter.Count, ++_currentVersion)); - } + notifications.EnqueueNext(new CacheUpdate(changes, _readerWriter.Count, ++_currentVersion)); } - // Emit the resume signal after releasing the delivery scope so that - // accumulated changes are delivered first if (emitResume) { - using var readLock = _notifications.AcquireReadLock(); _suspensionTracker.Value.EmitResumeNotification(); } } @@ -363,14 +487,22 @@ private void ResumeNotifications() /// /// The notification payload for cache delivery. Null Changes = count-only notification. /// + /// The Changes value. + /// The Count value. + /// The Version value. private readonly record struct CacheUpdate(ChangeSet? Changes, int Count, long Version = 0); - /// - /// Observer that dispatches items to the cache's - /// downstream subjects. Used as the delivery target for . - /// - private sealed class CacheUpdateObserver(ObservableCache cache) : IObserver +/// +/// Observer that dispatches items to the cache's +/// downstream subjects. Used as the delivery target for . +/// +/// The cache value. +private sealed class CacheUpdateObserver(ObservableCache cache) : IObserver { + /// + /// Executes the OnNext operation. + /// + /// The value value. public void OnNext(CacheUpdate value) { if (value.Changes is not null) @@ -382,6 +514,10 @@ public void OnNext(CacheUpdate value) EmitCount(value.Count); } + /// + /// Executes the OnError operation. + /// + /// The error value. public void OnError(Exception error) { cache._changesPreview.OnError(error); @@ -398,6 +534,9 @@ public void OnError(Exception error) } } + /// + /// Executes the OnCompleted operation. + /// public void OnCompleted() { cache._changes.OnCompleted(); @@ -414,6 +553,10 @@ public void OnCompleted() } } + /// + /// Executes the EmitChanges operation. + /// + /// The changes value. private void EmitChanges(ChangeSet changes) { if (cache._suspensionTracker.IsValueCreated) @@ -431,6 +574,10 @@ private void EmitChanges(ChangeSet changes) cache._changes.OnNext(changes); } + /// + /// Executes the EmitCount operation. + /// + /// The count value. private void EmitCount(int count) { if (cache._suspensionTracker.IsValueCreated) @@ -451,22 +598,50 @@ private void EmitCount(int count) } } - private sealed class SuspensionTracker : IDisposable +/// +/// Provides members for the SuspensionTracker class. +/// +private sealed class SuspensionTracker : IDisposable { - private readonly BehaviorSubject _areNotificationsSuspended = new(false); - + /// + /// The _areNotificationsSuspended field. + /// + private readonly BehaviorSignal _areNotificationsSuspended = new(false); + + /// + /// The _pendingChanges field. + /// private List> _pendingChanges = []; + /// + /// The _countSuspendCount field. + /// private int _countSuspendCount; + /// + /// The _notifySuspendCount field. + /// private int _notifySuspendCount; + /// + /// Gets the IsCountSuspended value. + /// public bool IsCountSuspended => _countSuspendCount > 0; + /// + /// Gets the AreNotificationsSuspended value. + /// public bool AreNotificationsSuspended => _notifySuspendCount > 0; + /// + /// Gets the NotificationsSuspendedObservable value. + /// public IObservable NotificationsSuspendedObservable => _areNotificationsSuspended; + /// + /// Executes the EnqueueChanges operation. + /// + /// The changes value. public void EnqueueChanges(IEnumerable> changes) { Debug.Assert(changes is not null, "Don't pass in a null Enumerable"); @@ -474,6 +649,9 @@ public void EnqueueChanges(IEnumerable> changes) _pendingChanges.AddRange(changes); } + /// + /// Executes the SuspendNotifications operation. + /// public void SuspendNotifications() { if (++_notifySuspendCount == 1) @@ -484,10 +662,21 @@ public void SuspendNotifications() } } + /// + /// Executes the SuspendCount operation. + /// public void SuspendCount() => ++_countSuspendCount; + /// + /// Executes the ResumeCount operation. + /// + /// The result of the operation. public bool ResumeCount() => --_countSuspendCount == 0; + /// + /// Executes the ResumeNotifications operation. + /// + /// The result of the operation. public (ChangeSet? Changes, bool EmitResume) ResumeNotifications() { if (--_notifySuspendCount == 0 && !_areNotificationsSuspended.IsDisposed) @@ -507,8 +696,14 @@ public void SuspendNotifications() return (null, false); } + /// + /// Executes the EmitResumeNotification operation. + /// public void EmitResumeNotification() => _areNotificationsSuspended.OnNext(false); + /// + /// Executes the Dispose operation. + /// public void Dispose() { _areNotificationsSuspended.OnCompleted(); diff --git a/src/DynamicData/Cache/ObservableCacheEx.Adapt.cs b/src/DynamicData/Cache/ObservableCacheEx.Adapt.cs index f99f46747..28d155f56 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Adapt.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Adapt.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -26,13 +24,13 @@ namespace DynamicData; public static partial class ObservableCacheEx { /// - /// Injects a side effect into the changeset stream by calling . + /// Injects a side effect into the changeset stream by calling .IChangeSetAdaptor<TObject, TKey>.Adapt(IChangeSet<TObject, TKey>) /// for every changeset, then forwarding it downstream unchanged. /// /// The type of items in the cache. /// The type of the key. - /// The source to observe and adapt. - /// The whose Adapt method is called for each changeset. + /// The source IObservable<IChangeSet<TObject, TKey>> to observe and adapt. + /// The IChangeSetAdaptor<TObject, TKey> whose Adapt method is called for each changeset. /// An observable that emits the same changesets as , after the adaptor has processed each one. /// /// @@ -41,28 +39,33 @@ public static partial class ObservableCacheEx /// /// /// or is . - /// - /// + /// Adapt<TObject, TKey>(IObservable<ISortedChangeSet<TObject, TKey>>, ISortedChangeSetAdaptor<TObject, TKey>) + /// Bind<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IObservableCollection<TObject>, IObservableCollectionAdaptor<TObject, TKey>) public static IObservable> Adapt(this IObservable> source, IChangeSetAdaptor adaptor) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - adaptor.ThrowArgumentNullExceptionIfNull(nameof(adaptor)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(adaptor); return source.Do(adaptor.Adapt); } - /// - /// The source to observe and adapt. - /// The whose Adapt method is called for each changeset. - /// This overload operates on . Delegates to Rx's Do operator. + /// + /// Provides an overload of Adapt for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<ISortedChangeSet<TObject, TKey>> to observe and adapt. + /// The ISortedChangeSetAdaptor<TObject, TKey> whose Adapt method is called for each changeset. + /// The resulting observable sequence. + /// This overload operates on ISortedChangeSet<TObject, TKey>. Delegates to Rx's Do operator. public static IObservable> Adapt(this IObservable> source, ISortedChangeSetAdaptor adaptor) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - adaptor.ThrowArgumentNullExceptionIfNull(nameof(adaptor)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(adaptor); return source.Do(adaptor.Adapt); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.AdaptSelector.cs b/src/DynamicData/Cache/ObservableCacheEx.AdaptSelector.cs index 07c984029..a8dafbbba 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.AdaptSelector.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.AdaptSelector.cs @@ -2,23 +2,14 @@ // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -26,6 +17,15 @@ namespace DynamicData; public static partial class ObservableCacheEx { // TODO: Apply the Adapter to more places + + /// + /// Executes the AdaptSelector operation. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The type of the TResult value. + /// The other value. + /// The result of the operation. private static Func AdaptSelector(Func other) where TObject : notnull where TKey : notnull diff --git a/src/DynamicData/Cache/ObservableCacheEx.AddOrUpdate.cs b/src/DynamicData/Cache/ObservableCacheEx.AddOrUpdate.cs index 3b217f6ed..2f2204782 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.AddOrUpdate.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.AddOrUpdate.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,10 +22,10 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The to add or update items in. + /// The ISourceCache<TObject, TKey> to add or update items in. /// The item to add or update. /// - /// Convenience method that wraps a single-item mutation inside . + /// Convenience method that wraps a single-item mutation inside ISourceCache<TObject,TKey>.Edit. /// /// EventBehavior /// AddProduced when the key does not already exist in the cache. @@ -44,69 +35,92 @@ public static partial class ObservableCacheEx /// /// /// is . - /// - /// + /// EditDiff<TObject, TKey>(ISourceCache<TObject, TKey>, IEnumerable<TObject>, IEqualityComparer<TObject>) + /// Remove<TObject, TKey>(ISourceCache<TObject, TKey>, TObject) public static void AddOrUpdate(this ISourceCache source, TObject item) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(item); source.Edit(updater => updater.AddOrUpdate(item)); } - /// - /// The to add or update items in. + /// + /// Provides an overload of AddOrUpdate for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The ISourceCache<TObject, TKey> to add or update items in. /// The item to add or update. - /// The used to determine whether a new item is the same as an existing cached item. When equal, the update is skipped. + /// The IEqualityComparer<TObject> used to determine whether a new item is the same as an existing cached item. When equal, the update is skipped. /// This overload uses to suppress no-op updates when the new value equals the existing one. public static void AddOrUpdate(this ISourceCache source, TObject item, IEqualityComparer equalityComparer) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(item); + ArgumentExceptionHelper.ThrowIfNull(equalityComparer); source.Edit(updater => updater.AddOrUpdate(item, equalityComparer)); } - /// - /// The to add or update items in. - /// The of items to add or update. - /// Batch overload. All items are added/updated inside a single call, producing one changeset. + /// + /// Provides an overload of AddOrUpdate for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The ISourceCache<TObject, TKey> to add or update items in. + /// The IEnumerable<TObject> of items to add or update. + /// Batch overload. All items are added/updated inside a single ISourceCache<TObject,TKey>.Edit call, producing one changeset. public static void AddOrUpdate(this ISourceCache source, IEnumerable items) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(items); source.Edit(updater => updater.AddOrUpdate(items)); } - /// - /// The to add or update items in. - /// The of items to add or update. - /// The used to determine whether a new item is the same as an existing cached item. When equal, the update is skipped. - /// Batch overload with equality comparison. All items are added/updated inside a single call. + /// + /// Provides an overload of AddOrUpdate for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The ISourceCache<TObject, TKey> to add or update items in. + /// The IEnumerable<TObject> of items to add or update. + /// The IEqualityComparer<TObject> used to determine whether a new item is the same as an existing cached item. When equal, the update is skipped. + /// Batch overload with equality comparison. All items are added/updated inside a single ISourceCache<TObject,TKey>.Edit call. public static void AddOrUpdate(this ISourceCache source, IEnumerable items, IEqualityComparer equalityComparer) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(items); + ArgumentExceptionHelper.ThrowIfNull(equalityComparer); source.Edit(updater => updater.AddOrUpdate(items, equalityComparer)); } - /// - /// The to add or update items in. + /// + /// Provides an overload of AddOrUpdate for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The IIntermediateCache<TObject, TKey> to add or update items in. /// The item to add or update. /// The key to associate with the item. - /// This overload operates on , which requires an explicit key parameter. + /// This overload operates on IIntermediateCache<TObject, TKey>, which requires an explicit key parameter. public static void AddOrUpdate(this IIntermediateCache source, TObject item, TKey key) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - item.ThrowArgumentNullExceptionIfNull(nameof(item)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(item); + ArgumentExceptionHelper.ThrowIfNull(key); source.Edit(updater => updater.AddOrUpdate(item, key)); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.And.cs b/src/DynamicData/Cache/ObservableCacheEx.And.cs index bae09dd4e..95c91039c 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.And.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.And.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,16 +29,16 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to combine. - /// The additional streams to combine with. + /// The source IObservable<IChangeSet<TObject, TKey>> to combine. + /// The additional IObservable<IChangeSet<TObject, TKey>> streams to combine with. /// An observable which emits change sets. /// source or others. - /// + /// ObservableListEx.And public static IObservable> And(this IObservable> source, params IObservable>[] others) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return others is null || others.Length == 0 ? throw new ArgumentNullException(nameof(others)) @@ -52,7 +50,7 @@ public static IObservable> And(this IOb /// /// The type of the object. /// The type of the key. - /// The of streams to combine. + /// The ICollection<T> of streams to combine. /// An observable which emits change sets. /// /// source @@ -63,7 +61,7 @@ public static IObservable> And(this ICo where TObject : notnull where TKey : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return sources.Combine(CombineOperator.And); } @@ -74,13 +72,13 @@ public static IObservable> And(this ICo /// /// The type of the object. /// The type of the key. - /// The of streams to combine. + /// The IObservableList<T> of streams to combine. /// An observable which emits change sets. public static IObservable> And(this IObservableList>> sources) where TObject : notnull where TKey : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return sources.Combine(CombineOperator.And); } @@ -91,13 +89,13 @@ public static IObservable> And(this IOb /// /// The type of the object. /// The type of the key. - /// The of changeset streams to combine. + /// The IObservableList<IObservableCache<TObject, TKey>> of changeset streams to combine. /// An observable which emits change sets. public static IObservable> And(this IObservableList> sources) where TObject : notnull where TKey : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return sources.Combine(CombineOperator.And); } @@ -108,13 +106,13 @@ public static IObservable> And(this IOb /// /// The type of the object. /// The type of the key. - /// The of changeset streams to combine. + /// The IObservableList<ISourceCache<TObject, TKey>> of changeset streams to combine. /// An observable which emits change sets. public static IObservable> And(this IObservableList> sources) where TObject : notnull where TKey : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return sources.Combine(CombineOperator.And); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.AsObservableCache.cs b/src/DynamicData/Cache/ObservableCacheEx.AsObservableCache.cs index 3a9f58875..9b571d6a2 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.AsObservableCache.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.AsObservableCache.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -26,30 +24,30 @@ namespace DynamicData; public static partial class ObservableCacheEx { /// - /// Wraps an in a read-only facade, hiding the mutable API. + /// Wraps an IObservableCache<TObject, TKey> in a read-only facade, hiding the mutable API. /// /// The type of the object. /// The type of the key. - /// The to operate on. - /// A read-only . + /// The IObservableCache<TObject, TKey> to operate on. + /// A read-only IObservableCache<TObject, TKey>. /// is . - /// + /// AsObservableCache<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, bool) public static IObservableCache AsObservableCache(this IObservableCache source) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new AnonymousObservableCache(source); } /// - /// Materializes a changeset stream into a queryable, read-only . + /// Materializes a changeset stream into a queryable, read-only IObservableCache<TObject, TKey>. /// The cache subscribes to the source on first access and maintains a live snapshot of all items. /// /// The type of the object. /// The type of the key. - /// The source to materialize into a read-only cache. + /// The source IObservable<IChangeSet<TObject, TKey>> to materialize into a read-only cache. /// If (default), all cache operations are synchronized. Set to when the caller guarantees single-threaded access. /// A read-only observable cache that reflects the current state of the pipeline. /// @@ -57,16 +55,16 @@ public static IObservableCache AsObservableCache(t /// Disposing the returned cache unsubscribes from the source stream. The cache's Connect() /// method provides a changeset stream of its own, which re-emits the current state on each new subscriber. /// - /// When is , a is used internally. + /// When is , a LockFreeObservableCache<TObject, TKey> is used internally. /// /// is . - /// - /// + /// AsObservableCache<TObject, TKey>(IObservableCache<TObject, TKey>) + /// PopulateInto<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, ISourceCache<TObject, TKey>) public static IObservableCache AsObservableCache(this IObservable> source, bool applyLocking = true) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); if (applyLocking) { diff --git a/src/DynamicData/Cache/ObservableCacheEx.AsyncDisposeMany.cs b/src/DynamicData/Cache/ObservableCacheEx.AsyncDisposeMany.cs index 0a15843d5..95499d53c 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.AsyncDisposeMany.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.AsyncDisposeMany.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -37,10 +28,10 @@ public static partial class ObservableCacheEx /// /// The type of items in the cache. /// The type of the key. - /// The source to track for async disposal on removal. + /// The source IObservable<IChangeSet<TObject, TKey>> to track for async disposal on removal. /// /// - /// Invoked once per subscription, providing an that signals when all + /// Invoked once per subscription, providing an IObservable<Unit> that signals when all /// calls have finished. The signal emits a single value /// and then completes. /// @@ -68,7 +59,7 @@ public static partial class ObservableCacheEx /// /// /// or is . - /// + /// DisposeMany<TObject,TKey> public static IObservable> AsyncDisposeMany( this IObservable> source, Action> disposalsCompletedAccessor) diff --git a/src/DynamicData/Cache/ObservableCacheEx.AutoRefresh.cs b/src/DynamicData/Cache/ObservableCacheEx.AutoRefresh.cs index 1e7bfb428..2590e9b64 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.AutoRefresh.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.AutoRefresh.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; +#if REACTIVE_SHIM +using DynamicData.Reactive.Binding; +#else using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -30,17 +28,17 @@ public static partial class ObservableCacheEx /// /// The object of the change set. /// The key of the change set. - /// The source to monitor for property-driven refresh signals. + /// The source IObservable<IChangeSet<TObject, TKey>> to monitor for property-driven refresh signals. /// An optional buffer duration. Batches multiple refresh signals into a single changeset, improving performance when many elements change in quick succession. This greatly increases performance when many elements have successive property changes. /// An optional throttle applied to each item's property change notifications, preventing excessive refresh invocations. /// An optional for scheduling work. /// An observable change set with additional refresh changes. - /// + /// ObservableListEx.AutoRefresh public static IObservable> AutoRefresh(this IObservable> source, TimeSpan? changeSetBuffer = null, TimeSpan? propertyChangeThrottle = null, IScheduler? scheduler = null) where TObject : INotifyPropertyChanged where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.AutoRefreshOnObservable( (t, _) => @@ -62,8 +60,8 @@ public static IObservable> AutoRefresh( /// The object of the change set. /// The key of the change set. /// The type of the property. - /// The source to monitor for property-driven refresh signals. - /// A that specify a property to observe changes. When it changes a Refresh is invoked. + /// The source IObservable<IChangeSet<TObject, TKey>> to monitor for property-driven refresh signals. + /// A Expression<TDelegate> that specify a property to observe changes. When it changes a Refresh is invoked. /// An optional buffer duration. Batches multiple refresh signals into a single changeset, improving performance when many elements change in quick succession. This greatly increases performance when many elements have successive property changes. /// An optional throttle applied to each item's property change notifications, preventing excessive refresh invocations. /// An optional for scheduling work. @@ -72,7 +70,7 @@ public static IObservable> AutoRefresh diff --git a/src/DynamicData/Cache/ObservableCacheEx.AutoRefreshOnObservable.cs b/src/DynamicData/Cache/ObservableCacheEx.AutoRefreshOnObservable.cs index 39a04294e..7b685c324 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.AutoRefreshOnObservable.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.AutoRefreshOnObservable.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,12 +29,12 @@ public static partial class ObservableCacheEx /// The object of the change set. /// The key of the change set. /// The type of evaluation. - /// The source to monitor for observable-driven refresh signals. - /// The observable which acts on items within the collection and produces a value when the item should be refreshed. + /// The source IObservable<IChangeSet<TObject, TKey>> to monitor for observable-driven refresh signals. + /// The Func<TObject, IObservable<TAny>> observable which acts on items within the collection and produces a value when the item should be refreshed. /// An optional buffer duration. Batches multiple refresh signals into a single changeset, improving performance when many elements change in quick succession. This greatly increases performance when many elements require a refresh. /// An optional for scheduling work. /// An observable change set with additional refresh changes. - /// + /// ObservableListEx.AutoRefreshOnObservable public static IObservable> AutoRefreshOnObservable(this IObservable> source, Func> reevaluator, TimeSpan? changeSetBuffer = null, IScheduler? scheduler = null) where TObject : notnull where TKey : notnull => source.AutoRefreshOnObservable((t, _) => reevaluator(t), changeSetBuffer, scheduler); @@ -47,8 +45,8 @@ public static IObservable> AutoRefreshOnObservableThe object of the change set. /// The key of the change set. /// The type of evaluation. - /// The source to monitor for observable-driven refresh signals. - /// The observable which acts on items within the collection and produces a value when the item should be refreshed. + /// The source IObservable<IChangeSet<TObject, TKey>> to monitor for observable-driven refresh signals. + /// The Func<TObject, TKey, IObservable<TAny>> observable which acts on items within the collection and produces a value when the item should be refreshed. /// An optional buffer duration. Batches multiple refresh signals into a single changeset, improving performance when many elements change in quick succession. This greatly increases performance when many elements require a refresh. /// An optional for scheduling work. /// An observable change set with additional refresh changes. @@ -59,8 +57,8 @@ public static IObservable> AutoRefreshOnObservable(source, reevaluator, changeSetBuffer, scheduler).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.Batch.cs b/src/DynamicData/Cache/ObservableCacheEx.Batch.cs index 1ed843deb..bb0f6bdfe 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Batch.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Batch.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -27,11 +18,11 @@ public static partial class ObservableCacheEx { /// /// Collects changesets emitted within a time window and merges them into a single changeset. - /// Uses Rx's Buffer operator followed by . + /// Uses Rx's Buffer operator followed by FlattenBufferResult<TObject, TKey>. /// /// The type of the object. /// The type of the key. - /// The source to batch. + /// The source IObservable<IChangeSet<TObject, TKey>> to batch. /// The time window for batching. /// The scheduler for timing. Defaults to . /// An observable that emits merged changesets, one per time window. @@ -51,13 +42,13 @@ public static partial class ObservableCacheEx /// Worth noting: The merged changeset may contain contradictory changes (e.g., Add then Remove for the same key). Downstream operators handle this correctly, but raw inspection of the changeset may be surprising. /// /// is . - /// - /// + /// BatchIf<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IObservable<bool>, bool, TimeSpan?, IScheduler?) + /// BufferInitial<TObject, TKey> public static IObservable> Batch(this IObservable> source, TimeSpan timeSpan, IScheduler? scheduler = null) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Buffer(timeSpan, scheduler ?? GlobalConfig.DefaultScheduler).FlattenBufferResult(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.BatchIf.cs b/src/DynamicData/Cache/ObservableCacheEx.BatchIf.cs index 99fb5ee13..437db418e 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.BatchIf.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.BatchIf.cs @@ -1,43 +1,67 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { - /// + /// + /// Provides an overload of BatchIf for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The pauseIfTrueSelector value. + /// The scheduler value. + /// The resulting observable sequence. /// This overload delegates to the primary overload with initialPauseState: false. public static IObservable> BatchIf(this IObservable> source, IObservable pauseIfTrueSelector, IScheduler? scheduler = null) where TObject : notnull where TKey : notnull => BatchIf(source, pauseIfTrueSelector, false, scheduler); - /// + /// + /// Provides an overload of Run for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The pauseIfTrueSelector value. + /// The initialPauseState value. + /// The scheduler value. + /// The resulting observable sequence. /// This overload delegates to the primary overload with default initialPauseState: false. public static IObservable> BatchIf(this IObservable> source, IObservable pauseIfTrueSelector, bool initialPauseState = false, IScheduler? scheduler = null) where TObject : notnull where TKey : notnull => new BatchIf(source, pauseIfTrueSelector, null, initialPauseState, scheduler: scheduler).Run(); - /// + /// + /// Provides an overload of BatchIf for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The pauseIfTrueSelector value. + /// The timeOut value. + /// The scheduler value. + /// The resulting observable sequence. /// This overload omits initialPauseState (defaults to ) but accepts a timeout. public static IObservable> BatchIf(this IObservable> source, IObservable pauseIfTrueSelector, TimeSpan? timeOut = null, IScheduler? scheduler = null) where TObject : notnull @@ -49,8 +73,8 @@ public static IObservable> BatchIf(this /// /// The type of the object. /// The type of the key. - /// The source to conditionally buffer. - /// An that when , buffering begins. When , the buffer is flushed. + /// The source IObservable<IChangeSet<TObject, TKey>> to conditionally buffer. + /// An IObservable<bool> that when , buffering begins. When , the buffer is flushed. /// If , starts in a paused (buffering) state. /// A that maximum time the buffer stays open. When elapsed, the buffer is flushed regardless of pause state. /// The for timeout timing. @@ -72,24 +96,29 @@ public static IObservable> BatchIf(this /// Worth noting: If the source completes while paused, buffered data IS flushed before OnCompleted. However, if the source errors while paused, buffered data is lost. /// /// or is . - /// - /// + /// Batch<TObject, TKey> + /// BufferInitial<TObject, TKey> public static IObservable> BatchIf(this IObservable> source, IObservable pauseIfTrueSelector, bool initialPauseState = false, TimeSpan? timeOut = null, IScheduler? scheduler = null) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - pauseIfTrueSelector.ThrowArgumentNullExceptionIfNull(nameof(pauseIfTrueSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(pauseIfTrueSelector); return new BatchIf(source, pauseIfTrueSelector, timeOut, initialPauseState, scheduler: scheduler).Run(); } - /// - /// The source to conditionally buffer. - /// An that controls buffering: begins buffering, flushes the buffer. + /// + /// Provides an overload of Run for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<IChangeSet<TObject, TKey>> to conditionally buffer. + /// An IObservable<bool> that controls buffering: begins buffering, flushes the buffer. /// If , starts in a paused (buffering) state. - /// An optional timer. The buffer is flushed each time the timer produces a value, and buffering ceases when it completes. + /// An optional IObservable<Unit> timer. The buffer is flushed each time the timer produces a value, and buffering ceases when it completes. /// An optional for scheduling work. + /// The resulting observable sequence. /// This overload accepts an explicit timer observable instead of a timeout. public static IObservable> BatchIf(this IObservable> source, IObservable pauseIfTrueSelector, bool initialPauseState = false, IObservable? timer = null, IScheduler? scheduler = null) where TObject : notnull diff --git a/src/DynamicData/Cache/ObservableCacheEx.Bind.cs b/src/DynamicData/Cache/ObservableCacheEx.Bind.cs index ec591efa2..06bc2f86b 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Bind.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Bind.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -30,17 +28,17 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to bind to a collection. - /// The that will receive the changes. + /// The source IObservable<IChangeSet<TObject, TKey>> to bind to a collection. + /// The IObservableCollection<TObject> that will receive the changes. /// The number of changes before a reset notification is triggered. /// An observable which will emit change sets. - /// source. - /// + /// source. + /// ObservableListEx.Bind public static IObservable> Bind(this IObservable> source, IObservableCollection destination, int refreshThreshold = BindingOptions.DefaultResetThreshold) where TObject : notnull where TKey : notnull { - destination.ThrowArgumentNullExceptionIfNull(nameof(destination)); + ArgumentExceptionHelper.ThrowIfNull(destination); // if user has not specified different defaults, use system wide defaults instead. // This is a hack to retro fit system wide defaults which override the hard coded defaults above @@ -58,16 +56,16 @@ public static IObservable> Bind(this IO /// /// The type of the object. /// The type of the key. - /// The source to bind to a collection. - /// The that will receive the changes. + /// The source IObservable<IChangeSet<TObject, TKey>> to bind to a collection. + /// The IObservableCollection<TObject> that will receive the changes. /// The that controls binding behavior. /// An observable which will emit change sets. - /// source. + /// source. public static IObservable> Bind(this IObservable> source, IObservableCollection destination, BindingOptions options) where TObject : notnull where TKey : notnull { - destination.ThrowArgumentNullExceptionIfNull(nameof(destination)); + ArgumentExceptionHelper.ThrowIfNull(destination); return source?.Bind(destination, new ObservableCollectionAdaptor(options)) ?? throw new ArgumentNullException(nameof(source)); } @@ -77,18 +75,18 @@ public static IObservable> Bind(this IO /// /// The type of the object. /// The type of the key. - /// The source to bind to a collection. - /// The that will receive the changes. - /// The that applies changes to the bound collection. + /// The source IObservable<IChangeSet<TObject, TKey>> to bind to a collection. + /// The IObservableCollection<TObject> that will receive the changes. + /// The IObservableCollectionAdaptor<TObject, TKey> that applies changes to the bound collection. /// An observable which will emit change sets. /// source. public static IObservable> Bind(this IObservable> source, IObservableCollection destination, IObservableCollectionAdaptor updater) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - destination.ThrowArgumentNullExceptionIfNull(nameof(destination)); - updater.ThrowArgumentNullExceptionIfNull(nameof(updater)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(destination); + ArgumentExceptionHelper.ThrowIfNull(updater); return Observable.Create>( observer => @@ -105,16 +103,16 @@ public static IObservable> Bind(this IO /// /// The type of the object. /// The type of the key. - /// The source to bind to a collection. - /// The output that will be populated with the results. + /// The source IObservable<IChangeSet<TObject, TKey>> to bind to a collection. + /// The output ReadOnlyObservableCollection<TObject> that will be populated with the results. /// The that controls binding behavior. /// An observable which will emit change sets. - /// source. + /// source. public static IObservable> Bind(this IObservable> source, out ReadOnlyObservableCollection readOnlyObservableCollection, BindingOptions options) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); var target = new ObservableCollectionExtended(); readOnlyObservableCollection = new ReadOnlyObservableCollection(target); @@ -126,18 +124,18 @@ public static IObservable> Bind(this IO /// /// The type of the object. /// The type of the key. - /// The source to bind to a collection. - /// The output that will be populated with the results. + /// The source IObservable<IChangeSet<TObject, TKey>> to bind to a collection. + /// The output ReadOnlyObservableCollection<TObject> that will be populated with the results. /// The number of changes before a reset notification is triggered. /// When , uses Replace instead of Remove/Add for updates in the bound collection. Not all platforms support replace notifications. - /// An optional that controls how the target collection is updated. + /// An optional IObservableCollectionAdaptor<TObject, TKey> that controls how the target collection is updated. /// An observable which will emit change sets. - /// source. + /// source. public static IObservable> Bind(this IObservable> source, out ReadOnlyObservableCollection readOnlyObservableCollection, int resetThreshold = BindingOptions.DefaultResetThreshold, bool useReplaceForUpdates = BindingOptions.DefaultUseReplaceForUpdates, IObservableCollectionAdaptor? adaptor = null) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); if (adaptor is not null) { @@ -162,16 +160,16 @@ public static IObservable> Bind(this IO /// /// The type of the object. /// The type of the key. - /// The source to bind to a collection. - /// The that will receive the changes. + /// The source IObservable<ISortedChangeSet<TObject, TKey>> to bind to a collection. + /// The IObservableCollection<TObject> that will receive the changes. /// An observable which will emit change sets. /// source. public static IObservable> Bind(this IObservable> source, IObservableCollection destination) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - destination.ThrowArgumentNullExceptionIfNull(nameof(destination)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(destination); return source.Bind(destination, DynamicDataOptions.Binding); } @@ -181,17 +179,17 @@ public static IObservable> Bind(t ///
/// The type of the object. /// The type of the key. - /// The source to bind to a collection. - /// The that will receive the changes. + /// The source IObservable<ISortedChangeSet<TObject, TKey>> to bind to a collection. + /// The IObservableCollection<TObject> that will receive the changes. /// The that controls binding behavior. /// An observable which will emit change sets. - /// source. + /// source. public static IObservable> Bind(this IObservable> source, IObservableCollection destination, BindingOptions options) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - destination.ThrowArgumentNullExceptionIfNull(nameof(destination)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(destination); var updater = new SortedObservableCollectionAdaptor(options); return source.Bind(destination, updater); @@ -202,18 +200,18 @@ public static IObservable> Bind(t ///
/// The type of the object. /// The type of the key. - /// The source to bind to a collection. - /// The that will receive the changes. - /// The that applies changes to the bound collection. + /// The source IObservable<ISortedChangeSet<TObject, TKey>> to bind to a collection. + /// The IObservableCollection<TObject> that will receive the changes. + /// The ISortedObservableCollectionAdaptor<TObject, TKey> that applies changes to the bound collection. /// An observable which will emit change sets. /// source. public static IObservable> Bind(this IObservable> source, IObservableCollection destination, ISortedObservableCollectionAdaptor updater) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - destination.ThrowArgumentNullExceptionIfNull(nameof(destination)); - updater.ThrowArgumentNullExceptionIfNull(nameof(updater)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(destination); + ArgumentExceptionHelper.ThrowIfNull(updater); return Observable.Create>( observer => @@ -230,16 +228,16 @@ public static IObservable> Bind(t ///
/// The type of the object. /// The type of the key. - /// The source to bind to a collection. - /// The output that will be populated with the results. + /// The source IObservable<ISortedChangeSet<TObject, TKey>> to bind to a collection. + /// The output ReadOnlyObservableCollection<TObject> that will be populated with the results. /// The that controls binding behavior. /// An observable which will emit change sets. - /// source. + /// source. public static IObservable> Bind(this IObservable> source, out ReadOnlyObservableCollection readOnlyObservableCollection, BindingOptions options) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); var target = new ObservableCollectionExtended(); var result = new ReadOnlyObservableCollection(target); @@ -253,18 +251,18 @@ public static IObservable> Bind(this IO ///
/// The type of the object. /// The type of the key. - /// The source to bind to a collection. - /// The output that will be populated with the results. + /// The source IObservable<ISortedChangeSet<TObject, TKey>> to bind to a collection. + /// The output ReadOnlyObservableCollection<TObject> that will be populated with the results. /// The number of changes before a reset event is called on the observable collection. /// When , uses Replace instead of Remove/Add for updates in the bound collection. Not all platforms support replace notifications. - /// An that specify an adaptor to change the algorithm to update the target collection. + /// An IChangeSetAdaptor<TObject, TKey> that specify an adaptor to change the algorithm to update the target collection. /// An observable which will emit change sets. - /// source. + /// source. public static IObservable> Bind(this IObservable> source, out ReadOnlyObservableCollection readOnlyObservableCollection, int resetThreshold = BindingOptions.DefaultResetThreshold, bool useReplaceForUpdates = BindingOptions.DefaultUseReplaceForUpdates, ISortedObservableCollectionAdaptor? adaptor = null) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); // if user has not specified different defaults, use system wide defaults instead. // This is a hack to retro fit system wide defaults which override the hard coded defaults above @@ -279,15 +277,15 @@ public static IObservable> Bind(this IO readOnlyObservableCollection = new ReadOnlyObservableCollection(target); return source.Bind(target, adaptor); } - #if SUPPORTS_BINDINGLIST + /// /// Binds a clone of the observable change set to the target observable collection. /// /// The object type. /// The key type. - /// The source to bind to a collection. - /// The that will receive the changes. + /// The source IObservable<IChangeSet<TObject, TKey>> to bind to a collection. + /// The BindingList<TObject> that will receive the changes. /// The reset threshold. /// An observable which will emit change sets. /// @@ -299,8 +297,8 @@ public static IObservable> Bind(this IO where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - bindingList.ThrowArgumentNullExceptionIfNull(nameof(bindingList)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(bindingList); return source.Adapt(new BindingListAdaptor(bindingList, resetThreshold)); } @@ -310,8 +308,8 @@ public static IObservable> Bind(this IO ///
/// The object type. /// The key type. - /// The source to bind to a collection. - /// The that will receive the changes. + /// The source IObservable<ISortedChangeSet<TObject, TKey>> to bind to a collection. + /// The BindingList<TObject> that will receive the changes. /// The reset threshold. /// An observable which will emit change sets. /// @@ -323,8 +321,8 @@ public static IObservable> Bind(this IO where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - bindingList.ThrowArgumentNullExceptionIfNull(nameof(bindingList)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(bindingList); return source.Adapt(new SortedBindingListAdaptor(bindingList, resetThreshold)); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.BufferInitial.cs b/src/DynamicData/Cache/ObservableCacheEx.BufferInitial.cs index c8d709c85..7db16f95d 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.BufferInitial.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.BufferInitial.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,7 +22,7 @@ public static partial class ObservableCacheEx /// /// The object type. /// The type of the key. - /// The source to buffer during the initial loading period. + /// The source IObservable<IChangeSet<TObject, TKey>> to buffer during the initial loading period. /// The time window to buffer, measured from when the first changeset arrives. /// The scheduler for timing. Defaults to . /// An observable that emits one merged changeset for the initial burst, then passthrough for the rest. @@ -40,10 +31,10 @@ public static partial class ObservableCacheEx /// Useful for aggregating the initial snapshot (which may arrive as many small changesets) into a /// single changeset for efficient downstream processing, while leaving subsequent live updates untouched. /// - /// Internally uses , Rx Buffer, and . + /// Internally uses DeferUntilLoaded<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>), Rx Buffer, and FlattenBufferResult<TObject, TKey>. /// - /// - /// + /// Batch<TObject, TKey> + /// DeferUntilLoaded<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>) public static IObservable> BufferInitial(this IObservable> source, TimeSpan initialBuffer, IScheduler? scheduler = null) where TObject : notnull where TKey : notnull => source.DeferUntilLoaded().Publish( diff --git a/src/DynamicData/Cache/ObservableCacheEx.Cast.cs b/src/DynamicData/Cache/ObservableCacheEx.Cast.cs index 90aa68b00..3d6214ed9 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Cast.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Cast.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -27,14 +25,14 @@ public static partial class ObservableCacheEx { /// /// Casts each item in the changeset to a new type using the provided converter function. - /// Equivalent to + /// Equivalent to Transform<TDestination, TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TDestination>, bool) /// but named for discoverability when a simple type cast or conversion is needed. /// /// The type of the source object. /// The type of the key. /// The type of the destination object. - /// The source to cast. - /// The conversion function applied to each item. + /// The source IObservable<IChangeSet<TSource, TKey>> to cast. + /// The Func<TSource, TDestination> conversion function applied to each item. /// An observable changeset of converted items. /// /// @@ -45,13 +43,13 @@ public static partial class ObservableCacheEx /// RefreshForwarded as Refresh. The converter is not called. /// /// - /// + /// OfType<TObject, TKey, TDestination> public static IObservable> Cast(this IObservable> source, Func converter) where TSource : notnull where TKey : notnull where TDestination : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new Cast(source, converter).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.ChangeKey.cs b/src/DynamicData/Cache/ObservableCacheEx.ChangeKey.cs index 53dc96e5d..19f1792db 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.ChangeKey.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.ChangeKey.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -32,8 +23,8 @@ public static partial class ObservableCacheEx /// The type of the object. /// The type of the source key. /// The type of the destination key. - /// The source to re-key. - /// The that computes the destination key from the item, e.g. (item) => item.NewId. + /// The source IObservable<IChangeSet<TObject, TSourceKey>> to re-key. + /// The Func<TObject, TDestinationKey> that computes the destination key from the item, e.g. (item) => item.NewId. /// An observable changeset with items re-keyed using . /// /// @@ -44,14 +35,14 @@ public static partial class ObservableCacheEx /// Refresh is called on the item. A Refresh is emitted with the destination key. /// /// - /// + /// Transform<TDestination, TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TDestination>, bool) public static IObservable> ChangeKey(this IObservable> source, Func keySelector) where TObject : notnull where TSourceKey : notnull where TDestinationKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - keySelector.ThrowArgumentNullExceptionIfNull(nameof(keySelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(keySelector); return source.Select( updates => @@ -61,7 +52,15 @@ public static IObservable> ChangeKey + /// + /// Provides an overload of ChangeKey for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TSourceKey value. + /// The type of the TDestinationKey value. + /// The source value. + /// The keySelector value. + /// The resulting observable sequence. /// /// This overload also provides the source key to , /// allowing the destination key to be derived from both the item and its original key. @@ -71,8 +70,8 @@ public static IObservable> ChangeKey diff --git a/src/DynamicData/Cache/ObservableCacheEx.Clear.cs b/src/DynamicData/Cache/ObservableCacheEx.Clear.cs index d29301dc8..4751870ab 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Clear.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Clear.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -30,7 +28,7 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The to clear. + /// The ISourceCache<TObject, TKey> to clear. /// /// /// EventBehavior @@ -45,27 +43,37 @@ public static void Clear(this ISourceCache source) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(updater => updater.Clear()); } - /// + /// + /// Provides an overload of Clear for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. public static void Clear(this IIntermediateCache source) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(updater => updater.Clear()); } - /// + /// + /// Provides an overload of Clear for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. public static void Clear(this LockFreeObservableCache source) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(updater => updater.Clear()); } } diff --git a/src/DynamicData/Cache/ObservableCacheEx.Clone.cs b/src/DynamicData/Cache/ObservableCacheEx.Clone.cs index 1f635fc58..f775c4bbb 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Clone.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Clone.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,8 +22,8 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to clone. - /// The target collection to which changes are applied. + /// The source IObservable<IChangeSet<TObject, TKey>> to clone. + /// The ICollection<TObject> target collection to which changes are applied. /// An observable that forwards all changesets from unchanged. /// /// @@ -40,15 +31,15 @@ public static partial class ObservableCacheEx /// AddThe item is added to . Forwarded as Add. /// UpdateThe previous item is removed from and the current item is added. Forwarded as Update. /// RemoveThe item is removed from . Forwarded as Remove. - /// RefreshIgnored ( has no concept of refresh). Forwarded as Refresh. + /// RefreshIgnored (ICollection<T> has no concept of refresh). Forwarded as Refresh. /// /// public static IObservable> Clone(this IObservable> source, ICollection target) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - target.ThrowArgumentNullExceptionIfNull(nameof(target)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(target); return source.Do( changes => diff --git a/src/DynamicData/Cache/ObservableCacheEx.Combine.cs b/src/DynamicData/Cache/ObservableCacheEx.Combine.cs index eb46b86b3..733ad710c 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Combine.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Combine.cs @@ -1,35 +1,41 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { + /// + /// Executes the Combine operation. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The type value. + /// The result of the operation. private static IObservable> Combine(this IObservableList> source, CombineOperator type) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return Observable.Create>( observer => @@ -40,11 +46,19 @@ private static IObservable> Combine(thi }); } + /// + /// Executes the Combine operation. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The type value. + /// The result of the operation. private static IObservable> Combine(this IObservableList> source, CombineOperator type) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return Observable.Create>( observer => @@ -55,20 +69,36 @@ private static IObservable> Combine(thi }); } + /// + /// Executes the Combine operation. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The type value. + /// The result of the operation. private static IObservable> Combine(this IObservableList>> source, CombineOperator type) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new DynamicCombiner(source, type).Run(); } + /// + /// Executes the Combine operation. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The sources value. + /// The type value. + /// The result of the operation. private static IObservable> Combine(this ICollection>> sources, CombineOperator type) where TObject : notnull where TKey : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return Observable.Create>( observer => @@ -101,11 +131,20 @@ void UpdateAction(IChangeSet updates) }); } + /// + /// Executes the Combine operation. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The type value. + /// The combineTarget value. + /// The result of the operation. private static IObservable> Combine(this IObservable> source, CombineOperator type, params IObservable>[] combineTarget) where TObject : notnull where TKey : notnull { - combineTarget.ThrowArgumentNullExceptionIfNull(nameof(combineTarget)); + ArgumentExceptionHelper.ThrowIfNull(combineTarget); return Observable.Create>( observer => diff --git a/src/DynamicData/Cache/ObservableCacheEx.Convert.cs b/src/DynamicData/Cache/ObservableCacheEx.Convert.cs index bf6d26c18..7ddb8e0b4 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Convert.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Convert.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -26,13 +17,13 @@ namespace DynamicData; public static partial class ObservableCacheEx { /// - /// Obsolete: use instead. + /// Obsolete: use Transform<TDestination, TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TDestination>, bool) instead. /// /// The type of the object. /// The type of the key. /// The type of the destination. - /// The source to convert. - /// The conversion factory. + /// The source IObservable<IChangeSet<TObject, TKey>> to convert. + /// The Func<TObject, TDestination> conversion factory. /// An observable which emits change sets. [Obsolete("This was an experiment that did not work. Use Transform instead")] public static IObservable> Convert(this IObservable> source, Func conversionFactory) @@ -40,8 +31,8 @@ public static IObservable> Convert diff --git a/src/DynamicData/Cache/ObservableCacheEx.CreateChangeSetTransformer.cs b/src/DynamicData/Cache/ObservableCacheEx.CreateChangeSetTransformer.cs index 47d11d3ca..5611ef3a1 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.CreateChangeSetTransformer.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.CreateChangeSetTransformer.cs @@ -2,35 +2,54 @@ // 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.Collections.ObjectModel; using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; +#if REACTIVE_SHIM +using DynamicData.Reactive.Binding; +#else using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { + /// + /// Executes the CreateChangeSetTransformer operation. + /// + /// The type of the TDestination value. + /// The type of the TDestinationKey value. + /// The type of the TSource value. + /// The type of the TSourceKey value. + /// The manySelector value. + /// The keySelector value. + /// The result of the operation. private static Func>>> CreateChangeSetTransformer(Func>> manySelector, Func keySelector) where TDestination : notnull where TDestinationKey : notnull where TSource : notnull where TSourceKey : notnull => async (val, key) => (await manySelector(val, key).ConfigureAwait(false)).AsObservableChangeSet(keySelector); + /// + /// Executes the CreateChangeSetTransformer operation. + /// + /// The type of the TDestination value. + /// The type of the TDestinationKey value. + /// The type of the TSource value. + /// The type of the TSourceKey value. + /// The type of the TCollection value. + /// The manySelector value. + /// The keySelector value. + /// The result of the operation. private static Func>>> CreateChangeSetTransformer(Func> manySelector, Func keySelector) where TDestination : notnull where TDestinationKey : notnull @@ -38,6 +57,15 @@ private static Func => async (val, key) => (await manySelector(val, key).ConfigureAwait(false)).ToObservableChangeSet().AddKey(keySelector); + /// + /// Executes the CreateChangeSetTransformer operation. + /// + /// The type of the TDestination value. + /// The type of the TDestinationKey value. + /// The type of the TSource value. + /// The type of the TSourceKey value. + /// The manySelector value. + /// The result of the operation. private static Func>>> CreateChangeSetTransformer(Func>> manySelector) where TDestination : notnull where TDestinationKey : notnull diff --git a/src/DynamicData/Cache/ObservableCacheEx.DeferUntilLoaded.cs b/src/DynamicData/Cache/ObservableCacheEx.DeferUntilLoaded.cs index 89cebf0da..5cff70429 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.DeferUntilLoaded.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.DeferUntilLoaded.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,27 +29,33 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to defer until the first changeset arrives. + /// The source IObservable<IChangeSet<TObject, TKey>> to defer until the first changeset arrives. /// An observable that begins emitting changesets once the first non-empty changeset is received. /// /// Worth noting: Blocks indefinitely if the cache or stream never receives any data. Ensure the source will eventually emit at least one changeset. /// - /// + /// SkipInitial<TObject, TKey> public static IObservable> DeferUntilLoaded(this IObservable> source) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new DeferUntilLoaded(source).Run(); } - /// + /// + /// Provides an overload of DeferUntilLoaded for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The resulting observable sequence. public static IObservable> DeferUntilLoaded(this IObservableCache source) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new DeferUntilLoaded(source).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.DisposeMany.cs b/src/DynamicData/Cache/ObservableCacheEx.DisposeMany.cs index 52ad51274..7249b5589 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.DisposeMany.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.DisposeMany.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -37,7 +35,7 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to track for disposal on removal. + /// The source IObservable<IChangeSet<TObject, TKey>> to track for disposal on removal. /// A stream that forwards all changesets from unchanged. /// /// @@ -53,18 +51,18 @@ public static partial class ObservableCacheEx /// /// On stream completion, error, or subscription disposal, all remaining tracked items are disposed. /// All disposal is synchronous via . - /// For items that implement , use instead. + /// For items that implement , use AsyncDisposeMany<TObject,TKey> instead. /// /// /// is . - /// - /// - /// + /// AsyncDisposeMany<TObject,TKey> + /// SubscribeMany<TObject,TKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, IDisposable>) + /// ObservableListEx.DisposeMany public static IObservable> DisposeMany(this IObservable> source) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new DisposeMany(source).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.DistinctValues.cs b/src/DynamicData/Cache/ObservableCacheEx.DistinctValues.cs index 6007f055c..4e17cf1b2 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.DistinctValues.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.DistinctValues.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,22 +29,22 @@ public static partial class ObservableCacheEx /// The type object from which the distinct values are selected. /// The type of the key. /// The type of the value. - /// The source to extract distinct values. - /// The value selector. + /// The source IObservable<IChangeSet<TObject, TKey>> to extract distinct values. + /// The Func<TObject, TValue> value selector. /// An observable which will emit distinct change sets. /// /// Due to it's nature only adds or removes can be returned. /// Worth noting: Reference counting assumes value equality is transitive. Mutable value objects with inconsistent Equals implementations can corrupt ref counts. /// /// source. - /// + /// ObservableListEx.DistinctValues public static IObservable> DistinctValues(this IObservable> source, Func valueSelector) where TObject : notnull where TKey : notnull where TValue : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - valueSelector.ThrowArgumentNullExceptionIfNull(nameof(valueSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); return Observable.Create>(observer => new DistinctCalculator(source, valueSelector).Run().SubscribeSafe(observer)); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.EditDiff.cs b/src/DynamicData/Cache/ObservableCacheEx.EditDiff.cs index 86a68c29a..066338838 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.EditDiff.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.EditDiff.cs @@ -1,45 +1,47 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { - /// - /// The to diff and update. - /// The representing the complete desired state to diff against the cache. - /// An used to determine whether a new item is the same as an existing cached item. + /// + /// Provides an overload of EditDiff for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The ISourceCache<TObject, TKey> to diff and update. + /// The IEnumerable<TObject> representing the complete desired state to diff against the cache. + /// An IEqualityComparer<TObject> used to determine whether a new item is the same as an existing cached item. /// - /// This overload uses an instead of a delegate + /// This overload uses an IEqualityComparer<T> instead of a Func<T, T, TResult> delegate /// to determine item equality. /// public static void EditDiff(this ISourceCache source, IEnumerable allItems, IEqualityComparer equalityComparer) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - allItems.ThrowArgumentNullExceptionIfNull(nameof(allItems)); - equalityComparer.ThrowArgumentNullExceptionIfNull(nameof(equalityComparer)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(allItems); + ArgumentExceptionHelper.ThrowIfNull(equalityComparer); source.EditDiff(allItems, equalityComparer.Equals); } @@ -50,9 +52,9 @@ public static void EditDiff(this ISourceCache sour /// /// The type of the object. /// The type of the key. - /// The to diff and update. - /// The representing the complete desired state. - /// The that returns when the current and previous items are considered equal, e.g. (current, previous) => current.Version == previous.Version. + /// The ISourceCache<TObject, TKey> to diff and update. + /// The IEnumerable<TObject> representing the complete desired state. + /// The Func<TObject, TObject, bool> that returns when the current and previous items are considered equal, e.g. (current, previous) => current.Version == previous.Version. /// /// /// EventBehavior @@ -67,24 +69,24 @@ public static void EditDiff(this ISourceCache sour where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - allItems.ThrowArgumentNullExceptionIfNull(nameof(allItems)); - areItemsEqual.ThrowArgumentNullExceptionIfNull(nameof(areItemsEqual)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(allItems); + ArgumentExceptionHelper.ThrowIfNull(areItemsEqual); var editDiff = new EditDiff(source, areItemsEqual); editDiff.Edit(allItems); } /// - /// Converts an of into a changeset stream by diffing each + /// Converts an IObservable<T> of IEnumerable<T> into a changeset stream by diffing each /// emission against the previous one. Each emission replaces the entire dataset. - /// Counterpart to . + /// Counterpart to ToCollection<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>). /// /// The type of the object. /// The type of the key. - /// The source to convert into a keyed changeset stream. - /// The that extracts the unique key from each item. - /// An optional for comparing items. Uses default equality if . + /// The source IObservable<IEnumerable<TObject>> to convert into a keyed changeset stream. + /// The Func<TObject, TKey> that extracts the unique key from each item. + /// An optional IEqualityComparer<TObject> for comparing items. Uses default equality if . /// An observable changeset representing the incremental differences between successive snapshots. /// /// @@ -96,26 +98,26 @@ public static void EditDiff(this ISourceCache sour /// /// /// or is . - /// + /// ToCollection<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>) public static IObservable> EditDiff(this IObservable> source, Func keySelector, IEqualityComparer? equalityComparer = null) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - keySelector.ThrowArgumentNullExceptionIfNull(nameof(keySelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(keySelector); return new EditDiffChangeSet(source, keySelector, equalityComparer).Run(); } /// - /// Converts an of into a changeset stream that tracks + /// Converts an IObservable<T> of Optional<T> into a changeset stream that tracks /// a single item: Some produces an Add or Update, and None produces a Remove. /// /// The type of the object. /// The type of the key. - /// The source to convert into a keyed changeset stream. - /// The that extracts the unique key from each item. - /// An optional for comparing items. Uses default equality if . + /// The source IObservable<Optional<TObject>> to convert into a keyed changeset stream. + /// The Func<TObject, TKey> that extracts the unique key from each item. + /// An optional IEqualityComparer<TObject> for comparing items. Uses default equality if . /// An observable changeset tracking the single optional item. /// /// @@ -127,12 +129,12 @@ public static IObservable> EditDiff(thi /// /// /// or is . - public static IObservable> EditDiff(this IObservable> source, Func keySelector, IEqualityComparer? equalityComparer = null) + public static IObservable> EditDiff(this IObservable> source, Func keySelector, IEqualityComparer? equalityComparer = null) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - keySelector.ThrowArgumentNullExceptionIfNull(nameof(keySelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(keySelector); return new EditDiffChangeSetOptional(source, keySelector, equalityComparer).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.EnsureUniqueKeys.cs b/src/DynamicData/Cache/ObservableCacheEx.EnsureUniqueKeys.cs index f3ec6e103..7e932a483 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.EnsureUniqueKeys.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.EnsureUniqueKeys.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,7 +29,7 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to validate for unique keys. + /// The source IObservable<IChangeSet<TObject, TKey>> to validate for unique keys. /// A changeset stream guaranteed to contain unique keys per changeset. /// /// @@ -47,7 +45,7 @@ public static IObservable> EnsureUniqueKeys(source).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.Except.cs b/src/DynamicData/Cache/ObservableCacheEx.Except.cs index 19699c8d2..06bf43ddf 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Except.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Except.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,20 +29,20 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to combine. - /// The additional streams to combine with. + /// The source IObservable<IChangeSet<TObject, TKey>> to combine. + /// The additional IObservable<IChangeSet<TObject, TKey>> streams to combine with. /// An observable which emits change sets. /// /// source /// or /// others. /// - /// + /// ObservableListEx.Except public static IObservable> Except(this IObservable> source, params IObservable>[] others) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); if (others is null || others.Length == 0) { @@ -60,7 +58,7 @@ public static IObservable> Except(this /// /// The type of the object. /// The type of the key. - /// The of streams to combine. + /// The ICollection<T> of streams to combine. /// An observable which emits change sets. /// /// source @@ -71,7 +69,7 @@ public static IObservable> Except(this where TObject : notnull where TKey : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return sources.Combine(CombineOperator.Except); } @@ -82,13 +80,13 @@ public static IObservable> Except(this /// /// The type of the object. /// The type of the key. - /// The of streams to combine. + /// The IObservableList<T> of streams to combine. /// An observable which emits change sets. public static IObservable> Except(this IObservableList>> sources) where TObject : notnull where TKey : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return sources.Combine(CombineOperator.Except); } @@ -99,13 +97,13 @@ public static IObservable> Except(this /// /// The type of the object. /// The type of the key. - /// The of changeset streams to combine. + /// The IObservableList<IObservableCache<TObject, TKey>> of changeset streams to combine. /// An observable which emits change sets. public static IObservable> Except(this IObservableList> sources) where TObject : notnull where TKey : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return sources.Combine(CombineOperator.Except); } @@ -116,13 +114,13 @@ public static IObservable> Except(this ///
/// The type of the object. /// The type of the key. - /// The of changeset streams to combine. + /// The IObservableList<ISourceCache<TObject, TKey>> of changeset streams to combine. /// An observable which emits change sets. public static IObservable> Except(this IObservableList> sources) where TObject : notnull where TKey : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return sources.Combine(CombineOperator.Except); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.ExpireAfter.cs b/src/DynamicData/Cache/ObservableCacheEx.ExpireAfter.cs index 168d8903e..304930368 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.ExpireAfter.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.ExpireAfter.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,8 +22,8 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to apply time-based expiration to. - /// An optional that returns the expiration timeout for each item, or for no expiration. + /// The source IObservable<IChangeSet<TObject, TKey>> to apply time-based expiration to. + /// An optional Func<T, TResult> that returns the expiration timeout for each item, or for no expiration. /// An observable changeset that includes timer-driven Remove changes for expired items. /// /// When a timer fires, a Remove is emitted for the expired item. @@ -57,10 +48,15 @@ public static IObservable> ExpireAfter( source: source, timeSelector: timeSelector); - /// - /// The source to apply time-based expiration to. - /// An optional that returns the expiration timeout for each item, or for no expiration. + /// + /// Provides an overload of ExpireAfter for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<IChangeSet<TObject, TKey>> to apply time-based expiration to. + /// An optional Func<T, TResult> that returns the expiration timeout for each item, or for no expiration. /// The used to schedule expiration timers. + /// The resulting observable sequence. public static IObservable> ExpireAfter( this IObservable> source, Func timeSelector, @@ -72,10 +68,15 @@ public static IObservable> ExpireAfter( timeSelector: timeSelector, scheduler: scheduler); - /// - /// The source to apply time-based expiration to. - /// An optional that returns the expiration timeout for each item, or for no expiration. + /// + /// Provides an overload of ExpireAfter for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<IChangeSet<TObject, TKey>> to apply time-based expiration to. + /// An optional Func<T, TResult> that returns the expiration timeout for each item, or for no expiration. /// An optional polling interval. If specified, items are expired on a polling interval rather than per-item timers. Less accurate but more efficient when many items share similar expiration times. + /// The resulting observable sequence. /// /// This overload uses periodic polling instead of per-item timers. Expired items are removed on the next /// poll after their timeout elapses, which trades accuracy for reduced timer overhead. @@ -91,11 +92,16 @@ public static IObservable> ExpireAfter( timeSelector: timeSelector, pollingInterval: pollingInterval); - /// - /// The source to apply time-based expiration to. - /// An optional that returns the expiration timeout for each item, or for no expiration. + /// + /// Provides an overload of ExpireAfter for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<IChangeSet<TObject, TKey>> to apply time-based expiration to. + /// An optional Func<T, TResult> that returns the expiration timeout for each item, or for no expiration. /// An optional if specified, items are expired on a polling interval rather than per-item timers. /// The used to schedule polling and expiration timers. + /// The resulting observable sequence. public static IObservable> ExpireAfter( this IObservable> source, Func timeSelector, @@ -110,19 +116,19 @@ public static IObservable> ExpireAfter( scheduler: scheduler); /// - /// Automatically removes items from the after the timeout returned + /// Automatically removes items from the ISourceCache<TObject, TKey> after the timeout returned /// by . Returns an observable of the removed key-value pairs (not a changeset stream). /// /// The type of the object. /// The type of the key. - /// The to operate on. - /// An optional that returns the expiration timeout for each item, or for no expiration. + /// The ISourceCache<TObject, TKey> to operate on. + /// An optional Func<T, TResult> that returns the expiration timeout for each item, or for no expiration. /// An optional if specified, items are expired on a polling interval rather than per-item timers. /// The scheduler used to schedule expiration timers. Defaults to if . /// An observable that emits the key-value pairs of items removed from the cache by expiration. /// - /// Unlike the stream-based overloads, this operates directly on the - /// and returns the removed items as collections, + /// Unlike the stream-based overloads, this operates directly on the ISourceCache<TObject, TKey> + /// and returns the removed items as KeyValuePair<TKey, TObject> collections, /// not as a changeset stream. /// /// or is . diff --git a/src/DynamicData/Cache/ObservableCacheEx.Filter.cs b/src/DynamicData/Cache/ObservableCacheEx.Filter.cs index 7b43d0173..595b802a4 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Filter.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Filter.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,8 +22,8 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to filter. - /// The predicate used to determine whether each item is included. + /// The source IObservable<IChangeSet<TObject, TKey>> to filter. + /// The Func<TObject, bool> predicate used to determine whether each item is included. /// When (default), empty changesets are suppressed for performance. Set to to emit empty changesets, which can be useful for monitoring loading status. /// An observable changeset containing only items that satisfy . /// @@ -43,11 +34,11 @@ public static partial class ObservableCacheEx /// RemoveIf the item was included downstream, a Remove is emitted. Otherwise dropped. /// RefreshThe predicate is re-evaluated. If the item now passes but previously did not, an Add is emitted. If it still passes, a Refresh is forwarded. If it no longer passes, a Remove is emitted. If it still fails, the change is dropped. /// - /// Worth noting: Refresh events trigger re-evaluation, which can promote or demote items. Pair with for property-change-driven filtering. + /// Worth noting: Refresh events trigger re-evaluation, which can promote or demote items. Pair with AutoRefresh<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, TimeSpan?, TimeSpan?, IScheduler?) for property-change-driven filtering. /// - /// - /// - /// + /// FilterImmutable<TObject, TKey> + /// FilterOnObservable<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TKey, IObservable<bool>>, TimeSpan?, IScheduler?) + /// ObservableListEx.Filter public static IObservable> Filter( this IObservable> source, Func filter, @@ -59,10 +50,18 @@ public static IObservable> Filter( filter: filter, suppressEmptyChangeSets: suppressEmptyChangeSets); - /// + /// + /// Provides an overload of Filter for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The predicateChanged value. + /// The suppressEmptyChangeSets value. + /// The resulting observable sequence. /// /// This overload does not accept a reapplyFilter signal. It is equivalent to calling the - /// full dynamic overload with as the reapply observable. + /// full dynamic overload with Observable.Empty<TResult>() as the reapply observable. /// public static IObservable> Filter( this IObservable> source, @@ -82,9 +81,9 @@ public static IObservable> Filter( /// The type of the object. /// The type of the key. /// The type of state value required by . - /// The source to filter. - /// The stream of state values to be passed to . - /// The predicate that receives the current state and an item, returning to include or to exclude. + /// The source IObservable<IChangeSet<TObject, TKey>> to filter. + /// The IObservable<TState> stream of state values to be passed to . + /// The Func<TState, TObject, bool> predicate that receives the current state and an item, returning to include or to exclude. /// When (default), empty changesets are suppressed for performance. Set to to emit empty changesets. /// An observable changeset containing only items satisfying for the latest state. /// , , or is . @@ -97,7 +96,7 @@ public static IObservable> Filter( /// /// EventBehavior /// AddEvaluated against the current state. If it passes, an Add is emitted. Otherwise dropped. - /// UpdateRe-evaluated. Four outcomes as with the static overload. + /// UpdateRe-evaluated. Four outcomes as with the static Filter<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, bool>, bool) overload. /// RemoveIf the item was included downstream, a Remove is emitted. Otherwise dropped. /// RefreshRe-evaluated against the current state. May produce Add, Refresh, Remove, or be dropped. /// @@ -117,13 +116,18 @@ public static IObservable> Filter(), suppressEmptyChangeSets: suppressEmptyChangeSets); - /// - /// The source to filter. - /// The that emits new predicates. Each emission replaces the current predicate and triggers a full re-evaluation of all items. - /// The that, when it emits, triggers a full re-evaluation of all items against the current predicate. Useful when filtering on mutable item properties. + /// + /// Provides an overload of Filter for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<IChangeSet<TObject, TKey>> to filter. + /// The IObservable<Func<TObject, bool>> that emits new predicates. Each emission replaces the current predicate and triggers a full re-evaluation of all items. + /// The IObservable<Unit> that, when it emits, triggers a full re-evaluation of all items against the current predicate. Useful when filtering on mutable item properties. /// When (default), empty changesets are suppressed for performance. + /// The resulting observable sequence. /// - /// In addition to the per-item behavior described in the static overload, + /// In addition to the per-item behavior described in the static Filter<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, bool>, bool) overload, /// emissions from replace the predicate and trigger full re-filtering, /// while emissions from re-evaluate all items against the current predicate. /// Worth noting: No items are included until the predicate observable emits its first value. diff --git a/src/DynamicData/Cache/ObservableCacheEx.FilterImmutable.cs b/src/DynamicData/Cache/ObservableCacheEx.FilterImmutable.cs index 3c4d432a7..393ce24b8 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.FilterImmutable.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.FilterImmutable.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -30,14 +28,14 @@ public static partial class ObservableCacheEx /// /// The type of collection items to be filtered. /// The type of the key values of each collection item. - /// The source to filter (items assumed immutable). - /// The filtering predicate to be applied to each item. + /// The source IObservable<IChangeSet<TObject, TKey>> to filter (items assumed immutable). + /// The Func<TObject, bool> filtering predicate to be applied to each item. /// A flag indicating whether the created stream should emit empty changesets. Empty changesets are suppressed by default, for performance. Set to ensure that a downstream changeset occurs for every upstream changeset. /// A stream of collection changesets where upstream collection items are filtered by the given predicate. /// /// The goal of this operator is to optimize a common use-case of reactive programming, where data values flowing through a stream are immutable, and state changes are distributed by publishing new immutable items as replacements, instead of mutating the items directly. /// In addition to assuming that all collection items are immutable, this operator also assumes that the given filter predicate is deterministic, such that the result it returns will always be the same each time a specific input is passed to it. In other words, the predicate itself also contains no mutable state. - /// Under these assumptions, this operator can bypass the need to keep track of every collection item that passes through it, which the normal operator must do, in order to re-evaluate the filtering status of items, during a refresh operation. + /// Under these assumptions, this operator can bypass the need to keep track of every collection item that passes through it, which the normal Filter<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, bool>, bool) operator must do, in order to re-evaluate the filtering status of items, during a refresh operation. /// Consider using this operator when the following are true: /// /// Your collection items are immutable, and changes are published by replacing entire items @@ -60,8 +58,8 @@ public static IObservable> FilterImmutable( predicate: predicate, diff --git a/src/DynamicData/Cache/ObservableCacheEx.FilterOnObservable.cs b/src/DynamicData/Cache/ObservableCacheEx.FilterOnObservable.cs index 82733f25a..3623da521 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.FilterOnObservable.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.FilterOnObservable.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -26,13 +24,13 @@ namespace DynamicData; public static partial class ObservableCacheEx { /// - /// Filters items using a per-item that controls inclusion. + /// Filters items using a per-item IObservable<Boolean> that controls inclusion. /// Each item's observable is created by and toggles the item in or out of the downstream stream. /// /// The type of the object. /// The type of the key. - /// The source to filter using per-item observables. - /// A factory that creates an for each item and its key. When the observable emits , the item is included; when , it is excluded. + /// The source IObservable<IChangeSet<TObject, TKey>> to filter using per-item observables. + /// A factory that creates an IObservable<Boolean> for each item and its key. When the observable emits , the item is included; when , it is excluded. /// A that optional time window to buffer inclusion changes from per-item observables before re-evaluating. /// An that optional scheduler used for buffering. /// An observable changeset containing only items whose per-item observable most recently emitted . @@ -67,19 +65,28 @@ public static partial class ObservableCacheEx /// /// /// or is . - /// - /// + /// Filter<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, bool>, bool) + /// ObservableListEx.FilterOnObservable public static IObservable> FilterOnObservable(this IObservable> source, Func> filterFactory, TimeSpan? buffer = null, IScheduler? scheduler = null) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - filterFactory.ThrowArgumentNullExceptionIfNull(nameof(filterFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(filterFactory); return new FilterOnObservable(source, filterFactory, buffer, scheduler).Run(); } - /// + /// + /// Provides an overload of FilterOnObservable for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The filterFactory value. + /// The buffer value. + /// The scheduler value. + /// The resulting observable sequence. /// /// This overload does not provide the key to ; only the item is passed. /// @@ -87,8 +94,8 @@ public static IObservable> FilterOnObservable filterFactory(obj), buffer, scheduler); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.FinallySafe.cs b/src/DynamicData/Cache/ObservableCacheEx.FinallySafe.cs index b50111d6b..0652cfd33 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.FinallySafe.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.FinallySafe.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -29,14 +27,14 @@ public static partial class ObservableCacheEx /// Obsolete: do not use. This can cause unhandled exception issues. Use the standard Rx Finally operator instead. /// /// The type contained within the observables. - /// The source to attach a finally action to. + /// The source IObservable<T> to attach a finally action to. /// The to invoke when the subscription terminates. /// An observable which has always a finally action applied. [Obsolete("This can cause unhandled exception issues so do not use")] public static IObservable FinallySafe(this IObservable source, Action finallyAction) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - finallyAction.ThrowArgumentNullExceptionIfNull(nameof(finallyAction)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(finallyAction); return new FinallySafe(source, finallyAction).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.Flatten.cs b/src/DynamicData/Cache/ObservableCacheEx.Flatten.cs index 23052c9e5..949b81a7f 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Flatten.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Flatten.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -26,20 +17,20 @@ namespace DynamicData; public static partial class ObservableCacheEx { /// - /// Unwraps each into individual - /// values via . + /// Unwraps each IChangeSet<TObject, TKey> into individual Change<TObject, TKey> + /// values via Observable.SelectMany<TSource, TResult>(IObservable<TSource>, Func<TSource, IEnumerable<TResult>>). /// /// The type of the object. /// The type of the key. - /// The source to flatten into individual changes. - /// An observable of individual values. + /// The source IObservable<IChangeSet<TObject, TKey>> to flatten into individual changes. + /// An observable of individual Change<TObject, TKey> values. /// is . - /// + /// ForEachChange<TObject, TKey> public static IObservable> Flatten(this IObservable> source) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.SelectMany(changes => changes); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.FlattenBufferResult.cs b/src/DynamicData/Cache/ObservableCacheEx.FlattenBufferResult.cs index 357b859ac..9741b27a6 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.FlattenBufferResult.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.FlattenBufferResult.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,14 +22,14 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to flatten. + /// The source IObservable<T> to flatten. /// An observable changeset combining all changes from each buffer into a single emission. /// is . public static IObservable> FlattenBufferResult(this IObservable>> source) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Where(x => x.Count != 0).Select(updates => new ChangeSet(updates.SelectMany(u => u))); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.ForEachChange.cs b/src/DynamicData/Cache/ObservableCacheEx.ForEachChange.cs index fb23c04b6..2c3d15ee5 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.ForEachChange.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.ForEachChange.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -26,21 +17,21 @@ namespace DynamicData; public static partial class ObservableCacheEx { /// - /// Invokes for every individual in each changeset, + /// Invokes for every individual Change<TObject,TKey> in each changeset, /// regardless of change reason. The changeset is forwarded downstream unchanged. /// /// The type of the object. /// The type of the key. - /// The source to observe each individual change in. - /// The action to invoke for each change. Receives the full struct, including , , , and . + /// The source IObservable<IChangeSet<TObject, TKey>> to observe each individual change in. + /// The action to invoke for each change. Receives the full Change<TObject,TKey> struct, including Change<TObject,TKey>.Reason, Change<TObject,TKey>.Key, Change<TObject,TKey>.Current, and Change<TObject,TKey>.Previous. /// A stream that forwards all changesets from unchanged. /// /// /// All change reasons (Add, Update, Remove, Refresh) trigger the callback. - /// Use , - /// , - /// , or - /// + /// Use OnItemAdded<TObject,TKey>(IObservable<IChangeSet<TObject,TKey>>, Action<TObject,TKey>), + /// OnItemUpdated<TObject,TKey>(IObservable<IChangeSet<TObject,TKey>>, Action<TObject,TObject,TKey>), + /// OnItemRemoved<TObject,TKey>(IObservable<IChangeSet<TObject,TKey>>, Action<TObject,TKey>, bool), or + /// OnItemRefreshed<TObject,TKey>(IObservable<IChangeSet<TObject,TKey>>, Action<TObject,TKey>) /// to target a specific reason. /// /// @@ -49,13 +40,13 @@ public static partial class ObservableCacheEx /// /// /// or is . - /// + /// ObservableListEx.ForEachChange public static IObservable> ForEachChange(this IObservable> source, Action> action) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - action.ThrowArgumentNullExceptionIfNull(nameof(action)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(action); return source.Do(changes => changes.ForEach(action)); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.ForForced.cs b/src/DynamicData/Cache/ObservableCacheEx.ForForced.cs index 02103498f..ee97660f1 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.ForForced.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.ForForced.cs @@ -2,29 +2,27 @@ // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { + /// + /// Executes the ForForced operation. + /// + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The result of the operation. private static IObservable>? ForForced(this IObservable? source) where TKey : notnull => source?.Select( _ => @@ -33,6 +31,13 @@ public static partial class ObservableCacheEx return (Func)Transformer; }); + /// + /// Executes the ForForced operation. + /// + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The result of the operation. private static IObservable>? ForForced(this IObservable>? source) where TKey : notnull => source?.Select( condition => diff --git a/src/DynamicData/Cache/ObservableCacheEx.FullJoin.cs b/src/DynamicData/Cache/ObservableCacheEx.FullJoin.cs index 9328527e2..1904f4641 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.FullJoin.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.FullJoin.cs @@ -1,54 +1,60 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { - /// - /// The left to join. - /// The right to join. - /// A that maps each right item to the left key it should join on. - /// A that combines the optional left and right values into a destination object. The key is not provided in this overload. - /// Overload that omits the key from the result selector. Delegates to . - public static IObservable> FullJoin(this IObservable> left, IObservable> right, Func rightKeySelector, Func, Optional, TDestination> resultSelector) + /// + /// Provides an overload of FullJoin for the supplied arguments. + /// + /// The type of the TLeft value. + /// The type of the TLeftKey value. + /// The type of the TRight value. + /// The type of the TRightKey value. + /// The type of the TDestination value. + /// The left IObservable<IChangeSet<TLeft, TLeftKey>> to join. + /// The right IObservable<IChangeSet<TRight, TRightKey>> to join. + /// A Func<T, TResult> that maps each right item to the left key it should join on. + /// A Func<T, TResult> that combines the optional left and right values into a destination object. The key is not provided in this overload. + /// The resulting observable sequence. + /// Overload that omits the key from the result selector. Delegates to FullJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, Optional<TLeft>, Optional<TRight>, TDestination>). + public static IObservable> FullJoin(this IObservable> left, IObservable> right, Func rightKeySelector, Func, ReactiveUI.Primitives.Optional, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull where TRight : notnull where TRightKey : notnull where TDestination : notnull { - left.ThrowArgumentNullExceptionIfNull(nameof(left)); - right.ThrowArgumentNullExceptionIfNull(nameof(right)); - rightKeySelector.ThrowArgumentNullExceptionIfNull(nameof(rightKeySelector)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(left); + ArgumentExceptionHelper.ThrowIfNull(right); + ArgumentExceptionHelper.ThrowIfNull(rightKeySelector); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return left.FullJoin(right, rightKeySelector, (_, leftValue, rightValue) => resultSelector(leftValue, rightValue)); } /// /// Joins two changeset streams, producing a result for every key that appears on either side (or both). - /// Both sides are because a given key may only exist on one side at any point. + /// Both sides are Optional<T> because a given key may only exist on one side at any point. /// Equivalent to SQL FULL OUTER JOIN. /// /// The item type of the left source. @@ -56,19 +62,19 @@ public static IObservable> FullJoinThe item type of the right source. /// The key type of the right source. /// The type produced by . - /// The left to join. - /// The right to join. - /// A that maps each right item to the left key it should join on. - /// A that combines the key, optional left, and optional right into a destination object. Example: (key, left, right) => new Result(key, left, right). + /// The left IObservable<IChangeSet<TLeft, TLeftKey>> to join. + /// The right IObservable<IChangeSet<TRight, TRightKey>> to join. + /// A Func<T, TResult> that maps each right item to the left key it should join on. + /// A Func<T, TResult> that combines the key, optional left, and optional right into a destination object. Example: (key, left, right) => new Result(key, left, right). /// An observable changeset keyed by . /// /// /// Left-side change handling: /// /// EventBehavior - /// AddEmits with the left value and the matching right (or if no right exists). + /// AddEmits with the left value and the matching right (or ReactiveUI.Primitives.Optional.None<T> if no right exists). /// UpdateRe-invokes with the new left value and current right (if any). - /// RemoveIf a right match still exists, re-invokes the selector with left as . If neither side remains, removes the joined result. + /// RemoveIf a right match still exists, re-invokes the selector with left as ReactiveUI.Primitives.Optional.None<T>. If neither side remains, removes the joined result. /// RefreshForwarded as Refresh on the joined result. /// /// @@ -76,30 +82,30 @@ public static IObservable> FullJoinRight-side change handling: /// /// EventBehavior - /// AddEmits with the matching left (or ) and the right value. + /// AddEmits with the matching left (or ReactiveUI.Primitives.Optional.None<T>) and the right value. /// UpdateRe-invokes selector with current left (if any) and the new right value. - /// RemoveIf a left match still exists, re-invokes the selector with right as . If neither side remains, removes the joined result. + /// RemoveIf a left match still exists, re-invokes the selector with right as ReactiveUI.Primitives.Optional.None<T>. If neither side remains, removes the joined result. /// RefreshForwarded as Refresh on the joined result. /// /// /// Both sources are serialized through a shared lock held during downstream delivery. Avoid blocking operations in subscribers. /// /// Any argument is . - /// - /// - /// - /// - public static IObservable> FullJoin(this IObservable> left, IObservable> right, Func rightKeySelector, Func, Optional, TDestination> resultSelector) + /// InnerJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<ValueTuple<TLeftKey, TRightKey>, TLeft, TRight, TDestination>) + /// LeftJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, TLeft, Optional<TRight>, TDestination>) + /// RightJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TRightKey, Optional<TLeft>, TRight, TDestination>) + /// FullJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, Optional<TLeft>, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>) + public static IObservable> FullJoin(this IObservable> left, IObservable> right, Func rightKeySelector, Func, ReactiveUI.Primitives.Optional, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull where TRight : notnull where TRightKey : notnull where TDestination : notnull { - left.ThrowArgumentNullExceptionIfNull(nameof(left)); - right.ThrowArgumentNullExceptionIfNull(nameof(right)); - rightKeySelector.ThrowArgumentNullExceptionIfNull(nameof(rightKeySelector)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(left); + ArgumentExceptionHelper.ThrowIfNull(right); + ArgumentExceptionHelper.ThrowIfNull(rightKeySelector); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return new FullJoin(left, right, rightKeySelector, resultSelector).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.FullJoinMany.cs b/src/DynamicData/Cache/ObservableCacheEx.FullJoinMany.cs index c2e5894bc..e14eea2b9 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.FullJoinMany.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.FullJoinMany.cs @@ -1,47 +1,53 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { - /// - /// The left to join. - /// The right to join. - /// A that maps each right item to the left key it should join on. - /// A that combines the optional left value and the right group into a destination object. The key is not provided in this overload. - /// Overload that omits the key from the result selector. Delegates to . - public static IObservable> FullJoinMany(this IObservable> left, IObservable> right, Func rightKeySelector, Func, IGrouping, TDestination> resultSelector) + /// + /// Provides an overload of FullJoinMany for the supplied arguments. + /// + /// The type of the TLeft value. + /// The type of the TLeftKey value. + /// The type of the TRight value. + /// The type of the TRightKey value. + /// The type of the TDestination value. + /// The left IObservable<IChangeSet<TLeft, TLeftKey>> to join. + /// The right IObservable<IChangeSet<TRight, TRightKey>> to join. + /// A Func<T, TResult> that maps each right item to the left key it should join on. + /// A Func<T, TResult> that combines the optional left value and the right group into a destination object. The key is not provided in this overload. + /// The resulting observable sequence. + /// Overload that omits the key from the result selector. Delegates to FullJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, Optional<TLeft>, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>). + public static IObservable> FullJoinMany(this IObservable> left, IObservable> right, Func rightKeySelector, Func, IGrouping, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull where TRight : notnull where TRightKey : notnull where TDestination : notnull { - left.ThrowArgumentNullExceptionIfNull(nameof(left)); - right.ThrowArgumentNullExceptionIfNull(nameof(right)); - rightKeySelector.ThrowArgumentNullExceptionIfNull(nameof(rightKeySelector)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(left); + ArgumentExceptionHelper.ThrowIfNull(right); + ArgumentExceptionHelper.ThrowIfNull(rightKeySelector); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return left.FullJoinMany(right, rightKeySelector, (_, leftValue, rightValue) => resultSelector(leftValue, rightValue)); } @@ -49,7 +55,7 @@ public static IObservable> FullJoinMany /// Groups right-side items by their mapped key, then full-joins each group to the left source. /// A result is produced for every key that appears on either side (or both). The left value is - /// because only the right side may have entries for a given key. + /// Optional<T> because only the right side may have entries for a given key. /// Equivalent to SQL FULL OUTER JOIN with the right side grouped. /// /// The item type of the left source. @@ -57,10 +63,10 @@ public static IObservable> FullJoinManyThe item type of the right source. /// The key type of the right source. /// The type produced by . - /// The left to join. - /// The right to join. - /// A that maps each right item to the left key it should join on. - /// A that combines the key, optional left value, and the right group into a destination object. Example: (key, left, group) => new Result(key, left, group). + /// The left IObservable<IChangeSet<TLeft, TLeftKey>> to join. + /// The right IObservable<IChangeSet<TRight, TRightKey>> to join. + /// A Func<T, TResult> that maps each right item to the left key it should join on. + /// A Func<T, TResult> that combines the key, optional left value, and the right group into a destination object. Example: (key, left, group) => new Result(key, left, group). /// An observable changeset keyed by . /// /// @@ -69,7 +75,7 @@ public static IObservable> FullJoinManyEventBehavior /// AddEmits with the left value and the current right group for that key (may be empty). /// UpdateRe-invokes with the new left value and current right group. - /// RemoveIf the right group is non-empty, re-invokes with left as . If both sides are empty, removes the result. + /// RemoveIf the right group is non-empty, re-invokes with left as ReactiveUI.Primitives.Optional.None<T>. If both sides are empty, removes the result. /// RefreshForwarded as Refresh on the joined result. /// /// @@ -86,21 +92,21 @@ public static IObservable> FullJoinManyBoth sources are serialized through a shared lock held during downstream delivery. Avoid blocking operations in subscribers. /// /// Any argument is . - /// - /// - /// - /// - public static IObservable> FullJoinMany(this IObservable> left, IObservable> right, Func rightKeySelector, Func, IGrouping, TDestination> resultSelector) + /// FullJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, Optional<TLeft>, Optional<TRight>, TDestination>) + /// InnerJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, TLeft, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>) + /// LeftJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, TLeft, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>) + /// RightJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, Optional<TLeft>, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>) + public static IObservable> FullJoinMany(this IObservable> left, IObservable> right, Func rightKeySelector, Func, IGrouping, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull where TRight : notnull where TRightKey : notnull where TDestination : notnull { - left.ThrowArgumentNullExceptionIfNull(nameof(left)); - right.ThrowArgumentNullExceptionIfNull(nameof(right)); - rightKeySelector.ThrowArgumentNullExceptionIfNull(nameof(rightKeySelector)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(left); + ArgumentExceptionHelper.ThrowIfNull(right); + ArgumentExceptionHelper.ThrowIfNull(rightKeySelector); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return new FullJoinMany(left, right, rightKeySelector, resultSelector).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.Group.cs b/src/DynamicData/Cache/ObservableCacheEx.Group.cs index b803146d8..c4c6deafc 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Group.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Group.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -32,9 +30,9 @@ public static partial class ObservableCacheEx /// The type of the object. /// The type of the key. /// The type of the group key. - /// The source to group. - /// The group selector factory. - /// An of used to determine which groups appear in the result. + /// The source IObservable<IChangeSet<TObject, TKey>> to group. + /// The Func<TObject, TGroupKey> group selector factory. + /// An IObservable<IDistinctChangeSet<TGroupKey>> of IDistinctChangeSet<TGroupKey> used to determine which groups appear in the result. /// /// Useful for parent-child collection when the parent and child are soured from different streams. /// @@ -44,9 +42,9 @@ public static IObservable> Group(source, groupSelector, resultGroupSource).Run(); } @@ -58,8 +56,8 @@ public static IObservable> GroupThe type of the object. /// The type of the key. /// The type of the group key. - /// The source to group. - /// A that extracts the group key from each item. + /// The source IObservable<IChangeSet<TObject, TKey>> to group. + /// A Func<T, TResult> that extracts the group key from each item. /// An observable that emits group changesets. Each group exposes a sub-cache of its members. /// /// @@ -79,24 +77,29 @@ public static IObservable> Group /// - /// - /// - /// + /// GroupWithImmutableState<TObject, TKey, TGroupKey> + /// GroupOnObservable<TObject, TKey, TGroupKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TKey, IObservable<TGroupKey>>, TimeSpan?, IScheduler?) + /// GroupOnProperty<TObject, TKey, TGroupKey> public static IObservable> Group(this IObservable> source, Func groupSelectorKey) where TObject : notnull where TKey : notnull where TGroupKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - groupSelectorKey.ThrowArgumentNullExceptionIfNull(nameof(groupSelectorKey)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(groupSelectorKey); return new GroupOn(source, groupSelectorKey, null).Run(); } - /// - /// The source to group. - /// A that extracts the group key from each item. - /// An that, when it emits, all items are re-evaluated against the group selector, potentially moving items between groups. + /// + /// Provides an overload of Group for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The type of the TGroupKey value. + /// The source IObservable<IChangeSet<TObject, TKey>> to group. + /// A Func<T, TResult> that extracts the group key from each item. + /// An IObservable<Unit> that, when it emits, all items are re-evaluated against the group selector, potentially moving items between groups. /// An observable that emits group changesets. /// This overload adds a signal. When it fires, every item in the cache is re-grouped using the current selector, which is useful when the grouping depends on mutable item state. public static IObservable> Group(this IObservable> source, Func groupSelectorKey, IObservable regrouper) @@ -104,9 +107,9 @@ public static IObservable> Group(source, groupSelectorKey, regrouper).Run(); } @@ -118,9 +121,9 @@ public static IObservable> GroupThe type of the object. /// The type of the key. /// The type of the group key. - /// The source to group. - /// The that emits group selector functions. Each emission triggers a full re-grouping of all items. - /// An that optional signal to force re-evaluation of all items against the current selector. + /// The source IObservable<IChangeSet<TObject, TKey>> to group. + /// The IObservable<Func<TObject, TKey, TGroupKey>> that emits group selector functions. Each emission triggers a full re-grouping of all items. + /// An IObservable<Unit> that optional signal to force re-evaluation of all items against the current selector. /// An observable that emits group changesets. /// /// @@ -136,30 +139,36 @@ public static IObservable> GroupRefreshGroup key re-evaluated. Item may move between groups. /// /// - /// - /// + /// Group<TObject, TKey, TGroupKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TGroupKey>) + /// GroupOnObservable<TObject, TKey, TGroupKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TKey, IObservable<TGroupKey>>, TimeSpan?, IScheduler?) public static IObservable> Group(this IObservable> source, IObservable> groupSelectorKeyObservable, IObservable? regrouper = null) where TObject : notnull where TKey : notnull where TGroupKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - groupSelectorKeyObservable.ThrowArgumentNullExceptionIfNull(nameof(groupSelectorKeyObservable)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(groupSelectorKeyObservable); return new GroupOnDynamic(source, groupSelectorKeyObservable, regrouper).Run(); } - /// - /// The source to group. - /// The of selector functions that take only the item (not the key). - /// An optional signal to force re-evaluation. + /// + /// Provides an overload of Group for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The type of the TGroupKey value. + /// The source IObservable<IChangeSet<TObject, TKey>> to group. + /// The IObservable<Func<TObject, TGroupKey>> of selector functions that take only the item (not the key). + /// An optional IObservable<Unit> signal to force re-evaluation. + /// The resulting observable sequence. /// This overload accepts a selector that does not receive the key. Delegates to the overload accepting Func<TObject, TKey, TGroupKey>. public static IObservable> Group(this IObservable> source, IObservable> groupSelectorKeyObservable, IObservable? regrouper = null) where TObject : notnull where TKey : notnull where TGroupKey : notnull { - groupSelectorKeyObservable.ThrowArgumentNullExceptionIfNull(nameof(groupSelectorKeyObservable)); + ArgumentExceptionHelper.ThrowIfNull(groupSelectorKeyObservable); return source.Group(groupSelectorKeyObservable.Select(AdaptSelector), regrouper); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.GroupOnObservable.cs b/src/DynamicData/Cache/ObservableCacheEx.GroupOnObservable.cs index 31115d013..91d231e9c 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.GroupOnObservable.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.GroupOnObservable.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -32,12 +30,12 @@ public static partial class ObservableCacheEx /// The type of the object. /// The type of the key. /// The type of the group key. - /// The source to group using per-item observables. - /// A factory that creates a group key observable for each item and its key. + /// The source IObservable<IChangeSet<TObject, TKey>> to group using per-item observables. + /// A Func<T, TResult> factory that creates a group key observable for each item and its key. /// An observable that emits group changesets. Each group is a live sub-cache of its members. /// /// - /// Unlike which evaluates + /// Unlike Group<TObject, TKey, TGroupKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TGroupKey>) which evaluates /// the group key synchronously, this operator defers group assignment until the per-item observable emits. /// /// @@ -68,17 +66,17 @@ public static partial class ObservableCacheEx /// also completed. /// /// - /// - /// - /// - /// + /// Group<TObject, TKey, TGroupKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TGroupKey>) + /// GroupOnProperty<TObject, TKey, TGroupKey> + /// FilterOnObservable<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TKey, IObservable<bool>>, TimeSpan?, IScheduler?) + /// TransformOnObservable<TSource, TKey, TDestination>(IObservable<IChangeSet<TSource, TKey>>, Func<TSource, TKey, IObservable<TDestination>>) public static IObservable> GroupOnObservable(this IObservable> source, Func> groupObservableSelector) where TObject : notnull where TKey : notnull where TGroupKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - groupObservableSelector.ThrowArgumentNullExceptionIfNull(nameof(groupObservableSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(groupObservableSelector); return new GroupOnObservable(source, groupObservableSelector).Run(); } @@ -89,15 +87,15 @@ public static IObservable> GroupOnObse /// The type of the object. /// The type of the key. /// The type of the group key. - /// The source to group using per-item observables. - /// The group selector key. + /// The source IObservable<IChangeSet<TObject, TKey>> to group using per-item observables. + /// The Func<TObject, IObservable<TGroupKey>> group selector key. /// An observable which will emit group change sets. public static IObservable> GroupOnObservable(this IObservable> source, Func> groupObservableSelector) where TObject : notnull where TKey : notnull where TGroupKey : notnull { - groupObservableSelector.ThrowArgumentNullExceptionIfNull(nameof(groupObservableSelector)); + ArgumentExceptionHelper.ThrowIfNull(groupObservableSelector); return source.GroupOnObservable(AdaptSelector>(groupObservableSelector)); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.GroupOnProperty.cs b/src/DynamicData/Cache/ObservableCacheEx.GroupOnProperty.cs index 5165d4091..78ed5c1af 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.GroupOnProperty.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.GroupOnProperty.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; +#if REACTIVE_SHIM +using DynamicData.Reactive.Cache.Internal; +#else using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -32,8 +30,8 @@ public static partial class ObservableCacheEx /// The type of the object. /// The type of the key. /// The type of the group key. - /// The source to group by a property value. - /// The property selector used to group the items. + /// The source IObservable<IChangeSet<TObject, TKey>> to group by a property value. + /// The Expression<Func<TObject, TGroupKey>> property selector used to group the items. /// An optional a time span that indicates the throttle to wait for property change events. /// An optional for scheduling work. /// An observable which will emit immutable group change sets. @@ -42,8 +40,8 @@ public static IObservable> GroupOnProp where TKey : notnull where TGroupKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - propertySelector.ThrowArgumentNullExceptionIfNull(nameof(propertySelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(propertySelector); return new GroupOnProperty(source, propertySelector, propertyChangedThrottle, scheduler).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.GroupOnPropertyWithImmutableState.cs b/src/DynamicData/Cache/ObservableCacheEx.GroupOnPropertyWithImmutableState.cs index 2be1f5ca0..9b216305d 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.GroupOnPropertyWithImmutableState.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.GroupOnPropertyWithImmutableState.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; +#if REACTIVE_SHIM +using DynamicData.Reactive.Cache.Internal; +#else using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -32,8 +30,8 @@ public static partial class ObservableCacheEx /// The type of the object. /// The type of the key. /// The type of the group key. - /// The source to group by a property value with immutable snapshots. - /// The property selector used to group the items. + /// The source IObservable<IChangeSet<TObject, TKey>> to group by a property value with immutable snapshots. + /// The Expression<Func<TObject, TGroupKey>> property selector used to group the items. /// An optional a time span that indicates the throttle to wait for property change events. /// An optional for scheduling work. /// An observable which will emit immutable group change sets. @@ -42,8 +40,8 @@ public static IObservable> Gr where TKey : notnull where TGroupKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - propertySelector.ThrowArgumentNullExceptionIfNull(nameof(propertySelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(propertySelector); return new GroupOnPropertyWithImmutableState(source, propertySelector, propertyChangedThrottle, scheduler).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.GroupWithImmutableState.cs b/src/DynamicData/Cache/ObservableCacheEx.GroupWithImmutableState.cs index 1284a4b4f..e0a2779d0 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.GroupWithImmutableState.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.GroupWithImmutableState.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -32,13 +30,13 @@ public static partial class ObservableCacheEx /// The type of the object. /// The type of the key. /// The type of the group key. - /// The source to group with immutable snapshots. - /// A that extracts the group key from each item. - /// An that optional signal to force re-evaluation of all items against the group selector. + /// The source IObservable<IChangeSet<TObject, TKey>> to group with immutable snapshots. + /// A Func<T, TResult> that extracts the group key from each item. + /// An IObservable<Unit> that optional signal to force re-evaluation of all items against the group selector. /// An observable that emits immutable group changesets. /// /// - /// Behaves identically to + /// Behaves identically to Group<TObject, TKey, TGroupKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TGroupKey>) /// in terms of how items are assigned to groups, but each group emission is an immutable snapshot. /// This makes it safe for parallel processing and eliminates race conditions on group state. /// The tradeoff is higher memory usage, since each change produces a new snapshot of the affected group. @@ -51,15 +49,15 @@ public static partial class ObservableCacheEx /// RefreshGroup key re-evaluated. If changed, item moves; affected group snapshots emitted. /// /// - /// - /// + /// Group<TObject, TKey, TGroupKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TGroupKey>) + /// GroupOnPropertyWithImmutableState<TObject, TKey, TGroupKey> public static IObservable> GroupWithImmutableState(this IObservable> source, Func groupSelectorKey, IObservable? regrouper = null) where TObject : notnull where TKey : notnull where TGroupKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - groupSelectorKey.ThrowArgumentNullExceptionIfNull(nameof(groupSelectorKey)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(groupSelectorKey); return new GroupOnImmutable(source, groupSelectorKey, regrouper).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.IgnoreSameReferenceUpdate.cs b/src/DynamicData/Cache/ObservableCacheEx.IgnoreSameReferenceUpdate.cs index f5ff65ace..bf90b05a5 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.IgnoreSameReferenceUpdate.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.IgnoreSameReferenceUpdate.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -30,7 +21,7 @@ public static partial class ObservableCacheEx /// /// The object of the change set. /// The key of the change set. - /// The source to suppress same-reference updates in. + /// The source IObservable<IChangeSet<TObject, TKey>> to suppress same-reference updates in. /// An observable which emits change sets and ignores equal value changes. public static IObservable> IgnoreSameReferenceUpdate(this IObservable> source) where TObject : notnull diff --git a/src/DynamicData/Cache/ObservableCacheEx.IgnoreUpdateWhen.cs b/src/DynamicData/Cache/ObservableCacheEx.IgnoreUpdateWhen.cs index 2b8f11a87..33b37e0bd 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.IgnoreUpdateWhen.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.IgnoreUpdateWhen.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,12 +22,16 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to selectively suppress updates in. - /// The ignore function (current,previous)=>{ return true to ignore }. + /// The source IObservable<IChangeSet<TObject, TKey>> to selectively suppress updates in. + /// The Func<TObject, TObject, bool> ignore function (current,previous)=>{ return true to ignore }. /// An observable which emits change sets and ignores updates equal to the lambda. public static IObservable> IgnoreUpdateWhen(this IObservable> source, Func ignoreFunction) where TObject : notnull - where TKey : notnull => source.Select( + where TKey : notnull + { + ArgumentExceptionHelper.ThrowIfNull(source); + + return source.Select( updates => { var result = updates.Where( @@ -51,4 +46,5 @@ public static IObservable> IgnoreUpdateWhen(result); }).NotEmpty(); + } } diff --git a/src/DynamicData/Cache/ObservableCacheEx.IncludeUpdateWhen.cs b/src/DynamicData/Cache/ObservableCacheEx.IncludeUpdateWhen.cs index 2c1a2e020..17ea8683f 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.IncludeUpdateWhen.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.IncludeUpdateWhen.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,15 +22,15 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to selectively include updates in. - /// The include function (current,previous)=>{ return true to include }. + /// The source IObservable<IChangeSet<TObject, TKey>> to selectively include updates in. + /// The Func<TObject, TObject, bool> include function (current,previous)=>{ return true to include }. /// An observable which emits change sets and ignores updates equal to the lambda. public static IObservable> IncludeUpdateWhen(this IObservable> source, Func includeFunction) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - includeFunction.ThrowArgumentNullExceptionIfNull(nameof(includeFunction)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(includeFunction); return source.Select( changes => diff --git a/src/DynamicData/Cache/ObservableCacheEx.InnerJoin.cs b/src/DynamicData/Cache/ObservableCacheEx.InnerJoin.cs index 8a80e640d..7b5e5e612 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.InnerJoin.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.InnerJoin.cs @@ -1,36 +1,42 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { - /// - /// The left to join. - /// The right to join. - /// A that maps each right item to the left key it should join on. - /// A that combines the left and right values into a destination object. The composite key is not provided in this overload. - /// Overload that omits the composite key from the result selector. Delegates to . + /// + /// Joins two changeset streams and projects matching left and right values without exposing the composite key to the selector. + /// + /// The item type of the left source. + /// The key type of the left source. + /// The item type of the right source. + /// The key type of the right source. + /// The type produced by . + /// The left IObservable<IChangeSet<TLeft, TLeftKey>> to join. + /// The right IObservable<IChangeSet<TRight, TRightKey>> to join. + /// A Func<T, TResult> that maps each right item to the left key it should join on. + /// A Func<T, TResult> that combines the left and right values into a destination object. The composite key is not provided in this overload. + /// An observable changeset keyed by a composite (TLeftKey, TRightKey) tuple. + /// Overload that omits the composite key from the result selector. Delegates to InnerJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<ValueTuple<TLeftKey, TRightKey>, TLeft, TRight, TDestination>). public static IObservable> InnerJoin(this IObservable> left, IObservable> right, Func rightKeySelector, Func resultSelector) where TLeft : notnull where TLeftKey : notnull @@ -38,10 +44,10 @@ public static partial class ObservableCacheEx where TRightKey : notnull where TDestination : notnull { - left.ThrowArgumentNullExceptionIfNull(nameof(left)); - right.ThrowArgumentNullExceptionIfNull(nameof(right)); - rightKeySelector.ThrowArgumentNullExceptionIfNull(nameof(rightKeySelector)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(left); + ArgumentExceptionHelper.ThrowIfNull(right); + ArgumentExceptionHelper.ThrowIfNull(rightKeySelector); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return left.InnerJoin(right, rightKeySelector, (_, leftValue, rightValue) => resultSelector(leftValue, rightValue)); } @@ -55,10 +61,10 @@ public static partial class ObservableCacheEx /// The item type of the right source. /// The key type of the right source. /// The type produced by . - /// The left to join. - /// The right to join. - /// A that maps each right item to the left key it should join on. - /// A that combines the composite key, left value, and right value into a destination object. Example: ((leftKey, rightKey), left, right) => new Result(leftKey, rightKey, left, right). + /// The left IObservable<IChangeSet<TLeft, TLeftKey>> to join. + /// The right IObservable<IChangeSet<TRight, TRightKey>> to join. + /// A Func<T, TResult> that maps each right item to the left key it should join on. + /// A Func<T, TResult> that combines the composite key, left value, and right value into a destination object. Example: ((leftKey, rightKey), left, right) => new Result(leftKey, rightKey, left, right). /// An observable changeset keyed by a composite (TLeftKey, TRightKey) tuple. /// /// @@ -85,10 +91,10 @@ public static partial class ObservableCacheEx /// Both sources are serialized through a shared lock held during downstream delivery. Avoid blocking operations in subscribers. /// /// Any argument is . - /// - /// - /// - /// + /// LeftJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, TLeft, Optional<TRight>, TDestination>) + /// RightJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TRightKey, Optional<TLeft>, TRight, TDestination>) + /// FullJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, Optional<TLeft>, Optional<TRight>, TDestination>) + /// InnerJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, TLeft, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>) public static IObservable> InnerJoin(this IObservable> left, IObservable> right, Func rightKeySelector, Func<(TLeftKey leftKey, TRightKey rightKey), TLeft, TRight, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull @@ -96,10 +102,10 @@ public static partial class ObservableCacheEx where TRightKey : notnull where TDestination : notnull { - left.ThrowArgumentNullExceptionIfNull(nameof(left)); - right.ThrowArgumentNullExceptionIfNull(nameof(right)); - rightKeySelector.ThrowArgumentNullExceptionIfNull(nameof(rightKeySelector)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(left); + ArgumentExceptionHelper.ThrowIfNull(right); + ArgumentExceptionHelper.ThrowIfNull(rightKeySelector); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return new InnerJoin(left, right, rightKeySelector, resultSelector).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.InnerJoinMany.cs b/src/DynamicData/Cache/ObservableCacheEx.InnerJoinMany.cs index 60bf2a343..a186f9f74 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.InnerJoinMany.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.InnerJoinMany.cs @@ -1,36 +1,42 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { - /// - /// The left to join. - /// The right to join. - /// A that maps each right item to the left key it should join on. - /// A that combines the left value and the right group into a destination object. The key is not provided in this overload. - /// Overload that omits the key from the result selector. Delegates to . + /// + /// Provides an overload of InnerJoinMany for the supplied arguments. + /// + /// The type of the TLeft value. + /// The type of the TLeftKey value. + /// The type of the TRight value. + /// The type of the TRightKey value. + /// The type of the TDestination value. + /// The left IObservable<IChangeSet<TLeft, TLeftKey>> to join. + /// The right IObservable<IChangeSet<TRight, TRightKey>> to join. + /// A Func<T, TResult> that maps each right item to the left key it should join on. + /// A Func<T, TResult> that combines the left value and the right group into a destination object. The key is not provided in this overload. + /// The resulting observable sequence. + /// Overload that omits the key from the result selector. Delegates to InnerJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, TLeft, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>). public static IObservable> InnerJoinMany(this IObservable> left, IObservable> right, Func rightKeySelector, Func, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull @@ -38,10 +44,10 @@ public static IObservable> InnerJoinMany resultSelector(leftValue, rightValue)); } @@ -56,10 +62,10 @@ public static IObservable> InnerJoinManyThe item type of the right source. /// The key type of the right source. /// The type produced by . - /// The left to join. - /// The right to join. - /// A that maps each right item to the left key it should join on. - /// A that combines the key, left value, and right group into a destination object. Example: (key, left, group) => new Result(key, left, group). + /// The left IObservable<IChangeSet<TLeft, TLeftKey>> to join. + /// The right IObservable<IChangeSet<TRight, TRightKey>> to join. + /// A Func<T, TResult> that maps each right item to the left key it should join on. + /// A Func<T, TResult> that combines the key, left value, and right group into a destination object. Example: (key, left, group) => new Result(key, left, group). /// An observable changeset keyed by . /// /// @@ -85,10 +91,10 @@ public static IObservable> InnerJoinManyBoth sources are serialized through a shared lock held during downstream delivery. Avoid blocking operations in subscribers. /// /// Any argument is . - /// - /// - /// - /// + /// InnerJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<ValueTuple<TLeftKey, TRightKey>, TLeft, TRight, TDestination>) + /// LeftJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, TLeft, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>) + /// RightJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, Optional<TLeft>, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>) + /// FullJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, Optional<TLeft>, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>) public static IObservable> InnerJoinMany(this IObservable> left, IObservable> right, Func rightKeySelector, Func, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull @@ -96,10 +102,10 @@ public static IObservable> InnerJoinMany(left, right, rightKeySelector, resultSelector).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.InvokeEvaluate.cs b/src/DynamicData/Cache/ObservableCacheEx.InvokeEvaluate.cs index f19fde951..e28de5485 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.InvokeEvaluate.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.InvokeEvaluate.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,7 +29,7 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to trigger re-evaluation on. + /// The source IObservable<IChangeSet<TObject, TKey>> to trigger re-evaluation on. /// An observable that emits the same changesets as , unchanged. /// /// @@ -44,5 +42,9 @@ public static partial class ObservableCacheEx /// public static IObservable> InvokeEvaluate(this IObservable> source) where TObject : IEvaluateAware - where TKey : notnull => source.Do(changes => changes.Where(u => u.Reason == ChangeReason.Refresh).ForEach(u => u.Current.Evaluate())); + where TKey : notnull + { + ArgumentExceptionHelper.ThrowIfNull(source); + return source.Do(changes => changes.Where(u => u.Reason == ChangeReason.Refresh).ForEach(u => u.Current.Evaluate())); + } } diff --git a/src/DynamicData/Cache/ObservableCacheEx.LeftJoin.cs b/src/DynamicData/Cache/ObservableCacheEx.LeftJoin.cs index 5da9d2abe..fa246e0e3 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.LeftJoin.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.LeftJoin.cs @@ -1,54 +1,60 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { - /// - /// The left to join. - /// The right to join. - /// A that maps each right item to the left key it should join on. - /// A that combines the left value and the optional right into a destination object. The key is not provided in this overload. - /// Overload that omits the key from the result selector. Delegates to . - public static IObservable> LeftJoin(this IObservable> left, IObservable> right, Func rightKeySelector, Func, TDestination> resultSelector) + /// + /// Provides an overload of LeftJoin for the supplied arguments. + /// + /// The type of the TLeft value. + /// The type of the TLeftKey value. + /// The type of the TRight value. + /// The type of the TRightKey value. + /// The type of the TDestination value. + /// The left IObservable<IChangeSet<TLeft, TLeftKey>> to join. + /// The right IObservable<IChangeSet<TRight, TRightKey>> to join. + /// A Func<T, TResult> that maps each right item to the left key it should join on. + /// A Func<T, TResult> that combines the left value and the optional right into a destination object. The key is not provided in this overload. + /// The resulting observable sequence. + /// Overload that omits the key from the result selector. Delegates to LeftJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, TLeft, Optional<TRight>, TDestination>). + public static IObservable> LeftJoin(this IObservable> left, IObservable> right, Func rightKeySelector, Func, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull where TRight : notnull where TRightKey : notnull where TDestination : notnull { - left.ThrowArgumentNullExceptionIfNull(nameof(left)); - right.ThrowArgumentNullExceptionIfNull(nameof(right)); - rightKeySelector.ThrowArgumentNullExceptionIfNull(nameof(rightKeySelector)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(left); + ArgumentExceptionHelper.ThrowIfNull(right); + ArgumentExceptionHelper.ThrowIfNull(rightKeySelector); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return left.LeftJoin(right, rightKeySelector, (_, leftValue, rightValue) => resultSelector(leftValue, rightValue)); } /// /// Joins two changeset streams, producing a result for every left-side key. The right side is - /// because a matching right item may or may not exist. All left items + /// Optional<T> because a matching right item may or may not exist. All left items /// appear in the output regardless. Equivalent to SQL LEFT OUTER JOIN. /// /// The item type of the left source. @@ -56,17 +62,17 @@ public static IObservable> LeftJoinThe item type of the right source. /// The key type of the right source. /// The type produced by . - /// The left to join. - /// The right to join. - /// A that maps each right item to the left key it should join on. - /// A that combines the key, left value, and optional right into a destination object. Example: (key, left, right) => new Result(key, left, right). + /// The left IObservable<IChangeSet<TLeft, TLeftKey>> to join. + /// The right IObservable<IChangeSet<TRight, TRightKey>> to join. + /// A Func<T, TResult> that maps each right item to the left key it should join on. + /// A Func<T, TResult> that combines the key, left value, and optional right into a destination object. Example: (key, left, right) => new Result(key, left, right). /// An observable changeset keyed by . /// /// /// Left-side change handling: /// /// EventBehavior - /// AddAlways emits. Invokes with the left value and matching right (or ). + /// AddAlways emits. Invokes with the left value and matching right (or ReactiveUI.Primitives.Optional.None<T>). /// UpdateRe-invokes the selector with the new left value and current right (if any). /// RemoveRemoves the joined result. /// RefreshForwarded as Refresh on the joined result. @@ -85,21 +91,21 @@ public static IObservable> LeftJoinBoth sources are serialized through a shared lock held during downstream delivery. Avoid blocking operations in subscribers. /// /// Any argument is . - /// - /// - /// - /// - public static IObservable> LeftJoin(this IObservable> left, IObservable> right, Func rightKeySelector, Func, TDestination> resultSelector) + /// InnerJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<ValueTuple<TLeftKey, TRightKey>, TLeft, TRight, TDestination>) + /// RightJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TRightKey, Optional<TLeft>, TRight, TDestination>) + /// FullJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, Optional<TLeft>, Optional<TRight>, TDestination>) + /// LeftJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, TLeft, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>) + public static IObservable> LeftJoin(this IObservable> left, IObservable> right, Func rightKeySelector, Func, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull where TRight : notnull where TRightKey : notnull where TDestination : notnull { - left.ThrowArgumentNullExceptionIfNull(nameof(left)); - right.ThrowArgumentNullExceptionIfNull(nameof(right)); - rightKeySelector.ThrowArgumentNullExceptionIfNull(nameof(rightKeySelector)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(left); + ArgumentExceptionHelper.ThrowIfNull(right); + ArgumentExceptionHelper.ThrowIfNull(rightKeySelector); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return new LeftJoin(left, right, rightKeySelector, resultSelector).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.LeftJoinMany.cs b/src/DynamicData/Cache/ObservableCacheEx.LeftJoinMany.cs index 726ec5f1f..ed93ff37d 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.LeftJoinMany.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.LeftJoinMany.cs @@ -1,36 +1,42 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { - /// - /// The left to join. - /// The right to join. - /// A that maps each right item to the left key it should join on. - /// A that combines the left value and the right group into a destination object. The key is not provided in this overload. - /// Overload that omits the key from the result selector. Delegates to . + /// + /// Provides an overload of LeftJoinMany for the supplied arguments. + /// + /// The type of the TLeft value. + /// The type of the TLeftKey value. + /// The type of the TRight value. + /// The type of the TRightKey value. + /// The type of the TDestination value. + /// The left IObservable<IChangeSet<TLeft, TLeftKey>> to join. + /// The right IObservable<IChangeSet<TRight, TRightKey>> to join. + /// A Func<T, TResult> that maps each right item to the left key it should join on. + /// A Func<T, TResult> that combines the left value and the right group into a destination object. The key is not provided in this overload. + /// The resulting observable sequence. + /// Overload that omits the key from the result selector. Delegates to LeftJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, TLeft, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>). public static IObservable> LeftJoinMany(this IObservable> left, IObservable> right, Func rightKeySelector, Func, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull @@ -38,10 +44,10 @@ public static IObservable> LeftJoinMany resultSelector(leftValue, rightValue)); } @@ -56,10 +62,10 @@ public static IObservable> LeftJoinManyThe item type of the right source. /// The key type of the right source. /// The type produced by . - /// The left to join. - /// The right to join. - /// A that maps each right item to the left key it should join on. - /// A that combines the key, left value, and right group into a destination object. Example: (key, left, group) => new Result(key, left, group). + /// The left IObservable<IChangeSet<TLeft, TLeftKey>> to join. + /// The right IObservable<IChangeSet<TRight, TRightKey>> to join. + /// A Func<T, TResult> that maps each right item to the left key it should join on. + /// A Func<T, TResult> that combines the key, left value, and right group into a destination object. Example: (key, left, group) => new Result(key, left, group). /// An observable changeset keyed by . /// /// @@ -85,10 +91,10 @@ public static IObservable> LeftJoinManyBoth sources are serialized through a shared lock held during downstream delivery. Avoid blocking operations in subscribers. /// /// Any argument is . - /// - /// - /// - /// + /// LeftJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, TLeft, Optional<TRight>, TDestination>) + /// InnerJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, TLeft, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>) + /// RightJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, Optional<TLeft>, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>) + /// FullJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, Optional<TLeft>, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>) public static IObservable> LeftJoinMany(this IObservable> left, IObservable> right, Func rightKeySelector, Func, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull @@ -96,10 +102,10 @@ public static IObservable> LeftJoinMany(left, right, rightKeySelector, resultSelector).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.LimitSizeTo.cs b/src/DynamicData/Cache/ObservableCacheEx.LimitSizeTo.cs index 810f118d0..c374c90e1 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.LimitSizeTo.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.LimitSizeTo.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,7 +29,7 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to apply size limits to. + /// The source IObservable<IChangeSet<TObject, TKey>> to apply size limits to. /// The maximum number of items allowed. Must be greater than zero. /// An observable changeset stream with size-limited contents. /// @@ -49,7 +47,7 @@ public static IObservable> LimitSizeTo( where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); if (size <= 0) { @@ -60,12 +58,12 @@ public static IObservable> LimitSizeTo( } /// - /// Operates directly on a , removing the oldest items when the cache + /// Operates directly on a ISourceCache<TObject, TKey>, removing the oldest items when the cache /// exceeds . Returns an observable of the evicted key-value pairs (not a changeset stream). /// /// The type of the object. /// The type of the key. - /// The to operate on. + /// The ISourceCache<TObject, TKey> to operate on. /// The maximum number of items allowed. Must be greater than zero. /// An optional for observing changes. Defaults to . /// An observable that emits batches of evicted key-value pairs whenever the cache exceeds the size limit. @@ -75,7 +73,7 @@ public static IObservable>> LimitSizeTo< where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); if (sizeLimit <= 0) { diff --git a/src/DynamicData/Cache/ObservableCacheEx.MergeChangeSets.cs b/src/DynamicData/Cache/ObservableCacheEx.MergeChangeSets.cs index c29bd2a6b..195996299 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.MergeChangeSets.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.MergeChangeSets.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -33,23 +31,23 @@ public static partial class ObservableCacheEx /// /// The type of items in the changesets. /// The type of the key identifying items. - /// An that emits changeset streams. Each inner stream is subscribed as it appears. + /// An IObservable<T> that emits changeset streams. Each inner stream is subscribed as it appears. /// A unified changeset stream containing changes from all active source streams. /// /// /// Each inner changeset stream is independently tracked in its own cache. When multiple sources provide the same key, /// this overload uses first-in-wins semantics: the value from whichever source added the key first is /// the one published downstream. To control which value wins for duplicate keys, use an overload that - /// accepts an , which selects the lowest-ordered value across all sources. - /// An can be provided separately to suppress no-op updates when + /// accepts an IComparer<T>, which selects the lowest-ordered value across all sources. + /// An IEqualityComparer<T> can be provided separately to suppress no-op updates when /// the new value equals the currently published value for a key. /// /// /// Overload families: MergeChangeSets has 16 overloads organized along three axes: /// (1) Source type: dynamic (IObservable<IObservable<IChangeSet>>, sources arrive at runtime), - /// pair (source + other, exactly two streams), or static (, all sources known up front). - /// (2) Conflict resolution: none (first-in-wins), (lowest-ordered wins), - /// (suppresses duplicate updates), or both. + /// pair (source + other, exactly two streams), or static (IEnumerable<T>, all sources known up front). + /// (2) Conflict resolution: none (first-in-wins), IComparer<T> (lowest-ordered wins), + /// IEqualityComparer<T> (suppresses duplicate updates), or both. /// (3) Completion: static overloads accept a completable flag; when , the output never completes /// even after all sources finish (useful for "live" merge scenarios). /// @@ -69,14 +67,14 @@ public static partial class ObservableCacheEx /// /// /// is . - /// - /// - /// + /// MergeMany<TObject, TKey, TDestination>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, IObservable<TDestination>>) + /// MergeManyChangeSets<TObject, TKey, TDestination, TDestinationKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TKey, IObservable<IChangeSet<TDestination, TDestinationKey>>>, IComparer<TDestination>, IEqualityComparer<TDestination>) + /// ObservableListEx.MergeChangeSets public static IObservable> MergeChangeSets(this IObservable>> source) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new MergeChangeSets(source, equalityComparer: null, comparer: null).Run(); } @@ -88,16 +86,16 @@ public static IObservable> MergeChangeSets /// The type of items in the changesets. /// The type of the key identifying items. - /// An that emits changeset streams. Each inner stream is subscribed as it appears. - /// An that comparer to determine which value wins when multiple sources provide the same key. The lowest-ordered value is published. + /// An IObservable<T> that emits changeset streams. Each inner stream is subscribed as it appears. + /// An IComparer<TObject> that comparer to determine which value wins when multiple sources provide the same key. The lowest-ordered value is published. /// A unified changeset stream containing changes from all active source streams. /// or is null. public static IObservable> MergeChangeSets(this IObservable>> source, IComparer comparer) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - comparer.ThrowArgumentNullExceptionIfNull(nameof(comparer)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(comparer); return new MergeChangeSets(source, equalityComparer: null, comparer).Run(); } @@ -109,16 +107,16 @@ public static IObservable> MergeChangeSets /// The type of items in the changesets. /// The type of the key identifying items. - /// An that emits changeset streams. Each inner stream is subscribed as it appears. - /// An that equality comparer to detect duplicate values for the same key, suppressing no-op updates. + /// An IObservable<T> that emits changeset streams. Each inner stream is subscribed as it appears. + /// An IEqualityComparer<TObject> that equality comparer to detect duplicate values for the same key, suppressing no-op updates. /// A unified changeset stream containing changes from all active source streams. /// or is null. public static IObservable> MergeChangeSets(this IObservable>> source, IEqualityComparer equalityComparer) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - equalityComparer.ThrowArgumentNullExceptionIfNull(nameof(equalityComparer)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(equalityComparer); return new MergeChangeSets(source, equalityComparer, comparer: null).Run(); } @@ -129,18 +127,18 @@ public static IObservable> MergeChangeSets /// The type of items in the changesets. /// The type of the key identifying items. - /// An that emits changeset streams. Each inner stream is subscribed as it appears. - /// An that equality comparer to detect duplicate values for the same key, suppressing no-op updates. - /// An that comparer to determine which value wins when multiple sources provide the same key. The lowest-ordered value is published. + /// An IObservable<T> that emits changeset streams. Each inner stream is subscribed as it appears. + /// An IEqualityComparer<TObject> that equality comparer to detect duplicate values for the same key, suppressing no-op updates. + /// An IComparer<TObject> that comparer to determine which value wins when multiple sources provide the same key. The lowest-ordered value is published. /// A unified changeset stream containing changes from all active source streams. /// , , or is null. public static IObservable> MergeChangeSets(this IObservable>> source, IEqualityComparer equalityComparer, IComparer comparer) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - equalityComparer.ThrowArgumentNullExceptionIfNull(nameof(equalityComparer)); - comparer.ThrowArgumentNullExceptionIfNull(nameof(comparer)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(equalityComparer); + ArgumentExceptionHelper.ThrowIfNull(comparer); return new MergeChangeSets(source, equalityComparer, comparer).Run(); } @@ -151,8 +149,8 @@ public static IObservable> MergeChangeSets /// The type of items in the changesets. /// The type of the key identifying items. - /// The source to merge. - /// The second to merge with . + /// The source IObservable<IChangeSet<TObject, TKey>> to merge. + /// The second IObservable<IChangeSet<TObject, TKey>> to merge with . /// An optional used when subscribing to the source streams. /// If (default), the output completes when both streams complete. If , the output never completes. /// A unified changeset stream containing changes from both sources. @@ -161,8 +159,8 @@ public static IObservable> MergeChangeSets> MergeChangeSets /// The type of items in the changesets. /// The type of the key identifying items. - /// The source to merge. - /// The second to merge with . - /// An that comparer to determine which value wins when both sources provide the same key. + /// The source IObservable<IChangeSet<TObject, TKey>> to merge. + /// The second IObservable<IChangeSet<TObject, TKey>> to merge with . + /// An IComparer<TObject> that comparer to determine which value wins when both sources provide the same key. /// An optional used when subscribing to the source streams. /// If (default), the output completes when both streams complete. If , the output never completes. /// A unified changeset stream containing changes from both sources. @@ -183,9 +181,9 @@ public static IObservable> MergeChangeSets> MergeChangeSets /// The type of items in the changesets. /// The type of the key identifying items. - /// The source to merge. - /// The second to merge with . - /// An that equality comparer to detect duplicate values for the same key. + /// The source IObservable<IChangeSet<TObject, TKey>> to merge. + /// The second IObservable<IChangeSet<TObject, TKey>> to merge with . + /// An IEqualityComparer<TObject> that equality comparer to detect duplicate values for the same key. /// An optional used when subscribing to the source streams. /// If (default), the output completes when both streams complete. If , the output never completes. /// A unified changeset stream containing changes from both sources. @@ -206,9 +204,9 @@ public static IObservable> MergeChangeSets> MergeChangeSets /// The type of items in the changesets. /// The type of the key identifying items. - /// The source to merge. - /// The second to merge with . - /// An that equality comparer to detect duplicate values for the same key. - /// An that comparer to determine which value wins when both sources provide the same key. + /// The source IObservable<IChangeSet<TObject, TKey>> to merge. + /// The second IObservable<IChangeSet<TObject, TKey>> to merge with . + /// An IEqualityComparer<TObject> that equality comparer to detect duplicate values for the same key. + /// An IComparer<TObject> that comparer to determine which value wins when both sources provide the same key. /// An optional used when subscribing to the source streams. /// If (default), the output completes when both streams complete. If , the output never completes. /// A unified changeset stream containing changes from both sources. @@ -230,10 +228,10 @@ public static IObservable> MergeChangeSets> MergeChangeSets /// The type of items in the changesets. /// The type of the key identifying items. - /// The source to merge. - /// The additional streams to merge with . + /// The source IObservable<IChangeSet<TObject, TKey>> to merge. + /// The additional IObservable<IChangeSet<TObject, TKey>> streams to merge with . /// An optional used when subscribing to the source streams. /// If (default), the output completes when all streams complete. If , the output never completes. /// A unified changeset stream containing changes from all sources. @@ -254,8 +252,8 @@ public static IObservable> MergeChangeSets> MergeChangeSets /// The type of items in the changesets. /// The type of the key identifying items. - /// The source to merge. - /// The additional streams to merge with . - /// An that comparer to determine which value wins when multiple sources provide the same key. + /// The source IObservable<IChangeSet<TObject, TKey>> to merge. + /// The additional IObservable<IChangeSet<TObject, TKey>> streams to merge with . + /// An IComparer<TObject> that comparer to determine which value wins when multiple sources provide the same key. /// An optional used when subscribing to the source streams. /// If (default), the output completes when all streams complete. If , the output never completes. /// A unified changeset stream containing changes from all sources. @@ -276,9 +274,9 @@ public static IObservable> MergeChangeSets> MergeChangeSets /// The type of items in the changesets. /// The type of the key identifying items. - /// The source to merge. - /// The additional streams to merge with . - /// An that equality comparer to detect duplicate values for the same key. + /// The source IObservable<IChangeSet<TObject, TKey>> to merge. + /// The additional IObservable<IChangeSet<TObject, TKey>> streams to merge with . + /// An IEqualityComparer<TObject> that equality comparer to detect duplicate values for the same key. /// An optional used when subscribing to the source streams. /// If (default), the output completes when all streams complete. If , the output never completes. /// A unified changeset stream containing changes from all sources. @@ -299,9 +297,9 @@ public static IObservable> MergeChangeSets> MergeChangeSets /// The type of items in the changesets. /// The type of the key identifying items. - /// The source to merge. - /// The additional streams to merge with . - /// An that equality comparer to detect duplicate values for the same key. - /// An that comparer to determine which value wins when multiple sources provide the same key. + /// The source IObservable<IChangeSet<TObject, TKey>> to merge. + /// The additional IObservable<IChangeSet<TObject, TKey>> streams to merge with . + /// An IEqualityComparer<TObject> that equality comparer to detect duplicate values for the same key. + /// An IComparer<TObject> that comparer to determine which value wins when multiple sources provide the same key. /// An optional used when subscribing to the source streams. /// If (default), the output completes when all streams complete. If , the output never completes. /// A unified changeset stream containing changes from all sources. @@ -323,10 +321,10 @@ public static IObservable> MergeChangeSets> MergeChangeSets /// The type of items in the changesets. /// The type of the key identifying items. - /// The source to merge. + /// The source IEnumerable<T> to merge. /// An optional used when subscribing to the source streams. /// If (default), the output completes when all source streams have completed. If , the output never completes. /// A unified changeset stream containing changes from all source streams. @@ -346,7 +344,7 @@ public static IObservable> MergeChangeSets. + /// that accepts an IComparer<T>. /// /// /// An error from any source terminates the entire merged output. @@ -357,7 +355,7 @@ public static IObservable> MergeChangeSets(source, equalityComparer: null, comparer: null, completable, scheduler).Run(); } @@ -369,8 +367,8 @@ public static IObservable> MergeChangeSets /// The type of items in the changesets. /// The type of the key identifying items. - /// The source to merge. - /// An that comparer to determine which value wins when multiple sources provide the same key. The lowest-ordered value is published. + /// The source IEnumerable<T> to merge. + /// An IComparer<TObject> that comparer to determine which value wins when multiple sources provide the same key. The lowest-ordered value is published. /// An optional used when subscribing to the source streams. /// If (default), the output completes when all source streams have completed. If , the output never completes. /// A unified changeset stream containing changes from all source streams. @@ -379,8 +377,8 @@ public static IObservable> MergeChangeSets(source, equalityComparer: null, comparer, completable, scheduler).Run(); } @@ -392,8 +390,8 @@ public static IObservable> MergeChangeSets /// The type of items in the changesets. /// The type of the key identifying items. - /// The source to merge. - /// An that equality comparer to detect duplicate values for the same key, suppressing no-op updates. + /// The source IEnumerable<T> to merge. + /// An IEqualityComparer<TObject> that equality comparer to detect duplicate values for the same key, suppressing no-op updates. /// An optional used when subscribing to the source streams. /// If (default), the output completes when all source streams have completed. If , the output never completes. /// A unified changeset stream containing changes from all source streams. @@ -402,8 +400,8 @@ public static IObservable> MergeChangeSets(source, equalityComparer, comparer: null, completable, scheduler).Run(); } @@ -414,9 +412,9 @@ public static IObservable> MergeChangeSets /// The type of items in the changesets. /// The type of the key identifying items. - /// The source to merge. - /// An that equality comparer to detect duplicate values for the same key, suppressing no-op updates. - /// An that comparer to determine which value wins when multiple sources provide the same key. The lowest-ordered value is published. + /// The source IEnumerable<T> to merge. + /// An IEqualityComparer<TObject> that equality comparer to detect duplicate values for the same key, suppressing no-op updates. + /// An IComparer<TObject> that comparer to determine which value wins when multiple sources provide the same key. The lowest-ordered value is published. /// An optional used when subscribing to the source streams. /// If (default), the output completes when all source streams have completed. If , the output never completes. /// A unified changeset stream containing changes from all source streams. @@ -425,9 +423,9 @@ public static IObservable> MergeChangeSets(source, equalityComparer, comparer, completable, scheduler).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.MergeMany.cs b/src/DynamicData/Cache/ObservableCacheEx.MergeMany.cs index b3c290d34..9dbde938b 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.MergeMany.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.MergeMany.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -27,15 +25,15 @@ public static partial class ObservableCacheEx { /// /// Subscribes to a child observable for each item in the source cache changeset stream and merges all child - /// emissions into a single . When an item is added, + /// emissions into a single IObservable<T>. When an item is added, /// creates its child subscription. When updated, the previous child subscription is disposed and a new one is created. /// When removed, its child subscription is disposed. Refresh changes have no effect on subscriptions. /// /// The type of items in the source cache. /// The type of the key identifying source cache items. /// The type of values emitted by child observables. - /// The source whose items each produce an observable. - /// A factory function that produces a child observable for each source item. + /// The source IObservable<IChangeSet<TObject, TKey>> whose items each produce an observable. + /// A Func<T, TResult> factory function that produces a child observable for each source item. /// An observable that emits values from all active child observables, interleaved by arrival order. /// /// @@ -51,32 +49,38 @@ public static partial class ObservableCacheEx /// RefreshNo effect on subscriptions. The child observable continues unchanged. /// OnErrorErrors from child observables are silently swallowed (the child is unsubscribed). Errors from the source changeset stream terminate the merged output. /// - /// Worth noting: The output is a plain , not a changeset stream. If you need merged changesets, use instead. + /// Worth noting: The output is a plain IObservable<TDestination>, not a changeset stream. If you need merged changesets, use MergeManyChangeSets<TObject, TKey, TDestination, TDestinationKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TKey, IObservable<IChangeSet<TDestination, TDestinationKey>>>, IComparer<TDestination>, IEqualityComparer<TDestination>) instead. /// /// or is null. - /// - /// - /// - /// + /// MergeManyChangeSets<TObject, TKey, TDestination, TDestinationKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TKey, IObservable<IChangeSet<TDestination, TDestinationKey>>>, IComparer<TDestination>, IEqualityComparer<TDestination>) + /// MergeChangeSets<TObject, TKey>(IObservable<IObservable<IChangeSet<TObject, TKey>>>) + /// SubscribeMany<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TKey, IDisposable>) + /// ObservableListEx.MergeMany public static IObservable MergeMany(this IObservable> source, Func> observableSelector) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); return new MergeMany(source, observableSelector).Run(); } - /// - /// The source whose items each produce an observable. - /// A factory function that receives both the item and its key, and returns a child observable. + /// + /// Provides an overload of MergeMany for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The type of the TDestination value. + /// The source IObservable<IChangeSet<TObject, TKey>> whose items each produce an observable. + /// A Func<T, TResult> factory function that receives both the item and its key, and returns a child observable. + /// The resulting observable sequence. public static IObservable MergeMany(this IObservable> source, Func> observableSelector) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); return new MergeMany(source, observableSelector).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.MergeManyChangeSets.cs b/src/DynamicData/Cache/ObservableCacheEx.MergeManyChangeSets.cs index 5fcbb9ed0..df5f51d1f 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.MergeManyChangeSets.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.MergeManyChangeSets.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -34,19 +32,19 @@ public static partial class ObservableCacheEx /// The type of the key identifying source cache items. /// The type of items in the child changeset streams. /// The type of the key identifying child items. - /// The source whose items each produce a child changeset stream. - /// A factory function that receives a source item and returns a child cache changeset stream. - /// An that comparer to resolve key conflicts when multiple child streams provide items with the same destination key. The lowest-ordered item wins. + /// The source IObservable<IChangeSet<TObject, TKey>> whose items each produce a child changeset stream. + /// A Func<T, TResult> factory function that receives a source item and returns a child cache changeset stream. + /// An IComparer<TDestination> that comparer to resolve key conflicts when multiple child streams provide items with the same destination key. The lowest-ordered item wins. /// A merged changeset stream containing items from all active child streams. /// or is null. - /// + /// ObservableListEx.MergeManyChangeSets public static IObservable> MergeManyChangeSets(this IObservable> source, Func>> observableSelector, IComparer comparer) where TObject : notnull where TKey : notnull where TDestination : notnull where TDestinationKey : notnull { - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); return source.MergeManyChangeSets((t, _) => observableSelector(t), comparer); } @@ -59,9 +57,9 @@ public static IObservable> MergeManyCh /// The type of the key identifying source cache items. /// The type of items in the child changeset streams. /// The type of the key identifying child items. - /// The source whose items each produce a child changeset stream. - /// A factory function that receives a source item and its key, and returns a child cache changeset stream. - /// An that comparer to resolve key conflicts when multiple child streams provide items with the same destination key. The lowest-ordered item wins. + /// The source IObservable<IChangeSet<TObject, TKey>> whose items each produce a child changeset stream. + /// A Func<T, TResult> factory function that receives a source item and its key, and returns a child cache changeset stream. + /// An IComparer<TDestination> that comparer to resolve key conflicts when multiple child streams provide items with the same destination key. The lowest-ordered item wins. /// A merged changeset stream containing items from all active child streams. /// , , or is null. public static IObservable> MergeManyChangeSets(this IObservable> source, Func>> observableSelector, IComparer comparer) @@ -70,9 +68,9 @@ public static IObservable> MergeManyCh where TDestination : notnull where TDestinationKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); - comparer.ThrowArgumentNullExceptionIfNull(nameof(comparer)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); + ArgumentExceptionHelper.ThrowIfNull(comparer); return source.MergeManyChangeSets(observableSelector, equalityComparer: null, comparer: comparer); } @@ -85,10 +83,10 @@ public static IObservable> MergeManyCh /// The type of the key identifying source cache items. /// The type of items in the child changeset streams. /// The type of the key identifying child items. - /// The source whose items each produce a child changeset stream. - /// A factory function that receives a source item and returns a child cache changeset stream. - /// An that optional equality comparer to suppress updates when the incoming child value equals the current value for a destination key. - /// An that optional comparer to resolve key conflicts when multiple child streams provide items with the same destination key. The lowest-ordered item wins. + /// The source IObservable<IChangeSet<TObject, TKey>> whose items each produce a child changeset stream. + /// A Func<T, TResult> factory function that receives a source item and returns a child cache changeset stream. + /// An IEqualityComparer<TDestination> that optional equality comparer to suppress updates when the incoming child value equals the current value for a destination key. + /// An IComparer<TDestination> that optional comparer to resolve key conflicts when multiple child streams provide items with the same destination key. The lowest-ordered item wins. /// A merged changeset stream containing items from all active child streams. /// or is null. public static IObservable> MergeManyChangeSets(this IObservable> source, Func>> observableSelector, IEqualityComparer? equalityComparer = null, IComparer? comparer = null) @@ -97,8 +95,8 @@ public static IObservable> MergeManyCh where TDestination : notnull where TDestinationKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); return source.MergeManyChangeSets((t, _) => observableSelector(t), equalityComparer, comparer); } @@ -112,14 +110,14 @@ public static IObservable> MergeManyCh /// The type of the key identifying parent items. /// The type of items in the child changeset streams. /// The type of the key identifying child items. - /// The source whose items each produce a child changeset stream. - /// A factory function that receives a parent item and its key, and returns a child cache changeset stream. Called once per parent Add/Update. - /// An that optional equality comparer to suppress no-op child updates. When a child key's new value equals the current value per this comparer, the update is not emitted. - /// An that optional comparer to resolve child key conflicts when multiple parents contribute children with the same destination key. The lowest-ordered child value wins. Without a comparer, the first parent to provide a key retains priority. + /// The source IObservable<IChangeSet<TObject, TKey>> whose items each produce a child changeset stream. + /// A Func<T, TResult> factory function that receives a parent item and its key, and returns a child cache changeset stream. Called once per parent Add/Update. + /// An IEqualityComparer<TDestination> that optional equality comparer to suppress no-op child updates. When a child key's new value equals the current value per this comparer, the update is not emitted. + /// An IComparer<TDestination> that optional comparer to resolve child key conflicts when multiple parents contribute children with the same destination key. The lowest-ordered child value wins. Without a comparer, the first parent to provide a key retains priority. /// A merged changeset stream containing all child items from all active parent subscriptions. /// /// - /// This is the changeset-aware counterpart to . + /// This is the changeset-aware counterpart to MergeMany<TObject, TKey, TDestination>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, IObservable<TDestination>>). /// Where MergeMany produces a flat IObservable<T>, MergeManyChangeSets produces an IObservable<IChangeSet> /// that tracks the full lifecycle of child items, including key conflict resolution across parents. /// @@ -148,7 +146,7 @@ public static IObservable> MergeManyCh /// /// /// EventBehavior - /// OnErrorAn error from the source (parent) stream or from any child changeset stream terminates the entire output. Unlike , child errors are NOT swallowed. + /// OnErrorAn error from the source (parent) stream or from any child changeset stream terminates the entire output. Unlike MergeMany<TObject, TKey, TDestination>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, IObservable<TDestination>>), child errors are NOT swallowed. /// OnCompletedThe output completes when the source (parent) stream completes and all active child changeset streams have also completed. /// /// @@ -160,17 +158,17 @@ public static IObservable> MergeManyCh /// /// /// or is . - /// - /// - /// + /// MergeMany<TObject, TKey, TDestination>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, IObservable<TDestination>>) + /// MergeChangeSets<TObject, TKey>(IObservable<IObservable<IChangeSet<TObject, TKey>>>) + /// TransformMany<TDestination, TDestinationKey, TSource, TSourceKey>(IObservable<IChangeSet<TSource, TSourceKey>>, Func<TSource, IObservable<IChangeSet<TDestination, TDestinationKey>>>, Func<TDestination, TDestinationKey>) public static IObservable> MergeManyChangeSets(this IObservable> source, Func>> observableSelector, IEqualityComparer? equalityComparer = null, IComparer? comparer = null) where TObject : notnull where TKey : notnull where TDestination : notnull where TDestinationKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); return new MergeManyCacheChangeSets(source, observableSelector, equalityComparer, comparer).Run(); } @@ -185,10 +183,10 @@ public static IObservable> MergeManyCh /// The type of the key identifying source cache items. /// The type of items in the child changeset streams. /// The type of the key identifying child items. - /// The source whose items each produce a child changeset stream. - /// A factory function that receives a source item and returns a child cache changeset stream. - /// An that comparer to prioritize between source items when their children produce the same destination key. Lower-ordered source wins. - /// An that fallback comparer to resolve destination key conflicts when source items compare equal. + /// The source IObservable<IChangeSet<TObject, TKey>> whose items each produce a child changeset stream. + /// A Func<T, TResult> factory function that receives a source item and returns a child cache changeset stream. + /// An IComparer<TObject> that comparer to prioritize between source items when their children produce the same destination key. Lower-ordered source wins. + /// An IComparer<TDestination> that fallback comparer to resolve destination key conflicts when source items compare equal. /// A merged changeset stream with conflicts resolved by source priority. /// or is null. public static IObservable> MergeManyChangeSets(this IObservable> source, Func>> observableSelector, IComparer sourceComparer, IComparer childComparer) @@ -197,8 +195,8 @@ public static IObservable> MergeManyCh where TDestination : notnull where TDestinationKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); return source.MergeManyChangeSets((t, _) => observableSelector(t), sourceComparer, DefaultResortOnSourceRefresh, equalityComparer: null, childComparer); } @@ -212,10 +210,10 @@ public static IObservable> MergeManyCh /// The type of the key identifying source cache items. /// The type of items in the child changeset streams. /// The type of the key identifying child items. - /// The source whose items each produce a child changeset stream. - /// A factory function that receives a source item and its key, and returns a child cache changeset stream. - /// An that comparer to prioritize between source items when their children produce the same destination key. Lower-ordered source wins. - /// An that fallback comparer to resolve destination key conflicts when source items compare equal. + /// The source IObservable<IChangeSet<TObject, TKey>> whose items each produce a child changeset stream. + /// A Func<T, TResult> factory function that receives a source item and its key, and returns a child cache changeset stream. + /// An IComparer<TObject> that comparer to prioritize between source items when their children produce the same destination key. Lower-ordered source wins. + /// An IComparer<TDestination> that fallback comparer to resolve destination key conflicts when source items compare equal. /// A merged changeset stream with conflicts resolved by source priority. /// or is null. public static IObservable> MergeManyChangeSets(this IObservable> source, Func>> observableSelector, IComparer sourceComparer, IComparer childComparer) @@ -232,11 +230,11 @@ public static IObservable> MergeManyCh /// The type of the key identifying source cache items. /// The type of items in the child changeset streams. /// The type of the key identifying child items. - /// The source whose items each produce a child changeset stream. - /// A factory function that receives a source item and returns a child cache changeset stream. - /// An that comparer to prioritize between source items when their children produce the same destination key. + /// The source IObservable<IChangeSet<TObject, TKey>> whose items each produce a child changeset stream. + /// A Func<T, TResult> factory function that receives a source item and returns a child cache changeset stream. + /// An IComparer<TObject> that comparer to prioritize between source items when their children produce the same destination key. /// If , a Refresh in the source stream re-evaluates source priorities. If , Refresh events are ignored for priority recalculation. - /// An that fallback comparer to resolve destination key conflicts when source items compare equal. + /// An IComparer<TDestination> that fallback comparer to resolve destination key conflicts when source items compare equal. /// A merged changeset stream with conflicts resolved by source priority. /// or is null. public static IObservable> MergeManyChangeSets(this IObservable> source, Func>> observableSelector, IComparer sourceComparer, bool resortOnSourceRefresh, IComparer childComparer) @@ -245,8 +243,8 @@ public static IObservable> MergeManyCh where TDestination : notnull where TDestinationKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); return source.MergeManyChangeSets((t, _) => observableSelector(t), sourceComparer, resortOnSourceRefresh, equalityComparer: null, childComparer); } @@ -259,11 +257,11 @@ public static IObservable> MergeManyCh /// The type of the key identifying source cache items. /// The type of items in the child changeset streams. /// The type of the key identifying child items. - /// The source whose items each produce a child changeset stream. - /// A factory function that receives a source item and its key, and returns a child cache changeset stream. - /// An that comparer to prioritize between source items when their children produce the same destination key. + /// The source IObservable<IChangeSet<TObject, TKey>> whose items each produce a child changeset stream. + /// A Func<T, TResult> factory function that receives a source item and its key, and returns a child cache changeset stream. + /// An IComparer<TObject> that comparer to prioritize between source items when their children produce the same destination key. /// If , a Refresh in the source stream re-evaluates source priorities. If , Refresh events are ignored for priority recalculation. - /// An that fallback comparer to resolve destination key conflicts when source items compare equal. + /// An IComparer<TDestination> that fallback comparer to resolve destination key conflicts when source items compare equal. /// A merged changeset stream with conflicts resolved by source priority. /// or is null. public static IObservable> MergeManyChangeSets(this IObservable> source, Func>> observableSelector, IComparer sourceComparer, bool resortOnSourceRefresh, IComparer childComparer) @@ -281,11 +279,11 @@ public static IObservable> MergeManyCh /// The type of the key identifying source cache items. /// The type of items in the child changeset streams. /// The type of the key identifying child items. - /// The source whose items each produce a child changeset stream. - /// A factory function that receives a source item and returns a child cache changeset stream. - /// An that comparer to prioritize between source items when their children produce the same destination key. - /// An that optional equality comparer to suppress updates when the incoming child value equals the current value. - /// An that optional fallback comparer for destination key conflicts when source items compare equal. + /// The source IObservable<IChangeSet<TObject, TKey>> whose items each produce a child changeset stream. + /// A Func<T, TResult> factory function that receives a source item and returns a child cache changeset stream. + /// An IComparer<TObject> that comparer to prioritize between source items when their children produce the same destination key. + /// An IEqualityComparer<TDestination> that optional equality comparer to suppress updates when the incoming child value equals the current value. + /// An IComparer<TDestination> that optional fallback comparer for destination key conflicts when source items compare equal. /// A merged changeset stream with conflicts resolved by source priority. /// or is null. public static IObservable> MergeManyChangeSets(this IObservable> source, Func>> observableSelector, IComparer sourceComparer, IEqualityComparer? equalityComparer = null, IComparer? childComparer = null) @@ -294,8 +292,8 @@ public static IObservable> MergeManyCh where TDestination : notnull where TDestinationKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); return source.MergeManyChangeSets((t, _) => observableSelector(t), sourceComparer, DefaultResortOnSourceRefresh, equalityComparer, childComparer); } @@ -308,11 +306,11 @@ public static IObservable> MergeManyCh /// The type of the key identifying source cache items. /// The type of items in the child changeset streams. /// The type of the key identifying child items. - /// The source whose items each produce a child changeset stream. - /// A factory function that receives a source item and its key, and returns a child cache changeset stream. - /// An that comparer to prioritize between source items when their children produce the same destination key. - /// An that optional equality comparer to suppress updates when the incoming child value equals the current value. - /// An that optional fallback comparer for destination key conflicts when source items compare equal. + /// The source IObservable<IChangeSet<TObject, TKey>> whose items each produce a child changeset stream. + /// A Func<T, TResult> factory function that receives a source item and its key, and returns a child cache changeset stream. + /// An IComparer<TObject> that comparer to prioritize between source items when their children produce the same destination key. + /// An IEqualityComparer<TDestination> that optional equality comparer to suppress updates when the incoming child value equals the current value. + /// An IComparer<TDestination> that optional fallback comparer for destination key conflicts when source items compare equal. /// A merged changeset stream with conflicts resolved by source priority. /// or is null. public static IObservable> MergeManyChangeSets(this IObservable> source, Func>> observableSelector, IComparer sourceComparer, IEqualityComparer? equalityComparer = null, IComparer? childComparer = null) @@ -329,12 +327,12 @@ public static IObservable> MergeManyCh /// The type of the key identifying source cache items. /// The type of items in the child changeset streams. /// The type of the key identifying child items. - /// The source whose items each produce a child changeset stream. - /// A factory function that receives a source item and returns a child cache changeset stream. - /// An that comparer to prioritize between source items when their children produce the same destination key. + /// The source IObservable<IChangeSet<TObject, TKey>> whose items each produce a child changeset stream. + /// A Func<T, TResult> factory function that receives a source item and returns a child cache changeset stream. + /// An IComparer<TObject> that comparer to prioritize between source items when their children produce the same destination key. /// If , a Refresh in the source stream re-evaluates source priorities. If , Refresh events are ignored for priority recalculation. - /// An that optional equality comparer to suppress updates when the incoming child value equals the current value. - /// An that optional fallback comparer for destination key conflicts when source items compare equal. + /// An IEqualityComparer<TDestination> that optional equality comparer to suppress updates when the incoming child value equals the current value. + /// An IComparer<TDestination> that optional fallback comparer for destination key conflicts when source items compare equal. /// A merged changeset stream with conflicts resolved by source priority. /// or is null. public static IObservable> MergeManyChangeSets(this IObservable> source, Func>> observableSelector, IComparer sourceComparer, bool resortOnSourceRefresh, IEqualityComparer? equalityComparer = null, IComparer? childComparer = null) @@ -343,8 +341,8 @@ public static IObservable> MergeManyCh where TDestination : notnull where TDestinationKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); return source.MergeManyChangeSets((t, _) => observableSelector(t), sourceComparer, resortOnSourceRefresh, equalityComparer, childComparer); } @@ -359,12 +357,12 @@ public static IObservable> MergeManyCh /// The type of the key identifying source cache items. /// The type of items in the child changeset streams. /// The type of the key identifying child items. - /// The source whose items each produce a child changeset stream. - /// A factory function that receives a source item and its key, and returns a child cache changeset stream. - /// An that comparer to prioritize between source items when their children produce the same destination key. Lower-ordered source wins. + /// The source IObservable<IChangeSet<TObject, TKey>> whose items each produce a child changeset stream. + /// A Func<T, TResult> factory function that receives a source item and its key, and returns a child cache changeset stream. + /// An IComparer<TObject> that comparer to prioritize between source items when their children produce the same destination key. Lower-ordered source wins. /// If (default), a Refresh in the source stream re-evaluates source priorities. If , Refresh events are ignored for priority recalculation. - /// An that optional equality comparer to suppress updates when the incoming child value equals the current value for a destination key. - /// An that optional fallback comparer to resolve destination key conflicts when source items compare equal. + /// An IEqualityComparer<TDestination> that optional equality comparer to suppress updates when the incoming child value equals the current value for a destination key. + /// An IComparer<TDestination> that optional fallback comparer to resolve destination key conflicts when source items compare equal. /// A merged changeset stream containing items from all active child streams, with conflicts resolved by source priority. /// /// @@ -383,9 +381,9 @@ public static IObservable> MergeManyCh where TDestination : notnull where TDestinationKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); - sourceComparer.ThrowArgumentNullExceptionIfNull(nameof(sourceComparer)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); + ArgumentExceptionHelper.ThrowIfNull(sourceComparer); return new MergeManyCacheChangeSetsSourceCompare(source, observableSelector, sourceComparer, equalityComparer, childComparer, resortOnSourceRefresh).Run(); } @@ -398,17 +396,17 @@ public static IObservable> MergeManyCh /// The type of items in the source cache. /// The type of the key identifying source cache items. /// The type of items in the child list changeset streams. - /// The source whose items each produce a child changeset stream. - /// A factory function that receives a source item and its key, and returns a child list changeset stream. - /// An that optional equality comparer to detect duplicate items in the merged list output. + /// The source IObservable<IChangeSet<TObject, TKey>> whose items each produce a child changeset stream. + /// A Func<T, TResult> factory function that receives a source item and its key, and returns a child list changeset stream. + /// An IEqualityComparer<TDestination> that optional equality comparer to detect duplicate items in the merged list output. /// A merged list changeset stream containing items from all active child streams. public static IObservable> MergeManyChangeSets(this IObservable> source, Func>> observableSelector, IEqualityComparer? equalityComparer = null) where TObject : notnull where TKey : notnull where TDestination : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); return new MergeManyListChangeSets(source, observableSelector, equalityComparer).Run(); } @@ -420,18 +418,21 @@ public static IObservable> MergeManyChangeSetsThe type of items in the source cache. /// The type of the key identifying source cache items. /// The type of items in the child list changeset streams. - /// The source whose items each produce a child changeset stream. - /// A factory function that receives a source item and returns a child list changeset stream. - /// An that optional equality comparer to detect duplicate items in the merged list output. + /// The source IObservable<IChangeSet<TObject, TKey>> whose items each produce a child changeset stream. + /// A Func<T, TResult> factory function that receives a source item and returns a child list changeset stream. + /// An IEqualityComparer<TDestination> that optional equality comparer to detect duplicate items in the merged list output. /// A merged list changeset stream containing items from all active child streams. public static IObservable> MergeManyChangeSets(this IObservable> source, Func>> observableSelector, IEqualityComparer? equalityComparer = null) where TObject : notnull where TKey : notnull where TDestination : notnull { - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); return source.MergeManyChangeSets((obj, _) => observableSelector(obj), equalityComparer); } + /// + /// The DefaultResortOnSourceRefresh field. + /// private const bool DefaultResortOnSourceRefresh = true; } diff --git a/src/DynamicData/Cache/ObservableCacheEx.MergeManyItems.cs b/src/DynamicData/Cache/ObservableCacheEx.MergeManyItems.cs index b2581c6c8..333bab75e 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.MergeManyItems.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.MergeManyItems.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -26,36 +24,42 @@ namespace DynamicData; public static partial class ObservableCacheEx { /// - /// Like , - /// but wraps each emitted value as an , pairing the source item + /// Like MergeMany<TObject, TKey, TDestination>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, IObservable<TDestination>>), + /// but wraps each emitted value as an ItemWithValue<TObject, TValue>, pairing the source item /// with the value it produced. This lets you identify which source item is responsible for each emission. /// /// The type of items in the source cache. /// The type of the key identifying source cache items. /// The type of values emitted by child observables. - /// The source whose items each produce an observable. - /// A factory function that produces a child observable for each source item. - /// An observable of pairing each emission with its source item. + /// The source IObservable<IChangeSet<TObject, TKey>> whose items each produce an observable. + /// A Func<T, TResult> factory function that produces a child observable for each source item. + /// An observable of ItemWithValue<TObject, TValue> pairing each emission with its source item. /// or is null. public static IObservable> MergeManyItems(this IObservable> source, Func> observableSelector) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); return new MergeManyItems(source, observableSelector).Run(); } - /// - /// The source whose items each produce an observable. - /// A factory function that receives both the item and its key, and returns a child observable. + /// + /// Provides an overload of MergeManyItems for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The type of the TDestination value. + /// The source IObservable<IChangeSet<TObject, TKey>> whose items each produce an observable. + /// A Func<T, TResult> factory function that receives both the item and its key, and returns a child observable. + /// The resulting observable sequence. public static IObservable> MergeManyItems(this IObservable> source, Func> observableSelector) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); return new MergeManyItems(source, observableSelector).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.MonitorStatus.cs b/src/DynamicData/Cache/ObservableCacheEx.MonitorStatus.cs index 93abf0f92..330da7bff 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.MonitorStatus.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.MonitorStatus.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,9 +29,9 @@ public static partial class ObservableCacheEx /// This is not a changeset operator. /// /// The type of the source observable. - /// The source to monitor for connection status. + /// The source IObservable<T> to monitor for connection status. /// An observable that emits values reflecting the source's lifecycle. /// is . - /// + /// DeferUntilLoaded<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>) public static IObservable MonitorStatus(this IObservable source) => new StatusMonitor(source).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.NotEmpty.cs b/src/DynamicData/Cache/ObservableCacheEx.NotEmpty.cs index a267d1511..6efbfbad7 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.NotEmpty.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.NotEmpty.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -30,15 +21,15 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to suppress empty changesets. + /// The source IObservable<IChangeSet<TObject, TKey>> to suppress empty changesets. /// An observable that emits only non-empty changesets. /// is . - /// + /// StartWithEmpty<TObject,TKey>(IObservable<IChangeSet<TObject,TKey>>) public static IObservable> NotEmpty(this IObservable> source) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Where(changes => changes.Count != 0); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.OfType.cs b/src/DynamicData/Cache/ObservableCacheEx.OfType.cs index dd12ae230..51a5cd785 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.OfType.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.OfType.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -32,7 +30,7 @@ public static partial class ObservableCacheEx /// The type of the objects in the source changeset. /// The type of the key. /// The destination type to filter and cast to. - /// The source to filter by type. + /// The source IObservable<IChangeSet<TObject, TKey>> to filter by type. /// If , changesets that become empty after filtering are suppressed. /// An observable changeset of items. /// @@ -50,7 +48,7 @@ public static IObservable> OfType(source, suppressEmptyChangeSets).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.OnChangeAction.cs b/src/DynamicData/Cache/ObservableCacheEx.OnChangeAction.cs index 00b222a68..97bb8ad5f 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.OnChangeAction.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.OnChangeAction.cs @@ -2,29 +2,29 @@ // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { + /// + /// Executes the OnChangeAction operation. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The predicate value. + /// The changeAction value. + /// The result of the operation. private static IObservable> OnChangeAction(this IObservable> source, Predicate> predicate, Action> changeAction) where TObject : notnull where TKey : notnull @@ -43,6 +43,15 @@ private static IObservable> OnChangeAction + /// Executes the OnChangeAction operation. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The reason value. + /// The action value. + /// The result of the operation. private static IObservable> OnChangeAction(this IObservable> source, ChangeReason reason, Action action) where TObject : notnull where TKey : notnull diff --git a/src/DynamicData/Cache/ObservableCacheEx.OnItemAdded.cs b/src/DynamicData/Cache/ObservableCacheEx.OnItemAdded.cs index 40e47a95b..ef19609f0 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.OnItemAdded.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.OnItemAdded.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -30,8 +21,8 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to observe item additions in. - /// The callback invoked for each added item. Receives the new item and its key. + /// The source IObservable<IChangeSet<TObject, TKey>> to observe item additions in. + /// The Action<TObject, TKey> callback invoked for each added item. Receives the new item and its key. /// A stream that forwards all changesets from unchanged. /// /// @@ -49,24 +40,29 @@ public static partial class ObservableCacheEx /// /// /// or is . - /// - /// - /// - /// + /// OnItemUpdated<TObject,TKey>(IObservable<IChangeSet<TObject,TKey>>, Action<TObject,TObject,TKey>) + /// OnItemRemoved<TObject,TKey>(IObservable<IChangeSet<TObject,TKey>>, Action<TObject,TKey>, bool) + /// ForEachChange<TObject,TKey> + /// ObservableListEx.OnItemAdded public static IObservable> OnItemAdded(this IObservable> source, Action addAction) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - addAction.ThrowArgumentNullExceptionIfNull(nameof(addAction)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(addAction); return source.OnChangeAction(ChangeReason.Add, addAction); } - /// - /// The source to observe item additions in. - /// The callback invoked for each added item. Receives only the item (no key). - /// Overload that omits the key from the callback. Delegates to . + /// + /// Provides an overload of addAction for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<IChangeSet<TObject, TKey>> to observe item additions in. + /// The Action<TObject> callback invoked for each added item. Receives only the item (no key). + /// The resulting observable sequence. + /// Overload that omits the key from the callback. Delegates to OnItemAdded<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Action<TObject, TKey>). public static IObservable> OnItemAdded(this IObservable> source, Action addAction) where TObject : notnull where TKey : notnull diff --git a/src/DynamicData/Cache/ObservableCacheEx.OnItemRefreshed.cs b/src/DynamicData/Cache/ObservableCacheEx.OnItemRefreshed.cs index 13441fd02..2de302775 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.OnItemRefreshed.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.OnItemRefreshed.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -30,8 +21,8 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to observe item refresh events in. - /// The callback invoked for each refreshed item. Receives the item and its key. + /// The source IObservable<IChangeSet<TObject, TKey>> to observe item refresh events in. + /// The Action<TObject, TKey> callback invoked for each refreshed item. Receives the item and its key. /// A stream that forwards all changesets from unchanged. /// /// @@ -49,22 +40,27 @@ public static partial class ObservableCacheEx /// /// /// or is . - /// - /// + /// AutoRefresh<TObject,TKey>(IObservable<IChangeSet<TObject,TKey>>, TimeSpan?, TimeSpan?, IScheduler?) + /// ObservableListEx.OnItemRefreshed public static IObservable> OnItemRefreshed(this IObservable> source, Action refreshAction) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - refreshAction.ThrowArgumentNullExceptionIfNull(nameof(refreshAction)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(refreshAction); return source.OnChangeAction(ChangeReason.Refresh, refreshAction); } - /// - /// The source to observe item refresh events in. - /// The callback invoked for each refreshed item. Receives only the item (no key). - /// Overload that omits the key from the callback. Delegates to . + /// + /// Provides an overload of refreshAction for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<IChangeSet<TObject, TKey>> to observe item refresh events in. + /// The Action<TObject> callback invoked for each refreshed item. Receives only the item (no key). + /// The resulting observable sequence. + /// Overload that omits the key from the callback. Delegates to OnItemRefreshed<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Action<TObject, TKey>). public static IObservable> OnItemRefreshed(this IObservable> source, Action refreshAction) where TObject : notnull where TKey : notnull diff --git a/src/DynamicData/Cache/ObservableCacheEx.OnItemRemoved.cs b/src/DynamicData/Cache/ObservableCacheEx.OnItemRemoved.cs index 6935103fe..306876816 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.OnItemRemoved.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.OnItemRemoved.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,8 +29,8 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to observe item removals in. - /// The callback invoked for each removed item. Receives the removed item and its key. + /// The source IObservable<IChangeSet<TObject, TKey>> to observe item removals in. + /// The Action<TObject, TKey> callback invoked for each removed item. Receives the removed item and its key. /// /// When (the default), the callback is also invoked for every item still in the cache /// when the subscription is disposed. When , only inline Remove changes trigger the callback. @@ -62,15 +60,15 @@ public static partial class ObservableCacheEx /// Worth noting: The action also fires for ALL remaining items when the subscription is disposed (unless invokeOnUnsubscribe is ). The action runs under a lock; avoid calling into other caches from within it. /// /// or is . - /// - /// - /// + /// DisposeMany<TObject,TKey> + /// SubscribeMany<TObject,TKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TKey, IDisposable>) + /// ObservableListEx.OnItemRemoved public static IObservable> OnItemRemoved(this IObservable> source, Action removeAction, bool invokeOnUnsubscribe = true) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - removeAction.ThrowArgumentNullExceptionIfNull(nameof(removeAction)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(removeAction); if (invokeOnUnsubscribe) { @@ -80,11 +78,16 @@ public static IObservable> OnItemRemoved - /// The source to observe item removals in. - /// The callback invoked for each removed item. Receives only the item (no key). + /// + /// Provides an overload of removeAction for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<IChangeSet<TObject, TKey>> to observe item removals in. + /// The Action<TObject> callback invoked for each removed item. Receives only the item (no key). /// When (the default), also invoked for all remaining items on disposal. - /// Overload that omits the key from the callback. Delegates to . + /// The resulting observable sequence. + /// Overload that omits the key from the callback. Delegates to OnItemRemoved<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Action<TObject, TKey>, bool). public static IObservable> OnItemRemoved(this IObservable> source, Action removeAction, bool invokeOnUnsubscribe = true) where TObject : notnull where TKey : notnull diff --git a/src/DynamicData/Cache/ObservableCacheEx.OnItemUpdated.cs b/src/DynamicData/Cache/ObservableCacheEx.OnItemUpdated.cs index 464bbf29b..fd77095f5 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.OnItemUpdated.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.OnItemUpdated.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,8 +22,8 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to observe item updates in. - /// The callback invoked for each updated item. Receives the current value, previous value, and key. + /// The source IObservable<IChangeSet<TObject, TKey>> to observe item updates in. + /// The Action<TObject, TObject, TKey> callback invoked for each updated item. Receives the current value, previous value, and key. /// A stream that forwards all changesets from unchanged. /// /// @@ -50,22 +41,27 @@ public static partial class ObservableCacheEx /// /// /// or is . - /// - /// + /// OnItemAdded<TObject,TKey>(IObservable<IChangeSet<TObject,TKey>>, Action<TObject,TKey>) + /// ForEachChange<TObject,TKey> public static IObservable> OnItemUpdated(this IObservable> source, Action updateAction) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - updateAction.ThrowArgumentNullExceptionIfNull(nameof(updateAction)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(updateAction); return source.OnChangeAction(static change => change.Reason == ChangeReason.Update, change => updateAction(change.Current, change.Previous.Value, change.Key)); } - /// - /// The source to observe item updates in. - /// The callback invoked for each updated item. Receives only the current and previous values (no key). - /// Overload that omits the key from the callback. Delegates to . + /// + /// Provides an overload of updateAction for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<IChangeSet<TObject, TKey>> to observe item updates in. + /// The Action<TObject, TObject> callback invoked for each updated item. Receives only the current and previous values (no key). + /// The resulting observable sequence. + /// Overload that omits the key from the callback. Delegates to OnItemUpdated<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Action<TObject, TObject, TKey>). public static IObservable> OnItemUpdated(this IObservable> source, Action updateAction) where TObject : notnull where TKey : notnull diff --git a/src/DynamicData/Cache/ObservableCacheEx.Or.cs b/src/DynamicData/Cache/ObservableCacheEx.Or.cs index a0de6f338..e8633b51a 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Or.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Or.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -30,8 +28,8 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to combine. - /// The additional streams to combine with. + /// The source IObservable<IChangeSet<TObject, TKey>> to combine. + /// The additional IObservable<IChangeSet<TObject, TKey>> streams to combine with. /// A changeset stream containing items present in any of the sources. /// /// @@ -47,16 +45,16 @@ public static partial class ObservableCacheEx /// /// /// or is . - /// - /// - /// - /// - /// + /// And<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IObservable<IChangeSet<TObject, TKey>>[]) + /// Except<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IObservable<IChangeSet<TObject, TKey>>[]) + /// Xor<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IObservable<IChangeSet<TObject, TKey>>[]) + /// MergeChangeSets<TObject, TKey>(IObservable<IObservable<IChangeSet<TObject, TKey>>>) + /// ObservableListEx.Or public static IObservable> Or(this IObservable> source, params IObservable>[] others) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); if (others is null || others.Length == 0) { @@ -66,14 +64,19 @@ public static IObservable> Or(this IObs return source.Combine(CombineOperator.Or, others); } - /// - /// The of streams to combine. + /// + /// Provides an overload of Or for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The ICollection<T> of streams to combine. + /// The resulting observable sequence. /// This overload accepts a pre-built collection of sources instead of a params array. public static IObservable> Or(this ICollection>> sources) where TObject : notnull where TKey : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return sources.Combine(CombineOperator.Or); } @@ -84,13 +87,13 @@ public static IObservable> Or(this ICol /// /// The type of the object. /// The type of the key. - /// The of streams to combine. + /// The IObservableList<T> of streams to combine. /// An observable which emits change sets. public static IObservable> Or(this IObservableList>> sources) where TObject : notnull where TKey : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return sources.Combine(CombineOperator.Or); } @@ -101,13 +104,13 @@ public static IObservable> Or(this IObs /// /// The type of the object. /// The type of the key. - /// The of changeset streams to combine. + /// The IObservableList<IObservableCache<TObject, TKey>> of changeset streams to combine. /// An observable which emits change sets. public static IObservable> Or(this IObservableList> sources) where TObject : notnull where TKey : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return sources.Combine(CombineOperator.Or); } @@ -118,13 +121,13 @@ public static IObservable> Or(this IObs /// /// The type of the object. /// The type of the key. - /// The of changeset streams to combine. + /// The IObservableList<ISourceCache<TObject, TKey>> of changeset streams to combine. /// An observable which emits change sets. public static IObservable> Or(this IObservableList> sources) where TObject : notnull where TKey : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return sources.Combine(CombineOperator.Or); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.PopulateFrom.cs b/src/DynamicData/Cache/ObservableCacheEx.PopulateFrom.cs index 24d4d407e..ad571e2c4 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.PopulateFrom.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.PopulateFrom.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -30,20 +21,21 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The to operate on. - /// The that emits batches of items. + /// The ISourceCache<TObject, TKey> to operate on. + /// The IObservable<IEnumerable<TObject>> that emits batches of items. /// An that, when disposed, unsubscribes from . /// - /// Each emission from is passed to , producing one changeset per emission containing Add or Update events for each item. Errors from propagate and terminate the subscription. Completion ends the subscription; the cache retains all items. + /// Each emission from is passed to AddOrUpdate<TObject, TKey>(ISourceCache<TObject, TKey>, IEnumerable<TObject>), producing one changeset per emission containing Add or Update events for each item. Errors from propagate and terminate the subscription. Completion ends the subscription; the cache retains all items. /// /// or is . - /// - /// + /// PopulateInto<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, ISourceCache<TObject, TKey>) + /// ToObservableChangeSet<TObject, TKey>(IObservable<IEnumerable<TObject>>, Func<TObject, TKey>, Func<TObject, TimeSpan?>, int, IScheduler?) public static IDisposable PopulateFrom(this ISourceCache source, IObservable> observable) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observable); return observable.Subscribe(source.AddOrUpdate); } @@ -53,15 +45,16 @@ public static IDisposable PopulateFrom(this ISourceCache /// The type of the object. /// The type of the key. - /// The to operate on. - /// The that emits individual items. + /// The ISourceCache<TObject, TKey> to operate on. + /// The IObservable<TObject> that emits individual items. /// An that, when disposed, unsubscribes from . /// or is . public static IDisposable PopulateFrom(this ISourceCache source, IObservable observable) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observable); return observable.Subscribe(source.AddOrUpdate); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.PopulateInto.cs b/src/DynamicData/Cache/ObservableCacheEx.PopulateInto.cs index d5ce99d73..eea3046b4 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.PopulateInto.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.PopulateInto.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -30,8 +28,8 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to pipe into a target cache. - /// The that will receive the changes. + /// The source IObservable<IChangeSet<TObject, TKey>> to pipe into a target cache. + /// The ISourceCache<TObject, TKey> that will receive the changes. /// An that, when disposed, unsubscribes from the source. /// /// @@ -48,43 +46,53 @@ public static partial class ObservableCacheEx /// /// /// or is . - /// - /// - /// + /// PopulateFrom<TObject, TKey>(ISourceCache<TObject, TKey>, IObservable<IEnumerable<TObject>>) + /// AsObservableCache<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, bool) + /// ObservableListEx.PopulateInto<T>(IObservable<IChangeSet<T>>, ISourceList<T>) public static IDisposable PopulateInto(this IObservable> source, ISourceCache destination) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - destination.ThrowArgumentNullExceptionIfNull(nameof(destination)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(destination); return source.Subscribe(changes => destination.Edit(updater => updater.Clone(changes))); } - /// - /// The source to pipe into a target cache. - /// The that will receive the changes. - /// Overload that targets an . + /// + /// Provides an overload of PopulateInto for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<IChangeSet<TObject, TKey>> to pipe into a target cache. + /// The IIntermediateCache<TObject, TKey> that will receive the changes. + /// The resulting observable sequence. + /// Overload that targets an IIntermediateCache<TObject, TKey>. public static IDisposable PopulateInto(this IObservable> source, IIntermediateCache destination) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - destination.ThrowArgumentNullExceptionIfNull(nameof(destination)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(destination); return source.Subscribe(changes => destination.Edit(updater => updater.Clone(changes))); } - /// - /// The source to pipe into a target cache. - /// The that will receive the changes. - /// Overload that targets a . + /// + /// Provides an overload of PopulateInto for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<IChangeSet<TObject, TKey>> to pipe into a target cache. + /// The LockFreeObservableCache<TObject, TKey> that will receive the changes. + /// The resulting observable sequence. + /// Overload that targets a LockFreeObservableCache<TObject, TKey>. public static IDisposable PopulateInto(this IObservable> source, LockFreeObservableCache destination) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - destination.ThrowArgumentNullExceptionIfNull(nameof(destination)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(destination); return source.Subscribe(changes => destination.Edit(updater => updater.Clone(changes))); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.QueryWhenChanged.cs b/src/DynamicData/Cache/ObservableCacheEx.QueryWhenChanged.cs index f294f119c..ce3e853d0 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.QueryWhenChanged.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.QueryWhenChanged.cs @@ -1,24 +1,27 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; using DynamicData.Binding; -using DynamicData.Cache; +#endif +#if REACTIVE_SHIM +using DynamicData.Reactive.Cache.Internal; +#else using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -32,22 +35,22 @@ public static partial class ObservableCacheEx /// The type of the object. /// The type of the key. /// The type of the destination. - /// The source to project on each change. - /// A function that projects the current snapshot to a result value. + /// The source IObservable<IChangeSet<TObject, TKey>> to project on each change. + /// A function that projects the current IQuery<TObject, TKey> snapshot to a result value. /// An observable that emits a projected value after each changeset. /// - /// Worth noting: The selector is called on every changeset, which can be chatty. The exposes the full cache state for LINQ-style queries. + /// Worth noting: The selector is called on every changeset, which can be chatty. The IQuery<TObject, TKey> exposes the full cache state for LINQ-style queries. /// /// or is . - /// - /// - /// + /// ToCollection<TObject, TKey> + /// ToSortedCollection<TObject, TKey, TSortKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TSortKey>, SortDirection) + /// ObservableListEx.QueryWhenChanged public static IObservable QueryWhenChanged(this IObservable> source, Func, TDestination> resultSelector) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return source.QueryWhenChanged().Select(resultSelector); } @@ -57,14 +60,14 @@ public static IObservable QueryWhenChanged /// The type of the object. /// The type of the key. - /// The source to project on each change. + /// The source IObservable<IChangeSet<TObject, TKey>> to project on each change. /// An observable which emits the query. /// source. public static IObservable> QueryWhenChanged(this IObservable> source) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new QueryWhenChanged(source).Run(); } @@ -75,16 +78,16 @@ public static IObservable> QueryWhenChanged /// The type of the object. /// The type of the key. /// The type of the value. - /// The source to project on each change. - /// A that should the query be triggered for observables on individual items. + /// The source IObservable<IChangeSet<TObject, TKey>> to project on each change. + /// A Func<T, TResult> that should the query be triggered for observables on individual items. /// An observable that emits the query. /// source. public static IObservable> QueryWhenChanged(this IObservable> source, Func> itemChangedTrigger) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - itemChangedTrigger.ThrowArgumentNullExceptionIfNull(nameof(itemChangedTrigger)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(itemChangedTrigger); return new QueryWhenChanged(source, itemChangedTrigger).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.RefCount.cs b/src/DynamicData/Cache/ObservableCacheEx.RefCount.cs index 66c8315b5..f0aa881e9 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.RefCount.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.RefCount.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,14 +29,14 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to share via reference counting. + /// The source IObservable<IChangeSet<TObject, TKey>> to share via reference counting. /// A ref-counted observable changeset stream. - /// + /// AsObservableCache<TObject,TKey>(IObservable<IChangeSet<TObject, TKey>>, bool) public static IObservable> RefCount(this IObservable> source) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new RefCount(source).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.Refresh.cs b/src/DynamicData/Cache/ObservableCacheEx.Refresh.cs index 4e9947f28..428eb4ea6 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Refresh.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Refresh.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -30,19 +21,19 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The to signal re-evaluation on. + /// The ISourceCache<TObject, TKey> to signal re-evaluation on. /// The item to refresh. /// - /// Convenience method that wraps a Refresh inside . A Refresh does not change data in the cache; it signals downstream operators (such as or ) to re-evaluate the item. + /// Convenience method that wraps a Refresh inside ISourceCache<TObject,TKey>.Edit. A Refresh does not change data in the cache; it signals downstream operators (such as Filter<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, bool>, bool) or Sort<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IComparer<TObject>, SortOptimisations, int)) to re-evaluate the item. /// /// is . - /// - /// + /// AutoRefresh<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, TimeSpan?, TimeSpan?, IScheduler?) + /// SuppressRefresh<TObject, TKey> public static void Refresh(this ISourceCache source, TObject item) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(updater => updater.Refresh(item)); } @@ -52,14 +43,14 @@ public static void Refresh(this ISourceCache sourc /// /// The type of the object. /// The type of the key. - /// The to signal re-evaluation on. - /// The of items to refresh. + /// The ISourceCache<TObject, TKey> to signal re-evaluation on. + /// The IEnumerable<TObject> of items to refresh. /// is . public static void Refresh(this ISourceCache source, IEnumerable items) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(updater => updater.Refresh(items)); } @@ -69,13 +60,13 @@ public static void Refresh(this ISourceCache sourc /// /// The type of the object. /// The type of the key. - /// The to signal re-evaluation on. + /// The ISourceCache<TObject, TKey> to signal re-evaluation on. /// is . public static void Refresh(this ISourceCache source) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(updater => updater.Refresh()); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.Remove.cs b/src/DynamicData/Cache/ObservableCacheEx.Remove.cs index d835ff00b..f82c2e02f 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Remove.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Remove.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -30,20 +21,20 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The from which to remove items. + /// The ISourceCache<TObject, TKey> from which to remove items. /// The item to remove. /// - /// Convenience method that wraps a single-item removal inside . The key is extracted from the item using the cache's key selector. + /// Convenience method that wraps a single-item removal inside ISourceCache<TObject,TKey>.Edit. The key is extracted from the item using the cache's key selector. /// /// is . - /// - /// - /// + /// AddOrUpdate<TObject, TKey>(ISourceCache<TObject, TKey>, TObject) + /// Clear<TObject, TKey>(ISourceCache<TObject, TKey>) + /// RemoveKeys<TObject, TKey>(ISourceCache<TObject, TKey>, IEnumerable<TKey>) public static void Remove(this ISourceCache source, TObject item) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(updater => updater.Remove(item)); } @@ -53,14 +44,14 @@ public static void Remove(this ISourceCache source /// /// The type of the object. /// The type of the key. - /// The from which to remove items. + /// The ISourceCache<TObject, TKey> from which to remove items. /// The key of the item to remove. /// is . public static void Remove(this ISourceCache source, TKey key) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(updater => updater.Remove(key)); } @@ -71,14 +62,14 @@ public static void Remove(this ISourceCache source /// /// The type of the object. /// The type of the key. - /// The from which to remove items. - /// The of items to remove. + /// The ISourceCache<TObject, TKey> from which to remove items. + /// The IEnumerable<TObject> of items to remove. /// is . public static void Remove(this ISourceCache source, IEnumerable items) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(updater => updater.Remove(items)); } @@ -89,40 +80,48 @@ public static void Remove(this ISourceCache source /// /// The type of the object. /// The type of the key. - /// The from which to remove items. - /// The keys to remove. + /// The ISourceCache<TObject, TKey> from which to remove items. + /// The IEnumerable<TKey> keys to remove. /// is . public static void Remove(this ISourceCache source, IEnumerable keys) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(updater => updater.Remove(keys)); } - /// - /// The from which to remove items. + /// + /// Provides an overload of Remove for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The IIntermediateCache<TObject, TKey> from which to remove items. /// The key of the item to remove. - /// Overload that targets an . + /// Overload that targets an IIntermediateCache<TObject, TKey>. public static void Remove(this IIntermediateCache source, TKey key) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(updater => updater.Remove(key)); } - /// - /// The from which to remove items. - /// The keys to remove. - /// Overload that targets an . + /// + /// Provides an overload of Remove for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The IIntermediateCache<TObject, TKey> from which to remove items. + /// The IEnumerable<TKey> keys to remove. + /// Overload that targets an IIntermediateCache<TObject, TKey>. public static void Remove(this IIntermediateCache source, IEnumerable keys) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(updater => updater.Remove(keys)); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.RemoveKey.cs b/src/DynamicData/Cache/ObservableCacheEx.RemoveKey.cs index 6e9f3ecd6..b6c243e66 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.RemoveKey.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.RemoveKey.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -26,20 +24,20 @@ namespace DynamicData; public static partial class ObservableCacheEx { /// - /// Strips the key from a cache changeset, converting to - /// (list changeset). All indexed changes are dropped (sorting is not supported). + /// Strips the key from a cache changeset, converting IChangeSet<TObject, TKey> to + /// IChangeSet<TObject> (list changeset). All indexed changes are dropped (sorting is not supported). /// /// The type of the object. /// The type of the key. - /// The source to strip keys from, producing an unkeyed list changeset. + /// The source IObservable<IChangeSet<TObject, TKey>> to strip keys from, producing an unkeyed list changeset. /// A list changeset stream without key information. - /// - /// + /// ObservableListEx.AddKey<TObject, TKey>(IObservable<IChangeSet<TObject>>, Func<TObject, TKey>) + /// ChangeKey<TObject, TSourceKey, TDestinationKey>(IObservable<IChangeSet<TObject, TSourceKey>>, Func<TObject, TDestinationKey>) public static IObservable> RemoveKey(this IObservable> source) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Select( changes => @@ -54,14 +52,14 @@ public static IObservable> RemoveKey(this IOb /// /// The type of the object. /// The type of the key. - /// The from which to remove a key. + /// The ISourceCache<TObject, TKey> from which to remove a key. /// The key to remove. /// is . public static void RemoveKey(this ISourceCache source, TKey key) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(updater => updater.RemoveKey(key)); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.RemoveKeys.cs b/src/DynamicData/Cache/ObservableCacheEx.RemoveKeys.cs index 837bf45f3..a516f6290 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.RemoveKeys.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.RemoveKeys.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -30,14 +21,14 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The from which to remove keys. - /// The keys to remove. + /// The ISourceCache<TObject, TKey> from which to remove keys. + /// The IEnumerable<TKey> keys to remove. /// is . public static void RemoveKeys(this ISourceCache source, IEnumerable keys) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(updater => updater.RemoveKeys(keys)); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.RightJoin.cs b/src/DynamicData/Cache/ObservableCacheEx.RightJoin.cs index 7bd66b375..188981ae0 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.RightJoin.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.RightJoin.cs @@ -1,54 +1,60 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { - /// - /// The left to join. - /// The right to join. - /// A that maps each right item to the left key it should join on. - /// A that combines the optional left and right values into a destination object. The key is not provided in this overload. - /// Overload that omits the key from the result selector. Delegates to . - public static IObservable> RightJoin(this IObservable> left, IObservable> right, Func rightKeySelector, Func, TRight, TDestination> resultSelector) + /// + /// Provides an overload of RightJoin for the supplied arguments. + /// + /// The type of the TLeft value. + /// The type of the TLeftKey value. + /// The type of the TRight value. + /// The type of the TRightKey value. + /// The type of the TDestination value. + /// The left IObservable<IChangeSet<TLeft, TLeftKey>> to join. + /// The right IObservable<IChangeSet<TRight, TRightKey>> to join. + /// A Func<T, TResult> that maps each right item to the left key it should join on. + /// A Func<T, TResult> that combines the optional left and right values into a destination object. The key is not provided in this overload. + /// The resulting observable sequence. + /// Overload that omits the key from the result selector. Delegates to RightJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TRightKey, Optional<TLeft>, TRight, TDestination>). + public static IObservable> RightJoin(this IObservable> left, IObservable> right, Func rightKeySelector, Func, TRight, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull where TRight : notnull where TRightKey : notnull where TDestination : notnull { - left.ThrowArgumentNullExceptionIfNull(nameof(left)); - right.ThrowArgumentNullExceptionIfNull(nameof(right)); - rightKeySelector.ThrowArgumentNullExceptionIfNull(nameof(rightKeySelector)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(left); + ArgumentExceptionHelper.ThrowIfNull(right); + ArgumentExceptionHelper.ThrowIfNull(rightKeySelector); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return left.RightJoin(right, rightKeySelector, (_, leftValue, rightValue) => resultSelector(leftValue, rightValue)); } /// /// Joins two changeset streams, producing a result for every right-side key. The left side is - /// because a matching left item may or may not exist. All right items + /// Optional<T> because a matching left item may or may not exist. All right items /// appear in the output regardless. Equivalent to SQL RIGHT OUTER JOIN. /// /// The item type of the left source. @@ -56,17 +62,17 @@ public static IObservable> RightJoinThe item type of the right source. /// The key type of the right source. /// The type produced by . - /// The left to join. - /// The right to join. - /// A that maps each right item to the left key it should join on. - /// A that combines the right key, optional left, and right value into a destination object. Example: (rightKey, left, right) => new Result(rightKey, left, right). + /// The left IObservable<IChangeSet<TLeft, TLeftKey>> to join. + /// The right IObservable<IChangeSet<TRight, TRightKey>> to join. + /// A Func<T, TResult> that maps each right item to the left key it should join on. + /// A Func<T, TResult> that combines the right key, optional left, and right value into a destination object. Example: (rightKey, left, right) => new Result(rightKey, left, right). /// An observable changeset keyed by . /// /// /// Right-side change handling: /// /// EventBehavior - /// AddAlways emits. Invokes with the matching left (or ) and the right value. + /// AddAlways emits. Invokes with the matching left (or ReactiveUI.Primitives.Optional.None<T>) and the right value. /// UpdateRe-invokes the selector with current left (if any) and the new right value. /// RemoveRemoves the joined result. /// RefreshForwarded as Refresh on the joined result. @@ -85,21 +91,21 @@ public static IObservable> RightJoinBoth sources are serialized through a shared lock held during downstream delivery. Avoid blocking operations in subscribers. /// /// Any argument is . - /// - /// - /// - /// - public static IObservable> RightJoin(this IObservable> left, IObservable> right, Func rightKeySelector, Func, TRight, TDestination> resultSelector) + /// InnerJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<ValueTuple<TLeftKey, TRightKey>, TLeft, TRight, TDestination>) + /// LeftJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, TLeft, Optional<TRight>, TDestination>) + /// FullJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, Optional<TLeft>, Optional<TRight>, TDestination>) + /// RightJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, Optional<TLeft>, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>) + public static IObservable> RightJoin(this IObservable> left, IObservable> right, Func rightKeySelector, Func, TRight, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull where TRight : notnull where TRightKey : notnull where TDestination : notnull { - left.ThrowArgumentNullExceptionIfNull(nameof(left)); - right.ThrowArgumentNullExceptionIfNull(nameof(right)); - rightKeySelector.ThrowArgumentNullExceptionIfNull(nameof(rightKeySelector)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(left); + ArgumentExceptionHelper.ThrowIfNull(right); + ArgumentExceptionHelper.ThrowIfNull(rightKeySelector); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return new RightJoin(left, right, rightKeySelector, resultSelector).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.RightJoinMany.cs b/src/DynamicData/Cache/ObservableCacheEx.RightJoinMany.cs index cb6a5a924..0ac0a2ad1 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.RightJoinMany.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.RightJoinMany.cs @@ -1,47 +1,53 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { - /// - /// The left to join. - /// The right to join. - /// A that maps each right item to the left key it should join on. - /// A that combines the optional left value and the right group into a destination object. The key is not provided in this overload. - /// Overload that omits the key from the result selector. Delegates to . - public static IObservable> RightJoinMany(this IObservable> left, IObservable> right, Func rightKeySelector, Func, IGrouping, TDestination> resultSelector) + /// + /// Provides an overload of RightJoinMany for the supplied arguments. + /// + /// The type of the TLeft value. + /// The type of the TLeftKey value. + /// The type of the TRight value. + /// The type of the TRightKey value. + /// The type of the TDestination value. + /// The left IObservable<IChangeSet<TLeft, TLeftKey>> to join. + /// The right IObservable<IChangeSet<TRight, TRightKey>> to join. + /// A Func<T, TResult> that maps each right item to the left key it should join on. + /// A Func<T, TResult> that combines the optional left value and the right group into a destination object. The key is not provided in this overload. + /// The resulting observable sequence. + /// Overload that omits the key from the result selector. Delegates to RightJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, Optional<TLeft>, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>). + public static IObservable> RightJoinMany(this IObservable> left, IObservable> right, Func rightKeySelector, Func, IGrouping, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull where TRight : notnull where TRightKey : notnull where TDestination : notnull { - left.ThrowArgumentNullExceptionIfNull(nameof(left)); - right.ThrowArgumentNullExceptionIfNull(nameof(right)); - rightKeySelector.ThrowArgumentNullExceptionIfNull(nameof(rightKeySelector)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(left); + ArgumentExceptionHelper.ThrowIfNull(right); + ArgumentExceptionHelper.ThrowIfNull(rightKeySelector); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return left.RightJoinMany(right, rightKeySelector, (_, leftValue, rightValue) => resultSelector(leftValue, rightValue)); } @@ -49,7 +55,7 @@ public static IObservable> RightJoinMany /// Groups right-side items by their mapped key, then right-joins each group to the left source. /// A result is produced for every key that has at least one right item. The left value is - /// because a matching left item may or may not exist. + /// Optional<T> because a matching left item may or may not exist. /// Equivalent to SQL RIGHT OUTER JOIN with the right side grouped. /// /// The item type of the left source. @@ -57,10 +63,10 @@ public static IObservable> RightJoinManyThe item type of the right source. /// The key type of the right source. /// The type produced by . - /// The left to join. - /// The right to join. - /// A that maps each right item to the left key it should join on. - /// A that combines the key, optional left value, and right group into a destination object. Example: (key, left, group) => new Result(key, left, group). + /// The left IObservable<IChangeSet<TLeft, TLeftKey>> to join. + /// The right IObservable<IChangeSet<TRight, TRightKey>> to join. + /// A Func<T, TResult> that maps each right item to the left key it should join on. + /// A Func<T, TResult> that combines the key, optional left value, and right group into a destination object. Example: (key, left, group) => new Result(key, left, group). /// An observable changeset keyed by . /// /// @@ -86,21 +92,21 @@ public static IObservable> RightJoinManyBoth sources are serialized through a shared lock held during downstream delivery. Avoid blocking operations in subscribers. /// /// Any argument is . - /// - /// - /// - /// - public static IObservable> RightJoinMany(this IObservable> left, IObservable> right, Func rightKeySelector, Func, IGrouping, TDestination> resultSelector) + /// RightJoin<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TRightKey, Optional<TLeft>, TRight, TDestination>) + /// InnerJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, TLeft, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>) + /// LeftJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, TLeft, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>) + /// FullJoinMany<TLeft, TLeftKey, TRight, TRightKey, TDestination>(IObservable<IChangeSet<TLeft, TLeftKey>>, IObservable<IChangeSet<TRight, TRightKey>>, Func<TRight, TLeftKey>, Func<TLeftKey, Optional<TLeft>, IGrouping<TRight, TRightKey, TLeftKey>, TDestination>) + public static IObservable> RightJoinMany(this IObservable> left, IObservable> right, Func rightKeySelector, Func, IGrouping, TDestination> resultSelector) where TLeft : notnull where TLeftKey : notnull where TRight : notnull where TRightKey : notnull where TDestination : notnull { - left.ThrowArgumentNullExceptionIfNull(nameof(left)); - right.ThrowArgumentNullExceptionIfNull(nameof(right)); - rightKeySelector.ThrowArgumentNullExceptionIfNull(nameof(rightKeySelector)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(left); + ArgumentExceptionHelper.ThrowIfNull(right); + ArgumentExceptionHelper.ThrowIfNull(rightKeySelector); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return new RightJoinMany(left, right, rightKeySelector, resultSelector).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.SkipInitial.cs b/src/DynamicData/Cache/ObservableCacheEx.SkipInitial.cs index 6a2ed2db4..4f75bf341 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.SkipInitial.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.SkipInitial.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,16 +22,16 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to skip the initial changeset. + /// The source IObservable<IChangeSet<TObject, TKey>> to skip the initial changeset. /// An observable that skips the first changeset and forwards all others. /// is . - /// - /// + /// DeferUntilLoaded<TObject,TKey>(IObservable<IChangeSet<TObject,TKey>>) + /// StartWithEmpty<TObject,TKey>(IObservable<IChangeSet<TObject,TKey>>) public static IObservable> SkipInitial(this IObservable> source) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.DeferUntilLoaded().Skip(1); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.Sort.cs b/src/DynamicData/Cache/ObservableCacheEx.Sort.cs index 87bccebe5..491d7f9c1 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Sort.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Sort.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -30,8 +28,8 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to sort. - /// The used to determine sort order. + /// The source IObservable<IChangeSet<TObject, TKey>> to sort. + /// The IComparer<TObject> used to determine sort order. /// A that sort optimisation flags. Specify one or more sort optimisations. /// The number of updates before the entire list is resorted (rather than inline sort). /// An observable which emits change sets. @@ -40,14 +38,14 @@ public static partial class ObservableCacheEx /// or /// comparer. /// - /// + /// ObservableListEx.Sort [Obsolete(Constants.SortIsObsolete)] public static IObservable> Sort(this IObservable> source, IComparer comparer, SortOptimisations sortOptimisations = SortOptimisations.None, int resetThreshold = DefaultSortResetThreshold) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - comparer.ThrowArgumentNullExceptionIfNull(nameof(comparer)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(comparer); return new Sort(source, comparer, sortOptimisations, resetThreshold: resetThreshold).Run(); } @@ -57,8 +55,8 @@ public static IObservable> Sort(t /// /// The type of the object. /// The type of the key. - /// The source to sort. - /// The comparer observable. + /// The source IObservable<IChangeSet<TObject, TKey>> to sort. + /// The IObservable<IComparer<TObject>> comparer observable. /// The sort optimisations. /// The reset threshold. /// An observable which emits change sets. @@ -67,8 +65,8 @@ public static IObservable> Sort(t where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - comparerObservable.ThrowArgumentNullExceptionIfNull(nameof(comparerObservable)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(comparerObservable); return new Sort(source, null, sortOptimisations, comparerObservable, resetThreshold: resetThreshold).Run(); } @@ -78,9 +76,9 @@ public static IObservable> Sort(t /// /// The type of the object. /// The type of the key. - /// The source to sort. - /// The comparer observable. - /// An that signals the algorithm to re-sort the entire data set. + /// The source IObservable<IChangeSet<TObject, TKey>> to sort. + /// The IObservable<IComparer<TObject>> comparer observable. + /// An IObservable<Unit> that signals the algorithm to re-sort the entire data set. /// The sort optimisations. /// The reset threshold. /// An observable which emits change sets. @@ -89,8 +87,8 @@ public static IObservable> Sort(t where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - comparerObservable.ThrowArgumentNullExceptionIfNull(nameof(comparerObservable)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(comparerObservable); return new Sort(source, null, sortOptimisations, comparerObservable, resorter, resetThreshold).Run(); } @@ -100,9 +98,9 @@ public static IObservable> Sort(t ///
/// The type of the object. /// The type of the key. - /// The source to sort. - /// The used to determine sort order. - /// An that signals the algorithm to re-sort the entire data set. + /// The source IObservable<IChangeSet<TObject, TKey>> to sort. + /// The IComparer<TObject> used to determine sort order. + /// An IObservable<Unit> that signals the algorithm to re-sort the entire data set. /// The sort optimisations. /// The reset threshold. /// An observable which emits change sets. @@ -111,8 +109,8 @@ public static IObservable> Sort(t where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - resorter.ThrowArgumentNullExceptionIfNull(nameof(resorter)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(resorter); return new Sort(source, comparer, sortOptimisations, null, resorter, resetThreshold).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.SortAndBind.cs b/src/DynamicData/Cache/ObservableCacheEx.SortAndBind.cs index 9f5e1444c..d10c90647 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.SortAndBind.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.SortAndBind.cs @@ -1,12 +1,20 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else -using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; using DynamicData.Binding; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -18,10 +26,10 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to sort and bind. - /// The output that will be populated with the sorted results. + /// The source IObservable<IChangeSet<TObject, TKey>> to sort and bind. + /// The output ReadOnlyObservableCollection<TObject> that will be populated with the sorted results. /// An observable which will emit change sets. - /// Creates a and delegates to . + /// Creates a ReadOnlyObservableCollection<T> and delegates to Bind<TObject, TKey>(IObservable<IChangeSet<TObject, TKey, PageContext<TObject>>>, IList<TObject>). public static IObservable> Bind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( this IObservable>> source, out ReadOnlyObservableCollection readOnlyObservableCollection) @@ -39,11 +47,11 @@ public static partial class ObservableCacheEx ///
/// The type of the object. /// The type of the key. - /// The source to sort and bind. - /// The output that will be populated with the sorted results. + /// The source IObservable<IChangeSet<TObject, TKey>> to sort and bind. + /// The output ReadOnlyObservableCollection<TObject> that will be populated with the sorted results. /// The with default settings. /// An observable which will emit change sets. - /// Creates a and delegates to . + /// Creates a ReadOnlyObservableCollection<T> and delegates to Bind<TObject, TKey>(IObservable<IChangeSet<TObject, TKey, PageContext<TObject>>>, IList<TObject>, SortAndBindOptions). public static IObservable> Bind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( this IObservable>> source, out ReadOnlyObservableCollection readOnlyObservableCollection, @@ -62,8 +70,8 @@ public static partial class ObservableCacheEx ///
/// The type of the object. /// The type of the key. - /// The source to sort and bind. - /// The to bind sorted results to. + /// The source IObservable<IChangeSet<TObject, TKey>> to sort and bind. + /// The IList<TObject> to bind sorted results to. /// An observable which will emit change sets. /// This is the primary Bind overload for paged data. It applies paged changeset mutations directly to the target list. public static IObservable> Bind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( @@ -78,8 +86,8 @@ public static partial class ObservableCacheEx ///
/// The type of the object. /// The type of the key. - /// The source to sort and bind. - /// The to bind sorted results to. + /// The source IObservable<IChangeSet<TObject, TKey>> to sort and bind. + /// The IList<TObject> to bind sorted results to. /// The with default settings. /// An observable which will emit change sets. /// This overload accepts to control reset threshold behavior. @@ -96,10 +104,10 @@ public static partial class ObservableCacheEx ///
/// The type of the object. /// The type of the key. - /// The source to sort and bind. - /// The output that will be populated with the sorted results. + /// The source IObservable<IChangeSet<TObject, TKey>> to sort and bind. + /// The output ReadOnlyObservableCollection<TObject> that will be populated with the sorted results. /// An observable which will emit change sets. - /// Creates a and delegates to . + /// Creates a ReadOnlyObservableCollection<T> and delegates to Bind<TObject, TKey>(IObservable<IChangeSet<TObject, TKey, VirtualContext<TObject>>>, IList<TObject>). public static IObservable> Bind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( this IObservable>> source, out ReadOnlyObservableCollection readOnlyObservableCollection) @@ -117,11 +125,11 @@ public static partial class ObservableCacheEx ///
/// The type of the object. /// The type of the key. - /// The source to sort and bind. - /// The output that will be populated with the sorted results. + /// The source IObservable<IChangeSet<TObject, TKey>> to sort and bind. + /// The output ReadOnlyObservableCollection<TObject> that will be populated with the sorted results. /// The with default settings. /// An observable which will emit change sets. - /// Creates a and delegates to . + /// Creates a ReadOnlyObservableCollection<T> and delegates to Bind<TObject, TKey>(IObservable<IChangeSet<TObject, TKey, VirtualContext<TObject>>>, IList<TObject>, SortAndBindOptions). public static IObservable> Bind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( this IObservable>> source, out ReadOnlyObservableCollection readOnlyObservableCollection, @@ -140,8 +148,8 @@ public static partial class ObservableCacheEx ///
/// The type of the object. /// The type of the key. - /// The source to sort and bind. - /// The to bind sorted results to. + /// The source IObservable<IChangeSet<TObject, TKey>> to sort and bind. + /// The IList<TObject> to bind sorted results to. /// An observable which will emit change sets. /// This is the primary Bind overload for virtualized data. It applies virtualized changeset mutations directly to the target list. public static IObservable> Bind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( @@ -156,8 +164,8 @@ public static partial class ObservableCacheEx ///
/// The type of the object. /// The type of the key. - /// The source to sort and bind. - /// The to bind sorted results to. + /// The source IObservable<IChangeSet<TObject, TKey>> to sort and bind. + /// The IList<TObject> to bind sorted results to. /// The with default settings. /// An observable which will emit change sets. /// This overload accepts to control reset threshold behavior. @@ -169,8 +177,15 @@ public static partial class ObservableCacheEx where TKey : notnull => new BindVirtualized(source, targetList, options).Run(); - /// - /// This overload uses for types implementing . + /// + /// Provides an overload of SortAndBind for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The targetList value. + /// The resulting observable sequence. + /// This overload uses Comparer<T>.Default for types implementing IComparable<T>. public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( this IObservable> source, IList targetList) @@ -178,8 +193,16 @@ public static partial class ObservableCacheEx where TKey : notnull => source.SortAndBind(targetList, DynamicDataOptions.SortAndBind); - /// - /// This overload uses for types implementing . + /// + /// Provides an overload of SortAndBind for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The targetList value. + /// The options value. + /// The resulting observable sequence. + /// This overload uses Comparer<T>.Default for types implementing IComparable<T>. public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( this IObservable> source, IList targetList, @@ -188,7 +211,15 @@ public static partial class ObservableCacheEx where TKey : notnull => source.SortAndBind(targetList, Comparer.Default, options); - /// + /// + /// Provides an overload of SortAndBind for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The targetList value. + /// The comparer value. + /// The resulting observable sequence. public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( this IObservable> source, IList targetList, @@ -204,9 +235,9 @@ public static partial class ObservableCacheEx ///
/// The type of the object. /// The type of the key. - /// The source to sort and bind. - /// The to bind sorted results to. Items are inserted, removed, and moved in-place to maintain sort order. - /// The that determines sort order. + /// The source IObservable<IChangeSet<TObject, TKey>> to sort and bind. + /// The IList<TObject> to bind sorted results to. Items are inserted, removed, and moved in-place to maintain sort order. + /// The IComparer<TObject> that determines sort order. /// The controlling reset threshold and initial capacity. /// An observable which will emit change sets. /// @@ -225,17 +256,29 @@ public static partial class ObservableCacheEx /// /// Worth noting: Large batches may trigger a full list reset (clear + re-add) instead of incremental moves, controlled by . This fires CollectionChanged with Reset action, which can be more efficient for UI virtualization but causes a visual flicker. /// - /// + /// SortAndBind<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IList<TObject>, IObservable<IComparer<TObject>>, SortAndBindOptions) public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( this IObservable> source, IList targetList, IComparer comparer, SortAndBindOptions options) where TObject : notnull - where TKey : notnull => - new SortAndBind(source, comparer, options, targetList).Run(); + where TKey : notnull + { + ArgumentExceptionHelper.ThrowIfNull(source); - /// + return new SortAndBind(source, comparer, options, targetList).Run(); + } + + /// + /// Provides an overload of SortAndBind for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The targetList value. + /// The comparerChanged value. + /// The resulting observable sequence. public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( this IObservable> source, IList targetList, @@ -250,9 +293,9 @@ public static partial class ObservableCacheEx ///
/// The type of the object. /// The type of the key. - /// The source to sort and bind. - /// The to bind sorted results to. Items are inserted, removed, and moved in-place to maintain sort order. - /// An that emits new comparers to re-sort with. + /// The source IObservable<IChangeSet<TObject, TKey>> to sort and bind. + /// The IList<TObject> to bind sorted results to. Items are inserted, removed, and moved in-place to maintain sort order. + /// An IObservable<IComparer<TObject>> that emits new comparers to re-sort with. /// The controlling reset threshold and initial capacity. /// An observable which will emit change sets. /// @@ -271,7 +314,7 @@ public static partial class ObservableCacheEx /// /// Worth noting: No data is emitted until the comparer observable produces its first value. Large batches or comparer changes may trigger a full list reset depending on . /// - /// + /// SortAndBind<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IList<TObject>, IComparer<TObject>, SortAndBindOptions) public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( this IObservable> source, IList targetList, @@ -281,8 +324,15 @@ public static partial class ObservableCacheEx where TKey : notnull => new SortAndBind(source, comparerChanged, options, targetList).Run(); - /// - /// This overload uses for types implementing . + /// + /// Provides an overload of SortAndBind for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The readOnlyObservableCollection value. + /// The resulting observable sequence. + /// This overload uses Comparer<T>.Default for types implementing IComparable<T>. public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( this IObservable> source, out ReadOnlyObservableCollection readOnlyObservableCollection) @@ -290,8 +340,16 @@ public static partial class ObservableCacheEx where TKey : notnull => source.SortAndBind(out readOnlyObservableCollection, Comparer.Default, DynamicDataOptions.SortAndBind); - /// - /// This overload uses for types implementing . + /// + /// Provides an overload of SortAndBind for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The readOnlyObservableCollection value. + /// The options value. + /// The resulting observable sequence. + /// This overload uses Comparer<T>.Default for types implementing IComparable<T>. public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( this IObservable> source, out ReadOnlyObservableCollection readOnlyObservableCollection, @@ -300,7 +358,15 @@ public static partial class ObservableCacheEx where TKey : notnull => source.SortAndBind(out readOnlyObservableCollection, Comparer.Default, options); - /// + /// + /// Provides an overload of SortAndBind for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The readOnlyObservableCollection value. + /// The comparer value. + /// The resulting observable sequence. public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( this IObservable> source, out ReadOnlyObservableCollection readOnlyObservableCollection, @@ -309,11 +375,16 @@ public static partial class ObservableCacheEx where TKey : notnull => source.SortAndBind(out readOnlyObservableCollection, comparer, DynamicDataOptions.SortAndBind); - /// - /// The source to sort and bind. - /// The output that will be populated with the sorted results. - /// The that determines sort order. + /// + /// Provides an overload of SortAndBind for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<IChangeSet<TObject, TKey>> to sort and bind. + /// The output ReadOnlyObservableCollection<TObject> that will be populated with the sorted results. + /// The IComparer<TObject> that determines sort order. /// The controlling reset threshold and initial capacity. + /// The resulting observable sequence. public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( this IObservable> source, out ReadOnlyObservableCollection readOnlyObservableCollection, @@ -322,6 +393,8 @@ public static partial class ObservableCacheEx where TObject : notnull where TKey : notnull { + ArgumentExceptionHelper.ThrowIfNull(source); + // allow options to set initial capacity for efficiency var observableCollection = options.InitialCapacity > 0 ? new ObservableCollectionExtended(new List(options.InitialCapacity)) @@ -332,7 +405,15 @@ public static partial class ObservableCacheEx return new SortAndBind(source, comparer, options, observableCollection).Run(); } - /// + /// + /// Provides an overload of SortAndBind for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The readOnlyObservableCollection value. + /// The comparerChanged value. + /// The resulting observable sequence. public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( this IObservable> source, out ReadOnlyObservableCollection readOnlyObservableCollection, @@ -341,11 +422,16 @@ public static partial class ObservableCacheEx where TKey : notnull => source.SortAndBind(out readOnlyObservableCollection, comparerChanged, DynamicDataOptions.SortAndBind); - /// - /// The source to sort and bind. - /// The output that will be populated with the sorted results. - /// An that emits new comparers to re-sort with. + /// + /// Provides an overload of SortAndBind for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<IChangeSet<TObject, TKey>> to sort and bind. + /// The output ReadOnlyObservableCollection<TObject> that will be populated with the sorted results. + /// An IObservable<IComparer<TObject>> that emits new comparers to re-sort with. /// The controlling reset threshold and initial capacity. + /// The resulting observable sequence. public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>( this IObservable> source, out ReadOnlyObservableCollection readOnlyObservableCollection, diff --git a/src/DynamicData/Cache/ObservableCacheEx.SortBy.cs b/src/DynamicData/Cache/ObservableCacheEx.SortBy.cs index 2834f6084..dd143ff26 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.SortBy.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.SortBy.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -27,13 +25,13 @@ public static partial class ObservableCacheEx { /// /// Sorts the changeset stream by the value returned from . Creates a comparer internally - /// and delegates to . + /// and delegates to Sort<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IComparer<TObject>, SortOptimisations, int). /// Since Sort is obsolete, prefer SortAndBind for new code. /// /// The type of the object. /// The type of the key. - /// The source to sort. - /// A that expression that selects a comparable value from each item. + /// The source IObservable<IChangeSet<TObject, TKey>> to sort. + /// A Func<T, TResult> that expression that selects a comparable value from each item. /// The sort direction. Defaults to ascending. /// A that sort optimization flags. /// The number of updates before the entire list is re-sorted (rather than inline sort). diff --git a/src/DynamicData/Cache/ObservableCacheEx.StartWithEmpty.cs b/src/DynamicData/Cache/ObservableCacheEx.StartWithEmpty.cs index 8960f03aa..e93578868 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.StartWithEmpty.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.StartWithEmpty.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,65 +29,118 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to prepend an empty changeset to. + /// The source IObservable<IChangeSet<TObject, TKey>> to prepend an empty changeset to. /// An observable that emits an empty changeset first, then all source changesets. - /// + /// ObservableListEx.StartWithEmpty<T>(IObservable<IChangeSet<T>>) public static IObservable> StartWithEmpty(this IObservable> source) where TObject : notnull - where TKey : notnull => source.StartWith(ChangeSet.Empty); + where TKey : notnull + { + ArgumentExceptionHelper.ThrowIfNull(source); + + return source.StartWith(ChangeSet.Empty); + } - /// - /// The source to prepend an empty changeset to. + /// + /// Provides an overload of StartWithEmpty for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<ISortedChangeSet<TObject, TKey>> to prepend an empty changeset to. /// An observable that emits an empty sorted changeset first, then all source changesets. - /// Overload for . + /// Overload for ISortedChangeSet<TObject, TKey>. public static IObservable> StartWithEmpty(this IObservable> source) where TObject : notnull - where TKey : notnull => source.StartWith(SortedChangeSet.Empty); + where TKey : notnull + { + ArgumentExceptionHelper.ThrowIfNull(source); - /// - /// The source to prepend an empty changeset to. + return source.StartWith(SortedChangeSet.Empty); + } + + /// + /// Provides an overload of StartWithEmpty for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<IVirtualChangeSet<TObject, TKey>> to prepend an empty changeset to. /// An observable that emits an empty virtual changeset first, then all source changesets. - /// Overload for . + /// Overload for IVirtualChangeSet<TObject, TKey>. public static IObservable> StartWithEmpty(this IObservable> source) where TObject : notnull - where TKey : notnull => source.StartWith(VirtualChangeSet.Empty); + where TKey : notnull + { + ArgumentExceptionHelper.ThrowIfNull(source); + + return source.StartWith(VirtualChangeSet.Empty); + } - /// - /// The source to prepend an empty changeset to. + /// + /// Provides an overload of StartWithEmpty for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<IPagedChangeSet<TObject, TKey>> to prepend an empty changeset to. /// An observable that emits an empty paged changeset first, then all source changesets. - /// Overload for . + /// Overload for IPagedChangeSet<TObject, TKey>. public static IObservable> StartWithEmpty(this IObservable> source) where TObject : notnull - where TKey : notnull => source.StartWith(PagedChangeSet.Empty); + where TKey : notnull + { + ArgumentExceptionHelper.ThrowIfNull(source); + + return source.StartWith(PagedChangeSet.Empty); + } - /// + /// + /// Provides an overload of StartWithEmpty for the supplied arguments. + /// /// The type of the object. /// The type of the key. /// The grouping key type. - /// The source to prepend an empty changeset to. + /// The source IObservable<IGroupChangeSet<TObject, TKey, TGroupKey>> to prepend an empty changeset to. /// An observable that emits an empty group changeset first, then all source changesets. - /// Overload for . + /// Overload for IGroupChangeSet<TObject, TKey, TGroupKey>. public static IObservable> StartWithEmpty(this IObservable> source) where TObject : notnull where TKey : notnull - where TGroupKey : notnull => source.StartWith(GroupChangeSet.Empty); + where TGroupKey : notnull + { + ArgumentExceptionHelper.ThrowIfNull(source); + + return source.StartWith(GroupChangeSet.Empty); + } - /// + /// + /// Provides an overload of StartWithEmpty for the supplied arguments. + /// /// The type of the object. /// The type of the key. /// The grouping key type. - /// The source to prepend an empty changeset to. + /// The source IObservable<IImmutableGroupChangeSet<TObject, TKey, TGroupKey>> to prepend an empty changeset to. /// An observable that emits an empty immutable group changeset first, then all source changesets. - /// Overload for . + /// Overload for IImmutableGroupChangeSet<TObject, TKey, TGroupKey>. public static IObservable> StartWithEmpty(this IObservable> source) where TObject : notnull where TKey : notnull - where TGroupKey : notnull => source.StartWith(ImmutableGroupChangeSet.Empty); + where TGroupKey : notnull + { + ArgumentExceptionHelper.ThrowIfNull(source); + + return source.StartWith(ImmutableGroupChangeSet.Empty); + } - /// + /// + /// Provides an overload of StartWithEmpty for the supplied arguments. + /// /// The type of the item. - /// The source of to prepend an empty changeset to. + /// The source IObservable<T> of IReadOnlyCollection<T> to prepend an empty changeset to. /// An observable that emits an empty collection first, then all source collections. - /// Overload for . - public static IObservable> StartWithEmpty(this IObservable> source) => source.StartWith(ReadOnlyCollectionLight.Empty); + /// Overload for IReadOnlyCollection<T>. + public static IObservable> StartWithEmpty(this IObservable> source) + { + ArgumentExceptionHelper.ThrowIfNull(source); + + return source.StartWith(ReadOnlyCollectionLight.Empty); + } } diff --git a/src/DynamicData/Cache/ObservableCacheEx.StartWithItem.cs b/src/DynamicData/Cache/ObservableCacheEx.StartWithItem.cs index 560f53723..b38ee99e5 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.StartWithItem.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.StartWithItem.cs @@ -1,39 +1,35 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { - /// - /// The source to prepend an initial item to. - /// The item to prepend. The key is extracted from . - /// Overload for items that implement . Delegates to the explicit key overload. + /// + /// Provides an overload of StartWithItem for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<IChangeSet<TObject, TKey>> to prepend an initial item to. + /// The item to prepend. The key is extracted from IKey<TKey>.Key. + /// The resulting observable sequence. + /// Overload for items that implement IKey<TKey>. Delegates to the explicit key overload. public static IObservable> StartWithItem(this IObservable> source, TObject item) where TObject : IKey where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.StartWithItem(item, item.Key); } @@ -44,7 +40,7 @@ public static IObservable> StartWithItem /// The type of the object. /// The type of the key. - /// The source to prepend an initial item to. + /// The source IObservable<IChangeSet<TObject, TKey>> to prepend an initial item to. /// The item to prepend. /// The key for the item. /// An observable that emits a single-item Add changeset first, then all source changesets. @@ -52,7 +48,7 @@ public static IObservable> StartWithItem(ChangeReason.Add, key, item); return source.StartWith(new ChangeSet { change }); diff --git a/src/DynamicData/Cache/ObservableCacheEx.SubscribeMany.cs b/src/DynamicData/Cache/ObservableCacheEx.SubscribeMany.cs index 4149907ba..ae6524d96 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.SubscribeMany.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.SubscribeMany.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -32,7 +30,7 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to create a subscription for each item in. + /// The source IObservable<IChangeSet<TObject, TKey>> to create a subscription for each item in. /// A factory that creates an for each item. Called on Add and Update (for the new value). /// A stream that forwards all changesets from unchanged. /// @@ -47,8 +45,8 @@ public static partial class ObservableCacheEx /// /// /// - /// Internally implemented using - /// and , so disposal semantics match . + /// Internally implemented using Transform<TDestination,TObject,TKey>(IObservable<IChangeSet<TObject,TKey>>, Func<TObject,TKey,TDestination>, bool) + /// and DisposeMany<TObject,TKey>, so disposal semantics match DisposeMany<TObject,TKey>. /// /// /// Use this to tie per-item side effects (event subscriptions, polling timers, child observable subscriptions) @@ -56,29 +54,34 @@ public static partial class ObservableCacheEx /// /// /// or is . - /// - /// - /// + /// DisposeMany<TObject,TKey> + /// OnItemRemoved<TObject,TKey>(IObservable<IChangeSet<TObject,TKey>>, Action<TObject,TKey>, bool) + /// ObservableListEx.SubscribeMany public static IObservable> SubscribeMany(this IObservable> source, Func subscriptionFactory) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - subscriptionFactory.ThrowArgumentNullExceptionIfNull(nameof(subscriptionFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(subscriptionFactory); return new SubscribeMany(source, subscriptionFactory).Run(); } - /// - /// The source to create a subscription for each item in. + /// + /// Provides an overload of SubscribeMany for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<IChangeSet<TObject, TKey>> to create a subscription for each item in. /// A factory that creates an for each item. Receives the item and its key. - /// Overload whose factory receives both the item and the key. See for full details. + /// The resulting observable sequence. + /// Overload whose factory receives both the item and the key. See SubscribeMany<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, IDisposable>) for full details. public static IObservable> SubscribeMany(this IObservable> source, Func subscriptionFactory) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - subscriptionFactory.ThrowArgumentNullExceptionIfNull(nameof(subscriptionFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(subscriptionFactory); return new SubscribeMany(source, subscriptionFactory).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.SuppressRefresh.cs b/src/DynamicData/Cache/ObservableCacheEx.SuppressRefresh.cs index 452c4ea0d..b34449f3b 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.SuppressRefresh.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.SuppressRefresh.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -30,7 +21,7 @@ public static partial class ObservableCacheEx /// /// The object of the change set. /// The key of the change set. - /// The source to strip refresh events. + /// The source IObservable<IChangeSet<TObject, TKey>> to strip refresh events. /// An observable which emits change sets. public static IObservable> SuppressRefresh(this IObservable> source) where TObject : notnull diff --git a/src/DynamicData/Cache/ObservableCacheEx.Switch.cs b/src/DynamicData/Cache/ObservableCacheEx.Switch.cs index c300179c1..9011e5927 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Switch.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Switch.cs @@ -1,38 +1,41 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { - /// - /// An observable that emits instances. + /// + /// Provides an overload of Switch for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// An observable that emits IObservableCache<TObject, TKey> instances. + /// The resulting observable sequence. /// Overload that accepts observable caches. Internally calls Connect() on each cache and delegates to the changeset overload. public static IObservable> Switch(this IObservable> sources) where TObject : notnull where TKey : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return sources.Select(cache => cache.Connect()).Switch(); } @@ -43,7 +46,7 @@ public static IObservable> Switch(this /// /// The type of the object. /// The type of the key. - /// An of changeset streams. The operator subscribes to the latest inner stream. + /// An IObservable<T> of IObservable<T> changeset streams. The operator subscribes to the latest inner stream. /// A changeset stream reflecting the items from the most recently emitted inner source. /// /// On switch: Remove is emitted for all items from the previous source, then Add for all items from the new source. @@ -53,7 +56,7 @@ public static IObservable> Switch(this where TObject : notnull where TKey : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return new Switch(sources).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.ToCollection.cs b/src/DynamicData/Cache/ObservableCacheEx.ToCollection.cs index 1871e4337..f40c2b1b6 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.ToCollection.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.ToCollection.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -30,9 +21,9 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to materialize into a collection on each change. + /// The source IObservable<IChangeSet<TObject, TKey>> to materialize into a collection on each change. /// An observable which emits the read only collection. - /// + /// ObservableListEx.ToCollection<TObject>(IObservable<IChangeSet<TObject>>) public static IObservable> ToCollection(this IObservable> source) where TObject : notnull where TKey : notnull => source.QueryWhenChanged(query => new ReadOnlyCollectionLight(query.Items)); diff --git a/src/DynamicData/Cache/ObservableCacheEx.ToObservableChangeSet.cs b/src/DynamicData/Cache/ObservableCacheEx.ToObservableChangeSet.cs index f04d2c998..545805b7d 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.ToObservableChangeSet.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.ToObservableChangeSet.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -32,9 +23,9 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to convert into a keyed changeset stream. - /// A that selects the unique key for each item. - /// An optional that specifies per-item expiration time. Return for no expiration. + /// The source IObservable<TObject> to convert into a keyed changeset stream. + /// A Func<T, TResult> that selects the unique key for each item. + /// An optional Func<T, TResult> that specifies per-item expiration time. Return for no expiration. /// The maximum cache size. Oldest items are removed when exceeded. Use -1 for no limit. /// An optional for expiration timing. /// An observable changeset stream. @@ -48,8 +39,8 @@ public static IObservable> ToObservableChangeSet.Create( source: source, @@ -66,9 +57,9 @@ public static IObservable> ToObservableChangeSet /// The type of the object. /// The type of the key. - /// The source to convert into a keyed changeset stream. - /// A that selects the unique key for each item. - /// An optional that specifies per-item expiration time. Return for no expiration. + /// The source IObservable<IEnumerable<TObject>> to convert into a keyed changeset stream. + /// A Func<T, TResult> that selects the unique key for each item. + /// An optional Func<T, TResult> that specifies per-item expiration time. Return for no expiration. /// The maximum cache size. Oldest items are removed when exceeded. Use -1 for no limit. /// An optional for expiration timing. /// An observable changeset stream. @@ -82,8 +73,8 @@ public static IObservable> ToObservableChangeSet.Create( source: source, diff --git a/src/DynamicData/Cache/ObservableCacheEx.ToObservableOptional.cs b/src/DynamicData/Cache/ObservableCacheEx.ToObservableOptional.cs index 43d2c39eb..4902eec6f 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.ToObservableOptional.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.ToObservableOptional.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -26,37 +24,37 @@ namespace DynamicData; public static partial class ObservableCacheEx { /// - /// Watches a single key in the source changeset stream, emitting Optional.Some(value) when the key - /// is present and when it is removed. Duplicate values are suppressed via . + /// Watches a single key in the source changeset stream, emitting ReactiveUI.Primitives.Optional.Some(value) when the key + /// is present and ReactiveUI.Primitives.Optional.None<T> when it is removed. Duplicate values are suppressed via . /// /// The type of the object. /// The type of the key. - /// The source to watch a single key in. + /// The source IObservable<IChangeSet<TObject, TKey>> to watch a single key in. /// The key to watch. - /// An that optional comparer to suppress duplicate emissions. Uses default equality if . - /// An observable of that reflects the presence or absence of the specified key. + /// An IEqualityComparer<TObject> that optional comparer to suppress duplicate emissions. Uses default equality if . + /// An observable of Optional<TObject> that reflects the presence or absence of the specified key. /// /// - /// Unlike , this emits None on removal + /// Unlike WatchValue<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, TKey), this emits None on removal /// (rather than the removed value), making it possible to distinguish "key is absent" from "key has a value". /// /// /// EventBehavior - /// AddEmits Optional.Some(value) if the key was not previously tracked. - /// UpdateEmits Optional.Some(newValue) if the new value differs from the previous per . Otherwise suppressed. - /// RemoveEmits . - /// RefreshEmits Optional.Some(value) if the value differs from the last emission per . Otherwise suppressed. + /// AddEmits ReactiveUI.Primitives.Optional.Some(value) if the key was not previously tracked. + /// UpdateEmits ReactiveUI.Primitives.Optional.Some(newValue) if the new value differs from the previous per . Otherwise suppressed. + /// RemoveEmits ReactiveUI.Primitives.Optional.None<T>. + /// RefreshEmits ReactiveUI.Primitives.Optional.Some(value) if the value differs from the last emission per . Otherwise suppressed. /// /// Worth noting: No emission occurs if the key is not present at subscription time. To get an initial None when the key is absent, use the overload with initialOptionalWhenMissing: true. /// /// is . - /// - /// - public static IObservable> ToObservableOptional(this IObservable> source, TKey key, IEqualityComparer? equalityComparer = null) + /// Watch<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, TKey) + /// WatchValue<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, TKey) + public static IObservable> ToObservableOptional(this IObservable> source, TKey key, IEqualityComparer? equalityComparer = null) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new ToObservableOptional(source, key, equalityComparer).Run(); } @@ -66,16 +64,16 @@ public static IObservable> ToObservableOptional /// /// The type of the object. /// The type of the key. - /// The source to watch a single key in. + /// The source IObservable<IChangeSet<TObject, TKey>> to watch a single key in. /// The key value. - /// When , emits an initial with no value if the key is not present in the cache. - /// An optional instance used to determine if an object value has changed. + /// When , emits an initial Optional<TObject> with no value if the key is not present in the cache. + /// An optional IEqualityComparer<TObject> instance used to determine if an object value has changed. /// An observable optional. /// source is null. /// /// Worth noting: Uses lock-based coordination. If the key exists synchronously on Connect(), the initial None may or may not be emitted depending on timing. /// - public static IObservable> ToObservableOptional(this IObservable> source, TKey key, bool initialOptionalWhenMissing, IEqualityComparer? equalityComparer = null) + public static IObservable> ToObservableOptional(this IObservable> source, TKey key, bool initialOptionalWhenMissing, IEqualityComparer? equalityComparer = null) where TObject : notnull where TKey : notnull { @@ -87,8 +85,8 @@ public static IObservable> ToObservableOptional return source.ToObservableOptional(key, equalityComparer) .Do(_ => seenValue = true) .Merge(Observable.Defer(() => seenValue - ? Observable.Empty>() - : Observable.Return(Optional.None()))); + ? Observable.Empty>() + : Observable.Return(ReactiveUI.Primitives.Optional.None))); }); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.ToSortedCollection.cs b/src/DynamicData/Cache/ObservableCacheEx.ToSortedCollection.cs index 81770d625..3bc8779d1 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.ToSortedCollection.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.ToSortedCollection.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,11 +29,11 @@ public static partial class ObservableCacheEx /// The type of the object. /// The type of the key. /// The sort key. - /// The source to materialize into a sorted collection on each change. - /// The sort function. + /// The source IObservable<IChangeSet<TObject, TKey>> to materialize into a sorted collection on each change. + /// The Func<TObject, TSortKey> sort function. /// The sort order. Defaults to ascending. /// An observable which emits the read only collection. - /// + /// ObservableListEx.ToSortedCollection<TObject, TSortKey>(IObservable<IChangeSet<TObject>>, Func<TObject, TSortKey>, SortDirection) public static IObservable> ToSortedCollection(this IObservable> source, Func sort, SortDirection sortOrder = SortDirection.Ascending) where TObject : notnull where TKey : notnull @@ -46,8 +44,8 @@ public static IObservable> ToSortedCollection /// The type of the object. /// The type of the key. - /// The source to materialize into a sorted collection on each change. - /// The sort comparer. + /// The source IObservable<IChangeSet<TObject, TKey>> to materialize into a sorted collection on each change. + /// The IComparer<TObject> sort comparer. /// An observable which emits the read only collection. public static IObservable> ToSortedCollection(this IObservable> source, IComparer comparer) where TObject : notnull diff --git a/src/DynamicData/Cache/ObservableCacheEx.Transform.cs b/src/DynamicData/Cache/ObservableCacheEx.Transform.cs index 8319591d0..87666bc4d 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Transform.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Transform.cs @@ -1,92 +1,135 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { - /// + /// + /// Provides an overload of Transform for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The transformOnRefresh value. + /// The resulting observable sequence. /// This overload accepts a bool transformOnRefresh flag. When , Refresh changes cause re-transformation (emitted as Update). The factory receives only the current item. - /// + /// ObservableListEx.Transform public static IObservable> Transform(this IObservable> source, Func transformFactory, bool transformOnRefresh) where TDestination : notnull where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); return source.Transform((current, _, _) => transformFactory(current), transformOnRefresh); } - /// + /// + /// Provides an overload of Transform for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The transformOnRefresh value. + /// The resulting observable sequence. /// This overload accepts a bool transformOnRefresh flag. When , Refresh changes cause re-transformation (emitted as Update). The factory receives the current item and key. public static IObservable> Transform(this IObservable> source, Func transformFactory, bool transformOnRefresh) where TDestination : notnull where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); return source.Transform((current, _, key) => transformFactory(current, key), transformOnRefresh); } - /// + /// + /// Provides an overload of Transform for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The transformOnRefresh value. + /// The resulting observable sequence. /// This overload accepts a bool transformOnRefresh flag. When , Refresh changes cause re-transformation (emitted as Update). - public static IObservable> Transform(this IObservable> source, Func, TKey, TDestination> transformFactory, bool transformOnRefresh) + public static IObservable> Transform(this IObservable> source, Func, TKey, TDestination> transformFactory, bool transformOnRefresh) where TDestination : notnull where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); return new Transform(source, transformFactory, transformOnRefresh: transformOnRefresh).Run(); } - /// + /// + /// Provides an overload of Transform for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The forceTransform value. + /// The resulting observable sequence. /// This overload accepts an optional forceTransform predicate filtering by source item only (without the key). The factory receives only the current item. public static IObservable> Transform(this IObservable> source, Func transformFactory, IObservable>? forceTransform = null) where TDestination : notnull where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); return source.Transform((current, _, _) => transformFactory(current), forceTransform?.ForForced()); } - /// + /// + /// Provides an overload of Transform for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The forceTransform value. + /// The resulting observable sequence. /// This overload accepts an optional forceTransform predicate filtering by source item and key. The factory receives the current item and key. public static IObservable> Transform(this IObservable> source, Func transformFactory, IObservable>? forceTransform = null) where TDestination : notnull where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); return source.Transform((current, _, key) => transformFactory(current, key), forceTransform); } @@ -97,8 +140,8 @@ public static IObservable> TransformThe type of the transformed items. /// The type of the source items. /// The type of the key. - /// The source to transform. - /// The that produces a from the current source item, the previous source item (if any), and the key. + /// The source IObservable<IChangeSet<TSource, TKey>> to transform. + /// The Func<TSource, Optional<TSource>, TKey, TDestination> that produces a from the current source item, the previous source item (if any), and the key. /// An observable that, when it emits a predicate, re-transforms all items for which the predicate returns . Re-transformed items are emitted as changes. If , no forced re-transforms occur. /// An observable changeset of transformed items. /// @@ -120,22 +163,22 @@ public static IObservable> Transform /// - /// Factory exceptions propagate as , terminating the stream. - /// Use + /// Factory exceptions propagate as IObserver<T>.OnError, terminating the stream. + /// Use TransformSafe<TDestination, TSource, TKey>(IObservable<IChangeSet<TSource, TKey>>, Func<TSource, Optional<TSource>, TKey, TDestination>, Action<Error<TSource, TKey>>, IObservable<Func<TSource, TKey, bool>>?) /// to catch factory errors without killing the stream. /// /// - /// - /// - /// + /// TransformSafe<TDestination, TSource, TKey>(IObservable<IChangeSet<TSource, TKey>>, Func<TSource, Optional<TSource>, TKey, TDestination>, Action<Error<TSource, TKey>>, IObservable<Func<TSource, TKey, bool>>?) + /// TransformAsync<TDestination, TSource, TKey>(IObservable<IChangeSet<TSource, TKey>>, Func<TSource, Optional<TSource>, TKey, Task<TDestination>>, IObservable<Func<TSource, TKey, bool>>?) + /// TransformImmutable<TDestination, TSource, TKey> /// or is . - public static IObservable> Transform(this IObservable> source, Func, TKey, TDestination> transformFactory, IObservable>? forceTransform = null) + public static IObservable> Transform(this IObservable> source, Func, TKey, TDestination> transformFactory, IObservable>? forceTransform = null) where TDestination : notnull where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); if (forceTransform is not null) { return new TransformWithForcedTransform(source, transformFactory, forceTransform).Run(); @@ -144,37 +187,64 @@ public static IObservable> Transform(source, transformFactory).Run(); } - /// - /// This overload accepts of to force re-transformation of ALL items when the observable emits. The factory receives only the current item. + /// + /// Provides an overload of ForForced for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The forceTransform value. + /// The resulting observable sequence. + /// This overload accepts IObservable<T> of to force re-transformation of ALL items when the observable emits. The factory receives only the current item. public static IObservable> Transform(this IObservable> source, Func transformFactory, IObservable forceTransform) where TDestination : notnull where TSource : notnull where TKey : notnull => source.Transform((cur, _, _) => transformFactory(cur), forceTransform.ForForced()); - /// - /// This overload accepts of to force re-transformation of ALL items when the observable emits. The factory receives the current item and key. + /// + /// Provides an overload of Transform for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The forceTransform value. + /// The resulting observable sequence. + /// This overload accepts IObservable<T> of to force re-transformation of ALL items when the observable emits. The factory receives the current item and key. public static IObservable> Transform(this IObservable> source, Func transformFactory, IObservable forceTransform) where TDestination : notnull where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); - forceTransform.ThrowArgumentNullExceptionIfNull(nameof(forceTransform)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); + ArgumentExceptionHelper.ThrowIfNull(forceTransform); return source.Transform((cur, _, key) => transformFactory(cur, key), forceTransform.ForForced()); } - /// - /// This overload accepts of to force re-transformation of ALL items when the observable emits. - public static IObservable> Transform(this IObservable> source, Func, TKey, TDestination> transformFactory, IObservable forceTransform) + /// + /// Provides an overload of Transform for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The forceTransform value. + /// The resulting observable sequence. + /// This overload accepts IObservable<T> of to force re-transformation of ALL items when the observable emits. + public static IObservable> Transform(this IObservable> source, Func, TKey, TDestination> transformFactory, IObservable forceTransform) where TDestination : notnull where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); - forceTransform.ThrowArgumentNullExceptionIfNull(nameof(forceTransform)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); + ArgumentExceptionHelper.ThrowIfNull(forceTransform); return source.Transform(transformFactory, forceTransform.ForForced()); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.TransformAsync.cs b/src/DynamicData/Cache/ObservableCacheEx.TransformAsync.cs index dc13c5a2f..934362bc2 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.TransformAsync.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.TransformAsync.cs @@ -1,46 +1,62 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { - /// + /// + /// Provides an overload of TransformAsync for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The forceTransform value. + /// The resulting observable sequence. /// This overload takes a simpler factory that receives only the current item. - /// + /// ObservableListEx.TransformAsync [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformAsync(this IObservable> source, Func> transformFactory, IObservable>? forceTransform = null) where TDestination : notnull where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); return source.TransformAsync((current, _, _) => transformFactory(current), forceTransform); } - /// + /// + /// Provides an overload of TransformAsync for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The forceTransform value. + /// The resulting observable sequence. /// This overload takes a factory that receives the current item and key. [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformAsync(this IObservable> source, Func> transformFactory, IObservable>? forceTransform = null) @@ -48,21 +64,21 @@ public static IObservable> TransformAsync transformFactory(current, key), forceTransform); } /// - /// Async version of . - /// Projects each item using an async factory that returns . + /// Async version of Transform<TDestination, TSource, TKey>(IObservable<IChangeSet<TSource, TKey>>, Func<TSource, Optional<TSource>, TKey, TDestination>, IObservable<Func<TSource, TKey, bool>>?). + /// Projects each item using an async factory that returns Task<TResult>. /// /// The type of the transformed items. /// The type of the source items. /// The type of the key. - /// The source to transform asynchronously. - /// The async function that produces a from the current source item, the previous source item (if any), and the key. + /// The source IObservable<IChangeSet<TSource, TKey>> to transform asynchronously. + /// The Func<TSource, Optional<TSource>, TKey, Task<TDestination>> async function that produces a from the current source item, the previous source item (if any), and the key. /// An observable that, when it emits a predicate, re-transforms all items for which the predicate returns . Re-transformed items are emitted as changes. If , no forced re-transforms occur. /// An observable changeset of transformed items. /// @@ -81,25 +97,34 @@ public static IObservable> TransformAsync /// Worth noting: Transforms are batched per changeset (all tasks must complete before the next changeset is processed). Completion waits for in-flight transforms. Remove does NOT cancel in-flight transforms for the removed key. /// - /// Factory exceptions propagate as . Use - /// + /// Factory exceptions propagate as IObserver<T>.OnError. Use + /// TransformSafeAsync<TDestination, TSource, TKey>(IObservable<IChangeSet<TSource, TKey>>, Func<TSource, Optional<TSource>, TKey, Task<TDestination>>, Action<Error<TSource, TKey>>, IObservable<Func<TSource, TKey, bool>>?) /// to catch factory errors without terminating the stream. /// /// /// or is . [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] - public static IObservable> TransformAsync(this IObservable> source, Func, TKey, Task> transformFactory, IObservable>? forceTransform = null) + public static IObservable> TransformAsync(this IObservable> source, Func, TKey, Task> transformFactory, IObservable>? forceTransform = null) where TDestination : notnull where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); return new TransformAsync(source, transformFactory, null, forceTransform).Run(); } - /// + /// + /// Provides an overload of TransformAsync for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The options value. + /// The resulting observable sequence. /// This overload accepts to control concurrency and Refresh handling. The factory receives only the current item. [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformAsync(this IObservable> source, Func> transformFactory, TransformAsyncOptions options) @@ -107,13 +132,22 @@ public static IObservable> TransformAsync transformFactory(current), options); } - /// + /// + /// Provides an overload of TransformAsync for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The options value. + /// The resulting observable sequence. /// This overload accepts to control concurrency and Refresh handling. The factory receives the current item and key. [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformAsync(this IObservable> source, Func> transformFactory, TransformAsyncOptions options) @@ -121,22 +155,31 @@ public static IObservable> TransformAsync transformFactory(current, key), options); } - /// + /// + /// Provides an overload of TransformAsync for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The options value. + /// The resulting observable sequence. /// This overload accepts to control concurrency and Refresh handling. [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] - public static IObservable> TransformAsync(this IObservable> source, Func, TKey, Task> transformFactory, TransformAsyncOptions options) + public static IObservable> TransformAsync(this IObservable> source, Func, TKey, Task> transformFactory, TransformAsyncOptions options) where TDestination : notnull where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); return new TransformAsync(source, transformFactory, null, null, options.MaximumConcurrency, options.TransformOnRefresh).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.TransformImmutable.cs b/src/DynamicData/Cache/ObservableCacheEx.TransformImmutable.cs index 940746e7a..f5e42ce9a 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.TransformImmutable.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.TransformImmutable.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -32,13 +30,13 @@ public static partial class ObservableCacheEx /// The type of the transformed items. /// The type of the source items. /// The type of the key. - /// The source to transform (items assumed immutable). - /// The pure function that maps a source item to a destination item. Must be deterministic: same input always produces equivalent output. + /// The source IObservable<IChangeSet<TSource, TKey>> to transform (items assumed immutable). + /// The Func<TSource, TDestination> pure function that maps a source item to a destination item. Must be deterministic: same input always produces equivalent output. /// An observable changeset of transformed items. /// /// /// Because the transform is assumed to be stateless and deterministic, this operator does not track - /// previously transformed items. This reduces memory overhead compared to . + /// previously transformed items. This reduces memory overhead compared to Transform<TDestination, TSource, TKey>(IObservable<IChangeSet<TSource, TKey>>, Func<TSource, Optional<TSource>, TKey, TDestination>, IObservable<Func<TSource, TKey, bool>>?). /// /// Change reason handling: /// @@ -48,7 +46,7 @@ public static partial class ObservableCacheEx /// RemoveEmits Remove. Factory is NOT called. /// RefreshDROPPED. Immutable items do not change, so Refresh is meaningless. /// - /// Use this when items are immutable, the factory is pure, and the factory is cheap. If any of these conditions are false, use instead. + /// Use this when items are immutable, the factory is pure, and the factory is cheap. If any of these conditions are false, use Transform<TDestination, TSource, TKey>(IObservable<IChangeSet<TSource, TKey>>, Func<TSource, Optional<TSource>, TKey, TDestination>, IObservable<Func<TSource, TKey, bool>>?) instead. /// /// or is . public static IObservable> TransformImmutable( @@ -58,8 +56,8 @@ public static IObservable> TransformImmutable( source: source, diff --git a/src/DynamicData/Cache/ObservableCacheEx.TransformMany.cs b/src/DynamicData/Cache/ObservableCacheEx.TransformMany.cs index 682f5592d..073d594d7 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.TransformMany.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.TransformMany.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -33,9 +31,9 @@ public static partial class ObservableCacheEx /// The type of the child item keys. /// The type of the source (parent) items. /// The type of the source (parent) keys. - /// The source to expand each item into multiple children. - /// A function that expands a parent item into its children. For or overloads, subsequent changes to the child collection are automatically tracked. - /// A that extracts a unique key from each child item. Keys must be unique across ALL parents, not just within one parent. + /// The source IObservable<IChangeSet<TSource, TSourceKey>> to expand each item into multiple children. + /// A function that expands a parent item into its children. For ObservableCollection<T> or IObservableCache<TObject, TKey> overloads, subsequent changes to the child collection are automatically tracked. + /// A Func<T, TResult> that extracts a unique key from each child item. Keys must be unique across ALL parents, not just within one parent. /// An observable changeset of flattened child items. /// /// Change reason handling: @@ -47,35 +45,65 @@ public static partial class ObservableCacheEx /// RefreshPropagated as Refresh to all children (no re-expansion). /// /// Worth noting: If two source items produce children with the same key, last-in-wins. Refresh does NOT re-expand children (only Update does). - /// If two parents produce children with the same key, last-in-wins. Use the async variant with a to control conflict resolution. + /// If two parents produce children with the same key, last-in-wins. Use the async variant with a IComparer<T> to control conflict resolution. /// /// , , or is . - /// - /// + /// TransformManyAsync<TDestination, TDestinationKey, TSource, TSourceKey>(IObservable<IChangeSet<TSource, TSourceKey>>, Func<TSource, TSourceKey, Task<IEnumerable<TDestination>>>, Func<TDestination, TDestinationKey>, IEqualityComparer<TDestination>?, IComparer<TDestination>?) + /// ObservableListEx.TransformMany public static IObservable> TransformMany(this IObservable> source, Func> manySelector, Func keySelector) where TDestination : notnull where TDestinationKey : notnull where TSource : notnull where TSourceKey : notnull => new TransformMany(source, manySelector, keySelector).Run(); - /// - /// This overload accepts an selector. Changes to the child collection (adds, removes, replacements) are automatically observed and reflected downstream. + /// + /// Provides an overload of Run for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TDestinationKey value. + /// The type of the TSource value. + /// The type of the TSourceKey value. + /// The source value. + /// The manySelector value. + /// The keySelector value. + /// The resulting observable sequence. + /// This overload accepts an ObservableCollection<T> selector. Changes to the child collection (adds, removes, replacements) are automatically observed and reflected downstream. public static IObservable> TransformMany(this IObservable> source, Func> manySelector, Func keySelector) where TDestination : notnull where TDestinationKey : notnull where TSource : notnull where TSourceKey : notnull => new TransformMany(source, manySelector, keySelector).Run(); - /// - /// This overload accepts a selector. Changes to the child collection are automatically observed and reflected downstream. + /// + /// Provides an overload of Run for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TDestinationKey value. + /// The type of the TSource value. + /// The type of the TSourceKey value. + /// The source value. + /// The manySelector value. + /// The keySelector value. + /// The resulting observable sequence. + /// This overload accepts a ReadOnlyObservableCollection<T> selector. Changes to the child collection are automatically observed and reflected downstream. public static IObservable> TransformMany(this IObservable> source, Func> manySelector, Func keySelector) where TDestination : notnull where TDestinationKey : notnull where TSource : notnull where TSourceKey : notnull => new TransformMany(source, manySelector, keySelector).Run(); - /// - /// This overload accepts an selector. The child cache is live: subsequent changes to it are automatically propagated downstream. + /// + /// Provides an overload of Run for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TDestinationKey value. + /// The type of the TSource value. + /// The type of the TSourceKey value. + /// The source value. + /// The manySelector value. + /// The keySelector value. + /// The resulting observable sequence. + /// This overload accepts an IObservableCache<TObject, TKey> selector. The child cache is live: subsequent changes to it are automatically propagated downstream. public static IObservable> TransformMany(this IObservable> source, Func> manySelector, Func keySelector) where TDestination : notnull where TDestinationKey : notnull diff --git a/src/DynamicData/Cache/ObservableCacheEx.TransformManyAsync.cs b/src/DynamicData/Cache/ObservableCacheEx.TransformManyAsync.cs index 4a6ac6005..32fd5489e 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.TransformManyAsync.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.TransformManyAsync.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; +#if REACTIVE_SHIM +using DynamicData.Reactive.Cache.Internal; +#else using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -26,18 +24,18 @@ namespace DynamicData; public static partial class ObservableCacheEx { /// - /// Async version of . + /// Async version of TransformMany<TDestination, TDestinationKey, TSource, TSourceKey>(IObservable<IChangeSet<TSource, TSourceKey>>, Func<TSource, IEnumerable<TDestination>>, Func<TDestination, TDestinationKey>). /// Flattens each source item into zero or more destination items using an async factory. /// /// The type of the child items. /// The type of the child item keys. /// The type of the source (parent) items. /// The type of the source (parent) keys. - /// The source to expand each item into multiple children asynchronously. - /// An async function that expands a parent item (and its key) into an of children. - /// A that extracts a unique key from each child item. - /// An that optional comparer to determine if two child items with the same key are equal. Used to suppress no-op updates. - /// An that optional comparer to resolve key collisions when the same destination key is produced by multiple parents. The winning item is determined by this comparer. + /// The source IObservable<IChangeSet<TSource, TSourceKey>> to expand each item into multiple children asynchronously. + /// An async function that expands a parent item (and its key) into an IEnumerable<T> of children. + /// A Func<T, TResult> that extracts a unique key from each child item. + /// An IEqualityComparer<TDestination> that optional comparer to determine if two child items with the same key are equal. Used to suppress no-op updates. + /// An IComparer<TDestination> that optional comparer to resolve key collisions when the same destination key is produced by multiple parents. The winning item is determined by this comparer. /// An observable changeset of flattened child items. /// /// @@ -45,8 +43,8 @@ public static partial class ObservableCacheEx /// (unlike the synchronous TransformMany which batches all children into one changeset). /// /// - /// Factory exceptions propagate as . Use - /// + /// Factory exceptions propagate as IObserver<T>.OnError. Use + /// TransformManySafeAsync<TDestination, TDestinationKey, TSource, TSourceKey>(IObservable<IChangeSet<TSource, TSourceKey>>, Func<TSource, TSourceKey, Task<IEnumerable<TDestination>>>, Func<TDestination, TDestinationKey>, Action<Error<TSource, TSourceKey>>, IEqualityComparer<TDestination>?, IComparer<TDestination>?) /// to catch errors without killing the stream. /// /// @@ -58,13 +56,25 @@ public static IObservable> TransformMa where TSource : notnull where TSourceKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - manySelector.ThrowArgumentNullExceptionIfNull(nameof(manySelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(manySelector); return new TransformManyAsync(source, CreateChangeSetTransformer(manySelector, keySelector), equalityComparer, comparer).Run(); } - /// + /// + /// Provides an overload of manySelector for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TDestinationKey value. + /// The type of the TSource value. + /// The type of the TSourceKey value. + /// The source value. + /// The manySelector value. + /// The keySelector value. + /// The equalityComparer value. + /// The comparer value. + /// The resulting observable sequence. /// This overload takes a factory that receives only the source item (without the key). [MethodImpl(MethodImplOptions.AggressiveInlining)] [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] @@ -74,8 +84,21 @@ public static IObservable> TransformMa where TSource : notnull where TSourceKey : notnull => source.TransformManyAsync((val, _) => manySelector(val), keySelector, equalityComparer, comparer); - /// - /// This overload returns an observable collection (of type implementing both and ) whose changes are tracked live. The factory receives the source item and its key. + /// + /// Provides an overload of TransformManyAsync for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TDestinationKey value. + /// The type of the TSource value. + /// The type of the TSourceKey value. + /// The type of the TCollection value. + /// The source value. + /// The manySelector value. + /// The keySelector value. + /// The equalityComparer value. + /// The comparer value. + /// The resulting observable sequence. + /// This overload returns an observable collection (of type implementing both and IEnumerable<T>) whose changes are tracked live. The factory receives the source item and its key. [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformManyAsync(this IObservable> source, Func> manySelector, Func keySelector, IEqualityComparer? equalityComparer = null, IComparer? comparer = null) where TDestination : notnull @@ -84,14 +107,27 @@ public static IObservable> TransformMa where TSourceKey : notnull where TCollection : INotifyCollectionChanged, IEnumerable { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - manySelector.ThrowArgumentNullExceptionIfNull(nameof(manySelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(manySelector); return new TransformManyAsync(source, CreateChangeSetTransformer(manySelector, keySelector), equalityComparer, comparer).Run(); } - /// - /// This overload returns an observable collection (of type implementing both and ) whose changes are tracked live. The factory receives only the source item. + /// + /// Provides an overload of manySelector for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TDestinationKey value. + /// The type of the TSource value. + /// The type of the TSourceKey value. + /// The type of the TCollection value. + /// The source value. + /// The manySelector value. + /// The keySelector value. + /// The equalityComparer value. + /// The comparer value. + /// The resulting observable sequence. + /// This overload returns an observable collection (of type implementing both and IEnumerable<T>) whose changes are tracked live. The factory receives only the source item. [MethodImpl(MethodImplOptions.AggressiveInlining)] [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformManyAsync(this IObservable> source, Func> manySelector, Func keySelector, IEqualityComparer? equalityComparer = null, IComparer? comparer = null) @@ -101,8 +137,19 @@ public static IObservable> TransformMa where TSourceKey : notnull where TCollection : INotifyCollectionChanged, IEnumerable => source.TransformManyAsync((val, _) => manySelector(val), keySelector, equalityComparer, comparer); - /// - /// This overload returns an per parent. The child cache is live: its changes propagate downstream. No keySelector is needed since the cache already has keys. The factory receives the source item and its key. + /// + /// Provides an overload of TransformManyAsync for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TDestinationKey value. + /// The type of the TSource value. + /// The type of the TSourceKey value. + /// The source value. + /// The manySelector value. + /// The equalityComparer value. + /// The comparer value. + /// The resulting observable sequence. + /// This overload returns an IObservableCache<TObject, TKey> per parent. The child cache is live: its changes propagate downstream. No keySelector is needed since the cache already has keys. The factory receives the source item and its key. [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformManyAsync(this IObservable> source, Func>> manySelector, IEqualityComparer? equalityComparer = null, IComparer? comparer = null) where TDestination : notnull @@ -110,14 +157,25 @@ public static IObservable> TransformMa where TSource : notnull where TSourceKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - manySelector.ThrowArgumentNullExceptionIfNull(nameof(manySelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(manySelector); return new TransformManyAsync(source, CreateChangeSetTransformer(manySelector), equalityComparer, comparer).Run(); } - /// - /// This overload returns an per parent. The child cache is live. The factory receives only the source item. + /// + /// Provides an overload of manySelector for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TDestinationKey value. + /// The type of the TSource value. + /// The type of the TSourceKey value. + /// The source value. + /// The manySelector value. + /// The equalityComparer value. + /// The comparer value. + /// The resulting observable sequence. + /// This overload returns an IObservableCache<TObject, TKey> per parent. The child cache is live. The factory receives only the source item. [MethodImpl(MethodImplOptions.AggressiveInlining)] [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformManyAsync(this IObservable> source, Func>> manySelector, IEqualityComparer? equalityComparer = null, IComparer? comparer = null) diff --git a/src/DynamicData/Cache/ObservableCacheEx.TransformManySafeAsync.cs b/src/DynamicData/Cache/ObservableCacheEx.TransformManySafeAsync.cs index 5e9d2c062..bb8f65084 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.TransformManySafeAsync.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.TransformManySafeAsync.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; +#if REACTIVE_SHIM +using DynamicData.Reactive.Cache.Internal; +#else using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -26,7 +24,7 @@ namespace DynamicData; public static partial class ObservableCacheEx { /// - /// Async version of + /// Async version of TransformMany<TDestination, TDestinationKey, TSource, TSourceKey>(IObservable<IChangeSet<TSource, TSourceKey>>, Func<TSource, IEnumerable<TDestination>>, Func<TDestination, TDestinationKey>) /// with error handling. Factory exceptions are caught and routed to instead of /// terminating the stream. /// @@ -34,12 +32,12 @@ public static partial class ObservableCacheEx /// The type of the child item keys. /// The type of the source (parent) items. /// The type of the source (parent) keys. - /// The source to expand each item into multiple children asynchronously with error handling. - /// An async function that expands a parent item (and its key) into an of children. - /// A that extracts a unique key from each child item. - /// A that called when throws. The faulting item is skipped and the stream continues. - /// An that optional comparer to determine if two child items with the same key are equal. - /// An that optional comparer to resolve key collisions when the same destination key is produced by multiple parents. + /// The source IObservable<IChangeSet<TSource, TSourceKey>> to expand each item into multiple children asynchronously with error handling. + /// An async function that expands a parent item (and its key) into an IEnumerable<T> of children. + /// A Func<T, TResult> that extracts a unique key from each child item. + /// A Action<T> that called when throws. The faulting item is skipped and the stream continues. + /// An IEqualityComparer<TDestination> that optional comparer to determine if two child items with the same key are equal. + /// An IComparer<TDestination> that optional comparer to resolve key collisions when the same destination key is produced by multiple parents. /// An observable changeset of flattened child items. /// Because the transformations are asynchronous, each sub-collection may be emitted via a separate changeset. /// , , or is . @@ -50,14 +48,27 @@ public static IObservable> TransformMa where TSource : notnull where TSourceKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - manySelector.ThrowArgumentNullExceptionIfNull(nameof(manySelector)); - errorHandler.ThrowArgumentNullExceptionIfNull(nameof(errorHandler)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(manySelector); + ArgumentExceptionHelper.ThrowIfNull(errorHandler); return new TransformManyAsync(source, CreateChangeSetTransformer(manySelector, keySelector), equalityComparer, comparer, errorHandler).Run(); } - /// + /// + /// Provides an overload of manySelector for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TDestinationKey value. + /// The type of the TSource value. + /// The type of the TSourceKey value. + /// The source value. + /// The manySelector value. + /// The keySelector value. + /// The errorHandler value. + /// The equalityComparer value. + /// The comparer value. + /// The resulting observable sequence. /// This overload takes a factory that receives only the source item (without the key). [MethodImpl(MethodImplOptions.AggressiveInlining)] [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] @@ -67,8 +78,22 @@ public static IObservable> TransformMa where TSource : notnull where TSourceKey : notnull => source.TransformManySafeAsync((val, _) => manySelector(val), keySelector, errorHandler, equalityComparer, comparer); - /// - /// This overload returns an observable collection (of type implementing both and ) whose changes are tracked live. The factory receives the source item and its key. + /// + /// Provides an overload of TransformManySafeAsync for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TDestinationKey value. + /// The type of the TSource value. + /// The type of the TSourceKey value. + /// The type of the TCollection value. + /// The source value. + /// The manySelector value. + /// The keySelector value. + /// The errorHandler value. + /// The equalityComparer value. + /// The comparer value. + /// The resulting observable sequence. + /// This overload returns an observable collection (of type implementing both and IEnumerable<T>) whose changes are tracked live. The factory receives the source item and its key. [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformManySafeAsync(this IObservable> source, Func> manySelector, Func keySelector, Action> errorHandler, IEqualityComparer? equalityComparer = null, IComparer? comparer = null) where TDestination : notnull @@ -77,15 +102,29 @@ public static IObservable> TransformMa where TSourceKey : notnull where TCollection : INotifyCollectionChanged, IEnumerable { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - manySelector.ThrowArgumentNullExceptionIfNull(nameof(manySelector)); - errorHandler.ThrowArgumentNullExceptionIfNull(nameof(errorHandler)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(manySelector); + ArgumentExceptionHelper.ThrowIfNull(errorHandler); return new TransformManyAsync(source, CreateChangeSetTransformer(manySelector, keySelector), equalityComparer, comparer, errorHandler).Run(); } - /// - /// This overload returns an observable collection (of type implementing both and ) whose changes are tracked live. The factory receives only the source item. + /// + /// Provides an overload of manySelector for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TDestinationKey value. + /// The type of the TSource value. + /// The type of the TSourceKey value. + /// The type of the TCollection value. + /// The source value. + /// The manySelector value. + /// The keySelector value. + /// The errorHandler value. + /// The equalityComparer value. + /// The comparer value. + /// The resulting observable sequence. + /// This overload returns an observable collection (of type implementing both and IEnumerable<T>) whose changes are tracked live. The factory receives only the source item. [MethodImpl(MethodImplOptions.AggressiveInlining)] [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformManySafeAsync(this IObservable> source, Func> manySelector, Func keySelector, Action> errorHandler, IEqualityComparer? equalityComparer = null, IComparer? comparer = null) @@ -95,8 +134,20 @@ public static IObservable> TransformMa where TSourceKey : notnull where TCollection : INotifyCollectionChanged, IEnumerable => source.TransformManySafeAsync((val, _) => manySelector(val), keySelector, errorHandler, equalityComparer, comparer); - /// - /// This overload returns an per parent. The child cache is live. The factory receives the source item and its key. + /// + /// Provides an overload of TransformManySafeAsync for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TDestinationKey value. + /// The type of the TSource value. + /// The type of the TSourceKey value. + /// The source value. + /// The manySelector value. + /// The errorHandler value. + /// The equalityComparer value. + /// The comparer value. + /// The resulting observable sequence. + /// This overload returns an IObservableCache<TObject, TKey> per parent. The child cache is live. The factory receives the source item and its key. [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformManySafeAsync(this IObservable> source, Func>> manySelector, Action> errorHandler, IEqualityComparer? equalityComparer = null, IComparer? comparer = null) where TDestination : notnull @@ -104,15 +155,27 @@ public static IObservable> TransformMa where TSource : notnull where TSourceKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - manySelector.ThrowArgumentNullExceptionIfNull(nameof(manySelector)); - errorHandler.ThrowArgumentNullExceptionIfNull(nameof(errorHandler)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(manySelector); + ArgumentExceptionHelper.ThrowIfNull(errorHandler); return new TransformManyAsync(source, CreateChangeSetTransformer(manySelector), equalityComparer, comparer, errorHandler).Run(); } - /// - /// This overload returns an per parent. The child cache is live. The factory receives only the source item. + /// + /// Provides an overload of manySelector for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TDestinationKey value. + /// The type of the TSource value. + /// The type of the TSourceKey value. + /// The source value. + /// The manySelector value. + /// The errorHandler value. + /// The equalityComparer value. + /// The comparer value. + /// The resulting observable sequence. + /// This overload returns an IObservableCache<TObject, TKey> per parent. The child cache is live. The factory receives only the source item. [MethodImpl(MethodImplOptions.AggressiveInlining)] [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformManySafeAsync(this IObservable> source, Func>> manySelector, Action> errorHandler, IEqualityComparer? equalityComparer = null, IComparer? comparer = null) diff --git a/src/DynamicData/Cache/ObservableCacheEx.TransformOnObservable.cs b/src/DynamicData/Cache/ObservableCacheEx.TransformOnObservable.cs index 24d9e5e04..47ccba2c9 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.TransformOnObservable.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.TransformOnObservable.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -32,8 +30,8 @@ public static partial class ObservableCacheEx /// The type of the source items. /// The type of the key. /// The type of the transformed items. - /// The source to transform using per-item observables. - /// A function that, given a source item and its key, returns an whose emissions become the transformed values. + /// The source IObservable<IChangeSet<TSource, TKey>> to transform using per-item observables. + /// A function that, given a source item and its key, returns an IObservable<TDestination> whose emissions become the transformed values. /// An observable changeset where each key's value is the latest emission from its per-item observable. /// /// @@ -65,28 +63,36 @@ public static partial class ObservableCacheEx /// /// /// or is . - /// - /// - /// + /// Transform<TDestination, TSource, TKey>(IObservable<IChangeSet<TSource, TKey>>, Func<TSource, TKey, TDestination>, bool) + /// FilterOnObservable<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TKey, IObservable<bool>>, TimeSpan?, IScheduler?) + /// GroupOnObservable<TObject, TKey, TGroupKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TKey, IObservable<TGroupKey>>) public static IObservable> TransformOnObservable(this IObservable> source, Func> transformFactory) where TSource : notnull where TKey : notnull where TDestination : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); return new TransformOnObservable(source, transformFactory).Run(); } - /// + /// + /// Provides an overload of TransformOnObservable for the supplied arguments. + /// + /// The type of the TSource value. + /// The type of the TKey value. + /// The type of the TDestination value. + /// The source value. + /// The transformFactory value. + /// The resulting observable sequence. /// This overload takes a factory that receives only the source item (without the key). public static IObservable> TransformOnObservable(this IObservable> source, Func> transformFactory) where TSource : notnull where TKey : notnull where TDestination : notnull { - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); return source.TransformOnObservable((obj, _) => transformFactory(obj)); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.TransformSafe.cs b/src/DynamicData/Cache/ObservableCacheEx.TransformSafe.cs index f8e68385d..1b438f647 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.TransformSafe.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.TransformSafe.cs @@ -1,54 +1,72 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { - /// + /// + /// Provides an overload of TransformSafe for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The errorHandler value. + /// The forceTransform value. + /// The resulting observable sequence. /// This overload accepts a simpler factory that receives only the current item, and a forceTransform predicate filtering by source item only. public static IObservable> TransformSafe(this IObservable> source, Func transformFactory, Action> errorHandler, IObservable>? forceTransform = null) where TDestination : notnull where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); - errorHandler.ThrowArgumentNullExceptionIfNull(nameof(errorHandler)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); + ArgumentExceptionHelper.ThrowIfNull(errorHandler); return source.TransformSafe((current, _, _) => transformFactory(current), errorHandler, forceTransform.ForForced()); } - /// + /// + /// Provides an overload of TransformSafe for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The errorHandler value. + /// The forceTransform value. + /// The resulting observable sequence. /// This overload accepts a factory that receives the current item and key. public static IObservable> TransformSafe(this IObservable> source, Func transformFactory, Action> errorHandler, IObservable>? forceTransform = null) where TDestination : notnull where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); - errorHandler.ThrowArgumentNullExceptionIfNull(nameof(errorHandler)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); + ArgumentExceptionHelper.ThrowIfNull(errorHandler); return source.TransformSafe((current, _, key) => transformFactory(current, key), errorHandler, forceTransform); } @@ -60,28 +78,28 @@ public static IObservable> TransformSafeThe type of the transformed items. /// The type of the source items. /// The type of the key. - /// The source to transform with error handling. - /// The that produces a from the current source item, the previous source item (if any), and the key. - /// A callback invoked when throws. Receives an containing the exception and the faulting item. The item is skipped and the stream continues. - /// An optional that, when it emits a predicate, re-transforms all items for which the predicate returns . If , no forced re-transforms occur. + /// The source IObservable<IChangeSet<TSource, TKey>> to transform with error handling. + /// The Func<TSource, Optional<TSource>, TKey, TDestination> that produces a from the current source item, the previous source item (if any), and the key. + /// A callback invoked when throws. Receives an Error<TSource, TKey> containing the exception and the faulting item. The item is skipped and the stream continues. + /// An optional IObservable<T> that, when it emits a predicate, re-transforms all items for which the predicate returns . If , no forced re-transforms occur. /// An observable changeset of transformed items. /// /// - /// Behaves identically to - /// except that factory exceptions are routed to instead of propagating as . + /// Behaves identically to Transform<TDestination, TSource, TKey>(IObservable<IChangeSet<TSource, TKey>>, Func<TSource, Optional<TSource>, TKey, TDestination>, IObservable<Func<TSource, TKey, bool>>?) + /// except that factory exceptions are routed to instead of propagating as IObserver<T>.OnError. /// Source-level errors (i.e. the source observable itself erroring) still propagate normally. /// /// Worth noting: Factory exceptions are caught per-item; the faulting item is skipped and reported to the error handler while the stream continues. Source-level errors still terminate the stream. /// /// , , or is . - public static IObservable> TransformSafe(this IObservable> source, Func, TKey, TDestination> transformFactory, Action> errorHandler, IObservable>? forceTransform = null) + public static IObservable> TransformSafe(this IObservable> source, Func, TKey, TDestination> transformFactory, Action> errorHandler, IObservable>? forceTransform = null) where TDestination : notnull where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); - errorHandler.ThrowArgumentNullExceptionIfNull(nameof(errorHandler)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); + ArgumentExceptionHelper.ThrowIfNull(errorHandler); if (forceTransform is not null) { return new TransformWithForcedTransform(source, transformFactory, forceTransform, errorHandler).Run(); @@ -90,37 +108,67 @@ public static IObservable> TransformSafe(source, transformFactory, errorHandler).Run(); } - /// - /// This overload accepts of to force re-transformation of ALL items. The factory receives only the current item. + /// + /// Provides an overload of ForForced for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The errorHandler value. + /// The forceTransform value. + /// The resulting observable sequence. + /// This overload accepts IObservable<T> of to force re-transformation of ALL items. The factory receives only the current item. public static IObservable> TransformSafe(this IObservable> source, Func transformFactory, Action> errorHandler, IObservable forceTransform) where TDestination : notnull where TSource : notnull where TKey : notnull => source.TransformSafe((cur, _, _) => transformFactory(cur), errorHandler, forceTransform.ForForced()); - /// - /// This overload accepts of to force re-transformation of ALL items. The factory receives the current item and key. + /// + /// Provides an overload of TransformSafe for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The errorHandler value. + /// The forceTransform value. + /// The resulting observable sequence. + /// This overload accepts IObservable<T> of to force re-transformation of ALL items. The factory receives the current item and key. public static IObservable> TransformSafe(this IObservable> source, Func transformFactory, Action> errorHandler, IObservable forceTransform) where TDestination : notnull where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); - forceTransform.ThrowArgumentNullExceptionIfNull(nameof(forceTransform)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); + ArgumentExceptionHelper.ThrowIfNull(forceTransform); return source.TransformSafe((cur, _, key) => transformFactory(cur, key), errorHandler, forceTransform.ForForced()); } - /// - /// This overload accepts of to force re-transformation of ALL items. - public static IObservable> TransformSafe(this IObservable> source, Func, TKey, TDestination> transformFactory, Action> errorHandler, IObservable forceTransform) + /// + /// Provides an overload of TransformSafe for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The errorHandler value. + /// The forceTransform value. + /// The resulting observable sequence. + /// This overload accepts IObservable<T> of to force re-transformation of ALL items. + public static IObservable> TransformSafe(this IObservable> source, Func, TKey, TDestination> transformFactory, Action> errorHandler, IObservable forceTransform) where TDestination : notnull where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); - forceTransform.ThrowArgumentNullExceptionIfNull(nameof(forceTransform)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); + ArgumentExceptionHelper.ThrowIfNull(forceTransform); return source.TransformSafe(transformFactory, errorHandler, forceTransform.ForForced()); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.TransformSafeAsync.cs b/src/DynamicData/Cache/ObservableCacheEx.TransformSafeAsync.cs index 45c842917..5aa43ffa1 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.TransformSafeAsync.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.TransformSafeAsync.cs @@ -1,31 +1,39 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { - /// + /// + /// Provides an overload of TransformSafeAsync for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The errorHandler value. + /// The forceTransform value. + /// The resulting observable sequence. /// This overload takes a factory that receives only the current item. [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformSafeAsync(this IObservable> source, Func> transformFactory, Action> errorHandler, IObservable>? forceTransform = null) @@ -33,14 +41,24 @@ public static IObservable> TransformSafeAsync transformFactory(current), errorHandler, forceTransform); } - /// + /// + /// Provides an overload of TransformSafeAsync for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The errorHandler value. + /// The forceTransform value. + /// The resulting observable sequence. /// This overload takes a factory that receives the current item and key. [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformSafeAsync(this IObservable> source, Func> transformFactory, Action> errorHandler, IObservable>? forceTransform = null) @@ -48,41 +66,51 @@ public static IObservable> TransformSafeAsync transformFactory(current, key), errorHandler, forceTransform); } /// - /// Async version of . + /// Async version of TransformSafe<TDestination, TSource, TKey>(IObservable<IChangeSet<TSource, TKey>>, Func<TSource, Optional<TSource>, TKey, TDestination>, Action<Error<TSource, TKey>>, IObservable<Func<TSource, TKey, bool>>?). /// Projects each item using an async factory, catching factory exceptions via a mandatory error handler. /// /// The type of the transformed items. /// The type of the source items. /// The type of the key. - /// The source to transform asynchronously with error handling. - /// The async function that produces a . - /// A that called when throws or faults. The item is skipped and the stream continues. - /// An optional that forces re-transformation of matching items. + /// The source IObservable<IChangeSet<TSource, TKey>> to transform asynchronously with error handling. + /// The Func<TSource, Optional<TSource>, TKey, Task<TDestination>> async function that produces a . + /// A Action<T> that called when throws or faults. The item is skipped and the stream continues. + /// An optional IObservable<T> that forces re-transformation of matching items. /// An observable changeset of transformed items. - /// Combines the async execution model of with the error-safe behavior of . + /// Combines the async execution model of TransformAsync<TDestination, TSource, TKey>(IObservable<IChangeSet<TSource, TKey>>, Func<TSource, Optional<TSource>, TKey, Task<TDestination>>, IObservable<Func<TSource, TKey, bool>>?) with the error-safe behavior of TransformSafe<TDestination, TSource, TKey>(IObservable<IChangeSet<TSource, TKey>>, Func<TSource, Optional<TSource>, TKey, TDestination>, Action<Error<TSource, TKey>>, IObservable<Func<TSource, TKey, bool>>?). /// , , or is . [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] - public static IObservable> TransformSafeAsync(this IObservable> source, Func, TKey, Task> transformFactory, Action> errorHandler, IObservable>? forceTransform = null) + public static IObservable> TransformSafeAsync(this IObservable> source, Func, TKey, Task> transformFactory, Action> errorHandler, IObservable>? forceTransform = null) where TDestination : notnull where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); - errorHandler.ThrowArgumentNullExceptionIfNull(nameof(errorHandler)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); + ArgumentExceptionHelper.ThrowIfNull(errorHandler); return new TransformAsync(source, transformFactory, errorHandler, forceTransform).Run(); } - /// + /// + /// Provides an overload of TransformSafeAsync for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The errorHandler value. + /// The options value. + /// The resulting observable sequence. /// This overload accepts to control concurrency and Refresh handling. The factory receives only the current item. [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformSafeAsync(this IObservable> source, Func> transformFactory, Action> errorHandler, TransformAsyncOptions options) @@ -90,14 +118,24 @@ public static IObservable> TransformSafeAsync transformFactory(current), errorHandler, options); } - /// + /// + /// Provides an overload of TransformSafeAsync for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The errorHandler value. + /// The options value. + /// The resulting observable sequence. /// This overload accepts to control concurrency and Refresh handling. The factory receives the current item and key. [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformSafeAsync(this IObservable> source, Func> transformFactory, Action> errorHandler, TransformAsyncOptions options) @@ -105,24 +143,34 @@ public static IObservable> TransformSafeAsync transformFactory(current, key), errorHandler, options); } - /// + /// + /// Provides an overload of TransformSafeAsync for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The errorHandler value. + /// The options value. + /// The resulting observable sequence. /// This overload accepts to control concurrency and Refresh handling. [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] - public static IObservable> TransformSafeAsync(this IObservable> source, Func, TKey, Task> transformFactory, Action> errorHandler, TransformAsyncOptions options) + public static IObservable> TransformSafeAsync(this IObservable> source, Func, TKey, Task> transformFactory, Action> errorHandler, TransformAsyncOptions options) where TDestination : notnull where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); - errorHandler.ThrowArgumentNullExceptionIfNull(nameof(errorHandler)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); + ArgumentExceptionHelper.ThrowIfNull(errorHandler); return new TransformAsync(source, transformFactory, errorHandler, null, options.MaximumConcurrency, options.TransformOnRefresh).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.TransformToTree.cs b/src/DynamicData/Cache/ObservableCacheEx.TransformToTree.cs index e0020280d..3322b971f 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.TransformToTree.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.TransformToTree.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -27,14 +25,14 @@ public static partial class ObservableCacheEx { /// /// Builds a hierarchical tree from a flat changeset using a parent key selector. - /// Each item becomes a with Parent, Children, Depth, and IsRoot properties. + /// Each item becomes a Node<TObject, TKey> with Parent, Children, Depth, and IsRoot properties. /// /// The type of the source items. Must be a reference type. /// The type of the key. - /// The source to transform into a hierarchical tree. - /// The that returns the key of an item's parent. Return the item's own key (or a non-existent key) for root items. - /// An optional that emits a filter predicate for nodes. When the predicate changes, nodes are re-evaluated and filtered. - /// An observable changeset of items representing the tree. + /// The source IObservable<IChangeSet<TObject, TKey>> to transform into a hierarchical tree. + /// The Func<TObject, TKey> that returns the key of an item's parent. Return the item's own key (or a non-existent key) for root items. + /// An optional IObservable<T> that emits a filter predicate for nodes. When the predicate changes, nodes are re-evaluated and filtered. + /// An observable changeset of Node<TObject, TKey> items representing the tree. /// /// Change reason handling: /// @@ -51,8 +49,8 @@ public static IObservable, TKey>> TransformToTree where TObject : class where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - pivotOn.ThrowArgumentNullExceptionIfNull(nameof(pivotOn)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(pivotOn); return new TreeBuilder(source, pivotOn, predicateChanged).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.TransformWithInlineUpdate.cs b/src/DynamicData/Cache/ObservableCacheEx.TransformWithInlineUpdate.cs index 99004fa73..7ef67e50b 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.TransformWithInlineUpdate.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.TransformWithInlineUpdate.cs @@ -1,69 +1,96 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { - /// + /// + /// Provides an overload of TransformWithInlineUpdate for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The updateAction value. + /// The resulting observable sequence. /// This overload defaults to transformOnRefresh: false and does not provide an error handler (factory exceptions propagate as OnError). public static IObservable> TransformWithInlineUpdate(this IObservable> source, Func transformFactory, Action updateAction) where TDestination : class where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); - updateAction.ThrowArgumentNullExceptionIfNull(nameof(updateAction)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); + ArgumentExceptionHelper.ThrowIfNull(updateAction); return source.TransformWithInlineUpdate(transformFactory, updateAction, false); } - /// + /// + /// Provides an overload of TransformWithInlineUpdate for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The updateAction value. + /// The transformOnRefresh value. + /// The resulting observable sequence. /// This overload does not provide an error handler (factory exceptions propagate as OnError). The transformOnRefresh parameter controls Refresh behavior. public static IObservable> TransformWithInlineUpdate(this IObservable> source, Func transformFactory, Action updateAction, bool transformOnRefresh) where TDestination : class where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); - updateAction.ThrowArgumentNullExceptionIfNull(nameof(updateAction)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); + ArgumentExceptionHelper.ThrowIfNull(updateAction); return new TransformWithInlineUpdate(source, transformFactory, updateAction, transformOnRefresh: transformOnRefresh).Run(); } - /// + /// + /// Provides an overload of TransformWithInlineUpdate for the supplied arguments. + /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The type of the TKey value. + /// The source value. + /// The transformFactory value. + /// The updateAction value. + /// The errorHandler value. + /// The resulting observable sequence. /// This overload defaults to transformOnRefresh: false but includes an error handler for factory/update action exceptions. public static IObservable> TransformWithInlineUpdate(this IObservable> source, Func transformFactory, Action updateAction, Action> errorHandler) where TDestination : class where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); - updateAction.ThrowArgumentNullExceptionIfNull(nameof(updateAction)); - errorHandler.ThrowArgumentNullExceptionIfNull(nameof(errorHandler)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); + ArgumentExceptionHelper.ThrowIfNull(updateAction); + ArgumentExceptionHelper.ThrowIfNull(errorHandler); return source.TransformWithInlineUpdate(transformFactory, updateAction, errorHandler, false); } @@ -75,10 +102,10 @@ public static IObservable> TransformWithInlineUpd /// The type of the transformed items. Must be a reference type since items are mutated in place. /// The type of the source items. /// The type of the key. - /// The source to transform with in-place mutation on updates. - /// A that called on Add (and optionally Refresh) to create a new . - /// A that called on Update. Receives (existingTransformed, newSource). Mutate the existing transformed item to reflect the new source value. Example: (vm, model) => vm.Value = model.Value. - /// A that called when or throws. The faulting item is skipped. + /// The source IObservable<IChangeSet<TSource, TKey>> to transform with in-place mutation on updates. + /// A Func<T, TResult> that called on Add (and optionally Refresh) to create a new . + /// A Action<T> that called on Update. Receives (existingTransformed, newSource). Mutate the existing transformed item to reflect the new source value. Example: (vm, model) => vm.Value = model.Value. + /// A Action<T> that called when or throws. The faulting item is skipped. /// When , Refresh changes call on the existing item. /// An observable changeset of transformed items. /// @@ -101,10 +128,10 @@ public static IObservable> TransformWithInlineUpd where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); - updateAction.ThrowArgumentNullExceptionIfNull(nameof(updateAction)); - errorHandler.ThrowArgumentNullExceptionIfNull(nameof(errorHandler)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); + ArgumentExceptionHelper.ThrowIfNull(updateAction); + ArgumentExceptionHelper.ThrowIfNull(errorHandler); return new TransformWithInlineUpdate(source, transformFactory, updateAction, errorHandler, transformOnRefresh).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.TreatMovesAsRemoveAdd.cs b/src/DynamicData/Cache/ObservableCacheEx.TreatMovesAsRemoveAdd.cs index 965001488..e7f1721e9 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.TreatMovesAsRemoveAdd.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.TreatMovesAsRemoveAdd.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -30,13 +21,13 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to convert move events into remove/add pairs. + /// The source IObservable<ISortedChangeSet<TObject, TKey>> to convert move events into remove/add pairs. /// the same SortedChangeSets, except all moves are replaced with remove + add. public static IObservable> TreatMovesAsRemoveAdd(this IObservable> source) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); static IEnumerable> ReplaceMoves(IChangeSet items) { diff --git a/src/DynamicData/Cache/ObservableCacheEx.TrueFor.cs b/src/DynamicData/Cache/ObservableCacheEx.TrueFor.cs index a49b33f24..bbc99fd6c 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.TrueFor.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.TrueFor.cs @@ -1,30 +1,38 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { + /// + /// Executes the TrueFor operation. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The type of the TValue value. + /// The source value. + /// The observableSelector value. + /// The collectionMatcher value. + /// The result of the operation. private static IObservable TrueFor(this IObservable> source, Func> observableSelector, Func>, bool> collectionMatcher) where TObject : notnull where TKey : notnull diff --git a/src/DynamicData/Cache/ObservableCacheEx.TrueForAll.cs b/src/DynamicData/Cache/ObservableCacheEx.TrueForAll.cs index e09acbedb..74a740205 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.TrueForAll.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.TrueForAll.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -32,9 +23,9 @@ public static partial class ObservableCacheEx /// The type of the object. /// The type of the key. /// The type of the value emitted by each per-item observable. - /// The source to evaluate a condition across all items in. - /// A factory that produces a condition observable for each item. - /// A that predicate applied to each per-item observable's latest value. + /// The source IObservable<IChangeSet<TObject, TKey>> to evaluate a condition across all items in. + /// A Func<T, TResult> factory that produces a condition observable for each item. + /// A Func<T, TResult> that predicate applied to each per-item observable's latest value. /// An observable of bool that emits whenever the all-items condition changes. /// , , or is . /// @@ -47,7 +38,7 @@ public static partial class ObservableCacheEx /// /// Worth noting: Items whose per-item observable has not yet emitted are treated as not satisfying the condition. An empty cache is vacuously . The result uses DistinctUntilChanged, so duplicate bool values are suppressed. /// - /// + /// TrueForAny<TObject, TKey, TValue>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, IObservable<TValue>>, Func<TValue, bool>) public static IObservable TrueForAll(this IObservable> source, Func> observableSelector, Func equalityCondition) where TObject : notnull where TKey : notnull @@ -66,9 +57,9 @@ public static IObservable TrueForAll(this IObservab /// The type of the object. /// The type of the key. /// The type of the value. - /// The source to evaluate a condition across all items in. - /// A that selector which returns the target observable. - /// The equality condition. + /// The source IObservable<IChangeSet<TObject, TKey>> to evaluate a condition across all items in. + /// A Func<T, TResult> that selector which returns the target observable. + /// The Func<TObject, TValue, bool> equality condition. /// An observable which boolean values indicating if true. /// source. public static IObservable TrueForAll(this IObservable> source, Func> observableSelector, Func equalityCondition) diff --git a/src/DynamicData/Cache/ObservableCacheEx.TrueForAny.cs b/src/DynamicData/Cache/ObservableCacheEx.TrueForAny.cs index f98e5270a..fa548b69e 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.TrueForAny.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.TrueForAny.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -32,9 +23,9 @@ public static partial class ObservableCacheEx /// The type of the object. /// The type of the key. /// The type of the value emitted by each per-item observable. - /// The source to evaluate a condition across any item in. - /// A factory that produces a condition observable for each item. - /// A that predicate applied to each item and its per-item observable's latest value. + /// The source IObservable<IChangeSet<TObject, TKey>> to evaluate a condition across any item in. + /// A Func<T, TResult> factory that produces a condition observable for each item. + /// A Func<T, TResult> that predicate applied to each item and its per-item observable's latest value. /// An observable of bool that emits whenever the any-item condition changes. /// , , or is . /// @@ -47,25 +38,31 @@ public static partial class ObservableCacheEx /// /// Worth noting: Items whose per-item observable has not yet emitted are treated as not satisfying the condition. An empty cache yields . The result uses DistinctUntilChanged, so duplicate bool values are suppressed. /// - /// + /// TrueForAll<TObject, TKey, TValue>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, IObservable<TValue>>, Func<TValue, bool>) public static IObservable TrueForAny(this IObservable> source, Func> observableSelector, Func equalityCondition) where TObject : notnull where TKey : notnull where TValue : notnull => source.TrueFor(observableSelector, items => items.Any(o => o.LatestValue.HasValue && equalityCondition(o.Item, o.LatestValue.Value))); - /// - /// The source to evaluate a condition across any item in. - /// A factory that produces a condition observable for each item. - /// A that predicate applied to each per-item observable's latest value (without the item). + /// + /// Provides an overload of TrueForAny for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The type of the TValue value. + /// The source IObservable<IChangeSet<TObject, TKey>> to evaluate a condition across any item in. + /// A Func<T, TResult> factory that produces a condition observable for each item. + /// A Func<T, TResult> that predicate applied to each per-item observable's latest value (without the item). + /// The resulting observable sequence. /// This overload accepts a predicate that takes only the value, not the item. Useful when the condition depends only on the observed value. public static IObservable TrueForAny(this IObservable> source, Func> observableSelector, Func equalityCondition) where TObject : notnull where TKey : notnull where TValue : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); - equalityCondition.ThrowArgumentNullExceptionIfNull(nameof(equalityCondition)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); + ArgumentExceptionHelper.ThrowIfNull(equalityCondition); return source.TrueFor(observableSelector, items => items.Any(o => o.LatestValue.HasValue && equalityCondition(o.LatestValue.Value))); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.UpdateIndex.cs b/src/DynamicData/Cache/ObservableCacheEx.UpdateIndex.cs index 933529ea0..48c71c51a 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.UpdateIndex.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.UpdateIndex.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -27,13 +25,18 @@ public static partial class ObservableCacheEx { /// /// Sets the Index property on each item (which must implement ) - /// to reflect its position in the sorted output. Operates on . + /// to reflect its position in the sorted output. Operates on ISortedChangeSet<TObject, TKey>. /// /// The type of the object. /// The type of the key. - /// The source to update index positions in. + /// The source IObservable<ISortedChangeSet<TObject, TKey>> to update index positions in. /// An observable that emits the sorted changesets after updating item indices. public static IObservable> UpdateIndex(this IObservable> source) where TObject : IIndexAware - where TKey : notnull => source.Do(changes => changes.SortedItems.Select((update, index) => new { update, index }).ForEach(u => u.update.Value.Index = u.index)); + where TKey : notnull + { + ArgumentExceptionHelper.ThrowIfNull(source); + + return source.Do(changes => changes.SortedItems.Select((update, index) => new { update, index }).ForEach(u => u.update.Value.Index = u.index)); + } } diff --git a/src/DynamicData/Cache/ObservableCacheEx.VirtualiseAndPage.cs b/src/DynamicData/Cache/ObservableCacheEx.VirtualiseAndPage.cs index 2dbeaf1e6..238e53fcd 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.VirtualiseAndPage.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.VirtualiseAndPage.cs @@ -1,18 +1,35 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Reactive.Linq; using DynamicData.Cache.Internal; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { - /// + /// + /// Provides an overload of SortAndVirtualize for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The comparer value. + /// The virtualRequests value. + /// The resulting observable sequence. /// This overload uses default . public static IObservable>> SortAndVirtualize(this IObservable> source, IComparer comparer, @@ -21,7 +38,15 @@ public static IObservable>> So where TKey : notnull => source.SortAndVirtualize(comparer, virtualRequests, new SortAndVirtualizeOptions()); - /// + /// + /// Provides an overload of SortAndVirtualize for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The comparerChanged value. + /// The virtualRequests value. + /// The resulting observable sequence. /// This overload uses default . public static IObservable>> SortAndVirtualize( this IObservable> source, @@ -30,8 +55,8 @@ public static IObservable>> So where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - virtualRequests.ThrowArgumentNullExceptionIfNull(nameof(virtualRequests)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(virtualRequests); return source.SortAndVirtualize(comparerChanged, virtualRequests, new SortAndVirtualizeOptions()); } @@ -42,9 +67,9 @@ public static IObservable>> So /// /// The type of the object. /// The type of the key. - /// The source to paginate. - /// The that determines sort order. - /// The that controls which window of sorted items to include. + /// The source IObservable<ISortedChangeSet<TObject, TKey>> to paginate. + /// The IComparer<TObject> that determines sort order. + /// The IObservable<IVirtualRequest> that controls which window of sorted items to include. /// The for controlling virtualization behavior. /// An observable which will emit virtual change sets. /// source. @@ -64,8 +89,8 @@ public static IObservable>> So /// /// Worth noting: No data is emitted until produces its first value. Changing the window can cause a full recalculation of visible items. /// - /// - /// + /// SortAndVirtualize<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IObservable<IComparer<TObject>>, IObservable<IVirtualRequest>, SortAndVirtualizeOptions) + /// Top<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IComparer<TObject>, int) public static IObservable>> SortAndVirtualize( this IObservable> source, IComparer comparer, @@ -74,8 +99,8 @@ public static IObservable>> So where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - virtualRequests.ThrowArgumentNullExceptionIfNull(nameof(virtualRequests)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(virtualRequests); return new SortAndVirtualize(source, comparer, virtualRequests, options).Run(); } @@ -86,9 +111,9 @@ public static IObservable>> So /// /// The type of the object. /// The type of the key. - /// The source to paginate. - /// An that emits new comparers to re-sort with. - /// The that controls which window of sorted items to include. + /// The source IObservable<ISortedChangeSet<TObject, TKey>> to paginate. + /// An IObservable<IComparer<TObject>> that emits new comparers to re-sort with. + /// The IObservable<IVirtualRequest> that controls which window of sorted items to include. /// The for controlling virtualization behavior. /// An observable which will emit virtual change sets. /// source. @@ -108,8 +133,8 @@ public static IObservable>> So /// /// Worth noting: No data is emitted until both the comparer observable and virtualRequests have produced their first values. Changing the window or comparer can cause a full recalculation of visible items. /// - /// - /// + /// SortAndPage<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IObservable<IComparer<TObject>>, IObservable<IPageRequest>, SortAndPageOptions) + /// Top<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IComparer<TObject>, int) public static IObservable>> SortAndVirtualize( this IObservable> source, IObservable> comparerChanged, @@ -118,8 +143,8 @@ public static IObservable>> So where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - virtualRequests.ThrowArgumentNullExceptionIfNull(nameof(virtualRequests)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(virtualRequests); return new SortAndVirtualize(source, comparerChanged, virtualRequests, options).Run(); } @@ -129,8 +154,8 @@ public static IObservable>> So /// /// The type of the object. /// The type of the key. - /// The source to paginate. - /// The that controls which window of sorted items to include. + /// The source IObservable<ISortedChangeSet<TObject, TKey>> to paginate. + /// The IObservable<IVirtualRequest> that controls which window of sorted items to include. /// An observable which will emit virtual change sets. /// source. [Obsolete(Constants.VirtualizeIsObsolete)] @@ -138,8 +163,8 @@ public static IObservable> Virtualise(source, virtualRequests).Run(); } @@ -150,26 +175,26 @@ public static IObservable> Virtualise /// The type of the object. /// The type of the key. - /// The source to limit. - /// The that determines sort order. + /// The source IObservable<IChangeSet<TObject, TKey>> to limit. + /// The IComparer<TObject> that determines sort order. /// The maximum number of items to return. /// An observable which will emit virtual change sets. /// source. /// size;Size should be greater than zero. /// /// - /// Internally delegates to + /// Internally delegates to SortAndVirtualize<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IComparer<TObject>, IObservable<IVirtualRequest>, SortAndVirtualizeOptions) /// with a fixed of (0, size). /// /// Worth noting: When the Nth item is displaced by a new item with higher sort priority, the displaced item is emitted as a Remove and the new item as an Add. /// - /// + /// SortAndVirtualize<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IComparer<TObject>, IObservable<IVirtualRequest>, SortAndVirtualizeOptions) public static IObservable>> Top(this IObservable> source, IComparer comparer, int size) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - comparer.ThrowArgumentNullExceptionIfNull(nameof(comparer)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(comparer); if (size <= 0) { @@ -184,7 +209,7 @@ public static IObservable>> To /// /// The type of the object. /// The type of the key. - /// The source to paginate. + /// The source IObservable<ISortedChangeSet<TObject, TKey>> to paginate. /// The maximum number of items to include. /// An observable which will emit virtual change sets. /// source. @@ -194,7 +219,7 @@ public static IObservable> Top(t where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); if (size <= 0) { @@ -204,7 +229,15 @@ public static IObservable> Top(t return new Virtualise(source, Observable.Return(new VirtualRequest(0, size))).Run(); } - /// + /// + /// Provides an overload of SortAndPage for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The comparer value. + /// The pageRequests value. + /// The resulting observable sequence. /// This overload uses default . public static IObservable>> SortAndPage(this IObservable> source, IComparer comparer, @@ -213,7 +246,15 @@ public static IObservable>> SortA where TKey : notnull => source.SortAndPage(comparer, pageRequests, new SortAndPageOptions()); - /// + /// + /// Provides an overload of SortAndPage for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The comparerChanged value. + /// The pageRequests value. + /// The resulting observable sequence. /// This overload uses default . public static IObservable>> SortAndPage( this IObservable> source, @@ -222,8 +263,8 @@ public static IObservable>> SortA where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - pageRequests.ThrowArgumentNullExceptionIfNull(nameof(pageRequests)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(pageRequests); return source.SortAndPage(comparerChanged, pageRequests, new SortAndPageOptions()); } @@ -234,9 +275,9 @@ public static IObservable>> SortA /// /// The type of the object. /// The type of the key. - /// The source to paginate. - /// The that determines sort order. - /// The that controls which page of sorted items to include. + /// The source IObservable<ISortedChangeSet<TObject, TKey>> to paginate. + /// The IComparer<TObject> that determines sort order. + /// The IObservable<IPageRequest> that controls which page of sorted items to include. /// The for controlling paging behavior. /// An observable which will emit paged change sets. /// source. @@ -256,7 +297,7 @@ public static IObservable>> SortA /// /// Worth noting: No data is emitted until produces its first value. Page numbers are 1-based. Requesting a page beyond the data range results in an empty page. /// - /// + /// SortAndPage<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IObservable<IComparer<TObject>>, IObservable<IPageRequest>, SortAndPageOptions) public static IObservable>> SortAndPage( this IObservable> source, IComparer comparer, @@ -265,8 +306,8 @@ public static IObservable>> SortA where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - pageRequests.ThrowArgumentNullExceptionIfNull(nameof(pageRequests)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(pageRequests); return new SortAndPage(source, comparer, pageRequests, options).Run(); } @@ -277,9 +318,9 @@ public static IObservable>> SortA /// /// The type of the object. /// The type of the key. - /// The source to paginate. - /// An that emits new comparers to re-sort with. - /// The that controls which page of sorted items to include. + /// The source IObservable<ISortedChangeSet<TObject, TKey>> to paginate. + /// An IObservable<IComparer<TObject>> that emits new comparers to re-sort with. + /// The IObservable<IPageRequest> that controls which page of sorted items to include. /// The for controlling paging behavior. /// An observable which will emit paged change sets. /// source. @@ -299,7 +340,7 @@ public static IObservable>> SortA /// /// Worth noting: No data is emitted until both the comparer observable and pageRequests have produced their first values. Page numbers are 1-based. Requesting a page beyond the data range results in an empty page. /// - /// + /// SortAndVirtualize<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IObservable<IComparer<TObject>>, IObservable<IVirtualRequest>, SortAndVirtualizeOptions) public static IObservable>> SortAndPage( this IObservable> source, IObservable> comparerChanged, @@ -308,8 +349,8 @@ public static IObservable>> SortA where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - pageRequests.ThrowArgumentNullExceptionIfNull(nameof(pageRequests)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(pageRequests); return new SortAndPage(source, comparerChanged, pageRequests, options).Run(); } @@ -319,16 +360,16 @@ public static IObservable>> SortA /// /// The type of the object. /// The type of the key. - /// The source to paginate. - /// The that controls which page of sorted items to include. + /// The source IObservable<ISortedChangeSet<TObject, TKey>> to paginate. + /// The IObservable<IPageRequest> that controls which page of sorted items to include. /// An observable which emits change sets. [Obsolete(Constants.PageIsObsolete)] public static IObservable> Page(this IObservable> source, IObservable pageRequests) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - pageRequests.ThrowArgumentNullExceptionIfNull(nameof(pageRequests)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(pageRequests); return new Page(source, pageRequests).Run(); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.Watch.cs b/src/DynamicData/Cache/ObservableCacheEx.Watch.cs index 8cccf6088..37fc60d27 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Watch.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Watch.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -26,29 +17,29 @@ namespace DynamicData; public static partial class ObservableCacheEx { /// - /// Filters the source changeset stream to a single key, emitting each for that key. + /// Filters the source changeset stream to a single key, emitting each Change<TObject, TKey> for that key. /// Changes for all other keys are ignored. /// /// The type of the object. /// The type of the key. - /// The source to watch a single key in. + /// The source IObservable<IChangeSet<TObject, TKey>> to watch a single key in. /// The key to observe. - /// An observable of for the specified key only. + /// An observable of Change<TObject, TKey> for the specified key only. /// /// /// Emits Add, Update, Remove, and Refresh changes as they occur for the target key. /// No initial emission occurs if the key is not yet present in the cache. This operator does not /// produce changesets; it produces individual change notifications. For Optional-based watching, - /// use . + /// use ToObservableOptional<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, TKey, IEqualityComparer<TObject>?). /// /// - /// - /// + /// WatchValue<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, TKey) + /// ToObservableOptional<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, TKey, IEqualityComparer<TObject>?) public static IObservable> Watch(this IObservable> source, TKey key) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.SelectMany(updates => updates).Where(update => update.Key.Equals(key)); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.WatchValue.cs b/src/DynamicData/Cache/ObservableCacheEx.WatchValue.cs index 50929fb3a..70d2dac08 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.WatchValue.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.WatchValue.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,44 +22,49 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to watch a single key in. + /// The source IObservableCache<TObject, TKey> to watch a single key in. /// The key to observe. /// An observable of the item's value whenever it changes for the specified key. /// /// - /// Unlike , - /// this does not emit on removal. It emits the removed item's value instead. + /// Unlike ToObservableOptional<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, TKey, IEqualityComparer<TObject>?), + /// this does not emit ReactiveUI.Primitives.Optional.None<T> on removal. It emits the removed item's value instead. /// If you need to distinguish presence from absence, use ToObservableOptional. /// /// /// EventBehavior /// AddEmits the added item's value. /// UpdateEmits the new value. - /// RemoveEmits the removed item's value (not None; use if you need removal detection). + /// RemoveEmits the removed item's value (not None; use ToObservableOptional<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, TKey, IEqualityComparer<TObject>?) if you need removal detection). /// RefreshEmits the current value. /// /// Worth noting: No emission occurs if the key is not present at subscription time. Changes to other keys are ignored entirely. /// - /// - /// + /// Watch<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, TKey) + /// ToObservableOptional<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, TKey, IEqualityComparer<TObject>?) public static IObservable WatchValue(this IObservableCache source, TKey key) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Watch(key).Select(u => u.Current); } - /// - /// The source to watch a single key in. + /// + /// Provides an overload of WatchValue for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<IChangeSet<TObject, TKey>> to watch a single key in. /// The key to observe. - /// This overload extends IObservable<> instead of . + /// The resulting observable sequence. + /// This overload extends IObservable<IChangeSet<TObject, TKey>> instead of IObservableCache<TObject, TKey>. public static IObservable WatchValue(this IObservable> source, TKey key) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Watch(key).Select(u => u.Current); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.WhenAnyPropertyChanged.cs b/src/DynamicData/Cache/ObservableCacheEx.WhenAnyPropertyChanged.cs index 15c3196dc..5a57c6d56 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.WhenAnyPropertyChanged.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.WhenAnyPropertyChanged.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -31,7 +29,7 @@ public static partial class ObservableCacheEx /// /// The type of the object (must implement ). /// The type of the key. - /// The source to observe property changes on items in. + /// The source IObservable<IChangeSet<TObject, TKey>> to observe property changes on items in. /// The specific property names to monitor. If empty, all property changes trigger emissions. /// An observable that emits the item itself each time a monitored property changes. /// @@ -50,15 +48,15 @@ public static partial class ObservableCacheEx /// OnErrorErrors from individual property subscriptions are silently ignored. Source errors terminate the stream. /// /// - /// - /// - /// - /// + /// WhenPropertyChanged<TObject, TKey, TValue> + /// WhenValueChanged<TObject, TKey, TValue> + /// AutoRefresh<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, TimeSpan?, TimeSpan?, IScheduler?) + /// ObservableListEx.WhenAnyPropertyChanged public static IObservable WhenAnyPropertyChanged(this IObservable> source, params string[] propertiesToMonitor) where TObject : INotifyPropertyChanged where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.MergeMany(t => t.WhenAnyPropertyChanged(propertiesToMonitor)); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.WhenPropertyChanged.cs b/src/DynamicData/Cache/ObservableCacheEx.WhenPropertyChanged.cs index 9efbb2791..8608dd73e 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.WhenPropertyChanged.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.WhenPropertyChanged.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; +#if REACTIVE_SHIM +using DynamicData.Reactive.Binding; +#else using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -26,21 +24,21 @@ namespace DynamicData; public static partial class ObservableCacheEx { /// - /// Emits a (item + property value) whenever the specified property + /// Emits a PropertyValue<TObject, TValue> (item + property value) whenever the specified property /// changes on any item in the cache. Subscribes via using MergeMany. /// /// The type of the object (must implement ). /// The type of the key. /// The type of the monitored property. - /// The source to observe a specific property on items in. - /// A that expression selecting the property to monitor. + /// The source IObservable<IChangeSet<TObject, TKey>> to observe a specific property on items in. + /// A Expression<TDelegate> that expression selecting the property to monitor. /// When (the default), the current property value is emitted immediately for each item upon subscription. - /// An observable of containing both the item and its property value. + /// An observable of PropertyValue<TObject, TValue> containing both the item and its property value. /// /// /// Per-item subscriptions are created on Add, replaced on Update, disposed on Remove. Errors from individual /// property subscriptions are silently ignored. The output is not a changeset stream. If you only need - /// the value (not the owning item), use instead. + /// the value (not the owning item), use WhenValueChanged<TObject, TKey, TValue> instead. /// /// /// EventBehavior @@ -51,13 +49,13 @@ public static partial class ObservableCacheEx /// OnErrorPer-item property subscription errors are silently ignored. Source errors terminate the stream. /// /// - /// + /// ObservableListEx.WhenPropertyChanged public static IObservable> WhenPropertyChanged(this IObservable> source, Expression> propertyAccessor, bool notifyOnInitialValue = true) where TObject : INotifyPropertyChanged where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - propertyAccessor.ThrowArgumentNullExceptionIfNull(nameof(propertyAccessor)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(propertyAccessor); return source.MergeMany(t => t.WhenPropertyChanged(propertyAccessor, notifyOnInitialValue)); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.WhenValueChanged.cs b/src/DynamicData/Cache/ObservableCacheEx.WhenValueChanged.cs index b27e31c4f..6f1a5ff49 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.WhenValueChanged.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.WhenValueChanged.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; +#if REACTIVE_SHIM +using DynamicData.Reactive.Binding; +#else using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -27,20 +25,20 @@ public static partial class ObservableCacheEx { /// /// Emits the property value whenever the specified property changes on any item in the cache. - /// Like but emits only the value, discarding the owning item. + /// Like WhenPropertyChanged<TObject, TKey, TValue> but emits only the value, discarding the owning item. /// /// The type of the object (must implement ). /// The type of the key. /// The type of the monitored property. - /// The source to observe a specific property value on items in. - /// A that expression selecting the property to monitor. + /// The source IObservable<IChangeSet<TObject, TKey>> to observe a specific property value on items in. + /// A Expression<TDelegate> that expression selecting the property to monitor. /// When (the default), the current property value is emitted immediately for each item upon subscription. - /// An observable of property values. The owning item is not included; use if you need it. + /// An observable of property values. The owning item is not included; use WhenPropertyChanged<TObject, TKey, TValue> if you need it. /// /// /// Per-item subscriptions are created on Add, replaced on Update, disposed on Remove. Errors from individual /// property subscriptions are silently ignored. If you need to correlate a value back to its source item, - /// use which returns a pair. + /// use WhenPropertyChanged<TObject, TKey, TValue> which returns a PropertyValue<TObject, TValue> pair. /// /// /// EventBehavior @@ -51,16 +49,16 @@ public static partial class ObservableCacheEx /// OnErrorPer-item errors silently ignored. Source errors terminate the stream. /// /// - /// - /// - /// - /// + /// WhenPropertyChanged<TObject, TKey, TValue> + /// WhenAnyPropertyChanged<TObject, TKey> + /// AutoRefresh<TObject, TKey, TProperty>(IObservable<IChangeSet<TObject, TKey>>, Expression<Func<TObject, TProperty>>, TimeSpan?, TimeSpan?, IScheduler?) + /// ObservableListEx.WhenValueChanged public static IObservable WhenValueChanged(this IObservable> source, Expression> propertyAccessor, bool notifyOnInitialValue = true) where TObject : INotifyPropertyChanged where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - propertyAccessor.ThrowArgumentNullExceptionIfNull(nameof(propertyAccessor)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(propertyAccessor); return source.MergeMany(t => t.WhenChanged(propertyAccessor, notifyOnInitialValue)); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.WhereReasonsAre.cs b/src/DynamicData/Cache/ObservableCacheEx.WhereReasonsAre.cs index 095e1ec11..82d8f4a36 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.WhereReasonsAre.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.WhereReasonsAre.cs @@ -1,24 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -30,7 +21,7 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to filter by change reason. + /// The source IObservable<IChangeSet<TObject, TKey>> to filter by change reason. /// The values to filter by. /// An observable which emits a change set with items matching the reasons. /// reasons. @@ -42,8 +33,8 @@ public static IObservable> WhereReasonsAre /// Extensions for dynamic data. @@ -30,7 +21,7 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to filter by excluding change reasons. + /// The source IObservable<IChangeSet<TObject, TKey>> to filter by excluding change reasons. /// The values to filter by. /// An observable which emits a change set with items not matching the reasons. /// reasons. @@ -42,7 +33,8 @@ public static IObservable> WhereReasonsAreNot /// Extensions for dynamic data. @@ -31,8 +29,8 @@ public static partial class ObservableCacheEx /// /// The type of the object. /// The type of the key. - /// The source to combine. - /// The additional streams to combine with. + /// The source IObservable<IChangeSet<TObject, TKey>> to combine. + /// The additional IObservable<IChangeSet<TObject, TKey>> streams to combine with. /// A changeset stream containing items present in exactly one source. /// /// @@ -49,15 +47,15 @@ public static partial class ObservableCacheEx /// /// /// or is . - /// - /// - /// - /// + /// And<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IObservable<IChangeSet<TObject, TKey>>[]) + /// Or<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IObservable<IChangeSet<TObject, TKey>>[]) + /// Except<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IObservable<IChangeSet<TObject, TKey>>[]) + /// ObservableListEx.Xor public static IObservable> Xor(this IObservable> source, params IObservable>[] others) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); if (others is null || others.Length == 0) { @@ -67,14 +65,19 @@ public static IObservable> Xor(this IOb return source.Combine(CombineOperator.Xor, others); } - /// - /// The of streams to combine. + /// + /// Provides an overload of Xor for the supplied arguments. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The ICollection<T> of streams to combine. + /// The resulting observable sequence. /// This overload accepts a pre-built collection of sources instead of a params array. public static IObservable> Xor(this ICollection>> sources) where TObject : notnull where TKey : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return sources.Combine(CombineOperator.Xor); } @@ -85,13 +88,13 @@ public static IObservable> Xor(this ICo /// /// The type of the object. /// The type of the key. - /// The of streams to combine. + /// The IObservableList<T> of streams to combine. /// An observable which emits a change set. public static IObservable> Xor(this IObservableList>> sources) where TObject : notnull where TKey : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return sources.Combine(CombineOperator.Xor); } @@ -102,13 +105,13 @@ public static IObservable> Xor(this IOb /// /// The type of the object. /// The type of the key. - /// The of changeset streams to combine. + /// The IObservableList<IObservableCache<TObject, TKey>> of changeset streams to combine. /// An observable which emits a change set. public static IObservable> Xor(this IObservableList> sources) where TObject : notnull where TKey : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return sources.Combine(CombineOperator.Xor); } @@ -119,13 +122,13 @@ public static IObservable> Xor(this IOb /// /// The type of the object. /// The type of the key. - /// The of changeset streams to combine. + /// The IObservableList<ISourceCache<TObject, TKey>> of changeset streams to combine. /// An observable which emits a change set. public static IObservable> Xor(this IObservableList> sources) where TObject : notnull where TKey : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return sources.Combine(CombineOperator.Xor); } diff --git a/src/DynamicData/Cache/ObservableCacheEx.cs b/src/DynamicData/Cache/ObservableCacheEx.cs index 76a0cb1f8..548a3af9e 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.cs @@ -1,29 +1,23 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Runtime.CompilerServices; -using DynamicData.Binding; -using DynamicData.Cache; -using DynamicData.Cache.Internal; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. /// public static partial class ObservableCacheEx { + /// + /// The DefaultSortResetThreshold field. + /// private const int DefaultSortResetThreshold = 100; } diff --git a/src/DynamicData/Cache/PageContext.cs b/src/DynamicData/Cache/PageContext.cs index a36afc396..d260e5d4f 100644 --- a/src/DynamicData/Cache/PageContext.cs +++ b/src/DynamicData/Cache/PageContext.cs @@ -1,10 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Operators; +#else using DynamicData.Operators; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Parameters associated with the page operation. @@ -18,6 +28,9 @@ public record PageContext( IComparer Comparer, SortAndPageOptions Options) { + /// + /// The Empty field. + /// internal static readonly PageContext Empty = new ( new PageResponse(0, 0, 0, 0), diff --git a/src/DynamicData/Cache/PageRequest.cs b/src/DynamicData/Cache/PageRequest.cs index bddf8f33a..8bba7cb4e 100644 --- a/src/DynamicData/Cache/PageRequest.cs +++ b/src/DynamicData/Cache/PageRequest.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Represents a new page request. @@ -67,12 +71,17 @@ public PageRequest() public int Size { get; } = 25; /// + /// The other value. + /// The result of the operation. public bool Equals(IPageRequest? other) => DefaultComparer.Equals(this, other); /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) => obj is IPageRequest value && Equals(value); /// + /// The result of the operation. public override int GetHashCode() { unchecked @@ -82,10 +91,20 @@ public override int GetHashCode() } /// + /// The result of the operation. public override string ToString() => $"Page: {Page}, Size: {Size}"; - private sealed class PageSizeEqualityComparer : IEqualityComparer +/// +/// Provides members for the PageSizeEqualityComparer class. +/// +private sealed class PageSizeEqualityComparer : IEqualityComparer { + /// + /// Executes the Equals operation. + /// + /// The x value. + /// The y value. + /// The result of the operation. public bool Equals(IPageRequest? x, IPageRequest? y) { if (ReferenceEquals(x, y)) @@ -111,6 +130,11 @@ public bool Equals(IPageRequest? x, IPageRequest? y) return x.Page == y.Page && x.Size == y.Size; } + /// + /// Executes the GetHashCode operation. + /// + /// The obj value. + /// The result of the operation. public int GetHashCode(IPageRequest? obj) { if (obj is null) diff --git a/src/DynamicData/Cache/PageResponse.cs b/src/DynamicData/Cache/PageResponse.cs index 425eb440e..4e445e73c 100644 --- a/src/DynamicData/Cache/PageResponse.cs +++ b/src/DynamicData/Cache/PageResponse.cs @@ -1,22 +1,53 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Operators; +#else using DynamicData.Operators; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; - +#endif + +/// +/// Provides members for the PageResponse class. +/// +/// The pageSize value. +/// The totalSize value. +/// The page value. +/// The pages value. internal sealed class PageResponse(int pageSize, int totalSize, int page, int pages) : IEquatable, IPageResponse { + /// + /// Gets the DefaultComparer value. + /// public static IEqualityComparer DefaultComparer { get; } = new PageResponseEqualityComparer(); + /// + /// Gets the Page value. + /// public int Page { get; } = page; + /// + /// Gets the Pages value. + /// public int Pages { get; } = pages; + /// + /// Gets the PageSize value. + /// public int PageSize { get; } = pageSize; + /// + /// Gets the TotalSize value. + /// public int TotalSize { get; } = totalSize; /// @@ -76,8 +107,17 @@ public override int GetHashCode() /// public override string ToString() => $"Page: {Page}, PageSize: {PageSize}, Pages: {Pages}, TotalSize: {TotalSize}"; - private sealed class PageResponseEqualityComparer : IEqualityComparer +/// +/// Provides members for the PageResponseEqualityComparer class. +/// +private sealed class PageResponseEqualityComparer : IEqualityComparer { + /// + /// Executes the Equals operation. + /// + /// The x value. + /// The y value. + /// The result of the operation. public bool Equals(IPageResponse? x, IPageResponse? y) { if (ReferenceEquals(x, y)) @@ -103,6 +143,11 @@ public bool Equals(IPageResponse? x, IPageResponse? y) return x.PageSize == y.PageSize && x.TotalSize == y.TotalSize && x.Page == y.Page && x.Pages == y.Pages; } + /// + /// Executes the GetHashCode operation. + /// + /// The obj value. + /// The result of the operation. public int GetHashCode(IPageResponse? obj) { if (obj is null) diff --git a/src/DynamicData/Cache/PagedChangeSet.cs b/src/DynamicData/Cache/PagedChangeSet.cs index f16135527..2c0541c30 100644 --- a/src/DynamicData/Cache/PagedChangeSet.cs +++ b/src/DynamicData/Cache/PagedChangeSet.cs @@ -1,19 +1,46 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else using DynamicData.Cache.Internal; +#endif +#if REACTIVE_SHIM +using DynamicData.Reactive.Operators; +#else using DynamicData.Operators; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif +/// +/// Provides members for the PagedChangeSet class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal sealed class PagedChangeSet : ChangeSet, IPagedChangeSet where TObject : notnull where TKey : notnull { + /// + /// The Empty field. + /// public static new readonly IPagedChangeSet Empty = new PagedChangeSet(); + /// + /// Initializes a new instance of the class. + /// + /// The sortedItems value. + /// The updates value. + /// The response value. public PagedChangeSet(IKeyValueCollection sortedItems, IEnumerable> updates, IPageResponse response) : base(updates) { @@ -21,14 +48,23 @@ public PagedChangeSet(IKeyValueCollection sortedItems, IEnumerabl SortedItems = sortedItems; } + /// + /// Initializes a new instance of the class. + /// private PagedChangeSet() { SortedItems = new KeyValueCollection(); Response = new PageResponse(0, 0, 0, 0); } + /// + /// Gets the Response value. + /// public IPageResponse Response { get; } + /// + /// Gets the SortedItems value. + /// public IKeyValueCollection SortedItems { get; } /// @@ -38,8 +74,17 @@ private PagedChangeSet() /// If the page change set equals the other. public bool Equals(PagedChangeSet other) => SortedItems.SequenceEqual(other.SortedItems); + /// + /// Executes the Equals operation. + /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) => obj is PagedChangeSet value && Equals(value); + /// + /// Executes the GetHashCode operation. + /// + /// The result of the operation. public override int GetHashCode() => SortedItems.GetHashCode(); /// diff --git a/src/DynamicData/Cache/SortAndPageOptions.cs b/src/DynamicData/Cache/SortAndPageOptions.cs index 1783eced2..5ed8dfb62 100644 --- a/src/DynamicData/Cache/SortAndPageOptions.cs +++ b/src/DynamicData/Cache/SortAndPageOptions.cs @@ -1,15 +1,25 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else using DynamicData.Binding; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Options for the sort and virtualize operator. /// -public record struct SortAndPageOptions() +public readonly record struct SortAndPageOptions() { /// /// The sort reset threshold ie the number of changes before a reset is fired. diff --git a/src/DynamicData/Cache/SortAndVirtualizeOptions.cs b/src/DynamicData/Cache/SortAndVirtualizeOptions.cs index fa6843347..d078218fd 100644 --- a/src/DynamicData/Cache/SortAndVirtualizeOptions.cs +++ b/src/DynamicData/Cache/SortAndVirtualizeOptions.cs @@ -1,15 +1,25 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else using DynamicData.Binding; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Options for the sort and virtualize operator. /// -public record struct SortAndVirtualizeOptions() +public readonly record struct SortAndVirtualizeOptions() { /// /// The sort reset threshold ie the number of changes before a reset is fired. diff --git a/src/DynamicData/Cache/SortOptimisations.cs b/src/DynamicData/Cache/SortOptimisations.cs index 52cda18a8..af1226366 100644 --- a/src/DynamicData/Cache/SortOptimisations.cs +++ b/src/DynamicData/Cache/SortOptimisations.cs @@ -1,9 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Flags used to specify one or more sort optimisations. diff --git a/src/DynamicData/Cache/SortReason.cs b/src/DynamicData/Cache/SortReason.cs index b94ab15e4..d503f61b6 100644 --- a/src/DynamicData/Cache/SortReason.cs +++ b/src/DynamicData/Cache/SortReason.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// The reason why the sorted collection has changed. diff --git a/src/DynamicData/Cache/SortedChangeSet.cs b/src/DynamicData/Cache/SortedChangeSet.cs index fa9e37be0..364c8121f 100644 --- a/src/DynamicData/Cache/SortedChangeSet.cs +++ b/src/DynamicData/Cache/SortedChangeSet.cs @@ -1,30 +1,76 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif +/// +/// Provides members for the SortedChangeSet class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal sealed class SortedChangeSet : ChangeSet, ISortedChangeSet where TObject : notnull where TKey : notnull { + /// + /// The Empty field. + /// public static new readonly ISortedChangeSet Empty = new SortedChangeSet(); + /// + /// Initializes a new instance of the class. + /// + /// The sortedItems value. + /// The updates value. public SortedChangeSet(IKeyValueCollection sortedItems, IEnumerable> updates) : base(updates) => SortedItems = sortedItems; + /// + /// Initializes a new instance of the class. + /// private SortedChangeSet() => SortedItems = new KeyValueCollection(); + /// + /// Gets the SortedItems value. + /// public IKeyValueCollection SortedItems { get; } + /// + /// Executes the Equals operation. + /// + /// The other value. + /// The result of the operation. public bool Equals(SortedChangeSet other) => SortedItems.SequenceEqual(other.SortedItems); + /// + /// Executes the Equals operation. + /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) => obj is SortedChangeSet value && Equals(value); + /// + /// Executes the GetHashCode operation. + /// + /// The result of the operation. public override int GetHashCode() => SortedItems.GetHashCode(); + /// + /// Executes the ToString operation. + /// + /// The result of the operation. public override string ToString() => $"SortedChangeSet. Count= {SortedItems.Count}. Updates = {Count}"; } diff --git a/src/DynamicData/Cache/SourceCache.cs b/src/DynamicData/Cache/SourceCache.cs index 5d281579c..4aba29d00 100644 --- a/src/DynamicData/Cache/SourceCache.cs +++ b/src/DynamicData/Cache/SourceCache.cs @@ -3,10 +3,18 @@ // See the LICENSE file in the project root for full license information. using System.Diagnostics; +#if REACTIVE_SHIM +using DynamicData.Reactive.Binding; +#else using DynamicData.Binding; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// An observable cache which exposes an update API. Used at the root @@ -24,6 +32,9 @@ public sealed class SourceCache(Func keySelector) where TObject : notnull where TKey : notnull { + /// + /// The _innerCache field. + /// private readonly ObservableCache _innerCache = new(keySelector); /// @@ -45,26 +56,38 @@ public sealed class SourceCache(Func keySelector) public IReadOnlyDictionary KeyValues => _innerCache.KeyValues; /// + /// The predicate value. + /// The suppressEmptyChangeSets value. + /// The result of the operation. public IObservable> Connect(Func? predicate = null, bool suppressEmptyChangeSets = true) => _innerCache.Connect(predicate, suppressEmptyChangeSets); /// public void Dispose() => _innerCache.Dispose(); /// + /// The updateAction value. public void Edit(Action> updateAction) => _innerCache.UpdateFromSource(updateAction); /// - public Optional Lookup(TKey key) => _innerCache.Lookup(key); + /// The key value. + /// The result of the operation. + public ReactiveUI.Primitives.Optional Lookup(TKey key) => _innerCache.Lookup(key); /// + /// The predicate value. + /// The result of the operation. public IObservable> Preview(Func? predicate = null) => _innerCache.Preview(predicate); /// + /// The key value. + /// The result of the operation. public IObservable> Watch(TKey key) => _innerCache.Watch(key); /// + /// The result of the operation. public IDisposable SuspendCount() => _innerCache.SuspendCount(); /// + /// The result of the operation. public IDisposable SuspendNotifications() => _innerCache.SuspendNotifications(); } diff --git a/src/DynamicData/Cache/SourceCacheEx.cs b/src/DynamicData/Cache/SourceCacheEx.cs index fa7a2e430..8d4c7dbb3 100644 --- a/src/DynamicData/Cache/SourceCacheEx.cs +++ b/src/DynamicData/Cache/SourceCacheEx.cs @@ -1,9 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Source cache convenience extensions. diff --git a/src/DynamicData/Cache/Tests/ChangeSetAggregator.cs b/src/DynamicData/Cache/Tests/ChangeSetAggregator.cs index dd2f6acbb..0b2e8a87c 100644 --- a/src/DynamicData/Cache/Tests/ChangeSetAggregator.cs +++ b/src/DynamicData/Cache/Tests/ChangeSetAggregator.cs @@ -1,13 +1,20 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +using DynamicData.Reactive.Diagnostics; +#else using DynamicData.Diagnostics; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Tests; +#else namespace DynamicData.Tests; +#endif /// /// Aggregates all events and statistics for a change set to help assertions when testing. @@ -19,6 +26,9 @@ public sealed class ChangeSetAggregator : IDisposable where TObject : notnull where TKey : notnull { + /// + /// The _disposer field. + /// private readonly IDisposable _disposer; /// @@ -27,6 +37,8 @@ public sealed class ChangeSetAggregator : IDisposable /// The source. public ChangeSetAggregator(IObservable> source) { + ArgumentExceptionHelper.ThrowIfNull(source); + var published = source.Publish(); Data = published.AsObservableCache(); @@ -100,6 +112,9 @@ public sealed class ChangeSetAggregator : IDisposable where TObject : notnull where TKey : notnull { + /// + /// The _disposer field. + /// private readonly IDisposable _disposer; /// @@ -108,6 +123,8 @@ public sealed class ChangeSetAggregator : IDisposable /// The source. public ChangeSetAggregator(IObservable> source) { + ArgumentExceptionHelper.ThrowIfNull(source); + var published = source.Publish(); Data = published.AsObservableCache(); diff --git a/src/DynamicData/Cache/Tests/DistinctChangeSetAggregator.cs b/src/DynamicData/Cache/Tests/DistinctChangeSetAggregator.cs index 745bf27ae..854d8f9f0 100644 --- a/src/DynamicData/Cache/Tests/DistinctChangeSetAggregator.cs +++ b/src/DynamicData/Cache/Tests/DistinctChangeSetAggregator.cs @@ -1,14 +1,20 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +using DynamicData.Reactive.Diagnostics; +#else using DynamicData.Diagnostics; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive.Tests; +#else namespace DynamicData.Tests; +#endif /// /// Aggregates all events and statistics for a distinct change set to help assertions when testing. @@ -17,8 +23,14 @@ namespace DynamicData.Tests; public class DistinctChangeSetAggregator : IDisposable where TValue : notnull { + /// + /// The _disposer field. + /// private readonly IDisposable _disposer; + /// + /// The _isDisposed field. + /// private bool _isDisposed; /// @@ -27,6 +39,8 @@ public class DistinctChangeSetAggregator : IDisposable /// The source. public DistinctChangeSetAggregator(IObservable> source) { + ArgumentExceptionHelper.ThrowIfNull(source); + var published = source.Publish(); var error = published.Subscribe(_ => { }, ex => Error = ex); diff --git a/src/DynamicData/Cache/Tests/GroupChangeSetAggregator.cs b/src/DynamicData/Cache/Tests/GroupChangeSetAggregator.cs index 7711123f6..3f69fed5e 100644 --- a/src/DynamicData/Cache/Tests/GroupChangeSetAggregator.cs +++ b/src/DynamicData/Cache/Tests/GroupChangeSetAggregator.cs @@ -1,13 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +using DynamicData.Reactive.Diagnostics; +#else using DynamicData.Diagnostics; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Tests; +#else namespace DynamicData.Tests; +#endif /// /// Aggregates all events and statistics for a group change set to help assertions when testing. @@ -20,8 +27,19 @@ public class GroupChangeSetAggregator : IDisposable where TKey : notnull where TGroupKey : notnull { + /// + /// The _compositeDisposable field. + /// private readonly CompositeDisposable _compositeDisposable; + + /// + /// The _messages field. + /// private readonly List> _messages = []; + + /// + /// The _disposedValue field. + /// private bool _disposedValue; /// @@ -30,6 +48,8 @@ public class GroupChangeSetAggregator : IDisposable /// The source. public GroupChangeSetAggregator(IObservable> source) { + ArgumentExceptionHelper.ThrowIfNull(source); + var published = source.Publish(); Data = published.AsObservableCache(); diff --git a/src/DynamicData/Cache/Tests/PagedChangeSetAggregator.cs b/src/DynamicData/Cache/Tests/PagedChangeSetAggregator.cs index 42ff87a74..14bce5c9b 100644 --- a/src/DynamicData/Cache/Tests/PagedChangeSetAggregator.cs +++ b/src/DynamicData/Cache/Tests/PagedChangeSetAggregator.cs @@ -1,14 +1,20 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +using DynamicData.Reactive.Diagnostics; +#else using DynamicData.Diagnostics; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive.Tests; +#else namespace DynamicData.Tests; +#endif /// /// Aggregates all events and statistics for a paged change set to help assertions when testing. @@ -19,8 +25,14 @@ public class PagedChangeSetAggregator : IDisposable where TObject : notnull where TKey : notnull { + /// + /// The _disposer field. + /// private readonly IDisposable _disposer; + /// + /// The _isDisposed field. + /// private bool _isDisposed; /// @@ -29,6 +41,8 @@ public class PagedChangeSetAggregator : IDisposable /// The source. public PagedChangeSetAggregator(IObservable> source) { + ArgumentExceptionHelper.ThrowIfNull(source); + var published = source.Publish(); var error = published.Subscribe(_ => { }, ex => Error = ex); diff --git a/src/DynamicData/Cache/Tests/SortedChangeSetAggregator.cs b/src/DynamicData/Cache/Tests/SortedChangeSetAggregator.cs index 70158e156..6984cc330 100644 --- a/src/DynamicData/Cache/Tests/SortedChangeSetAggregator.cs +++ b/src/DynamicData/Cache/Tests/SortedChangeSetAggregator.cs @@ -1,14 +1,20 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +using DynamicData.Reactive.Diagnostics; +#else using DynamicData.Diagnostics; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive.Tests; +#else namespace DynamicData.Tests; +#endif /// /// Aggregates all events and statistics for a sorted change set to help assertions when testing. @@ -19,8 +25,14 @@ public class SortedChangeSetAggregator : IDisposable where TObject : notnull where TKey : notnull { + /// + /// The _disposer field. + /// private readonly IDisposable _disposer; + /// + /// The _isDisposed field. + /// private bool _isDisposed; /// @@ -29,6 +41,8 @@ public class SortedChangeSetAggregator : IDisposable /// The source. public SortedChangeSetAggregator(IObservable> source) { + ArgumentExceptionHelper.ThrowIfNull(source); + var published = source.Publish(); var error = published.Subscribe(_ => { }, ex => Error = ex); diff --git a/src/DynamicData/Cache/Tests/TestEx.cs b/src/DynamicData/Cache/Tests/TestEx.cs index aada3066a..0c693745b 100644 --- a/src/DynamicData/Cache/Tests/TestEx.cs +++ b/src/DynamicData/Cache/Tests/TestEx.cs @@ -1,9 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive.Tests; +#else namespace DynamicData.Tests; +#endif /// /// Test extensions. @@ -43,7 +47,7 @@ public static ChangeSetAggregator AsAggregator AsAggregator(this IObservable> source) where TValue : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new DistinctChangeSetAggregator(source); } @@ -62,7 +66,7 @@ public static GroupChangeSetAggregator AsAggregator(source); } @@ -79,7 +83,7 @@ public static SortedChangeSetAggregator AsAggregator(source); } @@ -96,7 +100,7 @@ public static VirtualChangeSetAggregator AsAggregator(source); } @@ -112,7 +116,7 @@ public static PagedChangeSetAggregator AsAggregator(source); } diff --git a/src/DynamicData/Cache/Tests/VirtualChangeSetAggregator.cs b/src/DynamicData/Cache/Tests/VirtualChangeSetAggregator.cs index b972709a5..de0f14635 100644 --- a/src/DynamicData/Cache/Tests/VirtualChangeSetAggregator.cs +++ b/src/DynamicData/Cache/Tests/VirtualChangeSetAggregator.cs @@ -1,14 +1,20 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +using DynamicData.Reactive.Diagnostics; +#else using DynamicData.Diagnostics; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive.Tests; +#else namespace DynamicData.Tests; +#endif /// /// Aggregates all events and statistics for a virtual change set to help assertions when testing. @@ -19,8 +25,14 @@ public class VirtualChangeSetAggregator : IDisposable where TObject : notnull where TKey : notnull { + /// + /// The _disposer field. + /// private readonly IDisposable _disposer; + /// + /// The _isDisposed field. + /// private bool _isDisposed; /// @@ -29,6 +41,8 @@ public class VirtualChangeSetAggregator : IDisposable /// The source. public VirtualChangeSetAggregator(IObservable> source) { + ArgumentExceptionHelper.ThrowIfNull(source); + var published = source.Publish(); var error = published.Subscribe(_ => { }, ex => Error = ex); diff --git a/src/DynamicData/Cache/TransformAsyncOptions.cs b/src/DynamicData/Cache/TransformAsyncOptions.cs index 19ed9ab40..a2d1efc01 100644 --- a/src/DynamicData/Cache/TransformAsyncOptions.cs +++ b/src/DynamicData/Cache/TransformAsyncOptions.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Options for TransformAsync and TransformSafeAsync. diff --git a/src/DynamicData/Cache/VirtualChangeSet.cs b/src/DynamicData/Cache/VirtualChangeSet.cs index 3aae7f59f..6341236f9 100644 --- a/src/DynamicData/Cache/VirtualChangeSet.cs +++ b/src/DynamicData/Cache/VirtualChangeSet.cs @@ -1,18 +1,41 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif +/// +/// Provides members for the VirtualChangeSet class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal sealed class VirtualChangeSet : ChangeSet, IVirtualChangeSet, IEquatable> where TObject : notnull where TKey : notnull { + /// + /// The Empty field. + /// public static new readonly IVirtualChangeSet Empty = new VirtualChangeSet(); + /// + /// Initializes a new instance of the class. + /// + /// The items value. + /// The sortedItems value. + /// The response value. public VirtualChangeSet(IEnumerable> items, IKeyValueCollection sortedItems, IVirtualResponse response) : base(items) { @@ -20,20 +43,46 @@ public VirtualChangeSet(IEnumerable> items, IKeyValueColle Response = response; } + /// + /// Initializes a new instance of the class. + /// private VirtualChangeSet() { SortedItems = new KeyValueCollection(); Response = new VirtualResponse(0, 0, 0); } + /// + /// Gets the Response value. + /// public IVirtualResponse Response { get; } + /// + /// Gets the SortedItems value. + /// public IKeyValueCollection SortedItems { get; } + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator ==(VirtualChangeSet left, VirtualChangeSet right) => Equals(left, right); + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator !=(VirtualChangeSet left, VirtualChangeSet right) => !Equals(left, right); + /// + /// Executes the Equals operation. + /// + /// The other value. + /// The result of the operation. public bool Equals(VirtualChangeSet? other) { if (other is null) @@ -49,8 +98,17 @@ public bool Equals(VirtualChangeSet? other) return Response.Equals(other.Response) && Equals(SortedItems, other.SortedItems); } + /// + /// Executes the Equals operation. + /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) => obj is VirtualChangeSet item && Equals(item); + /// + /// Executes the GetHashCode operation. + /// + /// The result of the operation. public override int GetHashCode() { unchecked diff --git a/src/DynamicData/Cache/VirtualContext.cs b/src/DynamicData/Cache/VirtualContext.cs index f909ddbc0..3ebf5a2c7 100644 --- a/src/DynamicData/Cache/VirtualContext.cs +++ b/src/DynamicData/Cache/VirtualContext.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Parameters associated with the virtualize operation. @@ -16,6 +21,9 @@ public record VirtualContext( IComparer Comparer, SortAndVirtualizeOptions Options) { + /// + /// The Empty field. + /// internal static readonly VirtualContext Empty = new ( new VirtualResponse(0, 0, 0), diff --git a/src/DynamicData/Cache/VirtualRequest.cs b/src/DynamicData/Cache/VirtualRequest.cs index 4d94c9412..9ee304d82 100644 --- a/src/DynamicData/Cache/VirtualRequest.cs +++ b/src/DynamicData/Cache/VirtualRequest.cs @@ -1,9 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A request object for virtualisation. @@ -52,12 +56,17 @@ public VirtualRequest() public int StartIndex { get; } /// + /// The other value. + /// The result of the operation. public bool Equals(IVirtualRequest? other) => StartIndexSizeComparer.Equals(this, other); /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) => obj is IVirtualRequest item && Equals(item); /// + /// The result of the operation. public override int GetHashCode() { unchecked @@ -67,10 +76,20 @@ public override int GetHashCode() } /// + /// The result of the operation. public override string ToString() => $"StartIndex: {StartIndex}, Size: {Size}"; - private sealed class StartIndexSizeEqualityComparer : IEqualityComparer +/// +/// Provides members for the StartIndexSizeEqualityComparer class. +/// +private sealed class StartIndexSizeEqualityComparer : IEqualityComparer { + /// + /// Executes the Equals operation. + /// + /// The x value. + /// The y value. + /// The result of the operation. public bool Equals(IVirtualRequest? x, IVirtualRequest? y) { if (ReferenceEquals(x, y)) @@ -96,6 +115,11 @@ public bool Equals(IVirtualRequest? x, IVirtualRequest? y) return x.StartIndex == y.StartIndex && x.Size == y.Size; } + /// + /// Executes the GetHashCode operation. + /// + /// The obj value. + /// The result of the operation. public int GetHashCode(IVirtualRequest? obj) { if (obj is null) diff --git a/src/DynamicData/Cache/VirtualResponse.cs b/src/DynamicData/Cache/VirtualResponse.cs index 70152976c..af347f103 100644 --- a/src/DynamicData/Cache/VirtualResponse.cs +++ b/src/DynamicData/Cache/VirtualResponse.cs @@ -1,15 +1,25 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Defines values used to virtualise the result set. /// +/// The size value. +/// The startIndex value. +/// The totalSize value. internal sealed class VirtualResponse(int size, int startIndex, int totalSize) : IEquatable, IVirtualResponse { + /// + /// Gets the DefaultComparer value. + /// public static IEqualityComparer DefaultComparer { get; } = new TotalSizeStartIndexSizeEqualityComparer(); /// @@ -70,8 +80,17 @@ public override int GetHashCode() /// public override string ToString() => $"Size: {Size}, StartIndex: {StartIndex}, TotalSize: {TotalSize}"; - private sealed class TotalSizeStartIndexSizeEqualityComparer : IEqualityComparer +/// +/// Provides members for the TotalSizeStartIndexSizeEqualityComparer class. +/// +private sealed class TotalSizeStartIndexSizeEqualityComparer : IEqualityComparer { + /// + /// Executes the Equals operation. + /// + /// The x value. + /// The y value. + /// The result of the operation. public bool Equals(IVirtualResponse? x, IVirtualResponse? y) { if (ReferenceEquals(x, y)) @@ -97,6 +116,11 @@ public bool Equals(IVirtualResponse? x, IVirtualResponse? y) return x.TotalSize == y.TotalSize && x.StartIndex == y.StartIndex && x.Size == y.Size; } + /// + /// Executes the GetHashCode operation. + /// + /// The obj value. + /// The result of the operation. public int GetHashCode(IVirtualResponse? obj) { if (obj is null) diff --git a/src/DynamicData/Constants.cs b/src/DynamicData/Constants.cs index 11f6d9330..f03c46cd1 100644 --- a/src/DynamicData/Constants.cs +++ b/src/DynamicData/Constants.cs @@ -1,13 +1,36 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif +/// +/// Provides members for the Constants class. +/// internal static class Constants { + /// + /// The VirtualizeIsObsolete field. + /// public const string VirtualizeIsObsolete = "Use SortAndVirtualize as it's more efficient"; + + /// + /// The PageIsObsolete field. + /// public const string PageIsObsolete = "Use SortAndPage as it's more efficient"; + + /// + /// The TopIsObsolete field. + /// public const string TopIsObsolete = "Use Overload with comparer as it's more efficient"; + + /// + /// The SortIsObsolete field. + /// public const string SortIsObsolete = "Use SortAndBind as it's more efficient"; } diff --git a/src/DynamicData/Diagnostics/ChangeStatistics.cs b/src/DynamicData/Diagnostics/ChangeStatistics.cs index b9ddf9f49..09623de49 100644 --- a/src/DynamicData/Diagnostics/ChangeStatistics.cs +++ b/src/DynamicData/Diagnostics/ChangeStatistics.cs @@ -1,63 +1,31 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Diagnostics; +#else namespace DynamicData.Diagnostics; +#endif /// /// Object used to capture accumulated changes. /// -public class ChangeStatistics : IEquatable +/// Gets the index. +/// Gets the adds. +/// Gets the updates. +/// Gets the removes. +/// Gets the refreshes. +/// Gets the moves. +/// Gets the count. +public record ChangeStatistics(int Index, int Adds, int Updates, int Removes, int Refreshes, int Moves, int Count) { /// /// Initializes a new instance of the class. /// - public ChangeStatistics() => Index = -1; - - /// - /// Initializes a new instance of the class. - /// - /// The index of the change. - /// The number of additions. - /// The number of updates. - /// The number of removals. - /// The number of refreshes. - /// The number of moves. - /// The new count. - public ChangeStatistics(int index, int adds, int updates, int removes, int refreshes, int moves, int count) - { - Index = index; - Adds = adds; - Updates = updates; - Removes = removes; - Refreshes = refreshes; - Moves = moves; - Count = count; - } - - /// - /// Gets the adds. - /// - /// - /// The adds. - /// - public int Adds { get; } - - /// - /// Gets the count. - /// - /// - /// The count. - /// - public int Count { get; } - - /// - /// Gets the index. - /// - /// - /// The index. - /// - public int Index { get; } + public ChangeStatistics() + : this(-1, default, default, default, default, default, default) => Index = -1; /// /// Gets the last updated. @@ -67,74 +35,8 @@ public ChangeStatistics(int index, int adds, int updates, int removes, int refre /// public DateTime LastUpdated { get; } = DateTime.Now; - /// - /// Gets the moves. - /// - /// - /// The moves. - /// - public int Moves { get; } - - /// - /// Gets the refreshes. - /// - /// - /// The refreshes. - /// - public int Refreshes { get; } - - /// - /// Gets the removes. - /// - /// - /// The removes. - /// - public int Removes { get; } - - /// - /// Gets the updates. - /// - /// - /// The updates. - /// - public int Updates { get; } - - /// - /// Checks to see if both sides are equal. - /// - /// The left side to compare. - /// The right side to compare. - /// If the two sides are equal. - public static bool operator ==(ChangeStatistics left, ChangeStatistics right) => Equals(left, right); - - /// - /// Checks to see if both sides are not equal. - /// - /// The left side to compare. - /// The right side to compare. - /// If the two sides are not equal. - public static bool operator !=(ChangeStatistics left, ChangeStatistics right) => !Equals(left, right); - - /// - public bool Equals(ChangeStatistics? other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return Adds == other.Adds && Updates == other.Updates && Removes == other.Removes && Refreshes == other.Refreshes && Moves == other.Moves && Count == other.Count && Index == other.Index && LastUpdated.Equals(other.LastUpdated); - } - - /// - public override bool Equals(object? obj) => obj is ChangeStatistics change && Equals(change); - /// + /// The result of the operation. public override int GetHashCode() { unchecked @@ -152,5 +54,6 @@ public override int GetHashCode() } /// + /// The result of the operation. public override string ToString() => $"CurrentIndex: {Index}, Adds: {Adds}, Updates: {Updates}, Removes: {Removes}, Refreshes: {Refreshes}, Count: {Count}, Timestamp: {LastUpdated}"; } diff --git a/src/DynamicData/Diagnostics/ChangeSummary.cs b/src/DynamicData/Diagnostics/ChangeSummary.cs index 5b9f88a34..efe4b767b 100644 --- a/src/DynamicData/Diagnostics/ChangeSummary.cs +++ b/src/DynamicData/Diagnostics/ChangeSummary.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Diagnostics; +#else namespace DynamicData.Diagnostics; +#endif /// /// Accumulates change statics. @@ -14,6 +19,9 @@ public class ChangeSummary /// public static readonly ChangeSummary Empty = new(); + /// + /// The _index field. + /// private readonly int _index; /// @@ -56,6 +64,8 @@ private ChangeSummary() public ChangeStatistics Overall { get; } /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) { if (obj is null) @@ -72,6 +82,7 @@ public override bool Equals(object? obj) } /// + /// The result of the operation. public override int GetHashCode() { unchecked @@ -84,7 +95,13 @@ public override int GetHashCode() } /// + /// The result of the operation. public override string ToString() => $"CurrentIndex: {_index}, Latest Count: {Latest.Count}, Overall Count: {Overall.Count}"; + /// + /// Executes the Equals operation. + /// + /// The other value. + /// The result of the operation. private bool Equals(ChangeSummary other) => _index == other._index && Equals(Latest, other.Latest) && Equals(Overall, other.Overall); } diff --git a/src/DynamicData/Diagnostics/DiagnosticOperators.cs b/src/DynamicData/Diagnostics/DiagnosticOperators.cs index 0e13e735c..7e8b0193c 100644 --- a/src/DynamicData/Diagnostics/DiagnosticOperators.cs +++ b/src/DynamicData/Diagnostics/DiagnosticOperators.cs @@ -1,10 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.Diagnostics; +#else namespace DynamicData.Diagnostics; +#endif /// /// Extensions for diagnostics. @@ -23,7 +26,7 @@ public static IObservable CollectUpdateStats(this where TSource : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Scan( ChangeSummary.Empty, @@ -53,7 +56,7 @@ public static IObservable CollectUpdateStats(this public static IObservable CollectUpdateStats(this IObservable> source) where TSource : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Scan( ChangeSummary.Empty, diff --git a/src/DynamicData/DynamicData.csproj b/src/DynamicData/DynamicData.csproj index b3457ec93..19266ed59 100644 --- a/src/DynamicData/DynamicData.csproj +++ b/src/DynamicData/DynamicData.csproj @@ -1,34 +1,57 @@  - netstandard2.0;net462;net6.0;net7.0;net8.0;net9.0;net10.0 - true - true + Dynamic Data + +Bring the power of Rx to collections using Dynamic Data. +Dynamic Data is a comprehensive caching and data manipulation solution which introduces domain centric observable collections. + Linq extensions enable dynamic filtering, sorting, grouping, transforms, binding, pagination, data virtualisation, expiration, disposal management plus more. + + net462;net472;net48;net481;net8.0;net9.0;net10.0;net11.0 true - enable - + + + + + + + + + + + + + + + + + + + + + + + + + - Dynamic Data - -Bring the power of Rx to collections using Dynamic Data. -Dynamic Data is a comprehensive caching and data manipulation solution which introduces domain centric observable collections. - Linq extensions enable dynamic filtering, sorting, grouping, transforms, binding, pagination, data virtualisation, expiration, disposal management plus more. - + + ObservableCacheEx.cs @@ -40,4 +63,13 @@ Dynamic Data is a comprehensive caching and data manipulation solution which int - \ No newline at end of file + + + + + + + + + + diff --git a/src/DynamicData/DynamicDataOptions.cs b/src/DynamicData/DynamicDataOptions.cs index 4d0cececb..91019d5a9 100644 --- a/src/DynamicData/DynamicDataOptions.cs +++ b/src/DynamicData/DynamicDataOptions.cs @@ -1,10 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else using DynamicData.Binding; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// System-wide options container. diff --git a/src/DynamicData/EnumerableEx.cs b/src/DynamicData/EnumerableEx.cs index 124a4da30..35698a848 100644 --- a/src/DynamicData/EnumerableEx.cs +++ b/src/DynamicData/EnumerableEx.cs @@ -1,11 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for dynamic data. @@ -29,8 +31,8 @@ public static IObservable> AsObservableChangeSet>( obs => @@ -59,7 +61,7 @@ public static IObservable> AsObservableChangeSet> AsObservableChangeSet(this IEnumerable source, bool completable = false) where TObject : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return Observable.Create>( obs => diff --git a/src/DynamicData/Experimental/ExperimentalEx.cs b/src/DynamicData/Experimental/ExperimentalEx.cs index 69f7d3adc..8ba1573e8 100644 --- a/src/DynamicData/Experimental/ExperimentalEx.cs +++ b/src/DynamicData/Experimental/ExperimentalEx.cs @@ -1,10 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Concurrency; +namespace DynamicData.Reactive.Experimental; +#else namespace DynamicData.Experimental; +#endif /// /// Experimental operator extensions. @@ -24,7 +27,7 @@ public static IWatcher AsWatcher(this IObservable< where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new Watcher(source, scheduler ?? GlobalConfig.DefaultScheduler); } diff --git a/src/DynamicData/Experimental/ISubjectWithRefCount.cs b/src/DynamicData/Experimental/ISubjectWithRefCount.cs index 3bf4d4eda..2d4142f1f 100644 --- a/src/DynamicData/Experimental/ISubjectWithRefCount.cs +++ b/src/DynamicData/Experimental/ISubjectWithRefCount.cs @@ -1,16 +1,19 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Subjects; +namespace DynamicData.Reactive.Experimental; +#else namespace DynamicData.Experimental; +#endif /// /// A subject which also contains its current reference count. /// /// The type of item. -internal interface ISubjectWithRefCount : ISubject +internal interface ISubjectWithRefCount : ISignal { /// Gets number of subscribers. /// diff --git a/src/DynamicData/Experimental/IWatcher.cs b/src/DynamicData/Experimental/IWatcher.cs index c936472ac..ccc6d4a4b 100644 --- a/src/DynamicData/Experimental/IWatcher.cs +++ b/src/DynamicData/Experimental/IWatcher.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Experimental; +#else namespace DynamicData.Experimental; +#endif /// /// A specialisation of the SourceList which is optimised for watching individual items. diff --git a/src/DynamicData/Experimental/SubjectWithRefCount.cs b/src/DynamicData/Experimental/SubjectWithRefCount.cs index 0554eb718..f067ce700 100644 --- a/src/DynamicData/Experimental/SubjectWithRefCount.cs +++ b/src/DynamicData/Experimental/SubjectWithRefCount.cs @@ -1,11 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Subjects; +namespace DynamicData.Reactive.Experimental; +#else namespace DynamicData.Experimental; +#endif /// /// A subject with a count of the number of subscribers. @@ -15,10 +17,21 @@ namespace DynamicData.Experimental; /// Initializes a new instance of the class. /// /// The subject to perform reference counting on. -internal sealed class SubjectWithRefCount(ISubject? subject = null) : ISubjectWithRefCount +internal sealed class SubjectWithRefCount(ISignal? subject = null) : ISubjectWithRefCount { - private readonly ISubject _subject = subject ?? new Subject(); + /// + /// The _subject field. + /// + private readonly ISignal _subject = subject ?? new Signal(); + + /// + /// The _isDisposed field. + /// + private bool _isDisposed; + /// + /// The _refCount field. + /// private int _refCount; /// Gets number of subscribers. @@ -27,6 +40,19 @@ internal sealed class SubjectWithRefCount(ISubject? subject = null) : ISub /// public int RefCount => _refCount; + /// + public bool HasObservers => Volatile.Read(ref _refCount) > 0; + + /// + public bool IsDisposed => _isDisposed; + + /// + public void Dispose() + { + _isDisposed = true; + (_subject as IDisposable)?.Dispose(); + } + /// /// Notifies the observer that the provider has finished sending push-based notifications. /// diff --git a/src/DynamicData/Experimental/Watcher.cs b/src/DynamicData/Experimental/Watcher.cs index 9825ae686..392aac1da 100644 --- a/src/DynamicData/Experimental/Watcher.cs +++ b/src/DynamicData/Experimental/Watcher.cs @@ -1,32 +1,50 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; +namespace DynamicData.Reactive.Experimental; +#else namespace DynamicData.Experimental; +#endif +/// +/// Provides members for the Watcher class. +/// +/// The type of the TObject value. +/// The type of the TKey value. internal sealed class Watcher : IWatcher where TObject : notnull where TKey : notnull { + /// + /// The _disposer field. + /// private readonly IDisposable _disposer; -#if NET9_0_OR_GREATER + /// + /// The _locker field. + /// private readonly Lock _locker = new(); -#else - private readonly object _locker = new(); -#endif - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed with _cleanUp")] + /// + /// The _source field. + /// + [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed with _cleanUp")] private readonly IObservableCache _source; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed with _cleanUp")] + /// + /// The _subscribers field. + /// + [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed with _cleanUp")] private readonly IntermediateCache>, TKey> _subscribers = new(); + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The scheduler value. public Watcher(IObservable> source, IScheduler scheduler) { _source = source.AsObservableCache(); @@ -40,7 +58,13 @@ public Watcher(IObservable> source, IScheduler schedul var subscriber = _subscribers.Lookup(update.Key); if (subscriber.HasValue) { - scheduler.Schedule(() => subscriber.Value.OnNext(update)); + scheduler.Schedule( + state: (subject: subscriber.Value, update), + action: static (_, state) => + { + state.subject.OnNext(state.update); + return Disposable.Empty; + }); } })); @@ -55,8 +79,16 @@ public Watcher(IObservable> source, IScheduler schedul }); } + /// + /// Executes the Dispose operation. + /// public void Dispose() => _disposer.Dispose(); + /// + /// Executes the Watch operation. + /// + /// The key value. + /// The result of the operation. public IObservable> Watch(TKey key) => Observable.Create>( observer => { @@ -71,7 +103,7 @@ public IObservable> Watch(TKey key) => Observable.Create>(new ReplaySubject>(1)); + subject = new SubjectWithRefCount>(new ReplaySignal>(1)); var initial = _source.Lookup(key); if (initial.HasValue) diff --git a/src/DynamicData/GlobalConfig.cs b/src/DynamicData/GlobalConfig.cs index 106e66aec..26d52c8a3 100644 --- a/src/DynamicData/GlobalConfig.cs +++ b/src/DynamicData/GlobalConfig.cs @@ -1,12 +1,21 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Concurrency; +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif +/// +/// Provides members for the GlobalConfig class. +/// internal static class GlobalConfig { + /// + /// Gets the DefaultScheduler value. + /// public static IScheduler DefaultScheduler => TaskPoolScheduler.Default; } diff --git a/src/DynamicData/IChangeSet.cs b/src/DynamicData/IChangeSet.cs index e0396936e..de3314701 100644 --- a/src/DynamicData/IChangeSet.cs +++ b/src/DynamicData/IChangeSet.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Base interface representing a set of changes. diff --git a/src/DynamicData/Internal/Bitset.cs b/src/DynamicData/Internal/Bitset.cs index d28e4a669..0f1695a3e 100644 --- a/src/DynamicData/Internal/Bitset.cs +++ b/src/DynamicData/Internal/Bitset.cs @@ -5,9 +5,13 @@ #if NETCOREAPP3_0_OR_GREATER using System.Numerics; #endif -using System.Runtime.CompilerServices; +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Internal; +#else namespace DynamicData.Internal; +#endif /// /// @@ -24,10 +28,24 @@ namespace DynamicData.Internal; /// internal struct Bitset { + /// + /// The BitsPerWord field. + /// private const int BitsPerWord = 64; + + /// + /// The WordShift field. + /// private const int WordShift = 6; + + /// + /// The BitMask field. + /// private const int BitMask = BitsPerWord - 1; + /// + /// The _words field. + /// private long[] _words; /// Initializes a new instance of the struct with capacity for 64 slots. @@ -72,6 +90,7 @@ public void Clear(int index) /// Returns if the bit at is set. /// The zero-based slot index. + /// The result of the operation. [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly bool IsSet(int index) { @@ -95,6 +114,7 @@ public readonly bool IsSet(int index) /// in a single CPU instruction. /// /// + /// The result of the operation. [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly int FindHighest() { @@ -138,6 +158,7 @@ public readonly int FindHighest() /// in a single CPU instruction. /// /// + /// The result of the operation. [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly int FindLowest() { @@ -160,6 +181,7 @@ public readonly int FindLowest() } /// Returns if any bit is set. + /// The result of the operation. [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly bool HasAny() => Count > 0; @@ -215,7 +237,12 @@ public void Compact() } #if !NETCOREAPP3_0_OR_GREATER - private static int HighestSetBit(long value) + /// + /// Executes the HighestSetBit operation. + /// + /// The value value. + /// The result of the operation. +private static int HighestSetBit(long value) { var bit = 0; for (var v = (ulong)value; v > 1; v >>= 1) @@ -226,7 +253,12 @@ private static int HighestSetBit(long value) return bit; } - private static int LowestSetBit(long value) + /// + /// Executes the LowestSetBit operation. + /// + /// The value value. + /// The result of the operation. +private static int LowestSetBit(long value) { var bit = 0; var v = (ulong)value; diff --git a/src/DynamicData/Internal/CacheParentSubscription.cs b/src/DynamicData/Internal/CacheParentSubscription.cs index 3a33143df..cbd8c6c4d 100644 --- a/src/DynamicData/Internal/CacheParentSubscription.cs +++ b/src/DynamicData/Internal/CacheParentSubscription.cs @@ -1,12 +1,15 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Diagnostics; -using System.Reactive.Disposables; -using System.Reactive.Linq; +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Internal; +#else namespace DynamicData.Internal; +#endif /// /// Base class for subscriptions that need to manage child subscriptions and emit updates @@ -24,13 +27,44 @@ internal abstract class CacheParentSubscription + /// The _childSubscriptions field. + /// private readonly KeyedDisposable _childSubscriptions = new(); + + /// + /// The _parentSubscription field. + /// private readonly SingleAssignmentDisposable _parentSubscription = new(); + + /// + /// The _queue field. + /// private readonly SharedDeliveryQueue _queue; + + /// + /// The _observer field. + /// private readonly IObserver _observer; + + /// + /// The _subscriptionCounter field. + /// private int _subscriptionCounter = 1; // Starts at 1 for the parent subscription + + /// + /// The _isCompleted field. + /// private bool _isCompleted; + + /// + /// The _hasTerminated field. + /// private bool _hasTerminated; + + /// + /// The _disposedValue field. + /// private bool _disposedValue; /// @@ -50,12 +84,30 @@ public void Dispose() GC.SuppressFinalize(this); } + /// + /// Executes the ParentOnNext operation. + /// + /// The changes value. protected abstract void ParentOnNext(IChangeSet changes); + /// + /// Executes the ChildOnNext operation. + /// + /// The child value. + /// The parentKey value. protected abstract void ChildOnNext(TChild child, TKey parentKey); + /// + /// Executes the EmitChanges operation. + /// + /// The observer value. protected abstract void EmitChanges(IObserver observer); + /// + /// Executes the AddChildSubscription operation. + /// + /// The observable value. + /// The parentKey value. protected void AddChildSubscription(IObservable observable, TKey parentKey) { // Add a new subscription. Do first so cleanup of existing subs doesn't trigger OnCompleted. @@ -73,25 +125,35 @@ protected void AddChildSubscription(IObservable observable, TKey parentK // on normal completion (not disposal), so RemoveChildSubscription is NOT called when the // parent disposes child subscriptions during Dispose(). This asymmetry is intentional: // disposal cleanup is handled by KeyedDisposable, not by individual completion callbacks. - disposableContainer.Disposable = observable - .Finally(CheckCompleted) - .SubscribeSafe( - onNext: val => ChildOnNext(val, parentKey), - onError: TerminalError, - onCompleted: () => RemoveChildSubscription(parentKey)); + disposableContainer.Disposable = PrimitivesLinqExtensions.SubscribeSafe( + observable.Finally(CheckCompleted), + onNext: val => ChildOnNext(val, parentKey), + onError: TerminalError, + onCompleted: () => RemoveChildSubscription(parentKey)); } + /// + /// Executes the RemoveChildSubscription operation. + /// + /// The parentKey value. protected void RemoveChildSubscription(TKey parentKey) => _childSubscriptions.Remove(parentKey); + /// + /// Executes the CreateParentSubscription operation. + /// + /// The source value. protected void CreateParentSubscription(IObservable> source) => _parentSubscription.Disposable = - source - .SynchronizeSafe(_queue) - .SubscribeSafe( - onNext: ParentOnNext, - onError: TerminalError, - onCompleted: CheckCompleted); + PrimitivesLinqExtensions.SubscribeSafe( + source.SynchronizeSafe(_queue), + onNext: ParentOnNext, + onError: TerminalError, + onCompleted: CheckCompleted); + /// + /// Executes the Dispose operation. + /// + /// The disposing value. protected virtual void Dispose(bool disposing) { if (!_disposedValue) @@ -113,9 +175,15 @@ protected virtual void Dispose(bool disposing) /// Same-thread reentrant delivery ensures child items are delivered inline during /// parent processing, preserving the original Synchronize(lock) ordering semantics. /// + /// The type of the T value. + /// The observable value. + /// The result of the operation. protected IObservable MakeChildObservable(IObservable observable) => observable.SynchronizeSafe(_queue); + /// + /// Executes the OnDrainComplete operation. + /// private void OnDrainComplete() { EmitChanges(_observer); @@ -127,12 +195,19 @@ private void OnDrainComplete() } } + /// + /// Executes the TerminalError operation. + /// + /// The error value. private void TerminalError(Exception error) { _hasTerminated = true; _observer.OnError(error); } + /// + /// Executes the CheckCompleted operation. + /// private void CheckCompleted() { if (Interlocked.Decrement(ref _subscriptionCounter) == 0) diff --git a/src/DynamicData/Internal/DeliveryQueue.cs b/src/DynamicData/Internal/DeliveryQueue.cs index 74a688f25..85b366b7b 100644 --- a/src/DynamicData/Internal/DeliveryQueue.cs +++ b/src/DynamicData/Internal/DeliveryQueue.cs @@ -1,27 +1,56 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Internal; +#else namespace DynamicData.Internal; +#endif /// /// A queue that serializes item delivery outside a caller-owned lock. -/// Internally stores values. Delivery -/// is dispatched to an outside the lock. +/// Internally stores Notification<T> values. Delivery +/// is dispatched to an IObserver<T> outside the lock. /// /// The value type delivered via OnNext. internal sealed class DeliveryQueue : IObserver, IDisposable + where T : notnull { + /// + /// The _queue field. + /// private readonly Queue> _queue = new(1); -#if NET9_0_OR_GREATER + /// + /// The _gate field. + /// private readonly Lock _gate; -#else - private readonly object _gate; -#endif + /// + /// The _queueGate field. + /// + private readonly Lock _queueGate = new(); + + /// + /// The _observer field. + /// private readonly IObserver _observer; + + /// + /// The _activeAccessCount field. + /// + private int _activeAccessCount; + + /// + /// The _drainThreadId field. + /// private int _drainThreadId = -1; + + /// + /// The _isTerminated field. + /// private volatile bool _isTerminated; /// @@ -30,11 +59,7 @@ internal sealed class DeliveryQueue : IObserver, IDisposable /// The observer that receives delivered items. public DeliveryQueue(IObserver observer) { -#if NET9_0_OR_GREATER _gate = new Lock(); -#else - _gate = new object(); -#endif _observer = observer; } @@ -43,11 +68,7 @@ public DeliveryQueue(IObserver observer) /// /// The lock shared with the caller. /// The observer that receives delivered items. -#if NET9_0_OR_GREATER public DeliveryQueue(Lock gate, IObserver observer) -#else - public DeliveryQueue(object gate, IObserver observer) -#endif { _gate = gate; _observer = observer; @@ -58,6 +79,11 @@ public DeliveryQueue(object gate, IObserver observer) /// public bool IsTerminated => _isTerminated; + /// + /// Gets whether the current thread is delivering queued notifications. + /// + internal bool IsDeliveringOnCurrentThread => Volatile.Read(ref _drainThreadId) == Environment.CurrentManagedThreadId; + /// /// Terminates the queue (rejecting further enqueues) and blocks until /// any in-flight delivery has completed. After this returns, no more @@ -66,16 +92,26 @@ public DeliveryQueue(object gate, IObserver observer) /// private void EnsureDeliveryComplete() { - using (AcquireReadLock()) + var isDrainThread = false; + + EnterQueueLock(); + try { _isTerminated = true; _queue.Clear(); + isDrainThread = _drainThreadId == Environment.CurrentManagedThreadId; + } + finally + { + ExitQueueLock(); + } - // If we're being called from within the drain loop (e.g., downstream - // disposed during OnNext), the current thread IS the deliverer. - // The drain loop will see _isTerminated and exit after we return. - if (_drainThreadId == Environment.CurrentManagedThreadId) - return; + // If we're being called from within the drain loop (e.g., downstream + // disposed during OnNext), the current thread IS the deliverer. + // The drain loop will see _isTerminated and exit after we return. + if (isDrainThread) + { + return; } SpinWait spinner = default; @@ -90,14 +126,17 @@ private void EnsureDeliveryComplete() /// Acquires the gate and returns a scoped access for enqueueing notifications. /// Disposing releases the gate and triggers delivery if needed. /// + /// The result of the operation. public ScopedAccess AcquireLock() => new(this); /// /// Acquires the gate for read-only inspection. Does not trigger delivery on dispose. /// + /// The result of the operation. public ReadOnlyScopedAccess AcquireReadLock() => new(this); /// Enqueues an OnNext notification via the lock, then drains. + /// The value value. public void OnNext(T value) { using var scope = AcquireLock(); @@ -105,6 +144,7 @@ public void OnNext(T value) } /// Enqueues an OnError notification via the lock, then drains. + /// The error value. public void OnError(Exception error) { using var scope = AcquireLock(); @@ -117,31 +157,112 @@ public void OnCompleted() using var scope = AcquireLock(); scope.EnqueueCompleted(); } - #if NET9_0_OR_GREATER + + /// + /// Executes the EnterLock operation. + /// private void EnterLock() => _gate.Enter(); + /// + /// Executes the ExitLock operation. + /// private void ExitLock() => _gate.Exit(); #else + + /// + /// Executes the EnterLock operation. + /// private void EnterLock() => Monitor.Enter(_gate); + /// + /// Executes the ExitLock operation. + /// private void ExitLock() => Monitor.Exit(_gate); #endif +#if NET9_0_OR_GREATER - private void EnqueueNotification(Notification item) + /// + /// Executes the EnterQueueLock operation. + /// + private void EnterQueueLock() => _queueGate.Enter(); + + /// + /// Executes the ExitQueueLock operation. + /// + private void ExitQueueLock() => _queueGate.Exit(); +#else + + /// + /// Executes the EnterQueueLock operation. + /// + private void EnterQueueLock() => Monitor.Enter(_queueGate); + + /// + /// Executes the ExitQueueLock operation. + /// + private void ExitQueueLock() => Monitor.Exit(_queueGate); +#endif + + /// + /// Enters caller-owned access and prevents queue draining until the access is released. + /// + private void EnterAccess() { - if (_isTerminated) + Interlocked.Increment(ref _activeAccessCount); + + try { - return; + EnterLock(); } + catch + { + Interlocked.Decrement(ref _activeAccessCount); + throw; + } + } + + /// + /// Releases caller-owned access and starts delivery if this was the final active access. + /// + private void ExitAccessAndDeliver() + { + ExitLock(); + + if (Interlocked.Decrement(ref _activeAccessCount) == 0) + { + DeliverIfNeeded(); + } + } + + /// + /// Executes the EnqueueNotification operation. + /// + /// The item value. + private void EnqueueNotification(Notification item) + { + EnterQueueLock(); + try + { + if (_isTerminated) + { + return; + } - _queue.Enqueue(item); + _queue.Enqueue(item); + } + finally + { + ExitQueueLock(); + } } - private void ExitLockAndDeliver() + /// + /// Executes the DeliverIfNeeded operation. + /// + private void DeliverIfNeeded() { var shouldDeliver = TryStartDelivery(); - ExitLock(); if (shouldDeliver) { @@ -150,13 +271,21 @@ private void ExitLockAndDeliver() bool TryStartDelivery() { - if (_drainThreadId != -1 || _queue.Count == 0) + EnterQueueLock(); + try { - return false; - } + if (_drainThreadId != -1 || _queue.Count == 0 || _isTerminated || Volatile.Read(ref _activeAccessCount) != 0) + { + return false; + } - _drainThreadId = Environment.CurrentManagedThreadId; - return true; + Volatile.Write(ref _drainThreadId, Environment.CurrentManagedThreadId); + return true; + } + finally + { + ExitQueueLock(); + } } void DeliverAll() @@ -165,25 +294,9 @@ void DeliverAll() { while (true) { - Notification notification; - - using (AcquireReadLock()) + if (!TryTakeNotification(out var notification)) { - if (_queue.Count == 0 || _isTerminated) - { - _drainThreadId = -1; - return; - } - - notification = _queue.Dequeue(); - - // Mark terminated BEFORE delivery so concurrent code - // (e.g., InvokePreview) sees the terminal state immediately. - if (notification.IsTerminal) - { - _isTerminated = true; - _queue.Clear(); - } + return; } // Deliver outside the lock @@ -191,44 +304,105 @@ void DeliverAll() if (notification.IsTerminal) { - using (AcquireReadLock()) - { - _drainThreadId = -1; - } - + StopDelivery(); return; } } } catch { - using (AcquireReadLock()) + StopDelivery(); + throw; + } + } + + bool TryTakeNotification(out Notification notification) + { + EnterQueueLock(); + try + { + if (_queue.Count == 0 || _isTerminated || Volatile.Read(ref _activeAccessCount) != 0) { - _drainThreadId = -1; + Volatile.Write(ref _drainThreadId, -1); + notification = default; + return false; } - throw; + notification = _queue.Dequeue(); + + // Mark terminated BEFORE delivery so concurrent code + // (e.g., InvokePreview) sees the terminal state immediately. + if (notification.IsTerminal) + { + _isTerminated = true; + _queue.Clear(); + } + + return true; + } + finally + { + ExitQueueLock(); + } + } + + void StopDelivery() + { + EnterQueueLock(); + try + { + Volatile.Write(ref _drainThreadId, -1); + } + finally + { + ExitQueueLock(); } } } /// - /// Scoped access for enqueueing notifications under the gate lock. + /// Gets whether queue delivery is currently pending or in progress. /// - public ref struct ScopedAccess + /// when there are queued or in-flight notifications. + private bool HasPendingNotifications() { + EnterQueueLock(); + try + { + return _queue.Count > 0 || _drainThreadId != -1; + } + finally + { + ExitQueueLock(); + } + } + +/// +/// Scoped access for enqueueing notifications under the gate lock. +/// +public ref struct ScopedAccess + { + /// + /// The _owner field. + /// private DeliveryQueue? _owner; + /// + /// Initializes a new instance of the struct. + /// + /// The owner value. internal ScopedAccess(DeliveryQueue owner) { _owner = owner; - owner.EnterLock(); + owner.EnterAccess(); } /// Enqueues an OnNext notification. + /// The value value. public readonly void EnqueueNext(T value) => _owner?.EnqueueNotification(Notification.CreateNext(value)); /// Enqueues an OnError notification (terminal). + /// The error value. public readonly void EnqueueError(Exception error) => _owner?.EnqueueNotification(Notification.CreateError(error)); /// Enqueues an OnCompleted notification (terminal). @@ -244,26 +418,32 @@ public void Dispose() } _owner = null; - owner.ExitLockAndDeliver(); + owner.ExitAccessAndDeliver(); } } - /// - /// Read-only scoped access. Disposing releases the gate without triggering delivery. - /// - public ref struct ReadOnlyScopedAccess +/// +/// Read-only scoped access. Disposing releases the gate and resumes any deferred delivery. +/// +public ref struct ReadOnlyScopedAccess { + /// + /// The _owner field. + /// private DeliveryQueue? _owner; + /// + /// Initializes a new instance of the struct. + /// + /// The owner value. internal ReadOnlyScopedAccess(DeliveryQueue owner) { _owner = owner; - owner.EnterLock(); + owner.EnterAccess(); } /// Gets whether there are notifications pending delivery. - public readonly bool HasPending => - _owner is not null && (_owner._queue.Count > 0 || _owner._drainThreadId != -1); + public readonly bool HasPending => _owner?.HasPendingNotifications() == true; /// Releases the gate lock. public void Dispose() @@ -275,7 +455,7 @@ public void Dispose() } _owner = null; - owner.ExitLock(); + owner.ExitAccessAndDeliver(); } } } diff --git a/src/DynamicData/Internal/ExceptionMixins.cs b/src/DynamicData/Internal/ExceptionMixins.cs deleted file mode 100644 index 7baad015f..000000000 --- a/src/DynamicData/Internal/ExceptionMixins.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. -// Roland Pheasant licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -namespace DynamicData -{ - internal static class ExceptionMixins - { - public static void ThrowArgumentNullExceptionIfNull(this T? value, string name) - { - if (value is null) - { - throw new ArgumentNullException(name); - } - } - } -} diff --git a/src/DynamicData/Internal/KeyedDisposable.cs b/src/DynamicData/Internal/KeyedDisposable.cs index 4e2ab2951..92c013df9 100644 --- a/src/DynamicData/Internal/KeyedDisposable.cs +++ b/src/DynamicData/Internal/KeyedDisposable.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Internal; +#else namespace DynamicData.Internal; +#endif /// /// Manages Disposables by Key: @@ -14,16 +19,24 @@ namespace DynamicData.Internal; internal sealed class KeyedDisposable : IDisposable where TKey : notnull { + /// + /// The _disposables field. + /// private readonly Dictionary _disposables = []; -#if NET9_0_OR_GREATER + /// + /// The _gate field. + /// private readonly Lock _gate = new(); -#else - private readonly object _gate = new(); -#endif + /// + /// The _disposedValue field. + /// private bool _disposedValue; + /// + /// Gets the Count value. + /// public int Count { get @@ -33,6 +46,9 @@ public int Count } } + /// + /// Gets the Keys value. + /// public IEnumerable Keys { get @@ -42,12 +58,20 @@ public IEnumerable Keys } } + /// + /// Executes the ContainsKey operation. + /// + /// The key value. + /// The result of the operation. public bool ContainsKey(TKey key) { lock (_gate) return _disposables.ContainsKey(key); } + /// + /// Gets the IsDisposed value. + /// public bool IsDisposed { get @@ -63,6 +87,10 @@ public bool IsDisposed /// If the item is NOT disposable, any existing entry for the key is removed /// and disposed. /// + /// The type of the TItem value. + /// The key value. + /// The item value. + /// The result of the operation. public TItem Add(TKey key, TItem item) where TItem : notnull { @@ -105,6 +133,10 @@ public TItem Add(TKey key, TItem item) return item; } + /// + /// Executes the Remove operation. + /// + /// The key value. public void Remove(TKey key) { IDisposable? toDispose; @@ -123,6 +155,9 @@ public void Remove(TKey key) toDispose.Dispose(); } + /// + /// Executes the Dispose operation. + /// public void Dispose() { Dictionary? snapshot; diff --git a/src/DynamicData/Internal/Notification.cs b/src/DynamicData/Internal/Notification.cs index 9e3eaf4a0..6e9d39861 100644 --- a/src/DynamicData/Internal/Notification.cs +++ b/src/DynamicData/Internal/Notification.cs @@ -1,40 +1,62 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using DynamicData.Kernel; +namespace DynamicData.Reactive.Internal; +#else namespace DynamicData.Internal; +#endif /// /// A lightweight notification struct for delivery queues. Discriminates -/// OnNext, OnError, and OnCompleted using +/// OnNext, OnError, and OnCompleted using Optional<T>.HasValue /// and the error field, avoiding null discrimination on T? which /// is broken for value types in generic struct fields on .NET 9. /// +/// The type of the T value. internal readonly struct Notification + where T : notnull { - private readonly Optional _value; + /// + /// The _value field. + /// + private readonly ReactiveUI.Primitives.Optional _value; + + /// + /// The _error field. + /// private readonly Exception? _error; - private Notification(Optional value, Exception? error) + /// + /// Initializes a new instance of the struct. + /// + /// The value value. + /// The error value. + private Notification(ReactiveUI.Primitives.Optional value, Exception? error) { _value = value; _error = error; } /// Creates an OnNext notification. + /// The value value. + /// The result of the operation. public static Notification CreateNext(T value) => new(value, null); /// Creates an OnError notification (terminal). + /// The error value. + /// The result of the operation. public static Notification CreateError(Exception error) { - error.ThrowArgumentNullExceptionIfNull(nameof(error)); - return new(Optional.None(), error); + ArgumentExceptionHelper.ThrowIfNull(error); + return new(ReactiveUI.Primitives.Optional.None, error); } /// Creates an OnCompleted notification (terminal). - public static Notification CreateCompleted() => new(Optional.None(), null); + /// The result of the operation. + public static Notification CreateCompleted() => new(ReactiveUI.Primitives.Optional.None, null); /// Gets whether this is an OnError notification. public bool IsError => _error is not null; @@ -43,6 +65,7 @@ public static Notification CreateError(Exception error) public bool IsTerminal => !_value.HasValue; /// Delivers this notification to the specified observer. + /// The observer value. public void Accept(IObserver observer) { if (_value.HasValue) diff --git a/src/DynamicData/Internal/ObservableEx.cs b/src/DynamicData/Internal/ObservableEx.cs deleted file mode 100644 index 8fce9a875..000000000 --- a/src/DynamicData/Internal/ObservableEx.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. -// 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.Reactive; - -namespace DynamicData.Internal; - -internal static class ObservableEx -{ - public static IDisposable SubscribeSafe(this IObservable observable, Action onNext, Action onError, Action onCompleted) => - observable.SubscribeSafe(Observer.Create(onNext, onError, onCompleted)); - - public static IDisposable SubscribeSafe(this IObservable observable, Action onNext, Action onError) => - observable.SubscribeSafe(Observer.Create(onNext, onError)); - - public static IDisposable SubscribeSafe(this IObservable observable, Action onError, Action onCompleted) => - observable.SubscribeSafe(Observer.Create(Stub.Ignore, onError, onCompleted)); - - public static IDisposable SubscribeSafe(this IObservable observable, Action onError) => - observable.SubscribeSafe(Observer.Create(Stub.Ignore, onError)); - - private static class Stub - { - public static readonly Action Ignore = static _ => { }; - } -} diff --git a/src/DynamicData/Internal/Rxx.cs b/src/DynamicData/Internal/Rxx.cs deleted file mode 100644 index 8fe86a561..000000000 --- a/src/DynamicData/Internal/Rxx.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. -// Roland Pheasant licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -#if NET9_0_OR_GREATER -using DynamicData.Internal; - -namespace System.Reactive.Linq; - -internal static class Rxx -{ - /// - /// Keep this class internal as it should be supplied by System.Reactive and probably will be one day. - /// - public static IObservable Synchronize(this IObservable source, Lock locker) - { - return Observable.Create(observer => - { - return source.SubscribeSafe(t => - { - lock (locker) - { - observer.OnNext(t); - } - }, ex => - { - lock (locker) - { - observer.OnError(ex); - } - }, () => - { - lock (locker) - { - observer.OnCompleted(); - } - }); - }); - } -} -#endif diff --git a/src/DynamicData/Internal/SharedDeliveryQueue.cs b/src/DynamicData/Internal/SharedDeliveryQueue.cs index 6eab28894..afa19724f 100644 --- a/src/DynamicData/Internal/SharedDeliveryQueue.cs +++ b/src/DynamicData/Internal/SharedDeliveryQueue.cs @@ -1,32 +1,56 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Runtime.CompilerServices; +namespace DynamicData.Reactive.Internal; +#else namespace DynamicData.Internal; +#endif /// /// A type-erased delivery queue that serializes delivery across multiple sources -/// with different item types. Each source gets a typed -/// via . A single drain loop delivers items from all +/// with different item types. Each source gets a typed DeliverySubQueue<T> +/// via CreateQueue<T>. A single drain loop delivers items from all /// sub-queues outside the lock, one item per iteration. An /// tracks which sub-queues have pending items, replacing O(N) scans with O(1) lookups. /// internal sealed class SharedDeliveryQueue : IDisposable { + /// + /// The _sources field. + /// private readonly List _sources = []; + + /// + /// The _onDrainComplete field. + /// private readonly Action? _onDrainComplete; -#if NET9_0_OR_GREATER + /// + /// The _gate field. + /// private readonly Lock _gate; -#else - private readonly object _gate; -#endif + /// + /// The _activeBits field. + /// private Bitset _activeBits = new(); + + /// + /// The _deadCount field. + /// private int _deadCount; + + /// + /// The _drainThreadId field. + /// private int _drainThreadId = -1; + + /// + /// The _isTerminated field. + /// private volatile bool _isTerminated; /// Initializes a new instance of the class with its own internal lock. @@ -39,23 +63,16 @@ public SharedDeliveryQueue() /// Initializes a new instance of the class with its own internal lock /// and a callback that fires outside the lock after each drain cycle completes. /// + /// The onDrainComplete value. public SharedDeliveryQueue(Action? onDrainComplete) { -#if NET9_0_OR_GREATER _gate = new Lock(); -#else - _gate = new object(); -#endif _onDrainComplete = onDrainComplete; } -#if NET9_0_OR_GREATER /// Initializes a new instance of the class with a caller-provided lock. + /// The gate value. public SharedDeliveryQueue(Lock gate) => _gate = gate; -#else - /// Initializes a new instance of the class with a caller-provided lock. - public SharedDeliveryQueue(object gate) => _gate = gate; -#endif /// Gets whether this queue has been terminated. public bool IsTerminated @@ -99,7 +116,11 @@ private void EnsureDeliveryComplete() public void Dispose() => EnsureDeliveryComplete(); /// Creates a typed sub-queue bound to the specified observer. + /// The type of the T value. + /// The observer value. + /// The result of the operation. public DeliverySubQueue CreateQueue(IObserver observer) + where T : notnull { EnterLock(); try @@ -117,10 +138,12 @@ public DeliverySubQueue CreateQueue(IObserver observer) } /// Acquires the gate for read-only inspection. Does not trigger delivery on dispose. + /// The result of the operation. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlyScopedAccess AcquireReadLock() => new(this); /// Called by a sub-queue when it is disposed. Clears its active bit and tracks dead slots. + /// The index value. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void NotifyQueueRemoved(int index) { @@ -129,23 +152,38 @@ internal void NotifyQueueRemoved(int index) } /// Sets the active bit for a sub-queue when an item is enqueued. + /// The index value. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void SetActive(int index) => _activeBits.Set(index); - #if NET9_0_OR_GREATER + + /// + /// Executes the EnterLock operation. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void EnterLock() => _gate.Enter(); + /// + /// Executes the ExitLock operation. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void ExitLock() => _gate.Exit(); #else - [MethodImpl(MethodImplOptions.AggressiveInlining)] + + /// + /// Executes the EnterLock operation. + /// internal void EnterLock() => Monitor.Enter(_gate); - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// Executes the ExitLock operation. + /// internal void ExitLock() => Monitor.Exit(_gate); #endif + /// + /// Executes the ExitLockAndDrain operation. + /// internal void ExitLockAndDrain() { var currentThreadId = Environment.CurrentManagedThreadId; @@ -176,6 +214,9 @@ internal void ExitLockAndDrain() } } + /// + /// Executes the DrainAll operation. + /// private void DrainAll() { try @@ -337,11 +378,18 @@ private void CompactIfNeeded() _activeBits.Compact(); } - /// Read-only scoped access. Disposing releases the gate without triggering delivery. - public ref struct ReadOnlyScopedAccess +/// Read-only scoped access. Disposing releases the gate without triggering delivery. +public ref struct ReadOnlyScopedAccess { + /// + /// The _owner field. + /// private SharedDeliveryQueue? _owner; + /// + /// Initializes a new instance of the struct. + /// + /// The owner value. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ReadOnlyScopedAccess(SharedDeliveryQueue owner) { @@ -407,15 +455,46 @@ internal abstract class DrainableBase /// A typed sub-queue. All enqueue access goes through /// which acquires the parent's lock. /// +/// The type of the T value. internal sealed class DeliverySubQueue : DrainableBase, IObserver, IDisposable + where T : notnull { + /// + /// The _items field. + /// private readonly Queue> _items = new(1); + + /// + /// The _parent field. + /// private readonly SharedDeliveryQueue _parent; + + /// + /// The _observer field. + /// private readonly IObserver _observer; + + /// + /// The _staged field. + /// private Notification _staged; + + /// + /// The _index field. + /// private int _index; + + /// + /// The _isRemoved field. + /// private bool _isRemoved; + /// + /// Initializes a new instance of the class. + /// + /// The parent value. + /// The observer value. + /// The index value. internal DeliverySubQueue(SharedDeliveryQueue parent, IObserver observer, int index) { _parent = parent; @@ -448,10 +527,12 @@ internal override int Index } /// Acquires the parent gate. Disposing releases the lock and triggers drain. + /// The result of the operation. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ScopedAccess AcquireLock() => new(this); /// Enqueues an OnNext notification via the lock, then drains. + /// The value value. public void OnNext(T value) { using var scope = AcquireLock(); @@ -459,6 +540,7 @@ public void OnNext(T value) } /// Enqueues an OnError notification via the lock, then drains. + /// The error value. public void OnError(Exception error) { using var scope = AcquireLock(); @@ -497,6 +579,7 @@ public void Dispose() } /// + /// The result of the operation. internal override bool StageNext() { _staged = _items.Dequeue(); @@ -513,6 +596,10 @@ internal override void DeliverStaged() /// internal override void Clear() => _items.Clear(); + /// + /// Executes the EnqueueItem operation. + /// + /// The item value. [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EnqueueItem(Notification item) { @@ -525,11 +612,18 @@ private void EnqueueItem(Notification item) _parent.SetActive(_index); } - /// Scoped access for enqueueing items. Acquires the parent's gate lock. - public ref struct ScopedAccess +/// Scoped access for enqueueing items. Acquires the parent's gate lock. +public ref struct ScopedAccess { + /// + /// The _owner field. + /// private DeliverySubQueue? _owner; + /// + /// Initializes a new instance of the struct. + /// + /// The owner value. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ScopedAccess(DeliverySubQueue owner) { @@ -538,10 +632,12 @@ internal ScopedAccess(DeliverySubQueue owner) } /// Enqueues an OnNext item. + /// The item value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly void EnqueueNext(T item) => _owner?.EnqueueItem(Notification.CreateNext(item)); /// Enqueues a terminal error. + /// The error value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly void EnqueueError(Exception error) => _owner?.EnqueueItem(Notification.CreateError(error)); diff --git a/src/DynamicData/Internal/SwappableLock.cs b/src/DynamicData/Internal/SwappableLock.cs index 6b9fd31f3..5466de6a9 100644 --- a/src/DynamicData/Internal/SwappableLock.cs +++ b/src/DynamicData/Internal/SwappableLock.cs @@ -1,29 +1,47 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -namespace DynamicData; +namespace DynamicData.Reactive; +#else +namespace DynamicData; +#endif #if NET9_0_OR_GREATER +/// +/// Represents the SwappableLock value. +/// internal ref struct SwappableLock { + /// + /// Executes the CreateAndEnter operation. + /// + /// The gate value. + /// The result of the operation. public static SwappableLock CreateAndEnter(Lock gate) { gate.Enter(); return new SwappableLock { _gate = gate }; } + /// + /// Executes the SwapTo operation. + /// + /// The gate value. public void SwapTo(Lock gate) { if (_gate is null) throw new InvalidOperationException("Lock is not initialized"); - gate.Enter(); _gate.Exit(); _gate = gate; } + /// + /// Executes the Dispose operation. + /// public void Dispose() { if (_gate is not null) @@ -33,13 +51,23 @@ public void Dispose() } } + /// + /// The _gate field. + /// private Lock? _gate; } - #else +/// +/// Represents the SwappableLock value. +/// internal ref struct SwappableLock { + /// + /// Executes the CreateAndEnter operation. + /// + /// The gate value. + /// The result of the operation. public static SwappableLock CreateAndEnter(object gate) { var result = new SwappableLock() @@ -52,6 +80,10 @@ public static SwappableLock CreateAndEnter(object gate) return result; } + /// + /// Executes the SwapTo operation. + /// + /// The gate value. public void SwapTo(object gate) { if (_gate is null) @@ -69,6 +101,9 @@ public void SwapTo(object gate) _gate = gate; } + /// + /// Executes the Dispose operation. + /// public void Dispose() { if (_hasLock && (_gate is not null)) @@ -79,7 +114,14 @@ public void Dispose() } } + /// + /// The _hasLock field. + /// private bool _hasLock; + + /// + /// The _gate field. + /// private object? _gate; } diff --git a/src/DynamicData/Internal/SynchronizeSafeExtensions.cs b/src/DynamicData/Internal/SynchronizeSafeExtensions.cs index ace8ed4a1..d3cb6e1b2 100644 --- a/src/DynamicData/Internal/SynchronizeSafeExtensions.cs +++ b/src/DynamicData/Internal/SynchronizeSafeExtensions.cs @@ -1,11 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.Internal; +#else namespace DynamicData.Internal; +#endif /// /// Provides SynchronizeSafe extension methods, drop-in replacements @@ -25,7 +27,7 @@ namespace DynamicData.Internal; /// /// Queue-first (parameterless overload) /// Used by operators with teardown side effects (DisposeMany, OnBeingRemoved). -/// The queue is terminated first via , which ensures +/// The queue is terminated first via DeliveryQueue<T>.Dispose, which ensures /// all in-flight deliveries complete before the subscription is disposed and teardown logic /// (e.g., disposing removed items) runs. Terminal notifications are not needed because /// the subscriber is explicitly tearing down. @@ -38,7 +40,13 @@ internal static class SynchronizeSafeExtensions /// Synchronizes the source observable through a . /// Use when multiple sources of different types share a gate. /// - public static IObservable SynchronizeSafe(this IObservable source, SharedDeliveryQueue queue) => + /// The type of the T value. + /// The source value. + /// The queue value. + /// The result of the operation. + public static IObservable SynchronizeSafe(this IObservable source, SharedDeliveryQueue queue) + where T : notnull + => Observable.Create(observer => { var subQueue = queue.CreateQueue(observer); @@ -48,14 +56,15 @@ public static IObservable SynchronizeSafe(this IObservable source, Shar }); /// - /// Synchronizes the source observable through an implicitly created . + /// Synchronizes the source observable through an implicitly created DeliveryQueue<T>. /// Drop-in replacement for Synchronize(locker). /// -#if NET9_0_OR_GREATER - public static IObservable SynchronizeSafe(this IObservable source, Lock gate) => -#else - public static IObservable SynchronizeSafe(this IObservable source, object gate) => -#endif + /// The type of the T value. + /// The source value. + /// The gate value. + /// The result of the operation. + public static IObservable SynchronizeSafe(this IObservable source, Lock gate) + where T : notnull => Observable.Create(observer => { var queue = new DeliveryQueue(gate, observer); @@ -65,12 +74,16 @@ public static IObservable SynchronizeSafe(this IObservable source, obje }); /// - /// Synchronizes the source observable through an implicitly created + /// Synchronizes the source observable through an implicitly created DeliveryQueue<T> /// with automatic delivery completion on dispose. The queue is terminated and drained /// before the source subscription is disposed, ensuring all in-flight notifications /// are delivered before teardown. /// - public static IObservable SynchronizeSafe(this IObservable source) => + /// The type of the T value. + /// The source value. + /// The result of the operation. + public static IObservable SynchronizeSafe(this IObservable source) + where T : notnull => Observable.Create(observer => { var queue = new DeliveryQueue(observer); diff --git a/src/DynamicData/Kernel/ConnectionStatus.cs b/src/DynamicData/Kernel/ConnectionStatus.cs index a6a41d5cf..d6d0f49f2 100644 --- a/src/DynamicData/Kernel/ConnectionStatus.cs +++ b/src/DynamicData/Kernel/ConnectionStatus.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Kernel; +#else namespace DynamicData.Kernel; +#endif /// /// Connectable cache status. diff --git a/src/DynamicData/Kernel/EnumerableEx.cs b/src/DynamicData/Kernel/EnumerableEx.cs index afeca3d3e..08ad055e8 100644 --- a/src/DynamicData/Kernel/EnumerableEx.cs +++ b/src/DynamicData/Kernel/EnumerableEx.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Kernel; +#else namespace DynamicData.Kernel; +#endif /// /// Enumerable extensions. @@ -17,7 +22,7 @@ public static class EnumerableEx /// The array of items. public static T[] AsArray(this IEnumerable source) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source as T[] ?? source.ToArray(); } @@ -30,7 +35,7 @@ public static T[] AsArray(this IEnumerable source) /// The list. public static List AsList(this IEnumerable source) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source as List ?? source.ToList(); } @@ -45,8 +50,8 @@ public static List AsList(this IEnumerable source) /// The enumerable of items. public static IEnumerable Duplicates(this IEnumerable source, Func valueSelector) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - valueSelector.ThrowArgumentNullExceptionIfNull(nameof(valueSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); return source.GroupBy(valueSelector).Where(group => group.Count() > 1).SelectMany(t => t); } @@ -73,21 +78,39 @@ public static IEnumerable Duplicates(this IEnumerable source, F /// A result as specified by the result selector. public static IEnumerable IndexOfMany(this IEnumerable source, IEnumerable itemsToFind, Func resultSelector) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - itemsToFind.ThrowArgumentNullExceptionIfNull(nameof(itemsToFind)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(itemsToFind); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); var indexed = source.Select((element, index) => new { Element = element, Index = index }); return itemsToFind.Join(indexed, left => left, right => right.Element, (_, right) => right).Select(x => resultSelector(x.Element, x.Index)); } + /// + /// Executes the EmptyIfNull operation. + /// + /// The type of the T value. + /// The source value. + /// The result of the operation. internal static IEnumerable EmptyIfNull(this IEnumerable? source) => source ?? Enumerable.Empty(); + /// + /// Executes the EnumerateOne operation. + /// + /// The type of the T value. + /// The source value. + /// The result of the operation. internal static IEnumerable EnumerateOne(this T source) { yield return source; } + /// + /// Executes the ForEach operation. + /// + /// The type of the T value. + /// The source value. + /// The action value. internal static void ForEach(this IEnumerable source, Action action) { foreach (var item in source) @@ -96,6 +119,12 @@ internal static void ForEach(this IEnumerable source, Action action) } } + /// + /// Executes the ForEach operation. + /// + /// The type of the TObject value. + /// The source value. + /// The action value. internal static void ForEach(this IEnumerable source, Action action) { var i = 0; @@ -106,6 +135,12 @@ internal static void ForEach(this IEnumerable source, Action + /// Executes the ToHashSet operation. + /// + /// The type of the T value. + /// The source value. + /// The result of the operation. internal static HashSet ToHashSet(this IEnumerable source) => new(source); /// diff --git a/src/DynamicData/Kernel/EnumerableIList.cs b/src/DynamicData/Kernel/EnumerableIList.cs index 242f83b67..53bfcb3e6 100644 --- a/src/DynamicData/Kernel/EnumerableIList.cs +++ b/src/DynamicData/Kernel/EnumerableIList.cs @@ -1,16 +1,25 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections; -using System.Diagnostics.CodeAnalysis; - // Lifted from here https://github.com/benaadams/Ben.Enumerable. Many thanks to the genius of the man. +#if REACTIVE_SHIM +namespace DynamicData.Reactive.Kernel; +#else namespace DynamicData.Kernel; +#endif +/// +/// Represents the EnumerableIList value. +/// +/// The type of the T value. +/// The list value. [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Same class name, different generics.")] internal readonly struct EnumerableIList(IList list) : IEnumerableIList, IList { + /// + /// Gets the Empty value. + /// public static EnumerableIList Empty { get; } /// @@ -20,51 +29,102 @@ internal readonly struct EnumerableIList(IList list) : IEnumerableIList public bool IsReadOnly => list.IsReadOnly; /// + /// The index value. public T this[int index] { get => list[index]; set => list[index] = value; } + /// + /// Executes the conversion operator operation. + /// + /// The list value. + /// The result of the operation. public static implicit operator EnumerableIList(List list) => new(list); + /// + /// Executes the conversion operator operation. + /// + /// The array value. + /// The result of the operation. public static implicit operator EnumerableIList(T[] array) => new(array); + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. public EnumeratorIList GetEnumerator() => new(list); /// + /// The item value. public void Add(T item) => list.Add(item); /// public void Clear() => list.Clear(); /// + /// The item value. + /// The result of the operation. public bool Contains(T item) => list.Contains(item); /// + /// The array value. + /// The arrayIndex value. public void CopyTo(T[] array, int arrayIndex) => list.CopyTo(array, arrayIndex); /// + /// The item value. + /// The result of the operation. public int IndexOf(T item) => list.IndexOf(item); /// + /// The index value. + /// The item value. public void Insert(int index, T item) => list.Insert(index, item); /// + /// The item value. + /// The result of the operation. public bool Remove(T item) => list.Remove(item); /// + /// The index value. public void RemoveAt(int index) => list.RemoveAt(index); + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } +/// +/// Provides members for the EnumerableIList class. +/// internal static class EnumerableIList { + /// + /// Executes the Create operation. + /// + /// The type of the T value. + /// The list value. + /// The result of the operation. public static EnumerableIList Create(IList list) => new(list); + /// + /// Executes the Create operation. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The changeSet value. + /// The result of the operation. public static EnumerableIList> Create(IChangeSet changeSet) where TObject : notnull where TKey : notnull => diff --git a/src/DynamicData/Kernel/EnumeratorIList.cs b/src/DynamicData/Kernel/EnumeratorIList.cs index 237ad591e..b913693b4 100644 --- a/src/DynamicData/Kernel/EnumeratorIList.cs +++ b/src/DynamicData/Kernel/EnumeratorIList.cs @@ -1,20 +1,40 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections; - // Lifted from here https://github.com/benaadams/Ben.Enumerable. Many thanks to the genius of the man. +#if REACTIVE_SHIM +namespace DynamicData.Reactive.Kernel; +#else namespace DynamicData.Kernel; +#endif +/// +/// Represents the EnumeratorIList value. +/// +/// The type of the T value. +/// The list value. internal struct EnumeratorIList(IList list) : IEnumerator { + /// + /// The _index field. + /// private int _index = -1; + /// + /// Gets the Current value. + /// public readonly T Current => list[_index]; + /// + /// Gets the Current value. + /// readonly object? IEnumerator.Current => Current; + /// + /// Executes the MoveNext operation. + /// + /// The result of the operation. public bool MoveNext() { _index++; @@ -22,9 +42,15 @@ public bool MoveNext() return _index < list.Count; } + /// + /// Executes the Dispose operation. + /// public void Dispose() { } + /// + /// Executes the Reset operation. + /// public void Reset() => _index = -1; } diff --git a/src/DynamicData/Kernel/Error.cs b/src/DynamicData/Kernel/Error.cs index 05d61f4a4..0b78080b4 100644 --- a/src/DynamicData/Kernel/Error.cs +++ b/src/DynamicData/Kernel/Error.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Kernel; +#else namespace DynamicData.Kernel; +#endif /// /// An error container used to report errors from within dynamic data operators. @@ -15,7 +20,7 @@ namespace DynamicData.Kernel; /// The exception that caused the error. /// The value for the error. /// The key for the error. -[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "By Design.")] +[SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "By Design.")] public sealed class Error(Exception? exception, TObject value, TKey key) : IKeyValue, IEquatable> where TKey : notnull { @@ -34,11 +39,25 @@ public sealed class Error(Exception? exception, TObject value, TK /// public TObject Value { get; } = value; + /// + /// Determines whether two instances are equal. + /// + /// The left error to compare. + /// The right error to compare. + /// when the values are equal; otherwise, . public static bool operator ==(Error left, Error right) => Equals(left, right); + /// + /// Determines whether two instances are not equal. + /// + /// The left error to compare. + /// The right error to compare. + /// when the values are not equal; otherwise, . public static bool operator !=(Error left, Error right) => !Equals(left, right); /// + /// The other value. + /// The result of the operation. public bool Equals(Error? other) { if (other is null) @@ -55,6 +74,8 @@ public bool Equals(Error? other) } /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) { if (obj is null) @@ -71,6 +92,7 @@ public override bool Equals(object? obj) } /// + /// The result of the operation. public override int GetHashCode() { unchecked @@ -83,5 +105,6 @@ public override int GetHashCode() } /// + /// The result of the operation. public override string ToString() => $"Key: {Key}, Value: {Value}, Exception: {Exception}"; } diff --git a/src/DynamicData/Kernel/IEnumerableIList.cs b/src/DynamicData/Kernel/IEnumerableIList.cs index 34511c156..170376afa 100644 --- a/src/DynamicData/Kernel/IEnumerableIList.cs +++ b/src/DynamicData/Kernel/IEnumerableIList.cs @@ -1,9 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // Lifted from here https://github.com/benaadams/Ben.Enumerable. Many thanks to the genius of the man. +#if REACTIVE_SHIM +namespace DynamicData.Reactive.Kernel; +#else namespace DynamicData.Kernel; +#endif /// /// A enumerable that also contains the enumerable list. diff --git a/src/DynamicData/Kernel/ISupportsCapacity.cs b/src/DynamicData/Kernel/ISupportsCapacity.cs index ce0bfa83a..68e448f0a 100644 --- a/src/DynamicData/Kernel/ISupportsCapacity.cs +++ b/src/DynamicData/Kernel/ISupportsCapacity.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Kernel; +#else namespace DynamicData.Kernel; +#endif /// /// A collection type that supports a capacity. diff --git a/src/DynamicData/Kernel/InternalEx.cs b/src/DynamicData/Kernel/InternalEx.cs index 68e6fa47d..276264a58 100644 --- a/src/DynamicData/Kernel/InternalEx.cs +++ b/src/DynamicData/Kernel/InternalEx.cs @@ -1,24 +1,24 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Linq; -using System.Reactive.Subjects; +namespace DynamicData.Reactive.Kernel; +#else namespace DynamicData.Kernel; +#endif /// /// Extensions associated with times and intervals. /// public static class InternalEx { -#if NET9_0_OR_GREATER + /// + /// Executes the NewLock operation. + /// + /// The result of the operation. internal static Lock NewLock() => new(); -#else - internal static object NewLock() => new(); -#endif /// /// Retries the with back off. @@ -35,6 +35,9 @@ public static class InternalEx public static IObservable RetryWithBackOff(this IObservable source, Func backOffStrategy) where TException : Exception { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(backOffStrategy); + IObservable Retry(int failureCount) => source.Catch( error => @@ -62,13 +65,25 @@ IObservable Retry(int failureCount) => /// The interval. /// The action. /// A disposable that will stop the schedule. - public static IDisposable ScheduleRecurringAction(this IScheduler scheduler, TimeSpan interval, Action action) => scheduler.Schedule( - interval, - scheduleNext => - { - action(); - scheduleNext(interval); - }); + public static IDisposable ScheduleRecurringAction(this IScheduler scheduler, TimeSpan interval, Action action) + { + ArgumentExceptionHelper.ThrowIfNull(scheduler); + ArgumentExceptionHelper.ThrowIfNull(action); + + var disposable = new SerialDisposable(); + + void ScheduleNext() => + disposable.Disposable = scheduler.Schedule( + interval, + () => + { + action(); + ScheduleNext(); + }); + + ScheduleNext(); + return disposable; + } /// /// Schedules a recurring action. @@ -84,32 +99,58 @@ public static IDisposable ScheduleRecurringAction(this IScheduler scheduler, Tim /// A disposable that will stop the schedule. public static IDisposable ScheduleRecurringAction(this IScheduler scheduler, Func interval, Action action) { - interval.ThrowArgumentNullExceptionIfNull(nameof(interval)); - - return scheduler.Schedule( - interval(), - scheduleNext => - { - action(); - var next = interval(); - scheduleNext(next); - }); + ArgumentExceptionHelper.ThrowIfNull(interval); + ArgumentExceptionHelper.ThrowIfNull(scheduler); + ArgumentExceptionHelper.ThrowIfNull(action); + + var disposable = new SerialDisposable(); + + void ScheduleNext() => + disposable.Disposable = scheduler.Schedule( + interval(), + () => + { + action(); + ScheduleNext(); + }); + + ScheduleNext(); + return disposable; } - internal static void OnNext(this ISubject source) => source.OnNext(Unit.Default); + /// + /// Executes the OnNext operation. + /// + /// The source value. + internal static void OnNext(this ISignal source) => source.OnNext(Unit.Default); + /// + /// Executes the Swap operation. + /// + /// The type of the TSwap value. + /// The t1 value. + /// The t2 value. internal static void Swap(ref TSwap t1, ref TSwap t2) => (t2, t1) = (t1, t2); + /// + /// Executes the ToUnit operation. + /// + /// The type of the T value. + /// The source value. + /// The result of the operation. internal static IObservable ToUnit(this IObservable source) => source.Select(_ => Unit.Default); /// /// Observable.Return without the memory leak. /// + /// The type of the T value. + /// The source value. + /// The result of the operation. internal static IObservable Return(Func source) => Observable.Create(o => { o.OnNext(source()); o.OnCompleted(); - return () => { }; + return Disposable.Empty; }); } diff --git a/src/DynamicData/Kernel/ItemWithIndex.cs b/src/DynamicData/Kernel/ItemWithIndex.cs index 2fe6f7588..46f94f049 100644 --- a/src/DynamicData/Kernel/ItemWithIndex.cs +++ b/src/DynamicData/Kernel/ItemWithIndex.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Kernel; +#else namespace DynamicData.Kernel; +#endif /// /// Container for an item and it's index from a list. @@ -12,50 +17,15 @@ namespace DynamicData.Kernel; /// Initializes a new instance of the struct. /// Initializes a new instance of the class. /// -/// The item. -/// The index. -public readonly struct ItemWithIndex(T item, int index) : IEquatable> +/// The item. +/// The index. +public readonly record struct ItemWithIndex(T Item, int Index) { - /// - /// Gets the item. - /// - public T Item { get; } = item; - - /// - /// Gets the index. - /// - public int Index { get; } = index; - - /// Returns a value that indicates whether the values of two objects are equal. - /// The first value to compare. - /// The second value to compare. - /// true if the and parameters have the same value; otherwise, false. - public static bool operator ==(in ItemWithIndex left, in ItemWithIndex right) => left.Equals(right); - - /// Returns a value that indicates whether two objects have different values. - /// The first value to compare. - /// The second value to compare. - /// true if and are not equal; otherwise, false. - public static bool operator !=(in ItemWithIndex left, in ItemWithIndex right) => !left.Equals(right); - - /// - public bool Equals(ItemWithIndex other) => EqualityComparer.Default.Equals(Item, other.Item); - - /// - public override bool Equals(object? obj) - { - if (obj is null) - { - return false; - } - - return obj is ItemWithIndex itemWithIndex && Equals(itemWithIndex); - } - /// Returns the hash code for this instance. /// A 32-bit signed integer that is the hash code for this instance. public override int GetHashCode() => Item is null ? 0 : EqualityComparer.Default.GetHashCode(Item); /// + /// The result of the operation. public override string ToString() => $"{Item} ({Index})"; } diff --git a/src/DynamicData/Kernel/ItemWithValue.cs b/src/DynamicData/Kernel/ItemWithValue.cs index 16d22182b..957547bf4 100644 --- a/src/DynamicData/Kernel/ItemWithValue.cs +++ b/src/DynamicData/Kernel/ItemWithValue.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Kernel; +#else namespace DynamicData.Kernel; +#endif /// /// Container for an item and it's Value from a list. @@ -13,55 +18,12 @@ namespace DynamicData.Kernel; /// Initializes a new instance of the struct. /// Initializes a new instance of the class. /// -/// The item. -/// The Value. -public readonly struct ItemWithValue(TObject item, TValue value) : IEquatable> +/// The item. +/// The Value. +public readonly record struct ItemWithValue(TObject Item, TValue Value) { - /// - /// Gets the item. - /// - public TObject Item { get; } = item; - - /// - /// Gets the Value. - /// - public TValue Value { get; } = value; - - /// - /// Implements the operator ==. - /// - /// The left. - /// The right. - /// - /// The result of the operator. - /// - public static bool operator ==(in ItemWithValue left, in ItemWithValue right) => Equals(left, right); - - /// - /// Implements the operator !=. - /// - /// The left. - /// The right. - /// - /// The result of the operator. - /// - public static bool operator !=(in ItemWithValue left, in ItemWithValue right) => !Equals(left, right); - - /// - public bool Equals(ItemWithValue other) => EqualityComparer.Default.Equals(Item, other.Item) && EqualityComparer.Default.Equals(Value, other.Value); - - /// - public override bool Equals(object? obj) - { - if (obj is null) - { - return false; - } - - return obj is ItemWithValue itemWithValue && Equals(itemWithValue); - } - /// + /// The result of the operation. public override int GetHashCode() { unchecked @@ -71,5 +33,6 @@ public override int GetHashCode() } /// + /// The result of the operation. public override string ToString() => $"{Item} ({Value})"; } diff --git a/src/DynamicData/Kernel/OptionElse.cs b/src/DynamicData/Kernel/OptionElse.cs index d644e62de..57af263d4 100644 --- a/src/DynamicData/Kernel/OptionElse.cs +++ b/src/DynamicData/Kernel/OptionElse.cs @@ -1,18 +1,33 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Kernel; +#else namespace DynamicData.Kernel; +#endif /// /// Continuation container used for the else operator on an option object. /// public sealed class OptionElse { + /// + /// The NoAction field. + /// internal static readonly OptionElse NoAction = new(false); + /// + /// The _shouldRunAction field. + /// private readonly bool _shouldRunAction; + /// + /// Initializes a new instance of the class. + /// + /// The shouldRunAction value. internal OptionElse(bool shouldRunAction = true) => _shouldRunAction = shouldRunAction; /// @@ -22,7 +37,7 @@ public sealed class OptionElse /// action. public void Else(Action action) { - action.ThrowArgumentNullExceptionIfNull(nameof(action)); + ArgumentExceptionHelper.ThrowIfNull(action); if (_shouldRunAction) { diff --git a/src/DynamicData/Kernel/OptionExtensions.cs b/src/DynamicData/Kernel/OptionExtensions.cs index 0c6c982cc..b7dfcbee4 100644 --- a/src/DynamicData/Kernel/OptionExtensions.cs +++ b/src/DynamicData/Kernel/OptionExtensions.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Kernel; +#else namespace DynamicData.Kernel; +#endif /// /// Extensions for optional. @@ -18,13 +23,13 @@ public static class OptionExtensions /// The converter. /// The converted value. /// converter. - public static Optional Convert(this in Optional source, Func converter) + public static ReactiveUI.Primitives.Optional Convert(this in ReactiveUI.Primitives.Optional source, Func converter) where TSource : notnull where TDestination : notnull { - converter.ThrowArgumentNullExceptionIfNull(nameof(converter)); + ArgumentExceptionHelper.ThrowIfNull(converter); - return source.HasValue ? converter(source.Value) : Optional.None(); + return source.HasValue ? converter(source.Value) : ReactiveUI.Primitives.Optional.None; } /// @@ -36,13 +41,13 @@ public static Optional Convert(this in Opti /// The converter that returns an optional value. /// The converted value. /// converter. - public static Optional Convert(this in Optional source, Func> converter) + public static ReactiveUI.Primitives.Optional Convert(this in ReactiveUI.Primitives.Optional source, Func> converter) where TSource : notnull where TDestination : notnull { - converter.ThrowArgumentNullExceptionIfNull(nameof(converter)); + ArgumentExceptionHelper.ThrowIfNull(converter); - return source.HasValue ? converter(source.Value) : Optional.None(); + return source.HasValue ? converter(source.Value) : ReactiveUI.Primitives.Optional.None; } /// @@ -59,11 +64,11 @@ public static Optional Convert(this in Opti /// or /// fallbackConverter. /// - public static TDestination? ConvertOr(this in Optional source, Func converter, Func fallbackConverter) + public static TDestination? ConvertOr(this in ReactiveUI.Primitives.Optional source, Func converter, Func fallbackConverter) where TSource : notnull { - converter.ThrowArgumentNullExceptionIfNull(nameof(converter)); - fallbackConverter.ThrowArgumentNullExceptionIfNull(nameof(fallbackConverter)); + ArgumentExceptionHelper.ThrowIfNull(converter); + ArgumentExceptionHelper.ThrowIfNull(fallbackConverter); return source.HasValue ? converter(source.Value) : fallbackConverter(); } @@ -80,16 +85,16 @@ public static Optional Convert(this in Opti /// or /// fallbackOperation. /// - public static Optional OrElse(this in Optional source, Func> fallbackOperation) + public static ReactiveUI.Primitives.Optional OrElse(this in ReactiveUI.Primitives.Optional source, Func> fallbackOperation) where T : notnull { - fallbackOperation.ThrowArgumentNullExceptionIfNull(nameof(fallbackOperation)); + ArgumentExceptionHelper.ThrowIfNull(fallbackOperation); return source.HasValue ? source : fallbackOperation(); } /// - /// Overloads Enumerable.FirstOrDefault() and wraps the result in a Optional + /// Overloads Enumerable.FirstOrDefault() and wraps the result in a ReactiveUI.Primitives.Optional /// &gt;T /// container. /// @@ -97,18 +102,18 @@ public static Optional OrElse(this in Optional source, Func /// The source. /// The selector. /// The first value or none. - public static Optional FirstOrOptional(this IEnumerable source, Func selector) + public static ReactiveUI.Primitives.Optional FirstOrOptional(this IEnumerable source, Func selector) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - selector.ThrowArgumentNullExceptionIfNull(nameof(selector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(selector); foreach (var item in source.Where(item => selector(item))) { - return Optional.Create(item); + return ReactiveUI.Primitives.Optional.Create(item); } - return Optional.None(); + return ReactiveUI.Primitives.Optional.None; } /// @@ -118,7 +123,7 @@ public static Optional FirstOrOptional(this IEnumerable source, FuncThe source. /// The action. /// The optional else extension. - public static OptionElse IfHasValue(this in Optional source, Action action) + public static OptionElse IfHasValue(this in ReactiveUI.Primitives.Optional source, Action action) where T : notnull { if (!source.HasValue || source.Value is null) @@ -126,7 +131,7 @@ public static OptionElse IfHasValue(this in Optional source, Action act return new OptionElse(); } - action.ThrowArgumentNullExceptionIfNull(nameof(action)); + ArgumentExceptionHelper.ThrowIfNull(action); action(source.Value); return OptionElse.NoAction; @@ -139,7 +144,7 @@ public static OptionElse IfHasValue(this in Optional source, Action act /// The source. /// The action. /// The optional else extension. - public static OptionElse IfHasValue(this Optional? source, Action action) + public static OptionElse IfHasValue(this ReactiveUI.Primitives.Optional? source, Action action) where T : notnull { if (!source.HasValue) @@ -152,14 +157,14 @@ public static OptionElse IfHasValue(this Optional? source, Action actio return new OptionElse(); } - action.ThrowArgumentNullExceptionIfNull(nameof(action)); + ArgumentExceptionHelper.ThrowIfNull(action); action(source.Value.Value); return OptionElse.NoAction; } /// - /// Overloads a TryGetValue of the dictionary wrapping the result as an Optional. + /// Overloads a TryGetValue of the dictionary wrapping the result as an ReactiveUI.Primitives.Optional. /// &gt;TValue /// /// @@ -168,13 +173,13 @@ public static OptionElse IfHasValue(this Optional? source, Action actio /// The source. /// The key. /// The option of the looked up value. - public static Optional Lookup(this IDictionary source, TKey key) + public static ReactiveUI.Primitives.Optional Lookup(this IDictionary source, TKey key) where TValue : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); var result = source.TryGetValue(key, out var contained); - return result ? contained : Optional.None(); + return result ? contained : ReactiveUI.Primitives.Optional.None; } /// @@ -187,7 +192,7 @@ public static Optional Lookup(this IDictionaryIf the item was removed. public static bool RemoveIfContained(this IDictionary source, TKey key) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.ContainsKey(key) && source.Remove(key); } @@ -199,7 +204,7 @@ public static bool RemoveIfContained(this IDictionaryThe type of item. /// The source. /// An enumerable of the selected items. - public static IEnumerable SelectValues(this IEnumerable> source) + public static IEnumerable SelectValues(this IEnumerable> source) where T : notnull => source.Where(t => t.HasValue && t.Value is not null).Select(t => t.Value!); /// @@ -220,10 +225,10 @@ public static T ValueOr(this T? source, T defaultValue) /// The value selector. /// If the value or a provided default. /// valueSelector. - public static T ValueOr(this in Optional source, Func valueSelector) + public static T ValueOr(this in ReactiveUI.Primitives.Optional source, Func valueSelector) where T : notnull { - valueSelector.ThrowArgumentNullExceptionIfNull(nameof(valueSelector)); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); return source.HasValue ? source.Value : valueSelector(); } @@ -234,7 +239,7 @@ public static T ValueOr(this in Optional source, Func valueSelector) /// The type of the item. /// The source. /// The value or default. - public static T? ValueOrDefault(this in Optional source) + public static T? ValueOrDefault(this in ReactiveUI.Primitives.Optional source) where T : notnull { if (source.HasValue) @@ -253,10 +258,10 @@ public static T ValueOr(this in Optional source, Func valueSelector) /// The exception generator. /// The value. /// exceptionGenerator. - public static T ValueOrThrow(this in Optional source, Func exceptionGenerator) + public static T ValueOrThrow(this in ReactiveUI.Primitives.Optional source, Func exceptionGenerator) where T : notnull { - exceptionGenerator.ThrowArgumentNullExceptionIfNull(nameof(exceptionGenerator)); + ArgumentExceptionHelper.ThrowIfNull(exceptionGenerator); if (source.HasValue && source.Value is not null) { diff --git a/src/DynamicData/Kernel/OptionObservableExtensions.cs b/src/DynamicData/Kernel/OptionObservableExtensions.cs index bbb23a052..42c98faf7 100644 --- a/src/DynamicData/Kernel/OptionObservableExtensions.cs +++ b/src/DynamicData/Kernel/OptionObservableExtensions.cs @@ -1,10 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.Kernel; +#else namespace DynamicData.Kernel; +#endif /// /// Extensions for optional. @@ -21,20 +24,20 @@ public static class OptionObservableExtensions /// The converter. /// Observable Optional of . /// Source or Converter was null. - /// Observable version of . - public static IObservable> Convert(this IObservable> source, Func converter) + /// Observable version of OptionExtensions.Convert<TSource, TDestination>(in Optional<TSource>, Func<TSource, TDestination>). + public static IObservable> Convert(this IObservable> source, Func converter) where TSource : notnull where TDestination : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - converter.ThrowArgumentNullExceptionIfNull(nameof(converter)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(converter); - return source.Select(optional => optional.HasValue ? converter(optional.Value) : Optional.None()); + return source.Select(optional => optional.HasValue ? converter(optional.Value) : ReactiveUI.Primitives.Optional.None); } /// - /// Overload of that allows the conversion - /// operation to also return an Optional. + /// Overload of Convert<TSource, TDestination>(IObservable<Optional<TSource>>, Func<TSource, TDestination>) that allows the conversion + /// operation to also return an ReactiveUI.Primitives.Optional. /// /// The type of the source. /// The type of the destination. @@ -42,15 +45,15 @@ public static IObservable> Convert /// The converter that returns an optional value. /// Observable Optional of . /// Source or Converter was null. - /// Observable version of . - public static IObservable> Convert(this IObservable> source, Func> converter) + /// Observable version of OptionExtensions.Convert<TSource, TDestination>(in Optional<TSource>, Func<TSource, Optional<TDestination>>). + public static IObservable> Convert(this IObservable> source, Func> converter) where TSource : notnull where TDestination : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - converter.ThrowArgumentNullExceptionIfNull(nameof(converter)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(converter); - return source.Select(optional => optional.HasValue ? converter(optional.Value) : Optional.None()); + return source.Select(optional => optional.HasValue ? converter(optional.Value) : ReactiveUI.Primitives.Optional.None); } /// @@ -70,12 +73,12 @@ public static IObservable> Convert /// or /// fallbackConverter. /// - public static IObservable ConvertOr(this IObservable> source, Func converter, Func fallbackConverter) + public static IObservable ConvertOr(this IObservable> source, Func converter, Func fallbackConverter) where TSource : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - converter.ThrowArgumentNullExceptionIfNull(nameof(converter)); - fallbackConverter.ThrowArgumentNullExceptionIfNull(nameof(fallbackConverter)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(converter); + ArgumentExceptionHelper.ThrowIfNull(fallbackConverter); return source.Select(optional => optional.HasValue ? converter(optional.Value) : fallbackConverter()); } @@ -92,12 +95,12 @@ public static IObservable> Convert /// or /// fallbackOperation. /// - /// Observable version of . - public static IObservable> OrElse(this IObservable> source, Func> fallbackOperation) + /// Observable version of OptionExtensions.OrElse<T>(in Optional<T>, Func<Optional<T>>). + public static IObservable> OrElse(this IObservable> source, Func> fallbackOperation) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - fallbackOperation.ThrowArgumentNullExceptionIfNull(nameof(fallbackOperation)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(fallbackOperation); return source.Select(optional => optional.HasValue ? optional : fallbackOperation()); } @@ -109,13 +112,13 @@ public static IObservable> OrElse(this IObservable> s /// The source. /// The action. /// Optional alternative action for the Else case. - /// The same Observable Optional. - /// Observable version of . - public static IObservable> OnHasValue(this IObservable> source, Action action, Action? elseAction = null) + /// The same Observable ReactiveUI.Primitives.Optional. + /// Observable version of OptionExtensions.IfHasValue<T>(in Optional<T>, Action<T>). + public static IObservable> OnHasValue(this IObservable> source, Action action, Action? elseAction = null) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - action.ThrowArgumentNullExceptionIfNull(nameof(action)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(action); return source.Do(optional => optional.IfHasValue(action).Else(() => elseAction?.Invoke())); } @@ -127,29 +130,34 @@ public static IObservable> OnHasValue(this IObservableThe source. /// The action. /// Optional alternative action for the Else case. - /// The same Observable Optional. - public static IObservable> OnHasNoValue(this IObservable> source, Action action, Action? elseAction = null) + /// The same Observable ReactiveUI.Primitives.Optional. + public static IObservable> OnHasNoValue(this IObservable> source, Action action, Action? elseAction = null) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - action.ThrowArgumentNullExceptionIfNull(nameof(action)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(action); return source.Do(optional => optional.IfHasValue(val => elseAction?.Invoke(val)).Else(action)); } /// - /// Converts an Observable of into an IObservable of by extracting + /// Converts an Observable of Optional<T> into an IObservable of by extracting /// the values from Optionals that have one. /// /// The type of item. /// The source. /// An Observable with the Values. - /// Observable version of . - public static IObservable SelectValues(this IObservable> source) - where T : notnull => source.Where(t => t.HasValue && t.Value is not null).Select(t => t.Value!); + /// Observable version of OptionExtensions.SelectValues<T>(IEnumerable<Optional<T>>). + public static IObservable SelectValues(this IObservable> source) + where T : notnull + { + ArgumentExceptionHelper.ThrowIfNull(source); + + return source.Where(t => t.HasValue && t.Value is not null).Select(t => t.Value!); + } /// - /// Converts an Observable of into an IObservable of by extracting the + /// Converts an Observable of Optional<T> into an IObservable of by extracting the /// values from the ones that contain a value and then using to generate a value for the others. /// /// The type of the item. @@ -157,11 +165,12 @@ public static IObservable SelectValues(this IObservable> sourc /// The value selector. /// If the value or a provided default. /// valueSelector. - /// Observable version of . - public static IObservable ValueOr(this IObservable> source, Func valueSelector) + /// Observable version of OptionExtensions.ValueOr<T>(in Optional<T>, Func<T>). + public static IObservable ValueOr(this IObservable> source, Func valueSelector) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); return source.Select(optional => optional.HasValue ? optional.Value : valueSelector()); } @@ -172,17 +181,17 @@ public static IObservable ValueOr(this IObservable> source, Fu /// The type of the item. /// The source. /// The value or default. - /// Observable version of . - public static IObservable ValueOrDefault(this IObservable> source) + /// Observable version of OptionExtensions.ValueOrDefault<T>(in Optional<T>). + public static IObservable ValueOrDefault(this IObservable> source) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Select(optional => optional.ValueOrDefault()); } /// - /// Converts an Observable of into an IObservable of by extracting the values. + /// Converts an Observable of Optional<T> into an IObservable of by extracting the values. /// If it has no value, is used to generate an exception that is injected into the stream as error. /// /// The type of the item. @@ -190,13 +199,12 @@ public static IObservable ValueOr(this IObservable> source, Fu /// The exception generator. /// The value. /// exceptionGenerator. - /// Observable version of . - public static IObservable ValueOrThrow(this IObservable> source, Func exceptionGenerator) + /// Observable version of OptionExtensions.ValueOrThrow<T>(in Optional<T>, Func<Exception>). + public static IObservable ValueOrThrow(this IObservable> source, Func exceptionGenerator) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - - exceptionGenerator.ThrowArgumentNullExceptionIfNull(nameof(exceptionGenerator)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(exceptionGenerator); return Observable.Create(observer => source.Subscribe( diff --git a/src/DynamicData/Kernel/Optional.cs b/src/DynamicData/Kernel/Optional.cs deleted file mode 100644 index 0dbbd567f..000000000 --- a/src/DynamicData/Kernel/Optional.cs +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. -// 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.Diagnostics.CodeAnalysis; - -namespace DynamicData.Kernel; - -/// -/// The equivalent of a nullable type which works on value and reference types. -/// -/// The underlying value type of the generic type.1. -[SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "Deliberate usage.")] -[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Class names the same, generic differences.")] -public readonly struct Optional : IEquatable> - where T : notnull -{ - private readonly T? _value; - - /// - /// Initializes a new instance of the struct. - /// - /// The value. - internal Optional(T? value) - { - if (value is null) - { - HasValue = false; - _value = default; - } - else - { - _value = value; - HasValue = true; - } - } - - /// - /// Gets the default valueless optional. - /// - public static Optional None { get; } - - /// - /// Gets a value indicating whether the current object has a value. - /// - /// - /// - /// true if the current object has a value; false if the current object has no value. - /// - public bool HasValue { get; } - - /// - /// Gets the value of the current value. - /// - /// - /// - /// The value of the current object if the property is true. An exception is thrown if the property is false. - /// - /// The property is false. - public T Value - { - get - { - if (!HasValue || _value is null) - { - throw new InvalidOperationException("Optional has no value"); - } - - return _value; - } - } - - /// - /// Implicit cast from the vale to the optional. - /// - /// The value. - /// The optional value. - public static implicit operator Optional(T? value) => ToOptional(value); - - /// - /// Explicit cast from option to value. - /// - /// The value. - /// The optional value. - public static explicit operator T?(in Optional value) => FromOptional(value); - - public static bool operator ==(in Optional left, in Optional right) => left.Equals(right); - - public static bool operator !=(in Optional left, in Optional right) => !left.Equals(right); - - /// - /// Creates the specified value. - /// - /// The value. - /// The optional value. - public static Optional Create(T? value) => new(value); - - /// - /// Gets the value from the optional value. - /// - /// The optional value. - /// The value. - public static T? FromOptional(in Optional value) => value.Value; - - /// - /// Gets the optional from a value. - /// - /// The value to get the optional for. - /// The optional. - public static Optional ToOptional(T? value) => new(value); - - /// - public bool Equals(Optional other) - { - if (!HasValue) - { - return !other.HasValue; - } - - if (!other.HasValue) - { - return false; - } - - if (_value is null && other._value is null) - { - return true; - } - - if (_value is null || other._value is null) - { - return false; - } - - return HasValue.Equals(other.HasValue) && EqualityComparer.Default.Equals(_value, other._value); - } - - /// - public override bool Equals(object? obj) - { - if (obj is null) - { - return false; - } - - return obj is Optional optional && Equals(optional); - } - - /// - public override int GetHashCode() - { - unchecked - { - if (_value is null) - { - return 0; - } - - return (HasValue.GetHashCode() * 397) ^ EqualityComparer.Default.GetHashCode(_value); - } - } - - /// - public override string? ToString() - { - if (_value is null) - { - return ""; - } - - return !HasValue ? "" : _value.ToString(); - } -} - -/// -/// Optional factory class. -/// -[SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "By Design.")] -public static class Optional -{ - /// - /// Returns an None optional value for the specified type. - /// - /// The type of the item. - /// The optional value. - public static Optional None() - where T : notnull - => Optional.None; - - /// - /// Wraps the specified value in an Optional container. - /// - /// The type of the item. - /// The value. - /// The optional value. - public static Optional Some(T? value) - where T : notnull - => new(value); -} diff --git a/src/DynamicData/Kernel/ParallelEx.cs b/src/DynamicData/Kernel/ParallelEx.cs index c5b1ff14d..0c7163e24 100644 --- a/src/DynamicData/Kernel/ParallelEx.cs +++ b/src/DynamicData/Kernel/ParallelEx.cs @@ -1,15 +1,32 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Kernel; +#else namespace DynamicData.Kernel; +#endif +/// +/// Provides members for the ParallelEx class. +/// internal static class ParallelEx { + /// + /// Executes the SelectParallel operation. + /// + /// The type of the TSource value. + /// The type of the TDestination value. + /// The source value. + /// The selector value. + /// The maximumThreads value. + /// The result of the operation. public static async Task> SelectParallel(this IEnumerable source, Func> selector, int maximumThreads = 5) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - selector.ThrowArgumentNullExceptionIfNull(nameof(selector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(selector); var semaphore = new SemaphoreSlim(maximumThreads); var tasks = new List>(); diff --git a/src/DynamicData/Kernel/ReadOnlyCollectionLight.cs b/src/DynamicData/Kernel/ReadOnlyCollectionLight.cs index 3f621f60b..886a3b039 100644 --- a/src/DynamicData/Kernel/ReadOnlyCollectionLight.cs +++ b/src/DynamicData/Kernel/ReadOnlyCollectionLight.cs @@ -1,28 +1,59 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Collections; +namespace DynamicData.Reactive.Kernel; +#else namespace DynamicData.Kernel; +#endif +/// +/// Provides members for the ReadOnlyCollectionLight class. +/// +/// The type of the T value. internal sealed class ReadOnlyCollectionLight : IReadOnlyCollection { + /// + /// The _items field. + /// private readonly IList _items; + /// + /// Initializes a new instance of the class. + /// + /// The items value. public ReadOnlyCollectionLight(IEnumerable items) { _items = items.ToList(); Count = _items.Count; } + /// + /// Initializes a new instance of the class. + /// private ReadOnlyCollectionLight() => _items = new List(); + /// + /// Gets the Empty value. + /// public static IReadOnlyCollection Empty { get; } = new ReadOnlyCollectionLight(); + /// + /// Gets the Count value. + /// public int Count { get; } + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. public IEnumerator GetEnumerator() => _items.GetEnumerator(); + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/DynamicData/Kernel/ReferenceEqualityComparer.cs b/src/DynamicData/Kernel/ReferenceEqualityComparer.cs index 962be384f..f02477e44 100644 --- a/src/DynamicData/Kernel/ReferenceEqualityComparer.cs +++ b/src/DynamicData/Kernel/ReferenceEqualityComparer.cs @@ -1,14 +1,37 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.Kernel; +#else namespace DynamicData.Kernel; +#endif +/// +/// Provides members for the ReferenceEqualityComparer class. +/// +/// The type of the T value. internal sealed class ReferenceEqualityComparer : IEqualityComparer { + /// + /// The Instance field. + /// public static readonly IEqualityComparer Instance = new ReferenceEqualityComparer(); + /// + /// Executes the Equals operation. + /// + /// The x value. + /// The y value. + /// The result of the operation. public bool Equals(T? x, T? y) => ReferenceEquals(x, y); + /// + /// Executes the GetHashCode operation. + /// + /// The obj value. + /// The result of the operation. public int GetHashCode(T? obj) => obj is null ? 0 : obj.GetHashCode(); } diff --git a/src/DynamicData/List/Change.cs b/src/DynamicData/List/Change.cs index 07e830ae6..43851fe06 100644 --- a/src/DynamicData/List/Change.cs +++ b/src/DynamicData/List/Change.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Container to describe a single change to a cache. @@ -19,7 +23,7 @@ public sealed class Change : IEquatable> /// The current. /// The index. public Change(ListChangeReason reason, T current, int index = -1) - : this(reason, current, Optional.None(), index) + : this(reason, current, ReactiveUI.Primitives.Optional.None, index) { } @@ -70,13 +74,13 @@ public Change(T current, int currentIndex, int previousIndex) } Reason = ListChangeReason.Moved; - Item = new ItemChange(Reason, current, Optional.None(), currentIndex, previousIndex); + Item = new ItemChange(Reason, current, ReactiveUI.Primitives.Optional.None, currentIndex, previousIndex); Range = RangeChange.Empty; } /// /// Initializes a new instance of the class. - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// /// The reason. /// The current. @@ -90,7 +94,7 @@ public Change(T current, int currentIndex, int previousIndex) /// or /// For , must supply an index. /// - public Change(ListChangeReason reason, T current, in Optional previous, int currentIndex = -1, int previousIndex = -1) + public Change(ListChangeReason reason, T current, in ReactiveUI.Primitives.Optional previous, int currentIndex = -1, int previousIndex = -1) { if (reason == ListChangeReason.Add && previous.HasValue) { @@ -135,11 +139,25 @@ public Change(ListChangeReason reason, T current, in Optional previous, int c /// public ChangeType Type => Reason.GetChangeType(); + /// + /// Determines whether two instances are equal. + /// + /// The left change to compare. + /// The right change to compare. + /// when the values are equal; otherwise, . public static bool operator ==(Change left, Change right) => Equals(left, right); + /// + /// Determines whether two instances are not equal. + /// + /// The left change to compare. + /// The right change to compare. + /// when the values are not equal; otherwise, . public static bool operator !=(Change left, Change right) => !Equals(left, right); /// + /// The other value. + /// The result of the operation. public bool Equals(Change? other) { if (other is null) @@ -156,6 +174,8 @@ public bool Equals(Change? other) } /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) { if (obj is null) @@ -177,6 +197,7 @@ public override bool Equals(object? obj) } /// + /// The result of the operation. public override int GetHashCode() { unchecked @@ -189,5 +210,6 @@ public override int GetHashCode() } /// + /// The result of the operation. public override string ToString() => $"{Reason}. {Range.Count} changes"; } diff --git a/src/DynamicData/List/ChangeAwareList.cs b/src/DynamicData/List/ChangeAwareList.cs index 90d8e3abc..1a4e3bd05 100644 --- a/src/DynamicData/List/ChangeAwareList.cs +++ b/src/DynamicData/List/ChangeAwareList.cs @@ -1,21 +1,31 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Collections; +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A list which captures all changes which are made to it. These changes are recorded until CaptureChanges() at which point the changes are cleared. /// Used for creating custom operators. /// /// The item type. -/// +/// IExtendedList<T> public class ChangeAwareList : IExtendedList where T : notnull { + /// + /// The _innerList field. + /// private readonly List _innerList; + + /// + /// The _changes field. + /// private ChangeSet _changes = []; /// @@ -33,7 +43,7 @@ public ChangeAwareList(int capacity = -1) => /// The items to seed the change aware list with. public ChangeAwareList(IEnumerable items) { - items.ThrowArgumentNullExceptionIfNull(nameof(items)); + ArgumentExceptionHelper.ThrowIfNull(items); var list = items.ToList(); @@ -53,7 +63,7 @@ public ChangeAwareList(IEnumerable items) /// Should the list of changes also be copied over?. public ChangeAwareList(ChangeAwareList list, bool copyChanges) { - list.ThrowArgumentNullExceptionIfNull(nameof(list)); + ArgumentExceptionHelper.ThrowIfNull(list); _innerList = new List(list._innerList); @@ -85,7 +95,7 @@ public int Capacity /// /// Gets the last change in the collection. /// - private Optional> Last => _changes.Count == 0 ? Optional.None>() : _changes[_changes.Count - 1]; + private ReactiveUI.Primitives.Optional> Last => _changes.Count == 0 ? ReactiveUI.Primitives.Optional>.None : _changes[_changes.Count - 1]; /// /// Gets or sets the item at the specified index. @@ -177,6 +187,7 @@ public virtual void Clear() public void CopyTo(T[] array, int arrayIndex) => _innerList.CopyTo(array, arrayIndex); /// + /// The result of the operation. public IEnumerator GetEnumerator() => _innerList.ToList().GetEnumerator(); /// @@ -215,12 +226,12 @@ public void Insert(int index, T item) } /// - /// Inserts the elements of a collection into the at the specified index. + /// Inserts the elements of a collection into the List<T> at the specified index. /// /// Inserts the specified items. /// The zero-based index at which the new elements should be inserted. /// is null. - /// is less than 0.-or- is greater than . + /// is less than 0.-or- is greater than List<T>.Count. public void InsertRange(IEnumerable collection, int index) { var args = new Change(ListChangeReason.AddRange, collection, index); @@ -389,9 +400,9 @@ public void RemoveAt(int index) } /// - /// Removes a range of elements from the . + /// Removes a range of elements from the List<T>. /// - /// The zero-based starting index of the range of elements to remove.The number of elements to remove. is less than 0.-or- is less than 0. and do not denote a valid range of elements in the . + /// The zero-based starting index of the range of elements to remove.The number of elements to remove. is less than 0.-or- is less than 0. and do not denote a valid range of elements in the List<T>. public void RemoveRange(int index, int count) { if (index >= _innerList.Count || index + count > _innerList.Count) @@ -414,6 +425,7 @@ public void RemoveRange(int index, int count) } /// + /// The result of the operation. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// diff --git a/src/DynamicData/List/ChangeAwareListWithRefCounts.cs b/src/DynamicData/List/ChangeAwareListWithRefCounts.cs index 234b07930..6b27b2448 100644 --- a/src/DynamicData/List/ChangeAwareListWithRefCounts.cs +++ b/src/DynamicData/List/ChangeAwareListWithRefCounts.cs @@ -1,35 +1,80 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else using DynamicData.List.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif +/// +/// Provides members for the ChangeAwareListWithRefCounts class. +/// +/// The type of the T value. internal sealed class ChangeAwareListWithRefCounts : ChangeAwareList where T : notnull { + /// + /// The _tracker field. + /// private readonly ReferenceCountTracker _tracker = new(); + /// + /// Executes the Clear operation. + /// public override void Clear() { _tracker.Clear(); base.Clear(); } + /// + /// Executes the Contains operation. + /// + /// The item value. + /// The result of the operation. public override bool Contains(T item) => _tracker.Contains(item); + /// + /// Executes the InsertItem operation. + /// + /// The index value. + /// The item value. protected override void InsertItem(int index, T item) { _tracker.Add(item); base.InsertItem(index, item); } + /// + /// Executes the OnInsertItems operation. + /// + /// The startIndex value. + /// The items value. protected override void OnInsertItems(int startIndex, IEnumerable items) => items.ForEach(t => _tracker.Add(t)); + /// + /// Executes the OnRemoveItems operation. + /// + /// The startIndex value. + /// The items value. protected override void OnRemoveItems(int startIndex, IEnumerable items) => items.ForEach(t => _tracker.Remove(t)); + /// + /// Executes the OnSetItem operation. + /// + /// The index value. + /// The newItem value. + /// The oldItem value. protected override void OnSetItem(int index, T newItem, T oldItem) { _tracker.Remove(oldItem); @@ -37,6 +82,11 @@ protected override void OnSetItem(int index, T newItem, T oldItem) base.OnSetItem(index, newItem, oldItem); } + /// + /// Executes the RemoveItem operation. + /// + /// The index value. + /// The item value. protected override void RemoveItem(int index, T item) { _tracker.Remove(item); diff --git a/src/DynamicData/List/ChangeSet.cs b/src/DynamicData/List/ChangeSet.cs index d1b18aa09..e50a25f24 100644 --- a/src/DynamicData/List/ChangeSet.cs +++ b/src/DynamicData/List/ChangeSet.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A set of changes which has occurred since the last reported change. @@ -25,7 +29,7 @@ public ChangeSet() } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The items. /// items. diff --git a/src/DynamicData/List/ChangeSetEx.cs b/src/DynamicData/List/ChangeSetEx.cs index 7abe7c5fa..c01f3839f 100644 --- a/src/DynamicData/List/ChangeSetEx.cs +++ b/src/DynamicData/List/ChangeSetEx.cs @@ -1,12 +1,25 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else using DynamicData.List.Internal; +#endif +#if REACTIVE_SHIM +using DynamicData.Reactive.List.Linq; +#else using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Change set extensions. @@ -23,7 +36,7 @@ public static class ChangeSetEx public static IEnumerable> Flatten(this IChangeSet source) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new ItemChangeEnumerator(source); } @@ -57,8 +70,8 @@ public static IChangeSet Transform(this ICh where TSource : notnull where TDestination : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformer.ThrowArgumentNullExceptionIfNull(nameof(transformer)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformer); var changes = source.Select( change => @@ -83,7 +96,7 @@ public static IChangeSet Transform(this ICh public static IEnumerable> YieldWithoutIndex(this IEnumerable> source) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new WithoutIndexEnumerator(source); } @@ -98,7 +111,7 @@ public static IEnumerable> YieldWithoutIndex(this IEnumerable> Unified(this IChangeSet source) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new UnifiedChangeEnumerator(source); } diff --git a/src/DynamicData/List/ChangeType.cs b/src/DynamicData/List/ChangeType.cs index 481e7c1bf..cf91a311e 100644 --- a/src/DynamicData/List/ChangeType.cs +++ b/src/DynamicData/List/ChangeType.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Description of the type of change. diff --git a/src/DynamicData/List/IChangeSet.cs b/src/DynamicData/List/IChangeSet.cs index 36434a08d..f4dd92d5b 100644 --- a/src/DynamicData/List/IChangeSet.cs +++ b/src/DynamicData/List/IChangeSet.cs @@ -1,9 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A collection of changes. diff --git a/src/DynamicData/List/IChangeSetAdaptor.cs b/src/DynamicData/List/IChangeSetAdaptor.cs index db579cb12..79e579d01 100644 --- a/src/DynamicData/List/IChangeSetAdaptor.cs +++ b/src/DynamicData/List/IChangeSetAdaptor.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A simple adaptor to inject side effects into a change set observable. diff --git a/src/DynamicData/List/IExtendedList.cs b/src/DynamicData/List/IExtendedList.cs index 8afb9e5df..c1e2b8733 100644 --- a/src/DynamicData/List/IExtendedList.cs +++ b/src/DynamicData/List/IExtendedList.cs @@ -1,9 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Represents a list which supports range operations. @@ -19,12 +23,12 @@ public interface IExtendedList : IList void AddRange(IEnumerable collection); /// - /// Inserts the elements of a collection into the at the specified index. + /// Inserts the elements of a collection into the List<T> at the specified index. /// /// The items to insert. /// The zero-based index at which the new elements should be inserted. /// is null. - /// is less than 0.-or- is greater than . + /// is less than 0.-or- is greater than List<T>.Count. void InsertRange(IEnumerable collection, int index); /// @@ -35,8 +39,8 @@ public interface IExtendedList : IList void Move(int original, int destination); /// - /// Removes a range of elements from the . + /// Removes a range of elements from the List<T>. /// - /// The zero-based starting index of the range of elements to remove.The number of elements to remove. is less than 0.-or- is less than 0. and do not denote a valid range of elements in the . + /// The zero-based starting index of the range of elements to remove.The number of elements to remove. is less than 0.-or- is less than 0. and do not denote a valid range of elements in the List<T>. void RemoveRange(int index, int count); } diff --git a/src/DynamicData/List/IGroup.cs b/src/DynamicData/List/IGroup.cs index c8f4e09e9..60e28cb3b 100644 --- a/src/DynamicData/List/IGroup.cs +++ b/src/DynamicData/List/IGroup.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A grouping of observable lists. diff --git a/src/DynamicData/List/IGrouping.cs b/src/DynamicData/List/IGrouping.cs index 30af65d59..2568fb638 100644 --- a/src/DynamicData/List/IGrouping.cs +++ b/src/DynamicData/List/IGrouping.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List; +#else namespace DynamicData.List; +#endif /// /// Represents a group which provides an update after any value within the group changes. diff --git a/src/DynamicData/List/IObservableList.cs b/src/DynamicData/List/IObservableList.cs index 0935c0df0..f2b54db40 100644 --- a/src/DynamicData/List/IObservableList.cs +++ b/src/DynamicData/List/IObservableList.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// A readonly observable list, providing observable methods diff --git a/src/DynamicData/List/IPageChangeSet.cs b/src/DynamicData/List/IPageChangeSet.cs index 447612a76..b64f1c1ee 100644 --- a/src/DynamicData/List/IPageChangeSet.cs +++ b/src/DynamicData/List/IPageChangeSet.cs @@ -1,11 +1,20 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Operators; +#else using DynamicData.Operators; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Represents a paged subset of data reduced by a defined set of parameters diff --git a/src/DynamicData/List/ISourceList.cs b/src/DynamicData/List/ISourceList.cs index d75ec1042..981f279cd 100644 --- a/src/DynamicData/List/ISourceList.cs +++ b/src/DynamicData/List/ISourceList.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// An editable observable list, providing observable methods diff --git a/src/DynamicData/List/IVirtualChangeSet.cs b/src/DynamicData/List/IVirtualChangeSet.cs index c971d7a3f..fd590316f 100644 --- a/src/DynamicData/List/IVirtualChangeSet.cs +++ b/src/DynamicData/List/IVirtualChangeSet.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Represents a subset of data reduced by a defined set of parameters diff --git a/src/DynamicData/List/Internal/AnonymousObservableList.cs b/src/DynamicData/List/Internal/AnonymousObservableList.cs index 79051b1d6..a28471ebb 100644 --- a/src/DynamicData/List/Internal/AnonymousObservableList.cs +++ b/src/DynamicData/List/Internal/AnonymousObservableList.cs @@ -3,41 +3,87 @@ // See the LICENSE file in the project root for full license information. using System.Diagnostics; -using System.Reactive.Disposables; +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the AnonymousObservableList class. +/// +/// The type of the T value. [DebuggerDisplay("AnonymousObservableList<{typeof(T).Name}> ({Count} Items)")] internal sealed class AnonymousObservableList : IObservableList where T : notnull { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed through _cleanUp")] + /// + /// The _sourceList field. + /// + [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed through _cleanUp")] private readonly ISourceList _sourceList; + + /// + /// The _cleanUp field. + /// private readonly IDisposable _cleanUp; + /// + /// Initializes a new instance of the class. + /// + /// The source value. public AnonymousObservableList(IObservable> source) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); _sourceList = new SourceList(source); _cleanUp = _sourceList; } + /// + /// Initializes a new instance of the class. + /// + /// The sourceList value. public AnonymousObservableList(ISourceList sourceList) { - _sourceList = sourceList ?? throw new ArgumentNullException(nameof(sourceList)); + ArgumentExceptionHelper.ThrowIfNull(sourceList); + _sourceList = sourceList; _cleanUp = Disposable.Empty; } + /// + /// Gets the Count value. + /// public int Count => _sourceList.Count; + /// + /// Gets the CountChanged value. + /// public IObservable CountChanged => _sourceList.CountChanged; + /// + /// Gets the Items value. + /// public IReadOnlyList Items => _sourceList.Items; + /// + /// Executes the Connect operation. + /// + /// The predicate value. + /// The result of the operation. public IObservable> Connect(Func? predicate = null) => _sourceList.Connect(predicate); + /// + /// Executes the Preview operation. + /// + /// The predicate value. + /// The result of the operation. public IObservable> Preview(Func? predicate = null) => _sourceList.Preview(predicate); + /// + /// Executes the Dispose operation. + /// public void Dispose() => _cleanUp.Dispose(); } diff --git a/src/DynamicData/List/Internal/AutoRefresh.cs b/src/DynamicData/List/Internal/AutoRefresh.cs index da2e4dc6a..9f3a20e3f 100644 --- a/src/DynamicData/List/Internal/AutoRefresh.cs +++ b/src/DynamicData/List/Internal/AutoRefresh.cs @@ -1,19 +1,40 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the AutoRefresh class. +/// +/// The type of the TObject value. +/// The type of the TAny value. +/// The source value. +/// The reEvaluator value. +/// The buffer value. +/// The scheduler value. internal sealed class AutoRefresh(IObservable> source, Func> reEvaluator, TimeSpan? buffer = null, IScheduler? scheduler = null) where TObject : notnull { + /// + /// The _reEvaluator field. + /// private readonly Func> _reEvaluator = reEvaluator ?? throw new ArgumentNullException(nameof(reEvaluator)); + + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { diff --git a/src/DynamicData/List/Internal/BufferIf.cs b/src/DynamicData/List/Internal/BufferIf.cs index 8f7d1993c..65f708195 100644 --- a/src/DynamicData/List/Internal/BufferIf.cs +++ b/src/DynamicData/List/Internal/BufferIf.cs @@ -1,25 +1,50 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; - +#endif + +/// +/// Provides members for the BufferIf class. +/// +/// The type of the T value. +/// The source value. +/// The pauseIfTrueSelector value. +/// The initialPauseState value. +/// The timeOut value. +/// The scheduler value. internal sealed class BufferIf(IObservable> source, IObservable pauseIfTrueSelector, bool initialPauseState = false, TimeSpan? timeOut = null, IScheduler? scheduler = null) where T : notnull { + /// + /// The _pauseIfTrueSelector field. + /// private readonly IObservable _pauseIfTrueSelector = pauseIfTrueSelector ?? throw new ArgumentNullException(nameof(pauseIfTrueSelector)); + /// + /// The _scheduler field. + /// private readonly IScheduler _scheduler = scheduler ?? GlobalConfig.DefaultScheduler; + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// The _timeOut field. + /// private readonly TimeSpan _timeOut = timeOut ?? TimeSpan.Zero; + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -27,7 +52,7 @@ public IObservable> Run() => Observable.Create>( var paused = initialPauseState; var buffer = new ChangeSet(); var timeoutSubscriber = new SerialDisposable(); - var timeoutSubject = new Subject(); + var timeoutSubject = new Signal(); var bufferSelector = Observable.Return(initialPauseState).Concat(_pauseIfTrueSelector.Merge(timeoutSubject)).ObserveOn(_scheduler).Synchronize(locker).Publish(); diff --git a/src/DynamicData/List/Internal/ChangeSetMergeTracker.cs b/src/DynamicData/List/Internal/ChangeSetMergeTracker.cs index fa2e2cec0..30f026b75 100644 --- a/src/DynamicData/List/Internal/ChangeSetMergeTracker.cs +++ b/src/DynamicData/List/Internal/ChangeSetMergeTracker.cs @@ -1,14 +1,31 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the ChangeSetMergeTracker class. +/// +/// The type of the TObject value. internal sealed class ChangeSetMergeTracker where TObject : notnull { + /// + /// The _resultList field. + /// private readonly ChangeAwareList _resultList = new(); + /// + /// Executes the ProcessChangeSet operation. + /// + /// The changes value. + /// The observer value. public void ProcessChangeSet(IChangeSet changes, IObserver>? observer = null) { foreach (var change in changes) @@ -55,6 +72,11 @@ public void ProcessChangeSet(IChangeSet changes, IObserver + /// Executes the RemoveItems operation. + /// + /// The removeItems value. + /// The observer value. public void RemoveItems(IEnumerable removeItems, IObserver>? observer = null) { _resultList.Remove(removeItems); @@ -65,6 +87,10 @@ public void RemoveItems(IEnumerable removeItems, IObserver + /// Executes the EmitChanges operation. + /// + /// The observer value. public void EmitChanges(IObserver> observer) { var changeSet = _resultList.CaptureChanges(); @@ -74,17 +100,45 @@ public void EmitChanges(IObserver> observer) } } + /// + /// Executes the OnClear operation. + /// + /// The change value. private void OnClear(Change change) => _resultList.ClearOrRemoveMany(change); + /// + /// Executes the OnItemAdded operation. + /// + /// The item value. private void OnItemAdded(ItemChange item) => _resultList.Add(item.Current); + /// + /// Executes the OnItemRefreshed operation. + /// + /// The item value. private void OnItemRefreshed(ItemChange item) => _resultList.Refresh(item.Current); + /// + /// Executes the OnItemRemoved operation. + /// + /// The item value. private void OnItemRemoved(ItemChange item) => _resultList.Remove(item.Current); + /// + /// Executes the OnItemReplaced operation. + /// + /// The item value. private void OnItemReplaced(ItemChange item) => _resultList.ReplaceOrAdd(item.Previous.Value, item.Current); + /// + /// Executes the OnRangeAdded operation. + /// + /// The range value. private void OnRangeAdded(RangeChange range) => _resultList.AddRange(range); + /// + /// Executes the OnRangeRemoved operation. + /// + /// The range value. private void OnRangeRemoved(RangeChange range) => _resultList.Remove(range); } diff --git a/src/DynamicData/List/Internal/ClonedListChangeSet.cs b/src/DynamicData/List/Internal/ClonedListChangeSet.cs index 3b9996a1f..e90c4564c 100644 --- a/src/DynamicData/List/Internal/ClonedListChangeSet.cs +++ b/src/DynamicData/List/Internal/ClonedListChangeSet.cs @@ -1,18 +1,36 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the ClonedListChangeSet class. +/// +/// The type of the TObject value. internal sealed class ClonedListChangeSet where TObject : notnull { + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The equalityComparer value. public ClonedListChangeSet(IObservable> source, IEqualityComparer? equalityComparer) => Source = source.Do(changeSet => List.Clone(changeSet, equalityComparer)); + /// + /// Gets the List value. + /// public List List { get; } = []; + /// + /// Gets the Source value. + /// public IObservable> Source { get; } } diff --git a/src/DynamicData/List/Internal/Combiner.cs b/src/DynamicData/List/Internal/Combiner.cs index 64882a3ba..d16743626 100644 --- a/src/DynamicData/List/Internal/Combiner.cs +++ b/src/DynamicData/List/Internal/Combiner.cs @@ -1,25 +1,44 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +using DynamicData.Reactive.Cache.Internal; +#else using DynamicData.Cache.Internal; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the Combiner class. +/// +/// The type of the T value. +/// The source value. +/// The type value. internal sealed class Combiner(ICollection>> source, CombineOperator type) where T : notnull { -#if NET9_0_OR_GREATER + /// + /// The _locker field. + /// private readonly Lock _locker = new(); -#else - private readonly object _locker = new(); -#endif + /// + /// The _source field. + /// private readonly ICollection>> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -51,6 +70,11 @@ public IObservable> Run() => Observable.Create>( return disposable; }); + /// + /// Executes the CloneSourceList operation. + /// + /// The tracker value. + /// The changes value. private static void CloneSourceList(ReferenceCountTracker tracker, IChangeSet changes) { foreach (var change in changes) @@ -90,6 +114,12 @@ private static void CloneSourceList(ReferenceCountTracker tracker, IChangeSet } } + /// + /// Executes the MatchesConstraint operation. + /// + /// The sourceLists value. + /// The item value. + /// The result of the operation. private bool MatchesConstraint(List> sourceLists, T item) { switch (type) @@ -121,6 +151,12 @@ private bool MatchesConstraint(List> sourceLists, T ite } } + /// + /// Executes the UpdateItemMembership operation. + /// + /// The item value. + /// The sourceLists value. + /// The resultList value. private void UpdateItemMembership(T item, List> sourceLists, ChangeAwareListWithRefCounts resultList) { var isInResult = resultList.Contains(item); @@ -135,7 +171,14 @@ private void UpdateItemMembership(T item, List> sourceL } } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly", Justification = "By Design.")] + /// + /// Executes the UpdateResultList operation. + /// + /// The changes value. + /// The sourceLists value. + /// The resultList value. + /// The result of the operation. + [SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly", Justification = "By Design.")] private IChangeSet UpdateResultList(IChangeSet changes, List> sourceLists, ChangeAwareListWithRefCounts resultList) { // child caches have been updated before we reached this point. diff --git a/src/DynamicData/List/Internal/DeferUntilLoaded.cs b/src/DynamicData/List/Internal/DeferUntilLoaded.cs index aae382472..293e94413 100644 --- a/src/DynamicData/List/Internal/DeferUntilLoaded.cs +++ b/src/DynamicData/List/Internal/DeferUntilLoaded.cs @@ -1,15 +1,30 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the DeferUntilLoaded class. +/// +/// The type of the T value. +/// The source value. internal sealed class DeferUntilLoaded(IObservable> source) where T : notnull { + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => _source.MonitorStatus().Where(status => status == ConnectionStatus.Loaded).Take(1).Select(_ => new ChangeSet()).Concat(_source).NotEmpty(); } diff --git a/src/DynamicData/List/Internal/DisposeMany.cs b/src/DynamicData/List/Internal/DisposeMany.cs index 046e19e4d..a7d743ff9 100644 --- a/src/DynamicData/List/Internal/DisposeMany.cs +++ b/src/DynamicData/List/Internal/DisposeMany.cs @@ -1,16 +1,26 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the DisposeMany class. +/// +/// The type of the T value. +/// The source value. internal sealed class DisposeMany(IObservable> source) where T : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>(observer => { @@ -84,6 +94,10 @@ public IObservable> Run() }); }); + /// + /// Executes the ProcessFinalization operation. + /// + /// The cachedItems value. private static void ProcessFinalization(List cachedItems) { foreach (var item in cachedItems) diff --git a/src/DynamicData/List/Internal/Distinct.cs b/src/DynamicData/List/Internal/Distinct.cs index 00ab3a458..bd4f4f9a6 100644 --- a/src/DynamicData/List/Internal/Distinct.cs +++ b/src/DynamicData/List/Internal/Distinct.cs @@ -1,19 +1,39 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; - +#endif + +/// +/// Provides members for the Distinct class. +/// +/// The type of the T value. +/// The type of the TValue value. +/// The source value. +/// The valueSelector value. internal sealed class Distinct(IObservable> source, Func valueSelector) where T : notnull where TValue : notnull { + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// The _valueSelector field. + /// private readonly Func _valueSelector = valueSelector ?? throw new ArgumentNullException(nameof(valueSelector)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -30,6 +50,13 @@ public IObservable> Run() => Observable.Create Process(valueCounters, result, changes)).NotEmpty().SubscribeSafe(observer); }); + /// + /// Executes the Process operation. + /// + /// The values value. + /// The result value. + /// The changes value. + /// The result of the operation. private static IChangeSet Process(Dictionary values, ChangeAwareList result, IChangeSet changes) { void AddAction(TValue value) => @@ -135,26 +162,46 @@ void RemoveAction(TValue value) return result.CaptureChanges(); } - private sealed class ItemWithMatch(T item, TValue value, TValue? previousValue) : IEquatable +/// +/// Provides members for the ItemWithMatch class. +/// +/// The item value. +/// The value value. +/// The previousValue value. +private sealed class ItemWithMatch(T item, TValue value, TValue? previousValue) : IEquatable { + /// + /// Gets the Item value. + /// public T Item { get; } = item; + /// + /// Gets the Previous value. + /// public TValue? Previous { get; } = previousValue; + /// + /// Gets the Value value. + /// public TValue Value { get; } = value; - /// Returns a value that indicates whether the values of two objects are equal. + /// Returns a value that indicates whether the values of two Filter<T>.ItemWithMatch objects are equal. /// The first value to compare. /// The second value to compare. /// true if the and parameters have the same value; otherwise, false. public static bool operator ==(ItemWithMatch left, ItemWithMatch right) => Equals(left, right); - /// Returns a value that indicates whether two objects have different values. + /// Returns a value that indicates whether two Filter<T>.ItemWithMatch objects have different values. /// The first value to compare. /// The second value to compare. /// true if and are not equal; otherwise, false. public static bool operator !=(ItemWithMatch left, ItemWithMatch right) => !Equals(left, right); + /// + /// Executes the Equals operation. + /// + /// The other value. + /// The result of the operation. public bool Equals(ItemWithMatch? other) { if (other is null) @@ -170,6 +217,11 @@ public bool Equals(ItemWithMatch? other) return EqualityComparer.Default.Equals(Item, other.Item); } + /// + /// Executes the Equals operation. + /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) { if (obj is null) @@ -190,8 +242,16 @@ public override bool Equals(object? obj) return Equals((ItemWithMatch)obj); } + /// + /// Executes the GetHashCode operation. + /// + /// The result of the operation. public override int GetHashCode() => Item is null ? 0 : EqualityComparer.Default.GetHashCode(Item); + /// + /// Executes the ToString operation. + /// + /// The result of the operation. public override string ToString() => $"{nameof(Item)}: {Item}, {nameof(Value)}: {Value}, {nameof(Previous)}: {Previous}"; } } diff --git a/src/DynamicData/List/Internal/DynamicCombiner.cs b/src/DynamicData/List/Internal/DynamicCombiner.cs index bb3091378..bfb482f6c 100644 --- a/src/DynamicData/List/Internal/DynamicCombiner.cs +++ b/src/DynamicData/List/Internal/DynamicCombiner.cs @@ -1,25 +1,44 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +using DynamicData.Reactive.Cache.Internal; +#else using DynamicData.Cache.Internal; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the DynamicCombiner class. +/// +/// The type of the T value. +/// The source value. +/// The type value. internal sealed class DynamicCombiner(IObservableList>> source, CombineOperator type) where T : notnull { -#if NET9_0_OR_GREATER + /// + /// The _locker field. + /// private readonly Lock _locker = new(); -#else - private readonly object _locker = new(); -#endif + /// + /// The _source field. + /// private readonly IObservableList>> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -89,6 +108,12 @@ public IObservable> Run() => Observable.Create>( return new CompositeDisposable(sourceLists, allChanges, removedItem, sourceChanged); }); + /// + /// Executes the MatchesConstraint operation. + /// + /// The sourceLists value. + /// The item value. + /// The result of the operation. private bool MatchesConstraint(MergeContainer[] sourceLists, T item) { if (sourceLists.Length == 0) @@ -125,6 +150,12 @@ private bool MatchesConstraint(MergeContainer[] sourceLists, T item) } } + /// + /// Executes the UpdateItemMembership operation. + /// + /// The item value. + /// The sourceLists value. + /// The resultList value. private void UpdateItemMembership(T item, MergeContainer[] sourceLists, ChangeAwareListWithRefCounts resultList) { var isInResult = resultList.Contains(item); @@ -139,13 +170,27 @@ private void UpdateItemMembership(T item, MergeContainer[] sourceLists, ChangeAw } } + /// + /// Executes the UpdateItemSetMemberships operation. + /// + /// The sourceLists value. + /// The resultingList value. + /// The items value. + /// The result of the operation. private IChangeSet UpdateItemSetMemberships(MergeContainer[] sourceLists, ChangeAwareListWithRefCounts resultingList, IEnumerable items) { items.ForEach(item => UpdateItemMembership(item, sourceLists, resultingList)); return resultingList.CaptureChanges(); } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly", Justification = "By Design.")] + /// + /// Executes the UpdateResultList operation. + /// + /// The sourceLists value. + /// The resultList value. + /// The changes value. + /// The result of the operation. + [SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly", Justification = "By Design.")] private IChangeSet UpdateResultList(MergeContainer[] sourceLists, ChangeAwareListWithRefCounts resultList, IChangeSet changes) { // child caches have been updated before we reached this point. @@ -185,14 +230,31 @@ private IChangeSet UpdateResultList(MergeContainer[] sourceLists, ChangeAware return resultList.CaptureChanges(); } - private sealed class MergeContainer +/// +/// Provides members for the MergeContainer class. +/// +private sealed class MergeContainer { + /// + /// Initializes a new instance of the class. + /// + /// The source value. public MergeContainer(IObservable> source) => Source = source.Do(Clone); + /// + /// Gets the Source value. + /// public IObservable> Source { get; } + /// + /// Gets the Tracker value. + /// public ReferenceCountTracker Tracker { get; } = new(); + /// + /// Executes the Clone operation. + /// + /// The changes value. private void Clone(IChangeSet changes) { foreach (var change in changes) diff --git a/src/DynamicData/List/Internal/EditDiff.cs b/src/DynamicData/List/Internal/EditDiff.cs index 0c48ff45f..df7a96532 100644 --- a/src/DynamicData/List/Internal/EditDiff.cs +++ b/src/DynamicData/List/Internal/EditDiff.cs @@ -1,16 +1,37 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the EditDiff class. +/// +/// The type of the T value. +/// The source value. +/// The equalityComparer value. internal sealed class EditDiff(ISourceList source, IEqualityComparer? equalityComparer) where T : notnull { + /// + /// The _equalityComparer field. + /// private readonly IEqualityComparer _equalityComparer = equalityComparer ?? EqualityComparer.Default; + /// + /// The _source field. + /// private readonly ISourceList _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Edit operation. + /// + /// The items value. public void Edit(IEnumerable items) => _source.Edit( innerList => { diff --git a/src/DynamicData/List/Internal/ExpirableItem.cs b/src/DynamicData/List/Internal/ExpirableItem.cs index 23227719a..f03995ffb 100644 --- a/src/DynamicData/List/Internal/ExpirableItem.cs +++ b/src/DynamicData/List/Internal/ExpirableItem.cs @@ -1,21 +1,59 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the ExpirableItem class. +/// +/// The type of the TObject value. +/// The value value. +/// The dateTime value. +/// The index value. internal sealed class ExpirableItem(TObject value, DateTime dateTime, long index) : IEquatable> { + /// + /// Gets the ExpireAt value. + /// public DateTime ExpireAt { get; } = dateTime; + /// + /// Gets the Index value. + /// public long Index { get; } = index; + /// + /// Gets the Item value. + /// public TObject Item { get; } = value; + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator ==(ExpirableItem left, ExpirableItem right) => Equals(left, right); + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator !=(ExpirableItem left, ExpirableItem right) => !Equals(left, right); + /// + /// Executes the Equals operation. + /// + /// The other value. + /// The result of the operation. public bool Equals(ExpirableItem? other) { if (other is null) @@ -31,6 +69,11 @@ public bool Equals(ExpirableItem? other) return EqualityComparer.Default.Equals(Item, other.Item) && ExpireAt.Equals(other.ExpireAt) && Index == other.Index; } + /// + /// Executes the Equals operation. + /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) { if (obj is null) @@ -46,6 +89,10 @@ public override bool Equals(object? obj) return obj is ExpirableItem item && Equals(item); } + /// + /// Executes the GetHashCode operation. + /// + /// The result of the operation. public override int GetHashCode() { unchecked @@ -57,5 +104,9 @@ public override int GetHashCode() } } + /// + /// Executes the ToString operation. + /// + /// The result of the operation. public override string ToString() => $"{Item} @ {ExpireAt}"; } diff --git a/src/DynamicData/List/Internal/ExpireAfter.cs b/src/DynamicData/List/Internal/ExpireAfter.cs index 97a39c84e..b6b0858f9 100644 --- a/src/DynamicData/List/Internal/ExpireAfter.cs +++ b/src/DynamicData/List/Internal/ExpireAfter.cs @@ -1,26 +1,37 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; - -using DynamicData.Internal; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the ExpireAfter class. +/// +/// The type of the T value. internal sealed class ExpireAfter where T : notnull { + /// + /// Executes the Create operation. + /// + /// The source value. + /// The timeSelector value. + /// The pollingInterval value. + /// The scheduler value. + /// The result of the operation. public static IObservable> Create( ISourceList source, Func timeSelector, TimeSpan? pollingInterval = null, IScheduler? scheduler = null) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - timeSelector.ThrowArgumentNullExceptionIfNull(nameof(timeSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(timeSelector); return Observable.Create>(observer => (pollingInterval is { } pollingIntervalValue) ? new PollingSubscription( @@ -36,21 +47,74 @@ public static IObservable> Create( timeSelector: timeSelector)); } - private abstract class SubscriptionBase +/// +/// Provides members for the SubscriptionBase class. +/// +private abstract class SubscriptionBase : IDisposable { + /// + /// The _expirationDueTimes field. + /// private readonly List _expirationDueTimes; - private readonly List _expiringIndexesBuffer; + + /// + /// The _items field. + /// + private readonly List _items; + + /// + /// The _expiringItemsBuffer field. + /// + private readonly List _expiringItemsBuffer; + + /// + /// The _observer field. + /// private readonly IObserver> _observer; + + /// + /// The _onEditingSource field. + /// private readonly Action> _onEditingSource; + + /// + /// The _scheduler field. + /// private readonly IScheduler _scheduler; + + /// + /// The _source field. + /// private readonly ISourceList _source; + + /// + /// The _sourceSubscription field. + /// private readonly IDisposable _sourceSubscription; + + /// + /// The _timeSelector field. + /// private readonly Func _timeSelector; + /// + /// The _hasSourceCompleted field. + /// private bool _hasSourceCompleted; + + /// + /// The _nextScheduledManagement field. + /// private ScheduledManagement? _nextScheduledManagement; + /// + /// Initializes a new instance of the class. + /// + /// The observer value. + /// The scheduler value. + /// The source value. + /// The timeSelector value. protected SubscriptionBase( IObserver> observer, IScheduler? scheduler, @@ -66,19 +130,23 @@ protected SubscriptionBase( _onEditingSource = OnEditingSource; _expirationDueTimes = new(); - _expiringIndexesBuffer = new(); - - _sourceSubscription = source - .Connect() - // It's important to set this flag outside the context of a lock, because it'll be read outside of lock as well. - .Finally(() => _hasSourceCompleted = true) - .Synchronize(SynchronizationGate) - .SubscribeSafe( - onNext: OnSourceNext, - onError: OnSourceError, - onCompleted: OnSourceCompleted); + _items = new(); + _expiringItemsBuffer = new(); + + _sourceSubscription = PrimitivesLinqExtensions.SubscribeSafe( + source + .Connect() + // It's important to set this flag outside the context of a lock, because it'll be read outside of lock as well. + .Finally(() => _hasSourceCompleted = true) + .Synchronize(SynchronizationGate), + onNext: OnSourceNext, + onError: OnSourceError, + onCompleted: OnSourceCompleted); } + /// + /// Executes the Dispose operation. + /// public void Dispose() { lock (SynchronizationGate) @@ -89,15 +157,29 @@ public void Dispose() } } + /// + /// Gets the Scheduler value. + /// protected IScheduler Scheduler => _scheduler; - // Instead of using a dedicated _synchronizationGate object, we can save an allocation by using any object that is never exposed to public consumers. + + /// + /// Gets the SynchronizationGate value. + /// protected object SynchronizationGate => _expirationDueTimes; + /// + /// Executes the GetNextManagementDueTime operation. + /// + /// The result of the operation. protected abstract DateTimeOffset? GetNextManagementDueTime(); + /// + /// Executes the GetNextProposedExpirationDueTime operation. + /// + /// The result of the operation. protected DateTimeOffset? GetNextProposedExpirationDueTime() { var result = null as DateTimeOffset?; @@ -111,8 +193,15 @@ protected object SynchronizationGate return result; } + /// + /// Executes the OnExpirationsManaged operation. + /// + /// The dueTime value. protected abstract void OnExpirationsManaged(DateTimeOffset dueTime); + /// + /// Executes the ManageExpirations operation. + /// private void ManageExpirations() { // This check is needed, to make sure we don't try and call .Edit() on a disposed _source, @@ -129,6 +218,10 @@ private void ManageExpirations() _source.Edit(_onEditingSource); } + /// + /// Executes the OnEditingSource operation. + /// + /// The updater value. private void OnEditingSource(IExtendedList updater) { lock (SynchronizationGate) @@ -150,7 +243,7 @@ private void OnEditingSource(IExtendedList updater) { if ((_expirationDueTimes[i] is { } dueTime) && (dueTime <= now)) { - _expiringIndexesBuffer.Add(i); + _expiringItemsBuffer.Add(new ExpiringItem(i, _items[i])); // This shouldn't be necessary, but it guarantees we don't accidentally expire an item more than once, // in the event of a race condition or something we haven't predicted. @@ -159,22 +252,30 @@ private void OnEditingSource(IExtendedList updater) } // I'm pretty sure it shouldn't be possible to end up with no removals here, but it costs basically nothing to check. - if (_expiringIndexesBuffer.Count is not 0) + if (_expiringItemsBuffer.Count is not 0) { // Processing removals in reverse-index order eliminates the need for us to adjust index of each .RemoveAt() call, as we go. - _expiringIndexesBuffer.Sort(static (x, y) => y.CompareTo(x)); + _expiringItemsBuffer.Sort(static (x, y) => y.Index.CompareTo(x.Index)); - var removedItems = new T[_expiringIndexesBuffer.Count]; - for (var i = 0; i < _expiringIndexesBuffer.Count; ++i) + var removedItems = new List(_expiringItemsBuffer.Count); + for (var i = 0; i < _expiringItemsBuffer.Count; ++i) { - var removedIndex = _expiringIndexesBuffer[i]; - removedItems[i] = updater[removedIndex]; + var expiringItem = _expiringItemsBuffer[i]; + if (!TryGetCurrentIndex(updater, expiringItem, out var removedIndex)) + { + continue; + } + + removedItems.Add(updater[removedIndex]); updater.RemoveAt(removedIndex); } - _observer.OnNext(removedItems); + if (removedItems.Count != 0) + { + _observer.OnNext(removedItems); + } - _expiringIndexesBuffer.Clear(); + _expiringItemsBuffer.Clear(); } OnExpirationsManaged(thisScheduledManagement.DueTime); @@ -184,6 +285,9 @@ private void OnEditingSource(IExtendedList updater) } } + /// + /// Executes the OnExpirationDueTimesChanged operation. + /// private void OnExpirationDueTimesChanged() { // Check if we need to re-schedule the next management operation @@ -219,6 +323,9 @@ private void OnExpirationDueTimesChanged() } } + /// + /// Executes the OnSourceCompleted operation. + /// private void OnSourceCompleted() { // If the source completes, we can no longer remove items from it, so any pending expirations are moot. @@ -227,6 +334,10 @@ private void OnSourceCompleted() _observer.OnCompleted(); } + /// + /// Executes the OnSourceError operation. + /// + /// The error value. private void OnSourceError(Exception error) { TryCancelNextScheduledManagement(); @@ -234,6 +345,10 @@ private void OnSourceError(Exception error) _observer.OnError(error); } + /// + /// Executes the OnSourceNext operation. + /// + /// The changes value. private void OnSourceNext(IChangeSet changes) { try @@ -250,6 +365,10 @@ private void OnSourceNext(IChangeSet changes) { var dueTime = now + _timeSelector.Invoke(change.Item.Current); + _items.Insert( + index: change.Item.CurrentIndex, + item: change.Item.Current); + _expirationDueTimes.Insert( index: change.Item.CurrentIndex, item: dueTime); @@ -267,6 +386,10 @@ private void OnSourceNext(IChangeSet changes) { var dueTime = now + _timeSelector.Invoke(item); + _items.Insert( + index: itemIndex, + item: item); + _expirationDueTimes.Insert( index: itemIndex, item: dueTime); @@ -289,12 +412,19 @@ private void OnSourceNext(IChangeSet changes) } _expirationDueTimes.Clear(); + _items.Clear(); break; case ListChangeReason.Moved: { + var item = _items[change.Item.PreviousIndex]; var expirationDueTime = _expirationDueTimes[change.Item.PreviousIndex]; + _items.RemoveAt(change.Item.PreviousIndex); + _items.Insert( + index: change.Item.CurrentIndex, + item: item); + _expirationDueTimes.RemoveAt(change.Item.PreviousIndex); _expirationDueTimes.Insert( index: change.Item.CurrentIndex, @@ -310,6 +440,7 @@ private void OnSourceNext(IChangeSet changes) } _expirationDueTimes.RemoveAt(change.Item.CurrentIndex); + _items.RemoveAt(change.Item.CurrentIndex); } break; @@ -326,6 +457,7 @@ private void OnSourceNext(IChangeSet changes) } _expirationDueTimes.RemoveRange(change.Range.Index, change.Range.Count); + _items.RemoveRange(change.Range.Index, change.Range.Count); } break; @@ -337,6 +469,7 @@ private void OnSourceNext(IChangeSet changes) // Ignoring the possibility that the item's index has changed as well, because ISourceList does not allow for this. _expirationDueTimes[change.Item.CurrentIndex] = newDueTime; + _items[change.Item.CurrentIndex] = change.Item.Current; haveExpirationDueTimesChanged |= newDueTime != oldDueTime; } @@ -357,23 +490,82 @@ private void OnSourceNext(IChangeSet changes) } } + /// + /// Executes the TryCancelNextScheduledManagement operation. + /// private void TryCancelNextScheduledManagement() { _nextScheduledManagement?.Cancellation.Dispose(); _nextScheduledManagement = null; } - private readonly record struct ScheduledManagement + /// + /// Attempts to find the current source index for an item scheduled for expiration. + /// + /// The updater value. + /// The expiringItem value. + /// The index value. + /// when the item is still present in the source list. + private static bool TryGetCurrentIndex(IExtendedList updater, ExpiringItem expiringItem, out int index) + { + if (expiringItem.Index >= 0 && + expiringItem.Index < updater.Count && + EqualityComparer.Default.Equals(updater[expiringItem.Index], expiringItem.Item)) + { + index = expiringItem.Index; + return true; + } + + for (var i = 0; i < updater.Count; ++i) + { + if (EqualityComparer.Default.Equals(updater[i], expiringItem.Item)) + { + index = i; + return true; + } + } + + index = -1; + return false; + } + +/// +/// The source item and index captured when an expiration becomes due. +/// +/// The Index value. +/// The Item value. +private readonly record struct ExpiringItem(int Index, T Item); + +/// +/// Represents the ScheduledManagement record. +/// +private readonly record struct ScheduledManagement { + /// + /// Gets or sets the Cancellation value. + /// public required IDisposable Cancellation { get; init; } + /// + /// Gets or sets the DueTime value. + /// public required DateTimeOffset DueTime { get; init; } } } - private sealed class OnDemandSubscription +/// +/// Provides members for the OnDemandSubscription class. +/// +private sealed class OnDemandSubscription : SubscriptionBase { + /// + /// Initializes a new instance of the class. + /// + /// The observer value. + /// The scheduler value. + /// The source value. + /// The timeSelector value. public OnDemandSubscription( IObserver> observer, IScheduler? scheduler, @@ -387,21 +579,46 @@ public OnDemandSubscription( { } + /// + /// Executes the GetNextManagementDueTime operation. + /// + /// The result of the operation. protected override DateTimeOffset? GetNextManagementDueTime() => GetNextProposedExpirationDueTime(); + /// + /// Executes the OnExpirationsManaged operation. + /// + /// The dueTime value. protected override void OnExpirationsManaged(DateTimeOffset dueTime) { } } - private sealed class PollingSubscription +/// +/// Provides members for the PollingSubscription class. +/// +private sealed class PollingSubscription : SubscriptionBase { + /// + /// The _pollingInterval field. + /// private readonly TimeSpan _pollingInterval; + /// + /// The _lastManagementDueTime field. + /// private DateTimeOffset _lastManagementDueTime; + /// + /// Initializes a new instance of the class. + /// + /// The observer value. + /// The pollingInterval value. + /// The scheduler value. + /// The source value. + /// The timeSelector value. public PollingSubscription( IObserver> observer, TimeSpan pollingInterval, @@ -419,6 +636,10 @@ public PollingSubscription( _lastManagementDueTime = Scheduler.Now; } + /// + /// Executes the GetNextManagementDueTime operation. + /// + /// The result of the operation. protected override DateTimeOffset? GetNextManagementDueTime() { var now = Scheduler.Now; @@ -430,6 +651,10 @@ public PollingSubscription( : now; } + /// + /// Executes the OnExpirationsManaged operation. + /// + /// The dueTime value. protected override void OnExpirationsManaged(DateTimeOffset dueTime) => _lastManagementDueTime = dueTime; } diff --git a/src/DynamicData/List/Internal/Filter.Dynamic.cs b/src/DynamicData/List/Internal/Filter.Dynamic.cs index 8d9b5e8dc..97c0e21b5 100644 --- a/src/DynamicData/List/Internal/Filter.Dynamic.cs +++ b/src/DynamicData/List/Internal/Filter.Dynamic.cs @@ -1,24 +1,52 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the Filter class. +/// internal static partial class Filter { - internal sealed class Dynamic +/// +/// Provides members for the Dynamic class. +/// +/// The type of the T value. +internal sealed class Dynamic where T : notnull { + /// + /// The _policy field. + /// private readonly ListFilterPolicy _policy; + /// + /// The _predicate field. + /// private readonly Func? _predicate; + /// + /// The _predicates field. + /// private readonly IObservable>? _predicates; + /// + /// The _source field. + /// private readonly IObservable> _source; + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The predicates value. + /// The policy value. public Dynamic(IObservable> source, IObservable> predicates, ListFilterPolicy policy = ListFilterPolicy.CalculateDiff) { _policy = policy; @@ -26,6 +54,12 @@ public Dynamic(IObservable> source, IObservable> pre _predicates = predicates ?? throw new ArgumentNullException(nameof(predicates)); } + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The predicate value. + /// The policy value. public Dynamic(IObservable> source, Func predicate, ListFilterPolicy policy = ListFilterPolicy.CalculateDiff) { _policy = policy; @@ -33,6 +67,10 @@ public Dynamic(IObservable> source, Func predicate, ListF _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); } + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -95,6 +133,12 @@ public IObservable> Run() => Observable.Create>( .SubscribeSafe(observer); }); + /// + /// Executes the Process operation. + /// + /// The filtered value. + /// The changes value. + /// The result of the operation. private static IChangeSet Process(ChangeAwareList filtered, IChangeSet changes) { // Maintain all items as well as filtered list. This enables us to a) re-query when the predicate changes b) check the previous state when Refresh is called @@ -198,6 +242,13 @@ private static IChangeSet Process(ChangeAwareList return filtered.CaptureChanges(); } + /// + /// Executes the Requery operation. + /// + /// The predicate value. + /// The all value. + /// The filtered value. + /// The result of the operation. private IChangeSet Requery(Func predicate, List all, ChangeAwareList filtered) { if (all.Count == 0) @@ -248,20 +299,52 @@ private IChangeSet Requery(Func predicate, List +/// +/// Provides members for the ItemWithMatch class. +/// +/// The item value. +/// The isMatch value. +/// The wasMatch value. +private sealed class ItemWithMatch(T item, bool isMatch, bool wasMatch = false) : IEquatable { + /// + /// Gets the Item value. + /// public T Item { get; } = item; + /// + /// Gets or sets the IsMatch value. + /// public bool IsMatch { get; set; } = isMatch; + /// + /// Gets or sets the WasMatch value. + /// public bool WasMatch { get; set; } = wasMatch; + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator ==(ItemWithMatch? left, ItemWithMatch? right) => Equals(left, right); + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator !=(ItemWithMatch? left, ItemWithMatch? right) => !Equals(left, right); + /// + /// Executes the Equals operation. + /// + /// The other value. + /// The result of the operation. public bool Equals(ItemWithMatch? other) { if (other is null) @@ -277,6 +360,11 @@ public bool Equals(ItemWithMatch? other) return EqualityComparer.Default.Equals(Item, other.Item); } + /// + /// Executes the Equals operation. + /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) { if (obj is null) @@ -297,8 +385,16 @@ public override bool Equals(object? obj) return Equals((ItemWithMatch)obj); } + /// + /// Executes the GetHashCode operation. + /// + /// The result of the operation. public override int GetHashCode() => EqualityComparer.Default.GetHashCode(Item!); + /// + /// Executes the ToString operation. + /// + /// The result of the operation. public override string ToString() => $"{Item}, (was {IsMatch} is {WasMatch}"; } } diff --git a/src/DynamicData/List/Internal/Filter.Static.cs b/src/DynamicData/List/Internal/Filter.Static.cs index 36a49c5e9..b3a01b454 100644 --- a/src/DynamicData/List/Internal/Filter.Static.cs +++ b/src/DynamicData/List/Internal/Filter.Static.cs @@ -1,23 +1,40 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the Filter class. +/// internal static partial class Filter { - public static class Static +/// +/// Provides members for the Static class. +/// +/// The type of the T value. +public static class Static where T : notnull { + /// + /// Executes the Create operation. + /// + /// The source value. + /// The predicate value. + /// The suppressEmptyChangesets value. + /// The result of the operation. public static IObservable> Create( IObservable> source, Func predicate, bool suppressEmptyChangesets) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - predicate.ThrowArgumentNullExceptionIfNull(nameof(predicate)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(predicate); return Observable.Create>(downstreamObserver => { diff --git a/src/DynamicData/List/Internal/Filter.WithPredicateState.cs b/src/DynamicData/List/Internal/Filter.WithPredicateState.cs index c9f04be4f..b3b3b06da 100644 --- a/src/DynamicData/List/Internal/Filter.WithPredicateState.cs +++ b/src/DynamicData/List/Internal/Filter.WithPredicateState.cs @@ -1,18 +1,36 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; - -using DynamicData.Internal; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the Filter class. +/// internal static partial class Filter { - public static class WithPredicateState +/// +/// Provides members for the WithPredicateState class. +/// +/// The type of the T value. +/// The type of the TState value. +public static class WithPredicateState where T : notnull { + /// + /// Executes the Create operation. + /// + /// The source value. + /// The predicateState value. + /// The predicate value. + /// The filterPolicy value. + /// The suppressEmptyChangeSets value. + /// The result of the operation. public static IObservable> Create( IObservable> source, IObservable predicateState, @@ -20,9 +38,9 @@ public static IObservable> Create( ListFilterPolicy filterPolicy = ListFilterPolicy.CalculateDiff, bool suppressEmptyChangeSets = true) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - predicateState.ThrowArgumentNullExceptionIfNull(nameof(predicateState)); - predicate.ThrowArgumentNullExceptionIfNull(nameof(predicate)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(predicateState); + ArgumentExceptionHelper.ThrowIfNull(predicate); if (!EnumEx.IsDefined(filterPolicy)) throw new ArgumentException($"Invalid {nameof(ListFilterPolicy)} value {filterPolicy}"); @@ -47,24 +65,83 @@ public static IObservable> Create( }); } - private abstract class SubscriptionBase +/// +/// Provides members for the SubscriptionBase class. +/// +private abstract class SubscriptionBase : IDisposable { + /// + /// The _downstreamChangesBuffer field. + /// private readonly List> _downstreamChangesBuffer; + + /// + /// The _downstreamObserver field. + /// private readonly IObserver> _downstreamObserver; + + /// + /// The _itemsBuffer field. + /// private readonly List _itemsBuffer; + + /// + /// The _itemStates field. + /// private readonly List _itemStates; + + /// + /// The _itemStatesBuffer field. + /// private readonly List _itemStatesBuffer; + + /// + /// The _predicate field. + /// private readonly Func _predicate; + + /// + /// The _suppressEmptyChangeSets field. + /// private readonly bool _suppressEmptyChangeSets; + /// + /// The _hasPredicateStateCompleted field. + /// private bool _hasPredicateStateCompleted; + + /// + /// The _hasSourceCompleted field. + /// private bool _hasSourceCompleted; + + /// + /// The _isLatestPredicateStateValid field. + /// private bool _isLatestPredicateStateValid; + + /// + /// The _latestPredicateState field. + /// private TState _latestPredicateState; + + /// + /// The _predicateStateSubscription field. + /// private IDisposable? _predicateStateSubscription; + + /// + /// The _sourceSubscription field. + /// private IDisposable? _sourceSubscription; + /// + /// Initializes a new instance of the class. + /// + /// The downstreamObserver value. + /// The predicate value. + /// The suppressEmptyChangeSets value. protected SubscriptionBase( IObserver> downstreamObserver, Func predicate, @@ -80,78 +157,155 @@ protected SubscriptionBase( _itemStatesBuffer = new(); _latestPredicateState = default!; } - // Keeping subscriptions out of the constructor prevents subscriptions that emit immediately from triggering virtual method calls within the constructor. + + /// + /// Executes the Activate operation. + /// + /// The predicateState value. + /// The source value. public void Activate( IObservable predicateState, IObservable> source) { var onError = OnError; - - _predicateStateSubscription = predicateState - .SubscribeSafe( - onNext: OnPredicateStateNext, - onError: onError, - onCompleted: OnPredicateStateCompleted); - - _sourceSubscription = source - .SubscribeSafe( - onNext: OnSourceNext, - onError: onError, - onCompleted: OnSourceCompleted); + var predicateStateSubscription = new SingleAssignmentDisposable(); + var sourceSubscription = new SingleAssignmentDisposable(); + + _predicateStateSubscription = predicateStateSubscription; + _sourceSubscription = sourceSubscription; + + predicateStateSubscription.Disposable = predicateState.SubscribeSafe(Observer.Create( + onNext: OnPredicateStateNext, + onError: onError, + onCompleted: OnPredicateStateCompleted)); + + sourceSubscription.Disposable = PrimitivesLinqExtensions.SubscribeSafe( + source, + onNext: OnSourceNext, + onError: onError, + onCompleted: OnSourceCompleted); } + /// + /// Executes the Dispose operation. + /// public void Dispose() { _predicateStateSubscription?.Dispose(); _sourceSubscription?.Dispose(); } + /// + /// Gets the DownstreamChangesBuffer value. + /// protected List> DownstreamChangesBuffer => _downstreamChangesBuffer; + /// + /// Gets the IsLatestPredicateStateValid value. + /// protected bool IsLatestPredicateStateValid => _isLatestPredicateStateValid; + /// + /// Gets the ItemsBuffer value. + /// protected List ItemsBuffer => _itemsBuffer; + /// + /// Gets the ItemStates value. + /// protected List ItemStates => _itemStates; + /// + /// Gets the ItemStatesBuffer value. + /// protected List ItemStatesBuffer => _itemStatesBuffer; + /// + /// Gets the LatestPredicateState value. + /// protected TState LatestPredicateState => _latestPredicateState; + /// + /// Gets the Predicate value. + /// protected Func Predicate => _predicate; + /// + /// Executes the PerformAdd operation. + /// + /// The change value. protected abstract void PerformAdd(ItemChange change); + /// + /// Executes the PerformAddRange operation. + /// + /// The change value. protected abstract void PerformAddRange(RangeChange change); + /// + /// Executes the PerformClear operation. + /// protected abstract void PerformClear(); + /// + /// Executes the PerformMove operation. + /// + /// The change value. protected abstract void PerformMove(ItemChange change); + /// + /// Executes the PerformReFilter operation. + /// protected abstract void PerformReFilter(); + /// + /// Executes the PerformRefresh operation. + /// + /// The change value. protected abstract void PerformRefresh(ItemChange change); + /// + /// Executes the PerformRemove operation. + /// + /// The change value. protected abstract void PerformRemove(ItemChange change); + /// + /// Executes the PerformRemoveRange operation. + /// + /// The change value. protected abstract void PerformRemoveRange(RangeChange change); + /// + /// Executes the PerformReplace operation. + /// + /// The change value. protected abstract void PerformReplace(ItemChange change); + /// + /// Gets the DownstreamSynchronizationGate value. + /// private object DownstreamSynchronizationGate => _downstreamChangesBuffer; + /// + /// Gets the UpstreamSynchronizationGate value. + /// private object UpstreamSynchronizationGate => _itemStates; + /// + /// Executes the AssembleDownstreamChanges operation. + /// + /// The result of the operation. private IChangeSet AssembleDownstreamChanges() { if (_downstreamChangesBuffer.Count is 0) @@ -163,6 +317,10 @@ private IChangeSet AssembleDownstreamChanges() return downstreamChanges; } + /// + /// Executes the OnError operation. + /// + /// The error value. private void OnError(Exception error) { var hasUpstreamLock = false; @@ -194,6 +352,9 @@ private void OnError(Exception error) } } + /// + /// Executes the OnPredicateStateCompleted operation. + /// private void OnPredicateStateCompleted() { var hasUpstreamLock = false; @@ -208,6 +369,9 @@ private void OnPredicateStateCompleted() // no matter how many items come through from source, so just go ahead and complete now. if (_hasSourceCompleted || (!_isLatestPredicateStateValid && _suppressEmptyChangeSets)) { + _predicateStateSubscription?.Dispose(); + _sourceSubscription?.Dispose(); + Monitor.Enter(DownstreamSynchronizationGate, ref hasDownstreamLock); if (hasUpstreamLock) @@ -229,6 +393,10 @@ private void OnPredicateStateCompleted() } } + /// + /// Executes the OnPredicateStateNext operation. + /// + /// The predicateState value. private void OnPredicateStateNext(TState predicateState) { var hasUpstreamLock = false; @@ -267,6 +435,9 @@ private void OnPredicateStateNext(TState predicateState) } } + /// + /// Executes the OnSourceCompleted operation. + /// private void OnSourceCompleted() { var hasUpstreamLock = false; @@ -281,6 +452,9 @@ private void OnSourceCompleted() // and the source has reported that it'll never change, so go ahead and complete now. if (_hasPredicateStateCompleted || ((_itemStates.Count is 0) && _suppressEmptyChangeSets)) { + _predicateStateSubscription?.Dispose(); + _sourceSubscription?.Dispose(); + Monitor.Enter(DownstreamSynchronizationGate, ref hasDownstreamLock); if (hasUpstreamLock) @@ -302,6 +476,10 @@ private void OnSourceCompleted() } } + /// + /// Executes the OnSourceNext operation. + /// + /// The upstreamChanges value. private void OnSourceNext(IChangeSet upstreamChanges) { var hasUpstreamLock = false; @@ -375,17 +553,35 @@ private void OnSourceNext(IChangeSet upstreamChanges) } } - protected readonly struct ItemState +/// +/// Represents the ItemState value. +/// +protected readonly struct ItemState { + /// + /// Gets or sets the FilteredIndex value. + /// public required int? FilteredIndex { get; init; } + /// + /// Gets or sets the Item value. + /// public required T Item { get; init; } } } - private sealed class CalculateDiffSubscription +/// +/// Provides members for the CalculateDiffSubscription class. +/// +private sealed class CalculateDiffSubscription : SubscriptionBase { + /// + /// Initializes a new instance of the class. + /// + /// The downstreamObserver value. + /// The predicate value. + /// The suppressEmptyChangeSets value. public CalculateDiffSubscription( IObserver> downstreamObserver, Func predicate, @@ -397,6 +593,10 @@ public CalculateDiffSubscription( { } + /// + /// Executes the PerformAdd operation. + /// + /// The change value. protected override void PerformAdd(ItemChange change) { var isIncluded = IsLatestPredicateStateValid && Predicate.Invoke(LatestPredicateState, change.Current); @@ -441,6 +641,10 @@ protected override void PerformAdd(ItemChange change) } } + /// + /// Executes the PerformAddRange operation. + /// + /// The change value. protected override void PerformAddRange(RangeChange change) { var nextFilteredIndex = 0; @@ -500,6 +704,9 @@ protected override void PerformAddRange(RangeChange change) } } + /// + /// Executes the PerformClear operation. + /// protected override void PerformClear() { ItemsBuffer.EnsureCapacity(ItemStates.Count); @@ -520,6 +727,10 @@ protected override void PerformClear() } } + /// + /// Executes the PerformMove operation. + /// + /// The change value. protected override void PerformMove(ItemChange change) { var itemState = ItemStates[change.PreviousIndex]; @@ -606,6 +817,9 @@ protected override void PerformMove(ItemChange change) } } + /// + /// Executes the PerformReFilter operation. + /// protected override void PerformReFilter() { var nextFilteredIndex = 0; @@ -660,6 +874,10 @@ protected override void PerformReFilter() } } + /// + /// Executes the PerformRefresh operation. + /// + /// The change value. protected override void PerformRefresh(ItemChange change) { var itemState = ItemStates[change.CurrentIndex]; @@ -737,6 +955,10 @@ protected override void PerformRefresh(ItemChange change) } } + /// + /// Executes the PerformRemove operation. + /// + /// The change value. protected override void PerformRemove(ItemChange change) { var itemState = ItemStates[change.CurrentIndex]; @@ -763,6 +985,10 @@ protected override void PerformRemove(ItemChange change) } } + /// + /// Executes the PerformRemoveRange operation. + /// + /// The change value. protected override void PerformRemoveRange(RangeChange change) { ItemsBuffer.EnsureCapacity(change.Count); @@ -805,6 +1031,10 @@ protected override void PerformRemoveRange(RangeChange change) } } + /// + /// Executes the PerformReplace operation. + /// + /// The change value. protected override void PerformReplace(ItemChange change) { var itemState = ItemStates[change.CurrentIndex]; @@ -900,11 +1130,23 @@ protected override void PerformReplace(ItemChange change) } } - private sealed class ClearAndReplaceSubscription +/// +/// Provides members for the ClearAndReplaceSubscription class. +/// +private sealed class ClearAndReplaceSubscription : SubscriptionBase { + /// + /// The _filteredCount field. + /// private int _filteredCount; + /// + /// Initializes a new instance of the class. + /// + /// The downstreamObserver value. + /// The predicate value. + /// The suppressEmptyChangeSets value. public ClearAndReplaceSubscription( IObserver> downstreamObserver, Func predicate, @@ -916,6 +1158,10 @@ public ClearAndReplaceSubscription( { } + /// + /// Executes the PerformAdd operation. + /// + /// The change value. protected override void PerformAdd(ItemChange change) { var isIncluded = IsLatestPredicateStateValid && Predicate.Invoke(LatestPredicateState, change.Current); @@ -939,6 +1185,10 @@ protected override void PerformAdd(ItemChange change) }); } + /// + /// Executes the PerformAddRange operation. + /// + /// The change value. protected override void PerformAddRange(RangeChange change) { var priorFilteredCount = _filteredCount; @@ -977,6 +1227,9 @@ protected override void PerformAddRange(RangeChange change) } } + /// + /// Executes the PerformClear operation. + /// protected override void PerformClear() { // Not using ItemsBuffer, because we already know the exact size we need, so we can allocate a fresh one and use it directly. @@ -998,6 +1251,10 @@ protected override void PerformClear() } } + /// + /// Executes the PerformMove operation. + /// + /// The change value. protected override void PerformMove(ItemChange change) { // We're not supporting propagation of move changes, but we do still need to process them, to keep ItemStates correct. @@ -1006,6 +1263,9 @@ protected override void PerformMove(ItemChange change) ItemStates.Insert(change.CurrentIndex, itemState); } + /// + /// Executes the PerformReFilter operation. + /// protected override void PerformReFilter() { var nextFilteredIndex = 0; @@ -1077,6 +1337,10 @@ protected override void PerformReFilter() } } + /// + /// Executes the PerformRefresh operation. + /// + /// The change value. protected override void PerformRefresh(ItemChange change) { var itemState = ItemStates[change.CurrentIndex]; @@ -1134,6 +1398,10 @@ protected override void PerformRefresh(ItemChange change) } } + /// + /// Executes the PerformRemove operation. + /// + /// The change value. protected override void PerformRemove(ItemChange change) { var itemState = ItemStates[change.CurrentIndex]; @@ -1162,6 +1430,10 @@ protected override void PerformRemove(ItemChange change) } } + /// + /// Executes the PerformRemoveRange operation. + /// + /// The change value. protected override void PerformRemoveRange(RangeChange change) { for (var index = change.Index; index < change.Index + change.Count; ++index) @@ -1192,6 +1464,10 @@ protected override void PerformRemoveRange(RangeChange change) ItemStates.RemoveRange(change.Index, change.Count); } + /// + /// Executes the PerformReplace operation. + /// + /// The change value. protected override void PerformReplace(ItemChange change) { var itemState = ItemStates[change.CurrentIndex]; diff --git a/src/DynamicData/List/Internal/FilterOnObservable.cs b/src/DynamicData/List/Internal/FilterOnObservable.cs index 1d2dae5e0..fe9168aac 100644 --- a/src/DynamicData/List/Internal/FilterOnObservable.cs +++ b/src/DynamicData/List/Internal/FilterOnObservable.cs @@ -1,19 +1,39 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; - +#endif + +/// +/// Provides members for the FilterOnObservable class. +/// +/// The type of the TObject value. +/// The source value. +/// The filter value. +/// The buffer value. +/// The scheduler value. internal sealed class FilterOnObservable(IObservable> source, Func> filter, TimeSpan? buffer = null, IScheduler? scheduler = null) where TObject : notnull { + /// + /// The _filter field. + /// private readonly Func> _filter = filter ?? throw new ArgumentNullException(nameof(filter)); + + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -47,35 +67,88 @@ public IObservable> Run() => Observable.Create + /// Executes the IndexOfMany operation. + /// + /// The type of the TObj value. + /// The type of the TObjectProp value. + /// The type of the TResult value. + /// The source value. + /// The itemsToFind value. + /// The objectPropertyFunc value. + /// The resultSelector value. + /// The result of the operation. private static IEnumerable IndexOfMany(IEnumerable source, IEnumerable itemsToFind, Func objectPropertyFunc, Func resultSelector) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - itemsToFind.ThrowArgumentNullExceptionIfNull(nameof(itemsToFind)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(itemsToFind); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); var indexed = source.Select((element, index) => new { Element = element, Index = index }); return itemsToFind.Join(indexed, objectPropertyFunc, right => objectPropertyFunc(right.Element), (left, right) => resultSelector(left, right.Index)); } - private readonly struct ObjWithFilterValue(TObject obj, bool filter) : IEquatable +/// +/// Represents the ObjWithFilterValue value. +/// +/// The obj value. +/// The filter value. +private readonly struct ObjWithFilterValue(TObject obj, bool filter) : IEquatable { + /// + /// The Obj field. + /// public readonly TObject Obj = obj; + /// + /// The Filter field. + /// public readonly bool Filter = filter; + /// + /// Gets the ObjComparer value. + /// private static IEqualityComparer ObjComparer { get; } = new ObjEqualityComparer(); + /// + /// Executes the Equals operation. + /// + /// The other value. + /// The result of the operation. public bool Equals(ObjWithFilterValue other) => ObjComparer.Equals(this, other); // default equality does _not_ include Filter value, as that would cause the Filter operator that is used later to fail + /// + /// Executes the Equals operation. + /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) => obj is ObjWithFilterValue value && Equals(value); + /// + /// Executes the GetHashCode operation. + /// + /// The result of the operation. public override int GetHashCode() => ObjComparer.GetHashCode(this); - private sealed class ObjEqualityComparer : IEqualityComparer +/// +/// Provides members for the ObjEqualityComparer class. +/// +private sealed class ObjEqualityComparer : IEqualityComparer { + /// + /// Executes the Equals operation. + /// + /// The x value. + /// The y value. + /// The result of the operation. public bool Equals(ObjWithFilterValue x, ObjWithFilterValue y) => EqualityComparer.Default.Equals(x.Obj, y.Obj); + /// + /// Executes the GetHashCode operation. + /// + /// The obj value. + /// The result of the operation. public int GetHashCode(ObjWithFilterValue obj) { unchecked diff --git a/src/DynamicData/List/Internal/FilterOnProperty.cs b/src/DynamicData/List/Internal/FilterOnProperty.cs index d73b8e238..d172dab50 100644 --- a/src/DynamicData/List/Internal/FilterOnProperty.cs +++ b/src/DynamicData/List/Internal/FilterOnProperty.cs @@ -1,16 +1,33 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.ComponentModel; using System.Linq.Expressions; -using System.Reactive.Concurrency; +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the FilterOnProperty class. +/// +/// The type of the TObject value. +/// The type of the TProperty value. +/// The source value. +/// The propertySelector value. +/// The predicate value. +/// The throttle value. +/// The scheduler value. [Obsolete("Use AutoRefresh(), followed by Filter() instead")] internal sealed class FilterOnProperty(IObservable> source, Expression> propertySelector, Func predicate, TimeSpan? throttle = null, IScheduler? scheduler = null) where TObject : INotifyPropertyChanged { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => source.AutoRefresh(propertySelector, propertyChangeThrottle: throttle, scheduler: scheduler).Filter(predicate); } diff --git a/src/DynamicData/List/Internal/FilterStatic.cs b/src/DynamicData/List/Internal/FilterStatic.cs index 23786400e..eca70a7ee 100644 --- a/src/DynamicData/List/Internal/FilterStatic.cs +++ b/src/DynamicData/List/Internal/FilterStatic.cs @@ -1,18 +1,37 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the FilterStatic class. +/// +/// The type of the T value. +/// The source value. +/// The predicate value. internal sealed class FilterStatic(IObservable> source, Func predicate) where T : notnull { + /// + /// The _predicate field. + /// private readonly Func _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Defer(() => _source.Scan( new ChangeAwareList(), (state, changes) => @@ -21,6 +40,11 @@ public IObservable> Run() => Observable.Defer(() => _source.Scan( return state; }).Select(filtered => filtered.CaptureChanges()).NotEmpty()); + /// + /// Executes the Process operation. + /// + /// The filtered value. + /// The changes value. private void Process(ChangeAwareList filtered, IChangeSet changes) { foreach (var item in changes) diff --git a/src/DynamicData/List/Internal/Group.cs b/src/DynamicData/List/Internal/Group.cs index d6cd72e2d..98739bb2f 100644 --- a/src/DynamicData/List/Internal/Group.cs +++ b/src/DynamicData/List/Internal/Group.cs @@ -1,26 +1,70 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the Group class. +/// +/// The type of the TObject value. +/// The type of the TGroup value. +/// The groupKey value. internal sealed class Group(TGroup groupKey) : IGroup, IDisposable, IEquatable> where TObject : notnull { + /// + /// Gets the GroupKey value. + /// public TGroup GroupKey { get; } = groupKey; + /// + /// Gets the List value. + /// public IObservableList List => Source; + /// + /// Gets the Source value. + /// private SourceList Source { get; } = new(); + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator ==(Group left, Group right) => Equals(left, right); + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator !=(Group left, Group right) => !Equals(left, right); + /// + /// Executes the Dispose operation. + /// public void Dispose() => Source.Dispose(); + /// + /// Executes the Edit operation. + /// + /// The editAction value. public void Edit(Action> editAction) => Source.Edit(editAction); + /// + /// Executes the Equals operation. + /// + /// The other value. + /// The result of the operation. public bool Equals(Group? other) { if (other is null) @@ -36,9 +80,22 @@ public bool Equals(Group? other) return EqualityComparer.Default.Equals(GroupKey, other.GroupKey); } + /// + /// Executes the Equals operation. + /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) => obj is Group value && Equals(value); + /// + /// Executes the GetHashCode operation. + /// + /// The result of the operation. public override int GetHashCode() => GroupKey is null ? 0 : EqualityComparer.Default.GetHashCode(GroupKey); + /// + /// Executes the ToString operation. + /// + /// The result of the operation. public override string ToString() => $"Group of {GroupKey} ({List.Count} records)"; } diff --git a/src/DynamicData/List/Internal/GroupOn.cs b/src/DynamicData/List/Internal/GroupOn.cs index 6ea7deccd..6ecd2883a 100644 --- a/src/DynamicData/List/Internal/GroupOn.cs +++ b/src/DynamicData/List/Internal/GroupOn.cs @@ -1,23 +1,45 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; - +#endif + +/// +/// Provides members for the GroupOn class. +/// +/// The type of the TObject value. +/// The type of the TGroupKey value. +/// The source value. +/// The groupSelector value. +/// The regrouper value. internal sealed class GroupOn(IObservable> source, Func groupSelector, IObservable? regrouper) where TObject : notnull where TGroupKey : notnull { + /// + /// The _groupSelector field. + /// private readonly Func _groupSelector = groupSelector ?? throw new ArgumentNullException(nameof(groupSelector)); + /// + /// The _regrouper field. + /// private readonly IObservable? _regrouper = regrouper; + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable>> Run() => Observable.Create>>( observer => { @@ -42,6 +64,12 @@ public IObservable>> Run() => Observable.C return new CompositeDisposable(publisher, shared.Connect()); }); + /// + /// Executes the GetCache operation. + /// + /// The groupCaches value. + /// The key value. + /// The result of the operation. private static GroupWithAddIndicator GetCache(IDictionary> groupCaches, TGroupKey key) { var cache = groupCaches.Lookup(key); @@ -55,6 +83,13 @@ private static GroupWithAddIndicator GetCache(IDictionary + /// Executes the Process operation. + /// + /// The result value. + /// The groupCollection value. + /// The changes value. + /// The result of the operation. private static IChangeSet> Process(ChangeAwareList> result, IDictionary> groupCollection, IChangeSet changes) { foreach (var grouping in changes.Unified().GroupBy(change => change.Current.Group)) @@ -180,6 +215,13 @@ private static IChangeSet> Process(ChangeAwareList + /// Executes the Regroup operation. + /// + /// The result value. + /// The groupCollection value. + /// The currentItems value. + /// The result of the operation. private IChangeSet> Regroup(ChangeAwareList> result, IDictionary> groupCollection, IReadOnlyCollection currentItems) { // TODO: We need to update ItemWithValue> @@ -220,8 +262,16 @@ private IChangeSet> Regroup(ChangeAwareList +/// Represents the GroupWithAddIndicator value. +/// +private readonly struct GroupWithAddIndicator { + /// + /// Initializes a new instance of the struct. + /// + /// The group value. + /// The wasCreated value. public GroupWithAddIndicator(Group group, bool wasCreated) : this() { @@ -229,31 +279,57 @@ public GroupWithAddIndicator(Group group, bool wasCreated) WasCreated = wasCreated; } + /// + /// Gets the Group value. + /// public Group Group { get; } + /// + /// Gets the WasCreated value. + /// public bool WasCreated { get; } } - private sealed class ItemWithGroupKey(TObject item, TGroupKey group, Optional previousGroup) : IEquatable +/// +/// Provides members for the ItemWithGroupKey class. +/// +/// The item value. +/// The group value. +/// The previousGroup value. +private sealed class ItemWithGroupKey(TObject item, TGroupKey group, ReactiveUI.Primitives.Optional previousGroup) : IEquatable { + /// + /// Gets or sets the Group value. + /// public TGroupKey Group { get; set; } = group; + /// + /// Gets the Item value. + /// public TObject Item { get; } = item; - public Optional PreviousGroup { get; } = previousGroup; + /// + /// Gets the PreviousGroup value. + /// + public ReactiveUI.Primitives.Optional PreviousGroup { get; } = previousGroup; - /// Returns a value that indicates whether the values of two objects are equal. + /// Returns a value that indicates whether the values of two GroupOn<TObject, TGroupKey>.ItemWithGroupKey objects are equal. /// The first value to compare. /// The second value to compare. /// true if the and parameters have the same value; otherwise, false. public static bool operator ==(ItemWithGroupKey left, ItemWithGroupKey right) => Equals(left, right); - /// Returns a value that indicates whether two objects have different values. + /// Returns a value that indicates whether two GroupOn<TObject, TGroupKey>.ItemWithGroupKey objects have different values. /// The first value to compare. /// The second value to compare. /// true if and are not equal; otherwise, false. public static bool operator !=(ItemWithGroupKey left, ItemWithGroupKey right) => !Equals(left, right); + /// + /// Executes the Equals operation. + /// + /// The other value. + /// The result of the operation. public bool Equals(ItemWithGroupKey? other) { if (other is null) @@ -269,6 +345,11 @@ public bool Equals(ItemWithGroupKey? other) return EqualityComparer.Default.Equals(Item, other.Item); } + /// + /// Executes the Equals operation. + /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) { if (obj is null) @@ -284,8 +365,16 @@ public override bool Equals(object? obj) return obj is ItemWithGroupKey value && Equals(value); } + /// + /// Executes the GetHashCode operation. + /// + /// The result of the operation. public override int GetHashCode() => Item is null ? 0 : EqualityComparer.Default.GetHashCode(Item); + /// + /// Executes the ToString operation. + /// + /// The result of the operation. public override string ToString() => $"{Item} ({Group})"; } } diff --git a/src/DynamicData/List/Internal/GroupOnImmutable.cs b/src/DynamicData/List/Internal/GroupOnImmutable.cs index 2b4a08be8..377463b00 100644 --- a/src/DynamicData/List/Internal/GroupOnImmutable.cs +++ b/src/DynamicData/List/Internal/GroupOnImmutable.cs @@ -1,23 +1,45 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; - +#endif + +/// +/// Provides members for the GroupOnImmutable class. +/// +/// The type of the TObject value. +/// The type of the TGroupKey value. +/// The source value. +/// The groupSelector value. +/// The reGrouper value. internal sealed class GroupOnImmutable(IObservable> source, Func groupSelector, IObservable? reGrouper) where TObject : notnull where TGroupKey : notnull { + /// + /// The _groupSelector field. + /// private readonly Func _groupSelector = groupSelector ?? throw new ArgumentNullException(nameof(groupSelector)); + /// + /// The _reGrouper field. + /// private readonly IObservable? _reGrouper = reGrouper; + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable>> Run() => Observable.Create>>( observer => { @@ -44,6 +66,13 @@ public IObservable>> Run() => Observabl return new CompositeDisposable(publisher, shared.Connect()); }); + /// + /// Executes the CreateChangeSet operation. + /// + /// The result value. + /// The allGroupings value. + /// The initialStateOfGroups value. + /// The result of the operation. private static IChangeSet> CreateChangeSet(ChangeAwareList> result, IDictionary allGroupings, IDictionary> initialStateOfGroups) { // Now maintain target list @@ -77,6 +106,12 @@ private static IChangeSet> CreateChangeSet(ChangeA return result.CaptureChanges(); } + /// + /// Executes the GetGroup operation. + /// + /// The groupCaches value. + /// The key value. + /// The result of the operation. private static GroupContainer GetGroup(IDictionary groupCaches, TGroupKey key) { var cached = groupCaches.Lookup(key); @@ -90,10 +125,28 @@ private static GroupContainer GetGroup(IDictionary gr return newcache; } + /// + /// Executes the GetGroupState operation. + /// + /// The grouping value. + /// The result of the operation. private static ImmutableGroup GetGroupState(GroupContainer grouping) => new(grouping.Key, grouping.List); + /// + /// Executes the GetGroupState operation. + /// + /// The key value. + /// The list value. + /// The result of the operation. private static ImmutableGroup GetGroupState(TGroupKey key, IList list) => new(key, list); + /// + /// Executes the Process operation. + /// + /// The result value. + /// The allGroupings value. + /// The changes value. + /// The result of the operation. private static IChangeSet> Process(ChangeAwareList> result, IDictionary allGroupings, IChangeSet changes) { // need to keep track of effected groups to calculate correct notifications @@ -211,6 +264,13 @@ void GetInitialState() return CreateChangeSet(result, allGroupings, initialStateOfGroups); } + /// + /// Executes the Regroup operation. + /// + /// The result value. + /// The allGroupings value. + /// The currentItems value. + /// The result of the operation. private IChangeSet> Regroup(ChangeAwareList> result, IDictionary allGroupings, IReadOnlyCollection currentItems) { var initialStateOfGroups = new Dictionary>(); @@ -250,25 +310,67 @@ private IChangeSet> Regroup(ChangeAwareList +/// Provides members for the GroupContainer class. +/// +/// The key value. +private sealed class GroupContainer(TGroupKey key) { + /// + /// Gets the Key value. + /// public TGroupKey Key { get; } = key; + /// + /// Gets the List value. + /// public IList List { get; } = new List(); } - private sealed class ItemWithGroupKey(TObject item, TGroupKey group, Optional previousGroup) : IEquatable +/// +/// Provides members for the ItemWithGroupKey class. +/// +/// The item value. +/// The group value. +/// The previousGroup value. +private sealed class ItemWithGroupKey(TObject item, TGroupKey group, ReactiveUI.Primitives.Optional previousGroup) : IEquatable { + /// + /// Gets or sets the Group value. + /// public TGroupKey Group { get; set; } = group; + /// + /// Gets the Item value. + /// public TObject Item { get; } = item; - public Optional PreviousGroup { get; } = previousGroup; - + /// + /// Gets the PreviousGroup value. + /// + public ReactiveUI.Primitives.Optional PreviousGroup { get; } = previousGroup; + + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator ==(ItemWithGroupKey left, ItemWithGroupKey right) => Equals(left, right); + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator !=(ItemWithGroupKey left, ItemWithGroupKey right) => !Equals(left, right); + /// + /// Executes the Equals operation. + /// + /// The other value. + /// The result of the operation. public bool Equals(ItemWithGroupKey? other) { if (other is null) @@ -284,10 +386,23 @@ public bool Equals(ItemWithGroupKey? other) return EqualityComparer.Default.Equals(Item, other.Item); } + /// + /// Executes the Equals operation. + /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) => obj is ItemWithGroupKey value && Equals(value); + /// + /// Executes the GetHashCode operation. + /// + /// The result of the operation. public override int GetHashCode() => Item is null ? 0 : EqualityComparer.Default.GetHashCode(Item); + /// + /// Executes the ToString operation. + /// + /// The result of the operation. public override string ToString() => $"{Item} ({Group})"; } } diff --git a/src/DynamicData/List/Internal/GroupOnProperty.cs b/src/DynamicData/List/Internal/GroupOnProperty.cs index ab5998aed..433247ce5 100644 --- a/src/DynamicData/List/Internal/GroupOnProperty.cs +++ b/src/DynamicData/List/Internal/GroupOnProperty.cs @@ -1,21 +1,43 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.ComponentModel; using System.Linq.Expressions; -using System.Reactive.Concurrency; -using System.Reactive.Linq; +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the GroupOnProperty class. +/// +/// The type of the TObject value. +/// The type of the TGroup value. +/// The source value. +/// The groupSelectorKey value. +/// The throttle value. +/// The scheduler value. internal sealed class GroupOnProperty(IObservable> source, Expression> groupSelectorKey, TimeSpan? throttle = null, IScheduler? scheduler = null) where TObject : INotifyPropertyChanged where TGroup : notnull { + /// + /// The _groupSelector field. + /// private readonly Func _groupSelector = groupSelectorKey.Compile(); + + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable>> Run() => _source.Publish( shared => { diff --git a/src/DynamicData/List/Internal/GroupOnPropertyWithImmutableState.cs b/src/DynamicData/List/Internal/GroupOnPropertyWithImmutableState.cs index 99d8b1d75..a1803817f 100644 --- a/src/DynamicData/List/Internal/GroupOnPropertyWithImmutableState.cs +++ b/src/DynamicData/List/Internal/GroupOnPropertyWithImmutableState.cs @@ -1,21 +1,43 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.ComponentModel; using System.Linq.Expressions; -using System.Reactive.Concurrency; -using System.Reactive.Linq; +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the GroupOnPropertyWithImmutableState class. +/// +/// The type of the TObject value. +/// The type of the TGroup value. +/// The source value. +/// The groupSelectorKey value. +/// The throttle value. +/// The scheduler value. internal sealed class GroupOnPropertyWithImmutableState(IObservable> source, Expression> groupSelectorKey, TimeSpan? throttle = null, IScheduler? scheduler = null) where TObject : INotifyPropertyChanged where TGroup : notnull { + /// + /// The _groupSelector field. + /// private readonly Func _groupSelector = groupSelectorKey.Compile(); + + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable>> Run() => _source.Publish( shared => { diff --git a/src/DynamicData/List/Internal/ImmutableGroup.cs b/src/DynamicData/List/Internal/ImmutableGroup.cs index 3ad76d595..784695796 100644 --- a/src/DynamicData/List/Internal/ImmutableGroup.cs +++ b/src/DynamicData/List/Internal/ImmutableGroup.cs @@ -1,29 +1,73 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the ImmutableGroup class. +/// +/// The type of the TObject value. +/// The type of the TGroupKey value. internal sealed class ImmutableGroup : IGrouping, IEquatable> { + /// + /// The _items field. + /// private readonly IReadOnlyCollection _items; + /// + /// Initializes a new instance of the class. + /// + /// The key value. + /// The items value. internal ImmutableGroup(TGroupKey key, IList items) { Key = key; _items = new ReadOnlyCollectionLight(items); } + /// + /// Gets the Count value. + /// public int Count => _items.Count; + /// + /// Gets the Items value. + /// public IEnumerable Items => _items; + /// + /// Gets the Key value. + /// public TGroupKey Key { get; } + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator ==(ImmutableGroup left, ImmutableGroup right) => Equals(left, right); + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator !=(ImmutableGroup left, ImmutableGroup right) => !Equals(left, right); + /// + /// Executes the Equals operation. + /// + /// The other value. + /// The result of the operation. public bool Equals(ImmutableGroup? other) { if (other is null) @@ -39,6 +83,11 @@ public bool Equals(ImmutableGroup? other) return EqualityComparer.Default.Equals(Key, other.Key); } + /// + /// Executes the Equals operation. + /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) { if (obj is null) @@ -54,7 +103,15 @@ public override bool Equals(object? obj) return obj is ImmutableGroup value && Equals(value); } + /// + /// Executes the GetHashCode operation. + /// + /// The result of the operation. public override int GetHashCode() => Key is null ? 0 : EqualityComparer.Default.GetHashCode(Key); + /// + /// Executes the ToString operation. + /// + /// The result of the operation. public override string ToString() => $"Grouping for: {Key} ({Count} items)"; } diff --git a/src/DynamicData/List/Internal/LimitSizeTo.cs b/src/DynamicData/List/Internal/LimitSizeTo.cs index c2ed74896..ba04dfc75 100644 --- a/src/DynamicData/List/Internal/LimitSizeTo.cs +++ b/src/DynamicData/List/Internal/LimitSizeTo.cs @@ -1,23 +1,53 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Concurrency; -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; - +#endif #if NET9_0_OR_GREATER + +/// +/// Provides members for the LimitSizeTo class. +/// +/// The type of the T value. +/// The sourceList value. +/// The sizeLimit value. +/// The scheduler value. +/// The locker value. internal sealed class LimitSizeTo(ISourceList sourceList, int sizeLimit, IScheduler scheduler, Lock locker) #else + +/// +/// Provides members for the LimitSizeTo class. +/// +/// The type of the T value. +/// The sourceList value. +/// The sizeLimit value. +/// The scheduler value. +/// The locker value. internal sealed class LimitSizeTo(ISourceList sourceList, int sizeLimit, IScheduler scheduler, object locker) #endif where T : notnull { + /// + /// The _scheduler field. + /// private readonly IScheduler _scheduler = scheduler ?? throw new ArgumentNullException(nameof(scheduler)); + + /// + /// The _sourceList field. + /// private readonly ISourceList _sourceList = sourceList ?? throw new ArgumentNullException(nameof(sourceList)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() { var emptyResult = new List(); diff --git a/src/DynamicData/List/Internal/MergeChangeSets.cs b/src/DynamicData/List/Internal/MergeChangeSets.cs index 480c69b3a..3c227a00f 100644 --- a/src/DynamicData/List/Internal/MergeChangeSets.cs +++ b/src/DynamicData/List/Internal/MergeChangeSets.cs @@ -1,23 +1,39 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Concurrency; -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif /// /// Operator that is similiar to Merge but intelligently handles List ChangeSets. /// +/// The type of the TObject value. +/// The source value. +/// The equalityComparer value. internal sealed class MergeChangeSets(IObservable>> source, IEqualityComparer? equalityComparer) where TObject : notnull { + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The equalityComparer value. + /// The completable value. + /// The scheduler value. public MergeChangeSets(IEnumerable>> source, IEqualityComparer? equalityComparer, bool completable, IScheduler? scheduler = null) : this(CreateObservable(source, completable, scheduler), equalityComparer) { } + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -36,6 +52,13 @@ public IObservable> Run() => Observable.Create + /// Executes the CreateObservable operation. + /// + /// The source value. + /// The completable value. + /// The scheduler value. + /// The result of the operation. private static IObservable>> CreateObservable(IEnumerable>> source, bool completable, IScheduler? scheduler) { var obs = (scheduler != null) ? source.ToObservable(scheduler) : source.ToObservable(); @@ -47,20 +70,45 @@ private static IObservable>> CreateObservable(IE return obs; } - // Can optimize for the Add case because that's the only one that applies #if NET9_0_OR_GREATER + + /// + /// Executes the CreateChange operation. + /// + /// The source value. + /// The locker value. + /// The result of the operation. private Change> CreateChange(IObservable> source, Lock locker) => new(ListChangeReason.Add, new ClonedListChangeSet(source.Synchronize(locker), equalityComparer)); - // Create a ChangeSet Observable that produces ChangeSets with a single Add event for each new sub-observable + + /// + /// Executes the CreateClonedListObservable operation. + /// + /// The source value. + /// The locker value. + /// The result of the operation. private IObservable>> CreateClonedListObservable(IObservable>> source, Lock locker) => source.Select(src => new ChangeSet>(new[] { CreateChange(src, locker) })); #else + + /// + /// Executes the CreateChange operation. + /// + /// The source value. + /// The locker value. + /// The result of the operation. private Change> CreateChange(IObservable> source, object locker) => new(ListChangeReason.Add, new ClonedListChangeSet(source.Synchronize(locker), equalityComparer)); - // Create a ChangeSet Observable that produces ChangeSets with a single Add event for each new sub-observable + + /// + /// Executes the CreateClonedListObservable operation. + /// + /// The source value. + /// The locker value. + /// The result of the operation. private IObservable>> CreateClonedListObservable(IObservable>> source, object locker) => source.Select(src => new ChangeSet>(new[] { CreateChange(src, locker) })); #endif diff --git a/src/DynamicData/List/Internal/MergeMany.cs b/src/DynamicData/List/Internal/MergeMany.cs index 65b238068..5d1fbb298 100644 --- a/src/DynamicData/List/Internal/MergeMany.cs +++ b/src/DynamicData/List/Internal/MergeMany.cs @@ -1,53 +1,127 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the MergeMany class. +/// +/// The type of the T value. +/// The type of the TDestination value. +/// The source value. +/// The observableSelector value. internal sealed class MergeMany(IObservable> source, Func> observableSelector) where T : notnull { + /// + /// The _observableSelector field. + /// private readonly Func> _observableSelector = observableSelector ?? throw new ArgumentNullException(nameof(observableSelector)); + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable Run() => Observable.Create( observer => { var counter = new SubscriptionCounter(); var locker = InternalEx.NewLock(); var disposable = _source.Concat(counter.DeferCleanup) - .SubscribeMany(t => - { - counter.Added(); - return _observableSelector(t).Synchronize(locker).Finally(() => counter.Finally()).Subscribe(observer.OnNext, _ => { }, () => { }); - }) + .SubscribeMany(t => SubscribeChild(t, locker, counter, observer)) .Subscribe(_ => { }, observer.OnError, observer.OnCompleted); return new CompositeDisposable(disposable, counter); }); +#if NET9_0_OR_GREATER - private sealed class SubscriptionCounter : IDisposable + /// + /// Executes the SubscribeChild operation. + /// + /// The item value. + /// The locker value. + /// The counter value. + /// The observer value. + /// The result of the operation. + private IDisposable SubscribeChild(T item, Lock locker, SubscriptionCounter counter, IObserver observer) +#else + + /// + /// Executes the SubscribeChild operation. + /// + /// The item value. + /// The locker value. + /// The counter value. + /// The observer value. + /// The result of the operation. + private IDisposable SubscribeChild(T item, object locker, SubscriptionCounter counter, IObserver observer) +#endif { - private readonly Subject> _subject = new(); + counter.Added(); + try + { + return _observableSelector(item).Synchronize(locker).Finally(counter.Finally).Subscribe(observer.OnNext, _ => { }, () => { }); + } + catch (ObjectDisposedException) + { + counter.Finally(); + return Disposable.Empty; + } + } + +/// +/// Provides members for the SubscriptionCounter class. +/// +private sealed class SubscriptionCounter : IDisposable + { + /// + /// The _subject field. + /// + private readonly Signal> _subject = new(); + + /// + /// The _subscriptionCount field. + /// private int _subscriptionCount = 1; + /// + /// Gets the DeferCleanup value. + /// public IObservable> DeferCleanup => Observable.Defer(() => { CheckCompleted(); return _subject.AsObservable(); }); + /// + /// Executes the Added operation. + /// public void Added() => _ = Interlocked.Increment(ref _subscriptionCount); + /// + /// Executes the Finally operation. + /// public void Finally() => CheckCompleted(); + /// + /// Executes the Dispose operation. + /// public void Dispose() => _subject.Dispose(); + /// + /// Executes the CheckCompleted operation. + /// private void CheckCompleted() { if (Interlocked.Decrement(ref _subscriptionCount) == 0) diff --git a/src/DynamicData/List/Internal/MergeManyCacheChangeSets.cs b/src/DynamicData/List/Internal/MergeManyCacheChangeSets.cs index 3b63ea231..2e9ef53ee 100644 --- a/src/DynamicData/List/Internal/MergeManyCacheChangeSets.cs +++ b/src/DynamicData/List/Internal/MergeManyCacheChangeSets.cs @@ -1,58 +1,410 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +using DynamicData.Reactive.Internal; +#else -using System.Reactive.Disposables; -using System.Reactive.Linq; using DynamicData.Cache.Internal; using DynamicData.Internal; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif /// /// Operator that is similiar to MergeMany but intelligently handles Cache ChangeSets. /// +/// The type of the TObject value. +/// The type of the TDestination value. +/// The type of the TDestinationKey value. +/// The source value. +/// The changeSetSelector value. +/// The equalityComparer value. +/// The comparer value. internal sealed class MergeManyCacheChangeSets(IObservable> source, Func>> changeSetSelector, IEqualityComparer? equalityComparer, IComparer? comparer) where TObject : notnull where TDestination : notnull where TDestinationKey : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( - observer => - { - var locker = InternalEx.NewLock(); - var list = new List>(); - var parentUpdate = false; - - // This is manages all of the changes - var changeTracker = new ChangeSetMergeTracker(() => list, comparer, equalityComparer); - - // Transform to a list changeset of child caches, synchronize, update the local copy, and publish. - var shared = source - .Transform(obj => new ChangeSetCache(changeSetSelector(obj).Synchronize(locker))) - .Synchronize(locker) - .Do(list.Clone) - .Do(_ => parentUpdate = true) - .Publish(); - - // Merge the child changeset changes together and apply to the tracker - var subMergeMany = shared - .MergeMany(chanceSetCache => chanceSetCache.Source) - .SubscribeSafe( - changes => changeTracker.ProcessChangeSet(changes, !parentUpdate ? observer : null), - observer.OnError, - observer.OnCompleted); - - // When a source item is removed, all of its sub-items need to be removed - var subRemove = shared - .OnItemRemoved(changeSetCache => changeTracker.RemoveItems(changeSetCache.Cache.KeyValues), invokeOnUnsubscribe: false) - .Do(_ => + observer => new Subscription(source, changeSetSelector, observer, equalityComparer, comparer)); + + /// + /// Maintains state for a single subscription. + /// + private sealed class Subscription : CacheParentSubscription, int, IChangeSet, IChangeSet> + { + /// + /// The _cache field. + /// + private readonly Cache, int> _cache = new(); + + /// + /// The _changeSetMergeTracker field. + /// + private readonly ChangeSetMergeTracker _changeSetMergeTracker; + + /// + /// The _changeSetSelector field. + /// + private readonly Func>> _changeSetSelector; + + /// + /// The _parents field. + /// + private readonly List _parents = []; + + /// + /// The _nextParentKey field. + /// + private int _nextParentKey; + + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The changeSetSelector value. + /// The observer value. + /// The equalityComparer value. + /// The comparer value. + public Subscription( + IObservable> source, + Func>> changeSetSelector, + IObserver> observer, + IEqualityComparer? equalityComparer, + IComparer? comparer) + : base(observer) + { + _changeSetSelector = changeSetSelector; + _changeSetMergeTracker = new(() => _cache.Items, comparer, equalityComparer); + + CreateParentSubscription(source.Select(ConvertParentChanges).Where(static changes => changes.Count != 0)); + } + + /// + /// Executes the ParentOnNext operation. + /// + /// The changes value. + protected override void ParentOnNext(IChangeSet, int> changes) + { + foreach (var change in changes.ToConcreteType()) + { + switch (change.Reason) + { + case ChangeReason.Add or ChangeReason.Update: + _cache.AddOrUpdate(change.Current, change.Key); + AddChildSubscription(change.Current.Source, change.Key); + if (change.Previous.HasValue) + { + _changeSetMergeTracker.RemoveItems(change.Previous.Value.Cache.KeyValues); + } + + break; + + case ChangeReason.Remove: + _cache.Remove(change.Key); + _changeSetMergeTracker.RemoveItems(change.Current.Cache.KeyValues); + RemoveChildSubscription(change.Key); + break; + } + } + } + + /// + /// Executes the ChildOnNext operation. + /// + /// The changes value. + /// The parentKey value. + protected override void ChildOnNext(IChangeSet changes, int parentKey) => + _changeSetMergeTracker.ProcessChangeSet(changes, null); + + /// + /// Executes the EmitChanges operation. + /// + /// The observer value. + protected override void EmitChanges(IObserver> observer) => + _changeSetMergeTracker.EmitChanges(observer); + + /// + /// Executes the ConvertParentChanges operation. + /// + /// The changes value. + /// The result of the operation. + private IChangeSet, int> ConvertParentChanges(IChangeSet changes) + { + var results = new ChangeSet, int>(changes.Count); + + foreach (var change in changes) + { + switch (change.Reason) + { + case ListChangeReason.Add: + AddParent(change.Item.Current, change.Item.CurrentIndex, results); + break; + + case ListChangeReason.AddRange: + AddParents(change.Range, results); + break; + + case ListChangeReason.Remove: + RemoveParent(change.Item.Current, change.Item.CurrentIndex, results); + break; + + case ListChangeReason.RemoveRange: + RemoveParents(change.Range, results); + break; + + case ListChangeReason.Replace: + ReplaceParent(change.Item.Previous.Value, change.Item.Current, change.Item.PreviousIndex, change.Item.CurrentIndex, results); + break; + + case ListChangeReason.Clear: + RemoveAllParents(results); + break; + + case ListChangeReason.Moved: + MoveParent(change.Item.Current, change.Item.CurrentIndex, change.Item.PreviousIndex); + break; + } + } + + return results; + } + + /// + /// Executes the AddParents operation. + /// + /// The range value. + /// The changes value. + private void AddParents(RangeChange range, ChangeSet, int> changes) + { + var index = range.Index; + foreach (var item in range) + { + AddParent(item, index, changes); + if (index >= 0) + { + index++; + } + } + } + + /// + /// Executes the AddParent operation. + /// + /// The item value. + /// The index value. + /// The changes value. + private void AddParent(TObject item, int index, ChangeSet, int> changes) + { + var key = ++_nextParentKey; + var child = CreateChild(item); + var parent = new ParentItem(item, key, child); + var insertIndex = NormalizeAddIndex(index); + + _parents.Insert(insertIndex, parent); + changes.Add(new Change, int>(ChangeReason.Add, key, child, insertIndex)); + } + + /// + /// Executes the ReplaceParent operation. + /// + /// The previous value. + /// The current value. + /// The previousIndex value. + /// The currentIndex value. + /// The changes value. + private void ReplaceParent(TObject previous, TObject current, int previousIndex, int currentIndex, ChangeSet, int> changes) + { + var replaceIndex = NormalizeExistingIndex(previousIndex); + if (replaceIndex < 0 || !ReferenceEquals(_parents[replaceIndex].Source, previous)) + { + replaceIndex = FindParentIndex(previous); + } + + if (replaceIndex < 0) + { + AddParent(current, currentIndex, changes); + return; + } + + var existing = _parents[replaceIndex]; + var child = CreateChild(current); + var updated = new ParentItem(current, existing.Key, child); + _parents[replaceIndex] = updated; + + var destinationIndex = NormalizeReplacementIndex(currentIndex, replaceIndex); + if (destinationIndex != replaceIndex) + { + _parents.RemoveAt(replaceIndex); + _parents.Insert(destinationIndex, updated); + } + + changes.Add(new Change, int>(ChangeReason.Update, existing.Key, child, existing.Child, destinationIndex, replaceIndex)); + } + + /// + /// Executes the RemoveParents operation. + /// + /// The range value. + /// The changes value. + private void RemoveParents(RangeChange range, ChangeSet, int> changes) + { + if (range.Index >= 0) + { + for (var i = 0; i < range.Count; i++) + { + RemoveParentAt(range.Index, changes); + } + + return; + } + + foreach (var item in range) + { + RemoveParent(item, -1, changes); + } + } + + /// + /// Executes the RemoveParent operation. + /// + /// The item value. + /// The index value. + /// The changes value. + private void RemoveParent(TObject item, int index, ChangeSet, int> changes) + { + var removeIndex = NormalizeExistingIndex(index); + if (removeIndex < 0 || !ReferenceEquals(_parents[removeIndex].Source, item)) + { + removeIndex = FindParentIndex(item); + } + + if (removeIndex >= 0) + { + RemoveParentAt(removeIndex, changes); + } + } + + /// + /// Executes the RemoveParentAt operation. + /// + /// The index value. + /// The changes value. + private void RemoveParentAt(int index, ChangeSet, int> changes) + { + if (index < 0 || index >= _parents.Count) + { + return; + } + + var parent = _parents[index]; + _parents.RemoveAt(index); + changes.Add(new Change, int>(ChangeReason.Remove, parent.Key, parent.Child, index)); + } + + /// + /// Executes the RemoveAllParents operation. + /// + /// The changes value. + private void RemoveAllParents(ChangeSet, int> changes) + { + for (var i = _parents.Count - 1; i >= 0; i--) + { + RemoveParentAt(i, changes); + } + } + + /// + /// Executes the MoveParent operation. + /// + /// The item value. + /// The currentIndex value. + /// The previousIndex value. + private void MoveParent(TObject item, int currentIndex, int previousIndex) + { + var from = NormalizeExistingIndex(previousIndex); + if (from < 0 || !ReferenceEquals(_parents[from].Source, item)) + { + from = FindParentIndex(item); + } + + if (from < 0) + { + return; + } + + var parent = _parents[from]; + _parents.RemoveAt(from); + _parents.Insert(NormalizeAddIndex(currentIndex), parent); + } + + /// + /// Executes the FindParentIndex operation. + /// + /// The item value. + /// The result of the operation. + private int FindParentIndex(TObject item) + { + var comparer = EqualityComparer.Default; + for (var i = 0; i < _parents.Count; i++) + { + if (ReferenceEquals(_parents[i].Source, item) || comparer.Equals(_parents[i].Source, item)) { - changeTracker.EmitChanges(observer); - parentUpdate = false; - }) - .Subscribe(); + return i; + } + } + + return -1; + } + + /// + /// Executes the NormalizeAddIndex operation. + /// + /// The index value. + /// The result of the operation. + private int NormalizeAddIndex(int index) => index < 0 || index > _parents.Count ? _parents.Count : index; + + /// + /// Executes the NormalizeExistingIndex operation. + /// + /// The index value. + /// The result of the operation. + private int NormalizeExistingIndex(int index) => index >= 0 && index < _parents.Count ? index : -1; + + /// + /// Executes the NormalizeReplacementIndex operation. + /// + /// The index value. + /// The fallbackIndex value. + /// The result of the operation. + private int NormalizeReplacementIndex(int index, int fallbackIndex) => index >= 0 && index < _parents.Count ? index : fallbackIndex; + + /// + /// Executes the CreateChild operation. + /// + /// The item value. + /// The result of the operation. + private ChangeSetCache CreateChild(TObject item) => + new(MakeChildObservable(_changeSetSelector(item))); - return new CompositeDisposable(shared.Connect(), subMergeMany, subRemove); - }); + /// + /// Stores a source item and its child container. + /// + /// The Source value. + /// The Key value. + /// The Child value. + private sealed record ParentItem(TObject Source, int Key, ChangeSetCache Child); + } } diff --git a/src/DynamicData/List/Internal/MergeManyListChangeSets.cs b/src/DynamicData/List/Internal/MergeManyListChangeSets.cs index 482c4f46b..37a2e788f 100644 --- a/src/DynamicData/List/Internal/MergeManyListChangeSets.cs +++ b/src/DynamicData/List/Internal/MergeManyListChangeSets.cs @@ -1,54 +1,401 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Internal; +#else -using System.Reactive.Disposables; -using System.Reactive.Linq; using DynamicData.Internal; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif /// /// Operator that is similiar to MergeMany but intelligently handles List ChangeSets. /// +/// The type of the TObject value. +/// The type of the TDestination value. +/// The source value. +/// The selector value. +/// The equalityComparer value. internal sealed class MergeManyListChangeSets(IObservable> source, Func>> selector, IEqualityComparer? equalityComparer) where TObject : notnull where TDestination : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( - observer => - { - var locker = InternalEx.NewLock(); - var parentUpdate = false; - - // This is manages all of the changes - var changeTracker = new ChangeSetMergeTracker(); - - // Transform to a list changeset of child lists, synchronize, and publish. - var shared = source - .Transform(obj => new ClonedListChangeSet(selector(obj).Synchronize(locker), equalityComparer)) - .Synchronize(locker) - .Do(_ => parentUpdate = true) - .Publish(); - - // Merge the child changeset changes together and apply to the tracker - var subMergeMany = shared - .MergeMany(clonedList => clonedList.Source.RemoveIndex()) - .SubscribeSafe( - changes => changeTracker.ProcessChangeSet(changes, !parentUpdate ? observer : null), - observer.OnError, - observer.OnCompleted); - - // When a source item is removed, all of its sub-items need to be removed - var subRemove = shared - .OnItemRemoved(clonedList => changeTracker.RemoveItems(clonedList.List), invokeOnUnsubscribe: false) - .Do(_ => + observer => new Subscription(source, selector, observer, equalityComparer)); + + /// + /// Maintains state for a single subscription. + /// + private sealed class Subscription : CacheParentSubscription, int, IChangeSet, IChangeSet> + { + /// + /// The _changeSetMergeTracker field. + /// + private readonly ChangeSetMergeTracker _changeSetMergeTracker = new(); + + /// + /// The _parents field. + /// + private readonly List _parents = []; + + /// + /// The _selector field. + /// + private readonly Func>> _selector; + + /// + /// The _equalityComparer field. + /// + private readonly IEqualityComparer? _equalityComparer; + + /// + /// The _nextParentKey field. + /// + private int _nextParentKey; + + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The selector value. + /// The observer value. + /// The equalityComparer value. + public Subscription( + IObservable> source, + Func>> selector, + IObserver> observer, + IEqualityComparer? equalityComparer) + : base(observer) + { + _selector = selector; + _equalityComparer = equalityComparer; + + CreateParentSubscription(source.Select(ConvertParentChanges).Where(static changes => changes.Count != 0)); + } + + /// + /// Executes the ParentOnNext operation. + /// + /// The changes value. + protected override void ParentOnNext(IChangeSet, int> changes) + { + foreach (var change in changes.ToConcreteType()) + { + switch (change.Reason) + { + case ChangeReason.Add or ChangeReason.Update: + AddChildSubscription(change.Current.Source, change.Key); + if (change.Previous.HasValue) + { + _changeSetMergeTracker.RemoveItems(change.Previous.Value.List); + } + + break; + + case ChangeReason.Remove: + _changeSetMergeTracker.RemoveItems(change.Current.List); + RemoveChildSubscription(change.Key); + break; + } + } + } + + /// + /// Executes the ChildOnNext operation. + /// + /// The child value. + /// The parentKey value. + protected override void ChildOnNext(IChangeSet child, int parentKey) => + _changeSetMergeTracker.ProcessChangeSet(child, null); + + /// + /// Executes the EmitChanges operation. + /// + /// The observer value. + protected override void EmitChanges(IObserver> observer) => + _changeSetMergeTracker.EmitChanges(observer); + + /// + /// Executes the ConvertParentChanges operation. + /// + /// The changes value. + /// The result of the operation. + private IChangeSet, int> ConvertParentChanges(IChangeSet changes) + { + var results = new ChangeSet, int>(changes.Count); + + foreach (var change in changes) + { + switch (change.Reason) + { + case ListChangeReason.Add: + AddParent(change.Item.Current, change.Item.CurrentIndex, results); + break; + + case ListChangeReason.AddRange: + AddParents(change.Range, results); + break; + + case ListChangeReason.Remove: + RemoveParent(change.Item.Current, change.Item.CurrentIndex, results); + break; + + case ListChangeReason.RemoveRange: + RemoveParents(change.Range, results); + break; + + case ListChangeReason.Replace: + ReplaceParent(change.Item.Previous.Value, change.Item.Current, change.Item.PreviousIndex, change.Item.CurrentIndex, results); + break; + + case ListChangeReason.Clear: + RemoveAllParents(results); + break; + + case ListChangeReason.Moved: + MoveParent(change.Item.Current, change.Item.CurrentIndex, change.Item.PreviousIndex); + break; + } + } + + return results; + } + + /// + /// Executes the AddParents operation. + /// + /// The range value. + /// The changes value. + private void AddParents(RangeChange range, ChangeSet, int> changes) + { + var index = range.Index; + foreach (var item in range) + { + AddParent(item, index, changes); + if (index >= 0) + { + index++; + } + } + } + + /// + /// Executes the AddParent operation. + /// + /// The item value. + /// The index value. + /// The changes value. + private void AddParent(TObject item, int index, ChangeSet, int> changes) + { + var key = ++_nextParentKey; + var child = CreateChild(item); + var parent = new ParentItem(item, key, child); + var insertIndex = NormalizeAddIndex(index); + + _parents.Insert(insertIndex, parent); + changes.Add(new Change, int>(ChangeReason.Add, key, child, insertIndex)); + } + + /// + /// Executes the ReplaceParent operation. + /// + /// The previous value. + /// The current value. + /// The previousIndex value. + /// The currentIndex value. + /// The changes value. + private void ReplaceParent(TObject previous, TObject current, int previousIndex, int currentIndex, ChangeSet, int> changes) + { + var replaceIndex = NormalizeExistingIndex(previousIndex); + if (replaceIndex < 0 || !ReferenceEquals(_parents[replaceIndex].Source, previous)) + { + replaceIndex = FindParentIndex(previous); + } + + if (replaceIndex < 0) + { + AddParent(current, currentIndex, changes); + return; + } + + var existing = _parents[replaceIndex]; + var child = CreateChild(current); + var updated = new ParentItem(current, existing.Key, child); + _parents[replaceIndex] = updated; + + var destinationIndex = NormalizeReplacementIndex(currentIndex, replaceIndex); + if (destinationIndex != replaceIndex) + { + _parents.RemoveAt(replaceIndex); + _parents.Insert(destinationIndex, updated); + } + + changes.Add(new Change, int>(ChangeReason.Update, existing.Key, child, existing.Child, destinationIndex, replaceIndex)); + } + + /// + /// Executes the RemoveParents operation. + /// + /// The range value. + /// The changes value. + private void RemoveParents(RangeChange range, ChangeSet, int> changes) + { + if (range.Index >= 0) + { + for (var i = 0; i < range.Count; i++) + { + RemoveParentAt(range.Index, changes); + } + + return; + } + + foreach (var item in range) + { + RemoveParent(item, -1, changes); + } + } + + /// + /// Executes the RemoveParent operation. + /// + /// The item value. + /// The index value. + /// The changes value. + private void RemoveParent(TObject item, int index, ChangeSet, int> changes) + { + var removeIndex = NormalizeExistingIndex(index); + if (removeIndex < 0 || !ReferenceEquals(_parents[removeIndex].Source, item)) + { + removeIndex = FindParentIndex(item); + } + + if (removeIndex >= 0) + { + RemoveParentAt(removeIndex, changes); + } + } + + /// + /// Executes the RemoveParentAt operation. + /// + /// The index value. + /// The changes value. + private void RemoveParentAt(int index, ChangeSet, int> changes) + { + if (index < 0 || index >= _parents.Count) + { + return; + } + + var parent = _parents[index]; + _parents.RemoveAt(index); + changes.Add(new Change, int>(ChangeReason.Remove, parent.Key, parent.Child, index)); + } + + /// + /// Executes the RemoveAllParents operation. + /// + /// The changes value. + private void RemoveAllParents(ChangeSet, int> changes) + { + for (var i = _parents.Count - 1; i >= 0; i--) + { + RemoveParentAt(i, changes); + } + } + + /// + /// Executes the MoveParent operation. + /// + /// The item value. + /// The currentIndex value. + /// The previousIndex value. + private void MoveParent(TObject item, int currentIndex, int previousIndex) + { + var from = NormalizeExistingIndex(previousIndex); + if (from < 0 || !ReferenceEquals(_parents[from].Source, item)) + { + from = FindParentIndex(item); + } + + if (from < 0) + { + return; + } + + var parent = _parents[from]; + _parents.RemoveAt(from); + _parents.Insert(NormalizeAddIndex(currentIndex), parent); + } + + /// + /// Executes the FindParentIndex operation. + /// + /// The item value. + /// The result of the operation. + private int FindParentIndex(TObject item) + { + var comparer = EqualityComparer.Default; + for (var i = 0; i < _parents.Count; i++) + { + if (ReferenceEquals(_parents[i].Source, item) || comparer.Equals(_parents[i].Source, item)) { - changeTracker.EmitChanges(observer); - parentUpdate = false; - }) - .Subscribe(); + return i; + } + } + + return -1; + } + + /// + /// Executes the NormalizeAddIndex operation. + /// + /// The index value. + /// The result of the operation. + private int NormalizeAddIndex(int index) => index < 0 || index > _parents.Count ? _parents.Count : index; + + /// + /// Executes the NormalizeExistingIndex operation. + /// + /// The index value. + /// The result of the operation. + private int NormalizeExistingIndex(int index) => index >= 0 && index < _parents.Count ? index : -1; + + /// + /// Executes the NormalizeReplacementIndex operation. + /// + /// The index value. + /// The fallbackIndex value. + /// The result of the operation. + private int NormalizeReplacementIndex(int index, int fallbackIndex) => index >= 0 && index < _parents.Count ? index : fallbackIndex; + + /// + /// Executes the CreateChild operation. + /// + /// The item value. + /// The result of the operation. + private ClonedListChangeSet CreateChild(TObject item) => + new(MakeChildObservable(_selector(item).RemoveIndex()), _equalityComparer); - return new CompositeDisposable(shared.Connect(), subMergeMany, subRemove); - }); + /// + /// Stores a source item and its child container. + /// + /// The Source value. + /// The Key value. + /// The Child value. + private sealed record ParentItem(TObject Source, int Key, ClonedListChangeSet Child); + } } diff --git a/src/DynamicData/List/Internal/OnItemAdded.cs b/src/DynamicData/List/Internal/OnItemAdded.cs index c99b48d92..b61e05b9f 100644 --- a/src/DynamicData/List/Internal/OnItemAdded.cs +++ b/src/DynamicData/List/Internal/OnItemAdded.cs @@ -1,41 +1,54 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. -// 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.Reactive.Linq; - -namespace DynamicData.List.Internal; - -internal static class OnItemAdded - where T : notnull -{ - public static IObservable> Create( - IObservable> source, - Action addAction) - { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - addAction.ThrowArgumentNullExceptionIfNull(nameof(addAction)); - - return source.Do(changeSet => - { - foreach (var change in changeSet) - { - switch (change.Reason) - { - case ListChangeReason.Add: - addAction.Invoke(change.Item.Current); - break; - - case ListChangeReason.AddRange: - foreach (var item in change.Range) - addAction.Invoke(item); - break; - - case ListChangeReason.Replace: - addAction.Invoke(change.Item.Current); - break; - } - } - }); - } -} +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Roland Pheasant licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else + +namespace DynamicData.List.Internal; +#endif + +/// +/// Provides members for the OnItemAdded class. +/// +/// The type of the T value. +internal static class OnItemAdded + where T : notnull +{ + /// + /// Executes the Create operation. + /// + /// The source value. + /// The addAction value. + /// The result of the operation. + public static IObservable> Create( + IObservable> source, + Action addAction) + { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(addAction); + + return source.Do(changeSet => + { + foreach (var change in changeSet) + { + switch (change.Reason) + { + case ListChangeReason.Add: + addAction.Invoke(change.Item.Current); + break; + + case ListChangeReason.AddRange: + foreach (var item in change.Range) + addAction.Invoke(item); + break; + + case ListChangeReason.Replace: + addAction.Invoke(change.Item.Current); + break; + } + } + }); + } +} diff --git a/src/DynamicData/List/Internal/OnItemRefreshed.cs b/src/DynamicData/List/Internal/OnItemRefreshed.cs index f12e68294..89cf81bd2 100644 --- a/src/DynamicData/List/Internal/OnItemRefreshed.cs +++ b/src/DynamicData/List/Internal/OnItemRefreshed.cs @@ -1,28 +1,41 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. -// 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.Reactive.Linq; - -namespace DynamicData.List.Internal; - -internal static class OnItemRefreshed - where T : notnull -{ - public static IObservable> Create( - IObservable> source, - Action refreshAction) - { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - refreshAction.ThrowArgumentNullExceptionIfNull(nameof(refreshAction)); - - return source.Do(changeSet => - { - foreach (var change in changeSet) - { - if (change.Reason is ListChangeReason.Refresh) - refreshAction.Invoke(change.Item.Current); - } - }); - } -} +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Roland Pheasant licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else + +namespace DynamicData.List.Internal; +#endif + +/// +/// Provides members for the OnItemRefreshed class. +/// +/// The type of the T value. +internal static class OnItemRefreshed + where T : notnull +{ + /// + /// Executes the Create operation. + /// + /// The source value. + /// The refreshAction value. + /// The result of the operation. + public static IObservable> Create( + IObservable> source, + Action refreshAction) + { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(refreshAction); + + return source.Do(changeSet => + { + foreach (var change in changeSet) + { + if (change.Reason is ListChangeReason.Refresh) + refreshAction.Invoke(change.Item.Current); + } + }); + } +} diff --git a/src/DynamicData/List/Internal/OnItemRemoved.cs b/src/DynamicData/List/Internal/OnItemRemoved.cs index 178066bf2..31582e3f4 100644 --- a/src/DynamicData/List/Internal/OnItemRemoved.cs +++ b/src/DynamicData/List/Internal/OnItemRemoved.cs @@ -1,59 +1,73 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. -// 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.Reactive.Linq; - -namespace DynamicData.List.Internal; - -internal static class OnItemRemoved - where T : notnull -{ - public static IObservable> Create( - IObservable> source, - Action removeAction, - bool invokeOnUnsubscribe) - { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - removeAction.ThrowArgumentNullExceptionIfNull(nameof(removeAction)); - - var removalProcessor = source.Do(changeSet => - { - foreach (var change in changeSet) - { - switch (change.Reason) - { - case ListChangeReason.Clear: - case ListChangeReason.RemoveRange: - foreach (var item in change.Range) - removeAction.Invoke(item); - break; - - case ListChangeReason.Remove: - removeAction.Invoke(change.Item.Current); - break; - - case ListChangeReason.Replace: - removeAction.Invoke(change.Item.Previous.Value); - break; - } - } - }); - - return invokeOnUnsubscribe - ? Observable.Create>(observer => - { - var items = new List(); - - return removalProcessor - .Do(changeSet => items.Clone(changeSet)) - .Finally(() => - { - foreach (var item in items) - removeAction.Invoke(item); - }) - .SubscribeSafe(observer); - }) - : removalProcessor; - } -} +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Roland Pheasant licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else + +namespace DynamicData.List.Internal; +#endif + +/// +/// Provides members for the OnItemRemoved class. +/// +/// The type of the T value. +internal static class OnItemRemoved + where T : notnull +{ + /// + /// Executes the Create operation. + /// + /// The source value. + /// The removeAction value. + /// The invokeOnUnsubscribe value. + /// The result of the operation. + public static IObservable> Create( + IObservable> source, + Action removeAction, + bool invokeOnUnsubscribe) + { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(removeAction); + + var removalProcessor = source.Do(changeSet => + { + foreach (var change in changeSet) + { + switch (change.Reason) + { + case ListChangeReason.Clear: + case ListChangeReason.RemoveRange: + foreach (var item in change.Range) + removeAction.Invoke(item); + break; + + case ListChangeReason.Remove: + removeAction.Invoke(change.Item.Current); + break; + + case ListChangeReason.Replace: + removeAction.Invoke(change.Item.Previous.Value); + break; + } + } + }); + + return invokeOnUnsubscribe + ? Observable.Create>(observer => + { + var items = new List(); + + return removalProcessor + .Do(changeSet => items.Clone(changeSet)) + .Finally(() => + { + foreach (var item in items) + removeAction.Invoke(item); + }) + .SubscribeSafe(observer); + }) + : removalProcessor; + } +} diff --git a/src/DynamicData/List/Internal/Pager.cs b/src/DynamicData/List/Internal/Pager.cs index bd3238d2a..ddfcb0502 100644 --- a/src/DynamicData/List/Internal/Pager.cs +++ b/src/DynamicData/List/Internal/Pager.cs @@ -1,19 +1,37 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Collections; -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; - +#endif + +/// +/// Provides members for the Pager class. +/// +/// The type of the T value. +/// The source value. +/// The requests value. internal sealed class Pager(IObservable> source, IObservable requests) where T : notnull { + /// + /// The _requests field. + /// private readonly IObservable _requests = requests ?? throw new ArgumentNullException(nameof(requests)); + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -41,6 +59,12 @@ public IObservable> Run() => Observable.Create + /// Executes the CalculatePages operation. + /// + /// The all value. + /// The request value. + /// The result of the operation. private static int CalculatePages(ICollection all, IPageRequest? request) { if (request is null || request.Size >= all.Count || request.Size == 0) @@ -59,6 +83,13 @@ private static int CalculatePages(ICollection all, IPageRequest? request) return pages + 1; } + /// + /// Executes the CheckParametersAndPage operation. + /// + /// The all value. + /// The paged value. + /// The request value. + /// The result of the operation. private static PageChangeSet? CheckParametersAndPage(List all, ChangeAwareList paged, IPageRequest? request) { if (request is null || request.Page < 0 || request.Size < 1) @@ -69,6 +100,14 @@ private static int CalculatePages(ICollection all, IPageRequest? request) return Page(all, paged, request); } + /// + /// Executes the Page operation. + /// + /// The all value. + /// The paged value. + /// The request value. + /// The changeSet value. + /// The result of the operation. private static PageChangeSet Page(List all, ChangeAwareList paged, IPageRequest request, IChangeSet? changeSet = null) { if (changeSet is not null) diff --git a/src/DynamicData/List/Internal/QueryWhenChanged.cs b/src/DynamicData/List/Internal/QueryWhenChanged.cs index ef58d4925..204e5c75f 100644 --- a/src/DynamicData/List/Internal/QueryWhenChanged.cs +++ b/src/DynamicData/List/Internal/QueryWhenChanged.cs @@ -1,16 +1,31 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the QueryWhenChanged class. +/// +/// The type of the T value. +/// The source value. internal sealed class QueryWhenChanged(IObservable> source) where T : notnull { + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>(observer => { var list = new List(); diff --git a/src/DynamicData/List/Internal/ReaderWriter.cs b/src/DynamicData/List/Internal/ReaderWriter.cs index 57b413867..a41d34677 100644 --- a/src/DynamicData/List/Internal/ReaderWriter.cs +++ b/src/DynamicData/List/Internal/ReaderWriter.cs @@ -1,22 +1,39 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the ReaderWriter class. +/// +/// The type of the T value. internal sealed class ReaderWriter where T : notnull { -#if NET9_0_OR_GREATER + /// + /// The _locker field. + /// private readonly Lock _locker = new(); -#else - private readonly object _locker = new(); -#endif + /// + /// The _data field. + /// private ChangeAwareList _data = new(); + /// + /// The _updateInProgress field. + /// private bool _updateInProgress; + /// + /// Gets the Count value. + /// public int Count { get @@ -28,6 +45,9 @@ public int Count } } + /// + /// Gets the Items value. + /// public T[] Items { get @@ -41,9 +61,14 @@ public T[] Items } } + /// + /// Executes the Write operation. + /// + /// The changes value. + /// The result of the operation. public IChangeSet Write(IChangeSet changes) { - changes.ThrowArgumentNullExceptionIfNull(nameof(changes)); + ArgumentExceptionHelper.ThrowIfNull(changes); IChangeSet result; @@ -56,9 +81,14 @@ public IChangeSet Write(IChangeSet changes) return result; } + /// + /// Executes the Write operation. + /// + /// The updateAction value. + /// The result of the operation. public IChangeSet Write(Action> updateAction) { - updateAction.ThrowArgumentNullExceptionIfNull(nameof(updateAction)); + ArgumentExceptionHelper.ThrowIfNull(updateAction); IChangeSet result; @@ -82,7 +112,7 @@ public IChangeSet Write(Action> updateAction) /// The action to perform on the list. public void WriteNested(Action> updateAction) { - updateAction.ThrowArgumentNullExceptionIfNull(nameof(updateAction)); + ArgumentExceptionHelper.ThrowIfNull(updateAction); lock (_locker) { @@ -95,10 +125,16 @@ public void WriteNested(Action> updateAction) } } + /// + /// Executes the WriteWithPreview operation. + /// + /// The updateAction value. + /// The previewHandler value. + /// The result of the operation. public IChangeSet WriteWithPreview(Action> updateAction, Action> previewHandler) { - updateAction.ThrowArgumentNullExceptionIfNull(nameof(updateAction)); - previewHandler.ThrowArgumentNullExceptionIfNull(nameof(previewHandler)); + ArgumentExceptionHelper.ThrowIfNull(updateAction); + ArgumentExceptionHelper.ThrowIfNull(previewHandler); IChangeSet result; diff --git a/src/DynamicData/List/Internal/RefCount.cs b/src/DynamicData/List/Internal/RefCount.cs index b40036c3e..8333c9dc7 100644 --- a/src/DynamicData/List/Internal/RefCount.cs +++ b/src/DynamicData/List/Internal/RefCount.cs @@ -1,24 +1,41 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the RefCount class. +/// +/// The type of the T value. +/// The source value. internal sealed class RefCount(IObservable> source) where T : notnull { -#if NET9_0_OR_GREATER + /// + /// The _locker field. + /// private readonly Lock _locker = new(); -#else - private readonly object _locker = new(); -#endif + + /// + /// The _list field. + /// private IObservableList? _list; + /// + /// The _refCount field. + /// private int _refCount; + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { diff --git a/src/DynamicData/List/Internal/ReferenceCountTracker.cs b/src/DynamicData/List/Internal/ReferenceCountTracker.cs index 6959b83eb..4b416e95b 100644 --- a/src/DynamicData/List/Internal/ReferenceCountTracker.cs +++ b/src/DynamicData/List/Internal/ReferenceCountTracker.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif /// /// Ripped and adapted from https://clinq.codeplex.com/. @@ -12,19 +17,30 @@ namespace DynamicData.List.Internal; internal sealed class ReferenceCountTracker where T : notnull { + /// + /// Gets the Items value. + /// public IEnumerable Items => ReferenceCounts.Keys; + /// + /// Gets the ReferenceCounts value. + /// private Dictionary ReferenceCounts { get; } = []; + /// + /// Gets or sets the indexed value. + /// + /// The item value. public int this[T item] => ReferenceCounts[item]; /// /// Increments the reference count for the item. Returns true when reference count goes from 0 to 1. /// /// The item to add. + /// The result of the operation. public bool Add(T item) { - item.ThrowArgumentNullExceptionIfNull(nameof(item)); + ArgumentExceptionHelper.ThrowIfNull(item); if (!ReferenceCounts.TryGetValue(item, out var currentCount)) { @@ -36,17 +52,26 @@ public bool Add(T item) return false; } + /// + /// Executes the Clear operation. + /// public void Clear() => ReferenceCounts.Clear(); + /// + /// Executes the Contains operation. + /// + /// The item value. + /// The result of the operation. public bool Contains(T item) => ReferenceCounts.ContainsKey(item); /// /// Decrements the reference count for the item. Returns true when reference count goes from 1 to 0. /// /// The item to remove. + /// The result of the operation. public bool Remove(T item) { - item.ThrowArgumentNullExceptionIfNull(nameof(item)); + ArgumentExceptionHelper.ThrowIfNull(item); var currentCount = ReferenceCounts[item]; diff --git a/src/DynamicData/List/Internal/Sort.cs b/src/DynamicData/List/Internal/Sort.cs index ff03e2e70..2884fcd2a 100644 --- a/src/DynamicData/List/Internal/Sort.cs +++ b/src/DynamicData/List/Internal/Sort.cs @@ -1,21 +1,51 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; - +#endif + +/// +/// Provides members for the Sort class. +/// +/// The type of the T value. +/// The source value. +/// The comparer value. +/// The sortOptions value. +/// The resort value. +/// The comparerObservable value. +/// The resetThreshold value. internal sealed class Sort(IObservable> source, IComparer? comparer, SortOptions sortOptions, IObservable? resort, IObservable>? comparerObservable, int resetThreshold) where T : notnull { + /// + /// The _comparerObservable field. + /// private readonly IObservable> _comparerObservable = comparerObservable ?? Observable.Never>(); + + /// + /// The _resort field. + /// private readonly IObservable _resort = resort ?? Observable.Never(); + + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// The _comparer field. + /// private IComparer _comparer = comparer ?? Comparer.Default; + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -39,6 +69,12 @@ public IObservable> Run() => Observable.Create>( return changeComparer.Merge(resortSync).Merge(dataChanged).Where(changes => changes.Count != 0).SubscribeSafe(observer); }); + /// + /// Executes the ChangeComparer operation. + /// + /// The target value. + /// The comparer value. + /// The result of the operation. private IChangeSet ChangeComparer(ChangeAwareList target, IComparer comparer) { _comparer = comparer; @@ -53,6 +89,12 @@ private IChangeSet ChangeComparer(ChangeAwareList target, IComparer com return target.CaptureChanges(); } + /// + /// Executes the GetCurrentPosition operation. + /// + /// The target value. + /// The item value. + /// The result of the operation. private int GetCurrentPosition(ChangeAwareList target, T item) { var index = sortOptions == SortOptions.UseBinarySearch ? target.BinarySearch(item, _comparer) : target.IndexOf(item); @@ -65,8 +107,20 @@ private int GetCurrentPosition(ChangeAwareList target, T item) return index; } + /// + /// Executes the GetInsertPosition operation. + /// + /// The target value. + /// The item value. + /// The result of the operation. private int GetInsertPosition(ChangeAwareList target, T item) => sortOptions == SortOptions.UseBinarySearch ? GetInsertPositionBinary(target, item) : GetInsertPositionLinear(target, item); + /// + /// Executes the GetInsertPositionBinary operation. + /// + /// The target value. + /// The item value. + /// The result of the operation. private int GetInsertPositionBinary(ChangeAwareList target, T item) { var index = target.BinarySearch(item, _comparer); @@ -81,6 +135,12 @@ private int GetInsertPositionBinary(ChangeAwareList target, T item) return insertIndex; } + /// + /// Executes the GetInsertPositionLinear operation. + /// + /// The target value. + /// The item value. + /// The result of the operation. private int GetInsertPositionLinear(ChangeAwareList target, T item) { for (var i = 0; i < target.Count; i++) @@ -94,12 +154,23 @@ private int GetInsertPositionLinear(ChangeAwareList target, T item) return target.Count; } + /// + /// Executes the Insert operation. + /// + /// The target value. + /// The item value. private void Insert(ChangeAwareList target, T item) { var index = GetInsertPosition(target, item); target.Insert(index, item); } + /// + /// Executes the Process operation. + /// + /// The target value. + /// The changes value. + /// The result of the operation. private IChangeSet Process(ChangeAwareList target, IChangeSet changes) { // if all removes and not Clear, then more efficient to try clear range @@ -113,6 +184,12 @@ private IChangeSet Process(ChangeAwareList target, IChangeSet changes) return ProcessImpl(target, changes); } + /// + /// Executes the ProcessImpl operation. + /// + /// The target value. + /// The changes value. + /// The result of the operation. private IChangeSet ProcessImpl(ChangeAwareList target, IChangeSet changes) { var refreshes = new List(changes.Refreshes); @@ -214,12 +291,22 @@ private IChangeSet ProcessImpl(ChangeAwareList target, IChangeSet chang return target.CaptureChanges(); } + /// + /// Executes the Remove operation. + /// + /// The target value. + /// The item value. private void Remove(ChangeAwareList target, T item) { var index = GetCurrentPosition(target, item); target.RemoveAt(index); } + /// + /// Executes the Reorder operation. + /// + /// The target value. + /// The result of the operation. private IChangeSet Reorder(ChangeAwareList target) { var index = -1; @@ -243,6 +330,12 @@ private IChangeSet Reorder(ChangeAwareList target) return target.CaptureChanges(); } + /// + /// Executes the Reset operation. + /// + /// The original value. + /// The target value. + /// The result of the operation. private IChangeSet Reset(List original, ChangeAwareList target) { var sorted = original.OrderBy(t => t, _comparer).ToList(); diff --git a/src/DynamicData/List/Internal/SubscribeMany.cs b/src/DynamicData/List/Internal/SubscribeMany.cs index 75075b04e..7ff5c1a71 100644 --- a/src/DynamicData/List/Internal/SubscribeMany.cs +++ b/src/DynamicData/List/Internal/SubscribeMany.cs @@ -1,20 +1,37 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the SubscribeMany class. +/// +/// The type of the T value. +/// The source value. +/// The subscriptionFactory value. internal sealed class SubscribeMany(IObservable> source, Func subscriptionFactory) where T : notnull { + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// The _subscriptionFactory field. + /// private readonly Func _subscriptionFactory = subscriptionFactory ?? throw new ArgumentNullException(nameof(subscriptionFactory)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { diff --git a/src/DynamicData/List/Internal/Switch.cs b/src/DynamicData/List/Internal/Switch.cs index 9425771dd..4ddc7604d 100644 --- a/src/DynamicData/List/Internal/Switch.cs +++ b/src/DynamicData/List/Internal/Switch.cs @@ -1,17 +1,31 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the Switch class. +/// +/// The type of the T value. +/// The sources value. internal sealed class Switch(IObservable>> sources) where T : notnull { + /// + /// The _sources field. + /// private readonly IObservable>> _sources = sources ?? throw new ArgumentNullException(nameof(sources)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { diff --git a/src/DynamicData/List/Internal/ToObservableChangeSet.cs b/src/DynamicData/List/Internal/ToObservableChangeSet.cs index 8d1f853d2..a65653468 100644 --- a/src/DynamicData/List/Internal/ToObservableChangeSet.cs +++ b/src/DynamicData/List/Internal/ToObservableChangeSet.cs @@ -1,25 +1,36 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; - -using DynamicData.Internal; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the ToObservableChangeSet class. +/// +/// The type of the TObject value. internal static class ToObservableChangeSet where TObject : notnull { + /// + /// Executes the Create operation. + /// + /// The source value. + /// The expireAfter value. + /// The limitSizeTo value. + /// The scheduler value. + /// The result of the operation. public static IObservable> Create( IObservable source, Func? expireAfter, int limitSizeTo, IScheduler? scheduler) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return Observable.Create>(downstreamObserver => { @@ -39,13 +50,21 @@ public static IObservable> Create( }); } + /// + /// Executes the Create operation. + /// + /// The source value. + /// The expireAfter value. + /// The limitSizeTo value. + /// The scheduler value. + /// The result of the operation. public static IObservable> Create( IObservable> source, Func? expireAfter, int limitSizeTo, IScheduler? scheduler) { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return Observable.Create>(downstreamObserver => new Subscription( downstreamObserver: downstreamObserver, @@ -55,26 +74,75 @@ public static IObservable> Create( source: source)); } - private sealed class Subscription +/// +/// Provides members for the Subscription class. +/// +private sealed class Subscription : IDisposable { + /// + /// The _downstreamItems field. + /// private readonly ChangeAwareList _downstreamItems; + + /// + /// The _downstreamObserver field. + /// private readonly IObserver> _downstreamObserver; + + /// + /// The _expireAfter field. + /// private readonly Func? _expireAfter; + + /// + /// The _expirationQueue field. + /// private readonly List _expirationQueue; + + /// + /// The _limitSizeTo field. + /// private readonly int _limitSizeTo; + + /// + /// The _scheduler field. + /// private readonly IScheduler _scheduler; + + /// + /// The _sourceSubscription field. + /// private readonly IDisposable _sourceSubscription; - #if NET9_0_OR_GREATER + + /// + /// The _synchronizationGate field. + /// private readonly Lock _synchronizationGate; - #else - private readonly object _synchronizationGate; - #endif + /// + /// The _hasInitialized field. + /// private bool _hasInitialized; + + /// + /// The _hasSourceCompleted field. + /// private bool _hasSourceCompleted; + + /// + /// The _scheduledExpiration field. + /// private ScheduledExpiration? _scheduledExpiration; + /// + /// Initializes a new instance of the class. + /// + /// The downstreamObserver value. + /// The expireAfter value. + /// The limitSizeTo value. + /// The scheduler value. + /// The source value. public Subscription( IObserver> downstreamObserver, Func? expireAfter, @@ -90,7 +158,8 @@ public Subscription( _scheduler = scheduler ?? GlobalConfig.DefaultScheduler; _synchronizationGate = new(); - _sourceSubscription = source.SubscribeSafe( + _sourceSubscription = PrimitivesLinqExtensions.SubscribeSafe( + source, onNext: OnSourceNext, onError: downstreamObserver.OnError, onCompleted: OnSourceCompleted); @@ -104,12 +173,20 @@ public Subscription( } } + /// + /// Executes the Dispose operation. + /// public void Dispose() { _sourceSubscription.Dispose(); _scheduledExpiration?.Cancellation.Dispose(); } + /// + /// Executes the OnScheduledExpirationInvoked operation. + /// + /// The intendedExpiration value. + /// The result of the operation. private IDisposable OnScheduledExpirationInvoked(Expiration intendedExpiration) { try @@ -171,6 +248,10 @@ private IDisposable OnScheduledExpirationInvoked(Expiration intendedExpiration) return Disposable.Empty; } + /// + /// Executes the OnSourceNext operation. + /// + /// The upstreamItems value. private void OnSourceNext(IEnumerable upstreamItems) { try @@ -268,6 +349,9 @@ private void OnSourceNext(IEnumerable upstreamItems) } } + /// + /// Executes the OnSourceCompleted operation. + /// private void OnSourceCompleted() { lock (_synchronizationGate) @@ -277,13 +361,18 @@ private void OnSourceCompleted() TryPublishCompletion(); } } - // This method must NOT be invoked under the umbrella of _synchronizationGate, // as some IScheduler implementations perform locking internally, which can result in deadlocking if we invoke the scheduler within our own lock. // // Additionally, some IScheduler implementations can invoke actions synchronously, // so it's important that scheduler invocation is only performed AFTER downstream changes have been processed. // Otherwise, downstream notifications can end up published out-of-order. + + /// + /// Executes the FinishSchedulingExpiration operation. + /// + /// The unfinishedExpiration value. + /// The scheduler value. private void FinishSchedulingExpiration( ScheduledExpiration unfinishedExpiration, IScheduler scheduler) @@ -303,6 +392,9 @@ private void FinishSchedulingExpiration( return Disposable.Empty; }); + /// + /// Executes the TryPublishCompletion operation. + /// private void TryPublishCompletion() { // There needs to be no possibility of a new changeset being emitted before we can call the stream complete. @@ -310,6 +402,9 @@ private void TryPublishCompletion() _downstreamObserver.OnCompleted(); } + /// + /// Executes the TryPublishDownstreamChanges operation. + /// private void TryPublishDownstreamChanges() { var downstreamChanges = _downstreamItems.CaptureChanges(); @@ -321,6 +416,10 @@ private void TryPublishDownstreamChanges() } } + /// + /// Executes the TryBeginSchedulingExpiration operation. + /// + /// The result of the operation. private ScheduledExpiration? TryBeginSchedulingExpiration() { // If there's no expirations currently queued up, we don't need to schedule anything. @@ -346,20 +445,43 @@ private void TryPublishDownstreamChanges() } } - private readonly struct ScheduledExpiration +/// +/// Represents the ScheduledExpiration value. +/// +private readonly struct ScheduledExpiration { + /// + /// Gets or sets the Cancellation value. + /// public required SingleAssignmentDisposable Cancellation { get; init; } + /// + /// Gets or sets the Expiration value. + /// public required Expiration Expiration { get; init; } } - private readonly record struct Expiration +/// +/// Represents the Expiration record. +/// +private readonly record struct Expiration : IComparable { + /// + /// Gets or sets the ExpireAt value. + /// public required DateTimeOffset ExpireAt { get; init; } + /// + /// Gets or sets the Index value. + /// public required int Index { get; init; } + /// + /// Executes the CompareTo operation. + /// + /// The other value. + /// The result of the operation. public int CompareTo(Expiration other) => ExpireAt.CompareTo(other.ExpireAt); } diff --git a/src/DynamicData/List/Internal/TransformAsync.cs b/src/DynamicData/List/Internal/TransformAsync.cs index b43edc030..0ffa4a8dd 100644 --- a/src/DynamicData/List/Internal/TransformAsync.cs +++ b/src/DynamicData/List/Internal/TransformAsync.cs @@ -1,26 +1,50 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the TransformAsync class. +/// +/// The type of the TSource value. +/// The type of the TDestination value. internal sealed class TransformAsync where TSource : notnull where TDestination : notnull { - private readonly Func, int, Task.TransformedItemContainer>> _containerFactory; - + /// + /// The _containerFactory field. + /// + private readonly Func, int, Task.TransformedItemContainer>> _containerFactory; + + /// + /// The _source field. + /// private readonly IObservable> _source; + + /// + /// The _transformOnRefresh field. + /// private readonly bool _transformOnRefresh; + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The factory value. + /// The transformOnRefresh value. public TransformAsync( IObservable> source, - Func, int, Task> factory, + Func, int, Task> factory, bool transformOnRefresh) { - factory.ThrowArgumentNullExceptionIfNull(nameof(factory)); + ArgumentExceptionHelper.ThrowIfNull(factory); _source = source ?? throw new ArgumentNullException(nameof(source)); _transformOnRefresh = transformOnRefresh; @@ -31,8 +55,16 @@ public TransformAsync( }; } + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Defer(RunImpl); + /// + /// Executes the RunImpl operation. + /// + /// The result of the operation. private IObservable> RunImpl() { var state = new ChangeAwareList.TransformedItemContainer>(); @@ -59,11 +91,17 @@ private IObservable> RunImpl() }); } + /// + /// Executes the Transform operation. + /// + /// The transformed value. + /// The changes value. + /// The result of the operation. private async Task Transform( ChangeAwareList.TransformedItemContainer> transformed, IChangeSet changes) { - changes.ThrowArgumentNullExceptionIfNull(nameof(changes)); + ArgumentExceptionHelper.ThrowIfNull(changes); foreach (var item in changes) { @@ -77,7 +115,7 @@ private async Task Transform( var container = await _containerFactory( item.Item.Current, - Optional.None, + ReactiveUI.Primitives.Optional.None, transformed.Count).ConfigureAwait(false); transformed.Add(container); } @@ -86,7 +124,7 @@ await _containerFactory( var container = await _containerFactory( item.Item.Current, - Optional.None, + ReactiveUI.Primitives.Optional.None, change.CurrentIndex).ConfigureAwait(false); transformed.Insert(change.CurrentIndex, container); } @@ -97,7 +135,7 @@ await _containerFactory( case ListChangeReason.AddRange: { var startIndex = item.Range.Index < 0 ? transformed.Count : item.Range.Index; - var tasks = item.Range.Select((t, idx) => _containerFactory(t, Optional.None, idx + startIndex)); + var tasks = item.Range.Select((t, idx) => _containerFactory(t, ReactiveUI.Primitives.Optional.None, idx + startIndex)); var containers = await Task.WhenAll(tasks).ConfigureAwait(false); transformed.AddOrInsertRange(containers, item.Range.Index); break; @@ -108,7 +146,7 @@ await _containerFactory( var change = item.Item; if (_transformOnRefresh) { - Optional previous = transformed[change.CurrentIndex].Destination; + ReactiveUI.Primitives.Optional previous = transformed[change.CurrentIndex].Destination; var container = await _containerFactory(change.Current, previous, change.CurrentIndex) .ConfigureAwait(false); transformed[change.CurrentIndex] = container; @@ -125,7 +163,7 @@ await _containerFactory( { var change = item.Item; - Optional previous = transformed[change.PreviousIndex].Destination; + ReactiveUI.Primitives.Optional previous = transformed[change.PreviousIndex].Destination; if (change.CurrentIndex == change.PreviousIndex) { transformed[change.CurrentIndex] = await _containerFactory(change.Current, previous, change.CurrentIndex); @@ -133,7 +171,7 @@ await _containerFactory( else { transformed.RemoveAt(change.PreviousIndex); - transformed.Insert(change.CurrentIndex, await _containerFactory(change.Current, Optional.None, change.CurrentIndex)); + transformed.Insert(change.CurrentIndex, await _containerFactory(change.Current, ReactiveUI.Primitives.Optional.None, change.CurrentIndex)); } break; diff --git a/src/DynamicData/List/Internal/TransformMany.cs b/src/DynamicData/List/Internal/TransformMany.cs index d7bb2a69e..f5f136254 100644 --- a/src/DynamicData/List/Internal/TransformMany.cs +++ b/src/DynamicData/List/Internal/TransformMany.cs @@ -1,238 +1,325 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. -// 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.Collections; -using System.Collections.ObjectModel; -using System.Reactive.Disposables; -using System.Reactive.Linq; - -using DynamicData.Binding; - -namespace DynamicData.List.Internal; - -internal sealed class TransformMany(IObservable> source, Func> manySelector, IEqualityComparer? equalityComparer = null, Func>>? childChanges = null) - where TSource : notnull - where TDestination : notnull -{ - private readonly IEqualityComparer _equalityComparer = equalityComparer ?? EqualityComparer.Default; - private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); - - public TransformMany(IObservable> source, Func> manySelector, IEqualityComparer? equalityComparer = null) - : this( - source, - manySelector, - equalityComparer, - t => Observable.Defer( - () => - { - var subsequentChanges = manySelector(t).ToObservableChangeSet(); - - if (manySelector(t).Count > 0) - { - return subsequentChanges; - } - - return Observable.Return(ChangeSet.Empty).Concat(subsequentChanges); - })) - { - } - - public TransformMany(IObservable> source, Func> manySelector, IEqualityComparer? equalityComparer = null) - : this( - source, - manySelector, - equalityComparer, - t => Observable.Defer( - () => - { - var subsequentChanges = manySelector(t).ToObservableChangeSet(); - - if (manySelector(t).Count > 0) - { - return subsequentChanges; - } - - return Observable.Return(ChangeSet.Empty).Concat(subsequentChanges); - })) - { - } - - public TransformMany(IObservable> source, Func> manySelector, IEqualityComparer? equalityComparer = null) - : this( - source, - s => new ManySelectorFunc(s, x => manySelector(x).Items), - equalityComparer, - t => Observable.Defer( - () => - { - var subsequentChanges = manySelector(t).Connect(); - - if (manySelector(t).Count > 0) - { - return subsequentChanges; - } - - return Observable.Return(ChangeSet.Empty).Concat(subsequentChanges); - })) - { - } - - public IObservable> Run() - { - if (childChanges is not null) - { - return CreateWithChangeSet(); - } - - return Observable.Create>( - observer => - { - // NB: ChangeAwareList is used internally by dd to capture changes to a list and ensure they can be replayed by subsequent operators - var result = new ChangeAwareList(); - - return _source.Transform(item => new ManyContainer(manySelector(item).ToArray()), true).Select( - changes => - { - var destinationChanges = new DestinationEnumerator(changes, _equalityComparer); - result.Clone(destinationChanges, _equalityComparer); - return result.CaptureChanges(); - }).NotEmpty().SubscribeSafe(observer); - }); - } - - private IObservable> CreateWithChangeSet() - { - if (childChanges is null) - { - throw new InvalidOperationException("_childChanges must not be null."); - } - - return Observable.Create>( - observer => - { - var result = new ChangeAwareList(); - - var transformed = _source.Transform( - t => - { - var locker = InternalEx.NewLock(); - var collection = manySelector(t); - var changes = childChanges(t).Synchronize(locker).Skip(1); - return new ManyContainer(collection, changes); - }).Publish(); - - var outerLock = new object(); - var initial = transformed.Synchronize(outerLock).Select(changes => new ChangeSet(new DestinationEnumerator(changes, _equalityComparer))); - - var subsequent = transformed.MergeMany(x => x.Changes).Synchronize(outerLock); - - var init = initial.Select( - changes => - { - result.Clone(changes, _equalityComparer); - return result.CaptureChanges(); - }); - - var subsequentSelection = subsequent.RemoveIndex().Select( - changes => - { - result.Clone(changes, _equalityComparer); - return result.CaptureChanges(); - }); - - var allChanges = init.Merge(subsequentSelection); - - return new CompositeDisposable(allChanges.SubscribeSafe(observer), transformed.Connect()); - }); - } - - // make this an instance - private sealed class DestinationEnumerator(IChangeSet changes, IEqualityComparer equalityComparer) : IEnumerable> - { - public IEnumerator> GetEnumerator() - { - foreach (var change in changes) - { - switch (change.Reason) - { - case ListChangeReason.Add: - case ListChangeReason.Remove: - foreach (var destination in change.Item.Current.Destination) - { - yield return new Change(change.Reason, destination); - } - - break; - - case ListChangeReason.AddRange: - case ListChangeReason.Clear: - { - var items = change.Range.SelectMany(m => m.Destination); - yield return new Change(change.Reason, items); - } - - break; - - case ListChangeReason.Replace: - case ListChangeReason.Refresh: - { - // this is difficult as we need to discover adds and removes (and perhaps replaced) - var currentItems = change.Item.Current.Destination.AsArray(); - var previousItems = change.Item.Previous.Value.Destination.AsArray(); - - var adds = currentItems.Except(previousItems, equalityComparer); - - // I am not sure whether it is possible to translate the original change into a replace - foreach (var destination in previousItems.Except(currentItems, equalityComparer)) - { - yield return new Change(ListChangeReason.Remove, destination); - } - - foreach (var destination in adds) - { - yield return new Change(ListChangeReason.Add, destination); - } - } - - break; - - case ListChangeReason.RemoveRange: - { - foreach (var destination in change.Range.SelectMany(m => m.Destination)) - { - yield return new Change(ListChangeReason.Remove, destination); - } - } - - break; - - case ListChangeReason.Moved: - // do nothing as the original index has no bearing on the destination index - break; - - default: - throw new IndexOutOfRangeException("Unknown list reason " + change); - } - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - - private sealed class ManyContainer(IEnumerable destination, IObservable>? changes = null) - { - public IObservable> Changes { get; } = changes ?? Observable.Empty>(); - - public IEnumerable Destination { get; } = destination; - } - - private sealed class ManySelectorFunc(TSource source, Func> selector) : IEnumerable - { - private readonly Func> _selector = selector ?? throw new ArgumentNullException(nameof(selector)); - - public IEnumerator GetEnumerator() => _selector(source).GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => _selector(source).GetEnumerator(); - } -} +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Roland Pheasant licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else + +using DynamicData.Binding; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else + +namespace DynamicData.List.Internal; +#endif + +/// +/// Provides members for the TransformMany class. +/// +/// The type of the TSource value. +/// The type of the TDestination value. +/// The source value. +/// The manySelector value. +/// The equalityComparer value. +/// The childChanges value. +internal sealed class TransformMany(IObservable> source, Func> manySelector, IEqualityComparer? equalityComparer = null, Func>>? childChanges = null) + where TSource : notnull + where TDestination : notnull +{ + /// + /// The _equalityComparer field. + /// + private readonly IEqualityComparer _equalityComparer = equalityComparer ?? EqualityComparer.Default; + + /// + /// The _source field. + /// + private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The manySelector value. + /// The equalityComparer value. + public TransformMany(IObservable> source, Func> manySelector, IEqualityComparer? equalityComparer = null) + : this( + source, + manySelector, + equalityComparer, + t => Observable.Defer( + () => + { + var subsequentChanges = manySelector(t).ToObservableChangeSet(); + + if (manySelector(t).Count > 0) + { + return subsequentChanges; + } + + return Observable.Return(ChangeSet.Empty).Concat(subsequentChanges); + })) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The manySelector value. + /// The equalityComparer value. + public TransformMany(IObservable> source, Func> manySelector, IEqualityComparer? equalityComparer = null) + : this( + source, + manySelector, + equalityComparer, + t => Observable.Defer( + () => + { + var subsequentChanges = manySelector(t).ToObservableChangeSet(); + + if (manySelector(t).Count > 0) + { + return subsequentChanges; + } + + return Observable.Return(ChangeSet.Empty).Concat(subsequentChanges); + })) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The manySelector value. + /// The equalityComparer value. + public TransformMany(IObservable> source, Func> manySelector, IEqualityComparer? equalityComparer = null) + : this( + source, + s => new ManySelectorFunc(s, x => manySelector(x).Items), + equalityComparer, + t => Observable.Defer( + () => + { + var subsequentChanges = manySelector(t).Connect(); + + if (manySelector(t).Count > 0) + { + return subsequentChanges; + } + + return Observable.Return(ChangeSet.Empty).Concat(subsequentChanges); + })) + { + } + + /// + /// Executes the Run operation. + /// + /// The result of the operation. + public IObservable> Run() + { + if (childChanges is not null) + { + return CreateWithChangeSet(); + } + + return Observable.Create>( + observer => + { + // NB: ChangeAwareList is used internally by dd to capture changes to a list and ensure they can be replayed by subsequent operators + var result = new ChangeAwareList(); + + return _source.Transform(item => new ManyContainer(manySelector(item).ToArray()), true).Select( + changes => + { + var destinationChanges = new DestinationEnumerator(changes, _equalityComparer); + result.Clone(destinationChanges, _equalityComparer); + return result.CaptureChanges(); + }).NotEmpty().SubscribeSafe(observer); + }); + } + + /// + /// Executes the CreateWithChangeSet operation. + /// + /// The result of the operation. + private IObservable> CreateWithChangeSet() + { + if (childChanges is null) + { + throw new InvalidOperationException("_childChanges must not be null."); + } + + return Observable.Create>( + observer => + { + var result = new ChangeAwareList(); + + var transformed = _source.Transform( + t => + { + var locker = InternalEx.NewLock(); + var collection = manySelector(t); + var changes = childChanges(t).Synchronize(locker).Skip(1); + return new ManyContainer(collection, changes); + }).Publish(); + + var outerLock = new Lock(); + var initial = transformed.Synchronize(outerLock).Select(changes => new ChangeSet(new DestinationEnumerator(changes, _equalityComparer))); + + var subsequent = transformed.MergeMany(x => x.Changes).Synchronize(outerLock); + + var init = initial.Select( + changes => + { + result.Clone(changes, _equalityComparer); + return result.CaptureChanges(); + }); + + var subsequentSelection = subsequent.RemoveIndex().Select( + changes => + { + result.Clone(changes, _equalityComparer); + return result.CaptureChanges(); + }); + + var allChanges = init.Merge(subsequentSelection); + + return new CompositeDisposable(allChanges.SubscribeSafe(observer), transformed.Connect()); + }); + } + // make this an instance + +/// +/// Provides members for the DestinationEnumerator class. +/// +/// The changes value. +/// The equalityComparer value. +private sealed class DestinationEnumerator(IChangeSet changes, IEqualityComparer equalityComparer) : IEnumerable> + { + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. + public IEnumerator> GetEnumerator() + { + foreach (var change in changes) + { + switch (change.Reason) + { + case ListChangeReason.Add: + case ListChangeReason.Remove: + foreach (var destination in change.Item.Current.Destination) + { + yield return new Change(change.Reason, destination); + } + + break; + + case ListChangeReason.AddRange: + case ListChangeReason.Clear: + { + var items = change.Range.SelectMany(m => m.Destination); + yield return new Change(change.Reason, items); + } + + break; + + case ListChangeReason.Replace: + case ListChangeReason.Refresh: + { + // this is difficult as we need to discover adds and removes (and perhaps replaced) + var currentItems = change.Item.Current.Destination.AsArray(); + var previousItems = change.Item.Previous.Value.Destination.AsArray(); + + var adds = currentItems.Except(previousItems, equalityComparer); + + // I am not sure whether it is possible to translate the original change into a replace + foreach (var destination in previousItems.Except(currentItems, equalityComparer)) + { + yield return new Change(ListChangeReason.Remove, destination); + } + + foreach (var destination in adds) + { + yield return new Change(ListChangeReason.Add, destination); + } + } + + break; + + case ListChangeReason.RemoveRange: + { + foreach (var destination in change.Range.SelectMany(m => m.Destination)) + { + yield return new Change(ListChangeReason.Remove, destination); + } + } + + break; + + case ListChangeReason.Moved: + // do nothing as the original index has no bearing on the destination index + break; + + default: + throw new IndexOutOfRangeException("Unknown list reason " + change); + } + } + } + + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + +/// +/// Provides members for the ManyContainer class. +/// +/// The destination value. +/// The changes value. +private sealed class ManyContainer(IEnumerable destination, IObservable>? changes = null) + { + /// + /// Gets the Changes value. + /// + public IObservable> Changes { get; } = changes ?? Observable.Empty>(); + + /// + /// Gets the Destination value. + /// + public IEnumerable Destination { get; } = destination; + } + +/// +/// Provides members for the ManySelectorFunc class. +/// +/// The source value. +/// The selector value. +private sealed class ManySelectorFunc(TSource source, Func> selector) : IEnumerable + { + /// + /// The _selector field. + /// + private readonly Func> _selector = selector ?? throw new ArgumentNullException(nameof(selector)); + + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. + public IEnumerator GetEnumerator() => _selector(source).GetEnumerator(); + + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. + IEnumerator IEnumerable.GetEnumerator() => _selector(source).GetEnumerator(); + } +} diff --git a/src/DynamicData/List/Internal/Transformer.cs b/src/DynamicData/List/Internal/Transformer.cs index 69dbb11b2..10714662c 100644 --- a/src/DynamicData/List/Internal/Transformer.cs +++ b/src/DynamicData/List/Internal/Transformer.cs @@ -1,32 +1,64 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif +/// +/// Provides members for the Transformer class. +/// +/// The type of the TSource value. +/// The type of the TDestination value. internal sealed class Transformer where TSource : notnull where TDestination : notnull { - private readonly Func, int, TransformedItemContainer> _containerFactory; - + /// + /// The _containerFactory field. + /// + private readonly Func, int, TransformedItemContainer> _containerFactory; + + /// + /// The _source field. + /// private readonly IObservable> _source; + /// + /// The _transformOnRefresh field. + /// private readonly bool _transformOnRefresh; - public Transformer(IObservable> source, Func, int, TDestination> factory, bool transformOnRefresh) + /// + /// Initializes a new instance of the class. + /// + /// The source value. + /// The factory value. + /// The transformOnRefresh value. + public Transformer(IObservable> source, Func, int, TDestination> factory, bool transformOnRefresh) { - factory.ThrowArgumentNullExceptionIfNull(nameof(factory)); + ArgumentExceptionHelper.ThrowIfNull(factory); + ArgumentExceptionHelper.ThrowIfNull(source); - _source = source ?? throw new ArgumentNullException(nameof(source)); + _source = source; _transformOnRefresh = transformOnRefresh; _containerFactory = (item, prev, index) => new TransformedItemContainer(item, factory(item, prev, index)); } + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Defer(RunImpl); + /// + /// Executes the RunImpl operation. + /// + /// The result of the operation. private IObservable> RunImpl() => _source.Scan(new ChangeAwareList(), (state, changes) => { Transform(state, changes); @@ -38,9 +70,14 @@ private IObservable> RunImpl() => _source.Scan(new Chan return changed.Transform(container => container.Destination); }); + /// + /// Executes the Transform operation. + /// + /// The transformed value. + /// The changes value. private void Transform(ChangeAwareList transformed, IChangeSet changes) { - changes.ThrowArgumentNullExceptionIfNull(nameof(changes)); + ArgumentExceptionHelper.ThrowIfNull(changes); foreach (var item in changes) { @@ -51,11 +88,11 @@ private void Transform(ChangeAwareList transformed, IC var change = item.Item; if (change.CurrentIndex < 0 || change.CurrentIndex >= transformed.Count) { - transformed.Add(_containerFactory(change.Current, Optional.None, transformed.Count)); + transformed.Add(_containerFactory(change.Current, ReactiveUI.Primitives.Optional.None, transformed.Count)); } else { - var converted = _containerFactory(change.Current, Optional.None, change.CurrentIndex); + var converted = _containerFactory(change.Current, ReactiveUI.Primitives.Optional.None, change.CurrentIndex); transformed.Insert(change.CurrentIndex, converted); } @@ -66,7 +103,7 @@ private void Transform(ChangeAwareList transformed, IC { var startIndex = item.Range.Index < 0 ? transformed.Count : item.Range.Index; - transformed.AddOrInsertRange(item.Range.Select((t, idx) => _containerFactory(t, Optional.None, idx + startIndex)), item.Range.Index); + transformed.AddOrInsertRange(item.Range.Select((t, idx) => _containerFactory(t, ReactiveUI.Primitives.Optional.None, idx + startIndex)), item.Range.Index); break; } @@ -88,7 +125,7 @@ private void Transform(ChangeAwareList transformed, IC if (_transformOnRefresh) { - Optional previous = transformed[index].Destination; + ReactiveUI.Primitives.Optional previous = transformed[index].Destination; transformed[index] = _containerFactory(change.Current, previous, index); } else @@ -117,7 +154,7 @@ private void Transform(ChangeAwareList transformed, IC } else { - Optional previous = transformed[change.PreviousIndex].Destination; + ReactiveUI.Primitives.Optional previous = transformed[change.PreviousIndex].Destination; if (change.CurrentIndex == change.PreviousIndex) { transformed[change.CurrentIndex] = _containerFactory(change.Current, previous, change.CurrentIndex); @@ -125,7 +162,7 @@ private void Transform(ChangeAwareList transformed, IC else { transformed.RemoveAt(change.PreviousIndex); - transformed.Insert(change.CurrentIndex, _containerFactory(change.Current, Optional.None, change.CurrentIndex)); + transformed.Insert(change.CurrentIndex, _containerFactory(change.Current, ReactiveUI.Primitives.Optional.None, change.CurrentIndex)); } } @@ -194,16 +231,44 @@ private void Transform(ChangeAwareList transformed, IC } } - internal sealed class TransformedItemContainer(TSource source, TDestination destination) : IEquatable +/// +/// Provides members for the TransformedItemContainer class. +/// +/// The source value. +/// The destination value. +internal sealed class TransformedItemContainer(TSource source, TDestination destination) : IEquatable { + /// + /// Gets the Destination value. + /// public TDestination Destination { get; } = destination; + /// + /// Gets the Source value. + /// public TSource Source { get; } = source; + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator ==(TransformedItemContainer left, TransformedItemContainer right) => Equals(left, right); + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator !=(TransformedItemContainer left, TransformedItemContainer right) => !Equals(left, right); + /// + /// Executes the Equals operation. + /// + /// The other value. + /// The result of the operation. public bool Equals(TransformedItemContainer? other) { if (other is null) @@ -219,6 +284,11 @@ public bool Equals(TransformedItemContainer? other) return EqualityComparer.Default.Equals(Source, other.Source); } + /// + /// Executes the Equals operation. + /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) { if (obj is null) @@ -239,6 +309,10 @@ public override bool Equals(object? obj) return Equals((TransformedItemContainer)obj); } + /// + /// Executes the GetHashCode operation. + /// + /// The result of the operation. public override int GetHashCode() => Source is null ? 0 : EqualityComparer.Default.GetHashCode(Source); } } diff --git a/src/DynamicData/List/Internal/UnifiedChange.cs b/src/DynamicData/List/Internal/UnifiedChange.cs index eb971b7ae..005631987 100644 --- a/src/DynamicData/List/Internal/UnifiedChange.cs +++ b/src/DynamicData/List/Internal/UnifiedChange.cs @@ -1,29 +1,77 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; +#endif -internal readonly struct UnifiedChange(ListChangeReason reason, T current, Optional previous) : IEquatable> +/// +/// Represents the UnifiedChange value. +/// +/// The type of the T value. +/// The reason value. +/// The current value. +/// The previous value. +internal readonly struct UnifiedChange(ListChangeReason reason, T current, ReactiveUI.Primitives.Optional previous) : IEquatable> where T : notnull { + /// + /// Initializes a new instance of the struct. + /// + /// The reason value. + /// The current value. public UnifiedChange(ListChangeReason reason, T current) - : this(reason, current, Optional.None()) + : this(reason, current, ReactiveUI.Primitives.Optional.None) { } + /// + /// Gets the Reason value. + /// public ListChangeReason Reason { get; } = reason; + /// + /// Gets the Current value. + /// public T Current { get; } = current; - public Optional Previous { get; } = previous; + /// + /// Gets the Previous value. + /// + public ReactiveUI.Primitives.Optional Previous { get; } = previous; + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator ==(in UnifiedChange left, in UnifiedChange right) => left.Equals(right); + /// + /// Executes the operator operation. + /// + /// The left value. + /// The right value. + /// The result of the operation. public static bool operator !=(in UnifiedChange left, in UnifiedChange right) => !left.Equals(right); + /// + /// Executes the Equals operation. + /// + /// The other value. + /// The result of the operation. public bool Equals(UnifiedChange other) => Reason == other.Reason && EqualityComparer.Default.Equals(Current, other.Current) && Previous.Equals(other.Previous); + /// + /// Executes the Equals operation. + /// + /// The obj value. + /// The result of the operation. public override bool Equals(object? obj) { if (obj is null) @@ -34,6 +82,10 @@ public override bool Equals(object? obj) return obj is UnifiedChange unifiedChange && Equals(unifiedChange); } + /// + /// Executes the GetHashCode operation. + /// + /// The result of the operation. public override int GetHashCode() { unchecked @@ -45,5 +97,9 @@ public override int GetHashCode() } } + /// + /// Executes the ToString operation. + /// + /// The result of the operation. public override string ToString() => $"Reason: {Reason}, Current: {Current}, Previous: {Previous}"; } diff --git a/src/DynamicData/List/Internal/Virtualiser.cs b/src/DynamicData/List/Internal/Virtualiser.cs index bb2f894c2..ca0789680 100644 --- a/src/DynamicData/List/Internal/Virtualiser.cs +++ b/src/DynamicData/List/Internal/Virtualiser.cs @@ -1,18 +1,37 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Linq; +namespace DynamicData.Reactive.List.Internal; +#else namespace DynamicData.List.Internal; - +#endif + +/// +/// Provides members for the Virtualiser class. +/// +/// The type of the T value. +/// The source value. +/// The requests value. internal sealed class Virtualiser(IObservable> source, IObservable requests) where T : notnull { + /// + /// The _requests field. + /// private readonly IObservable _requests = requests ?? throw new ArgumentNullException(nameof(requests)); + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -37,6 +56,13 @@ public IObservable> Run() => Observable.Create new VirtualChangeSet(changes, new VirtualResponse(virtualised.Count, parameters.StartIndex, all.Count))).SubscribeSafe(observer); }); + /// + /// Executes the CheckParamsAndVirtualise operation. + /// + /// The all value. + /// The virtualised value. + /// The request value. + /// The result of the operation. private static IChangeSet? CheckParamsAndVirtualise(IList all, ChangeAwareList virtualised, IVirtualRequest? request) { if (request is null || request.StartIndex < 0 || request.Size < 1) @@ -47,6 +73,14 @@ public IObservable> Run() => Observable.Create + /// Executes the Virtualise operation. + /// + /// The all value. + /// The virtualised value. + /// The request value. + /// The changeSet value. + /// The result of the operation. private static IChangeSet Virtualise(IList all, ChangeAwareList virtualised, IVirtualRequest request, IChangeSet? changeSet = null) { if (changeSet is not null) diff --git a/src/DynamicData/List/ItemChange.cs b/src/DynamicData/List/ItemChange.cs index 4b7d31a46..3ddf64a5a 100644 --- a/src/DynamicData/List/ItemChange.cs +++ b/src/DynamicData/List/ItemChange.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Container to describe a single change to a cache. @@ -18,14 +22,14 @@ namespace DynamicData; public static readonly ItemChange Empty; /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// /// The reason. /// The current. /// The previous. /// Value of the current. /// Value of the previous. - public ItemChange(ListChangeReason reason, T current, in Optional previous, int currentIndex = -1, int previousIndex = -1) + public ItemChange(ListChangeReason reason, T current, in ReactiveUI.Primitives.Optional previous, int currentIndex = -1, int previousIndex = -1) : this() { Reason = reason; @@ -48,7 +52,7 @@ public ItemChange(ListChangeReason reason, T current, int currentIndex) Current = current; CurrentIndex = currentIndex; PreviousIndex = -1; - Previous = Optional.None; + Previous = ReactiveUI.Primitives.Optional.None; } /// @@ -70,7 +74,7 @@ public ItemChange(ListChangeReason reason, T current, int currentIndex) /// Gets the item from before the change. /// This is only when is . /// - public Optional Previous { get; } + public ReactiveUI.Primitives.Optional Previous { get; } /// /// Gets the previous index. diff --git a/src/DynamicData/List/Linq/AddKeyEnumerator.cs b/src/DynamicData/List/Linq/AddKeyEnumerator.cs index 150c7c4a6..bf8135839 100644 --- a/src/DynamicData/List/Linq/AddKeyEnumerator.cs +++ b/src/DynamicData/List/Linq/AddKeyEnumerator.cs @@ -1,24 +1,40 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Collections; +namespace DynamicData.Reactive.List.Linq; +#else namespace DynamicData.List.Linq; - +#endif + +/// +/// Provides members for the AddKeyEnumerator class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. +/// The keySelector value. internal sealed class AddKeyEnumerator(IChangeSet source, Func keySelector) : IEnumerable> where TObject : notnull where TKey : notnull { + /// + /// The _keySelector field. + /// private readonly Func _keySelector = keySelector ?? throw new ArgumentNullException(nameof(keySelector)); + /// + /// The _source field. + /// private readonly IChangeSet _source = source ?? throw new ArgumentNullException(nameof(source)); /// /// Returns an enumerator that iterates through the collection. /// /// - /// A that can be used to iterate through the collection. + /// A IEnumerator<T> that can be used to iterate through the collection. /// public IEnumerator> GetEnumerator() { @@ -103,5 +119,9 @@ public IEnumerator> GetEnumerator() } } + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/DynamicData/List/Linq/ItemChangeEnumerator.cs b/src/DynamicData/List/Linq/ItemChangeEnumerator.cs index 53c975d0a..e9be3c55e 100644 --- a/src/DynamicData/List/Linq/ItemChangeEnumerator.cs +++ b/src/DynamicData/List/Linq/ItemChangeEnumerator.cs @@ -1,14 +1,26 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Collections; +namespace DynamicData.Reactive.List.Linq; +#else namespace DynamicData.List.Linq; +#endif +/// +/// Provides members for the ItemChangeEnumerator class. +/// +/// The type of the T value. +/// The changeSet value. internal sealed class ItemChangeEnumerator(IChangeSet changeSet) : IEnumerable> where T : notnull { + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. public IEnumerator> GetEnumerator() { var lastKnownIndex = 0; @@ -48,5 +60,9 @@ public IEnumerator> GetEnumerator() } } + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/DynamicData/List/Linq/Reverser.cs b/src/DynamicData/List/Linq/Reverser.cs index 9be4b678a..9c02426a1 100644 --- a/src/DynamicData/List/Linq/Reverser.cs +++ b/src/DynamicData/List/Linq/Reverser.cs @@ -1,14 +1,31 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Linq; +#else namespace DynamicData.List.Linq; +#endif +/// +/// Provides members for the Reverser class. +/// +/// The type of the T value. internal sealed class Reverser where T : notnull { + /// + /// The _length field. + /// private int _length; + /// + /// Executes the Reverse operation. + /// + /// The changes value. + /// The result of the operation. public IEnumerable> Reverse(IChangeSet changes) { foreach (var change in changes) diff --git a/src/DynamicData/List/Linq/UnifiedChangeEnumerator.cs b/src/DynamicData/List/Linq/UnifiedChangeEnumerator.cs index 4a8d0c352..30aed895a 100644 --- a/src/DynamicData/List/Linq/UnifiedChangeEnumerator.cs +++ b/src/DynamicData/List/Linq/UnifiedChangeEnumerator.cs @@ -1,16 +1,33 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Collections; +using DynamicData.Reactive.List.Internal; +#else using DynamicData.List.Internal; +#endif +#if REACTIVE_SHIM + +namespace DynamicData.Reactive.List.Linq; +#else namespace DynamicData.List.Linq; +#endif +/// +/// Provides members for the UnifiedChangeEnumerator class. +/// +/// The type of the T value. +/// The changeSet value. internal sealed class UnifiedChangeEnumerator(IChangeSet changeSet) : IEnumerable> where T : notnull { + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. public IEnumerator> GetEnumerator() { foreach (var change in changeSet) @@ -45,5 +62,9 @@ public IEnumerator> GetEnumerator() } } + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/DynamicData/List/Linq/WithoutIndexEnumerator.cs b/src/DynamicData/List/Linq/WithoutIndexEnumerator.cs index 269bf2c2e..625b8b76c 100644 --- a/src/DynamicData/List/Linq/WithoutIndexEnumerator.cs +++ b/src/DynamicData/List/Linq/WithoutIndexEnumerator.cs @@ -1,19 +1,27 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Collections; +namespace DynamicData.Reactive.List.Linq; +#else namespace DynamicData.List.Linq; +#endif /// /// Index to remove the index. This is necessary for WhereReasonAre* operators. /// Otherwise these operators could break subsequent operators when the subsequent operator relies on the index. /// /// The type of the item. +/// The changeSet value. internal sealed class WithoutIndexEnumerator(IEnumerable> changeSet) : IEnumerable> where T : notnull { + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. public IEnumerator> GetEnumerator() { foreach (var change in changeSet) @@ -35,5 +43,9 @@ public IEnumerator> GetEnumerator() } } + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/DynamicData/List/ListChangeReason.cs b/src/DynamicData/List/ListChangeReason.cs index 89d8aa2dc..10fc0ea1c 100644 --- a/src/DynamicData/List/ListChangeReason.cs +++ b/src/DynamicData/List/ListChangeReason.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// The reason for an individual change to an observable list. diff --git a/src/DynamicData/List/ListEx.cs b/src/DynamicData/List/ListEx.cs index 7035b1d4d..b0f59a197 100644 --- a/src/DynamicData/List/ListEx.cs +++ b/src/DynamicData/List/ListEx.cs @@ -1,644 +1,661 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. -// 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.Collections.ObjectModel; - -// ReSharper disable once CheckNamespace -namespace DynamicData; - -/// -/// Extensions to help with maintenance of a list. -/// -public static class ListEx -{ - /// - /// Adds the items to the specified list. - /// - /// The type of the item. - /// The source. - /// The items. - /// - /// source - /// or - /// items. - /// - public static void Add(this IList source, IEnumerable items) - { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - items.ThrowArgumentNullExceptionIfNull(nameof(items)); - - items.ForEach(source.Add); - } - - /// - /// Adds the range if a negative is specified, otherwise the range is added at the end of the list. - /// - /// The type of the item. - /// The source. - /// The items. - /// The index. - public static void AddOrInsertRange(this IList source, IEnumerable items, int index) - { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - items.ThrowArgumentNullExceptionIfNull(nameof(items)); - - switch (source) - { - case List list when index >= 0: - list.InsertRange(index, items); - break; - case List list: - list.AddRange(items); - break; - case IExtendedList extendedList when index >= 0: - extendedList.InsertRange(items, index); - break; - case IExtendedList extendedList: - extendedList.AddRange(items); - break; - default: - { - if (index >= 0) - { - // TODO: Why the hell reverse? Surely there must be as reason otherwise I would not have done it. - items.Reverse().ForEach(t => source.Insert(index, t)); - } - else - { - items.ForEach(source.Add); - } - - break; - } - } - } - - /// - /// Adds the range to the source list. - /// - /// The type of the item. - /// The source. - /// The items. - /// - /// source - /// or - /// items. - /// - public static void AddRange(this IList source, IEnumerable items) - { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - items.ThrowArgumentNullExceptionIfNull(nameof(items)); - - switch (source) - { - case List list: - list.AddRange(items); - break; - case IExtendedList extendedList: - extendedList.AddRange(items); - break; - default: - foreach (var t in items) - source.Add(t); - break; - } - } - - /// - /// Adds the range to the list. The starting range is at the specified index. - /// - /// The type of the item. - /// The source. - /// The items. - /// The index. - public static void AddRange(this IList source, IEnumerable items, int index) - { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - items.ThrowArgumentNullExceptionIfNull(nameof(items)); - - switch (source) - { - case List list: - list.InsertRange(index, items); - break; - case IExtendedList list: - list.InsertRange(items, index); - break; - default: - items.ForEach(source.Add); - break; - } - } - - /// - /// Performs a binary search on the specified collection. - /// - /// The type of the item. - /// The list to be searched. - /// The value to search for. - /// The index of the specified value in the specified array, if value is found; otherwise, a negative number. - public static int BinarySearch(this IList list, TItem value) => BinarySearch(list, value, Comparer.Default); - - /// - /// Performs a binary search on the specified collection. - /// - /// The type of the item. - /// The list to be searched. - /// The value to search for. - /// The comparer that is used to compare the value with the list items. - /// The index of the specified value in the specified array, if value is found; otherwise, a negative number. - public static int BinarySearch(this IList list, TItem value, IComparer comparer) - { - comparer.ThrowArgumentNullExceptionIfNull(nameof(comparer)); - - return list.BinarySearch(value, comparer.Compare); - } - - /// - /// Performs a binary search on the specified collection. - /// Thanks to https://stackoverflow.com/questions/967047/how-to-perform-a-binary-search-on-ilistt. - /// - /// The type of the item. - /// The type of the searched item. - /// The list to be searched. - /// The value to search for. - /// The comparer that is used to compare the value with the list items. - /// The index of the specified value in the specified array, if value is found; otherwise, a negative number. - public static int BinarySearch(this IList list, TSearch value, Func comparer) - { - list.ThrowArgumentNullExceptionIfNull(nameof(list)); - comparer.ThrowArgumentNullExceptionIfNull(nameof(comparer)); - - var lower = 0; - var upper = list.Count - 1; - - while (lower <= upper) - { - var middle = lower + ((upper - lower) / 2); - var comparisonResult = comparer(value, list[middle]); - if (comparisonResult < 0) - { - upper = middle - 1; - } - else if (comparisonResult > 0) - { - lower = middle + 1; - } - else - { - return middle; - } - } - - return ~lower; - } - - /// - /// Clones the list from the specified change set. - /// - /// The type of the item. - /// The source. - /// The changes. - /// - /// source - /// or - /// changes. - /// - public static void Clone(this IList source, IChangeSet changes) - where T : notnull => Clone(source, changes, null); - - /// - /// Clones the list from the specified change set. - /// - /// The type of the item. - /// The source. - /// The changes. - /// An equality comparer to match items in the changes. - /// - /// source - /// or - /// changes. - /// - public static void Clone(this IList source, IChangeSet changes, IEqualityComparer? equalityComparer) - where T : notnull => Clone(source, (IEnumerable>)changes, equalityComparer); - - /// - /// Clones the list from the specified enumerable of changes. - /// - /// The type of the item. - /// The source. - /// The changes. - /// An equality comparer to match items in the changes. - /// - /// source - /// or - /// changes. - /// - public static void Clone(this IList source, IEnumerable> changes, IEqualityComparer? equalityComparer) - where T : notnull - { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - changes.ThrowArgumentNullExceptionIfNull(nameof(changes)); - - foreach (var item in changes) - { - Clone(source, item, equalityComparer ?? EqualityComparer.Default); - } - } - - /// - /// Finds the index of the current item using the specified equality comparer. - /// - /// The type of the item. - /// The source enumerable. - /// The item to get the index of. - /// The index. - public static int IndexOf(this IEnumerable source, T item) => IndexOf(source, item, EqualityComparer.Default); - - /// - /// Finds the index of the current item using the specified equality comparer. - /// - /// The type of the item. - /// The source enumerable. - /// The item to get the index of. - /// Use to determine object equality. - /// The index. - public static int IndexOf(this IEnumerable source, T item, IEqualityComparer equalityComparer) - { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - equalityComparer.ThrowArgumentNullExceptionIfNull(nameof(equalityComparer)); - - var i = 0; - foreach (var candidate in source) - { - if (equalityComparer.Equals(item, candidate)) - { - return i; - } - - i++; - } - - return -1; - } - - /// - /// Lookups the item using the specified comparer. If matched, the item's index is also returned. - /// - /// The type of the item. - /// The source. - /// The item. - /// The equality comparer. - /// The index of the item if available. - public static Optional> IndexOfOptional(this IEnumerable source, T item, IEqualityComparer? equalityComparer = null) - { - var comparer = equalityComparer ?? EqualityComparer.Default; - var index = source.IndexOf(item, comparer); - return index < 0 ? Optional>.None : new ItemWithIndex(item, index); - } - - /// - /// Removes the items from the specified list. - /// - /// The type of the item. - /// The source. - /// The items. - /// - /// source - /// or - /// items. - /// - public static void Remove(this IList source, IEnumerable items) - { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - items.ThrowArgumentNullExceptionIfNull(nameof(items)); - - items.ForEach(t => source.Remove(t)); - } - - /// - /// Removes many items from the collection in an optimal way. - /// - /// The type of the item. - /// The source. - /// The items to remove. - public static void RemoveMany(this IList source, IEnumerable itemsToRemove) - { - /* - This may seem OTT but for large sets of data where there are many removes scattered - across the source collection IndexOf lookups can result in very slow updates - (especially for subsequent operators) - */ - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - itemsToRemove.ThrowArgumentNullExceptionIfNull(nameof(itemsToRemove)); - - var toRemoveArray = itemsToRemove.AsArray(); - - // match all indexes and and remove in reverse as it is more efficient - var toRemove = source.IndexOfMany(toRemoveArray).OrderByDescending(x => x.Index).ToArray(); - - // if there are duplicates, it could be that an item exists in the - // source collection more than once - in that case the fast remove - // would remove each instance - var hasDuplicates = toRemove.Duplicates(t => t.Item).Any(); - - if (hasDuplicates) - { - // Slow remove but safe - toRemoveArray.ForEach(t => source.Remove(t)); - } - else - { - // Fast remove because we know the index of all and we remove in order - toRemove.ForEach(t => source.RemoveAt(t.Index)); - } - } - - /// - /// Replaces the specified item. - /// - /// The type of the item. - /// The source. - /// The original. - /// The value to replace with. - /// source - /// or - /// items. - public static void Replace(this IList source, T original, T replaceWith) - { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - original.ThrowArgumentNullExceptionIfNull(nameof(original)); - replaceWith.ThrowArgumentNullExceptionIfNull(nameof(replaceWith)); - - var index = source.IndexOf(original); - if (index == -1) - { - throw new ArgumentException("Cannot find index of original item. Either it does not exist in the list or the hashcode has mutated"); - } - - source[index] = replaceWith; - } - - /// - /// Replaces the specified item. - /// - /// The type of item. - /// The source. - /// The item which is to be replaced. If not in the list and argument exception will be thrown. - /// The new item. - /// The equality comparer to be used to find the original item in the list. - /// source - /// or - /// items. - public static void Replace(this IList source, T original, T replaceWith, IEqualityComparer comparer) - { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - original.ThrowArgumentNullExceptionIfNull(nameof(original)); - replaceWith.ThrowArgumentNullExceptionIfNull(nameof(replaceWith)); - comparer.ThrowArgumentNullExceptionIfNull(nameof(comparer)); - - var index = source.IndexOf(original); - if (index == -1) - { - throw new ArgumentException("Cannot find index of original item. Either it does not exist in the list or the hashcode has mutated"); - } - - if (comparer.Equals(source[index], replaceWith)) - { - source[index] = replaceWith; - } - } - - /// - /// Replaces the item if found, otherwise the item is added to the list. - /// - /// The type of the item. - /// The source. - /// The original. - /// The value to replace with. - public static void ReplaceOrAdd(this IList source, T original, T replaceWith) - { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - original.ThrowArgumentNullExceptionIfNull(nameof(original)); - replaceWith.ThrowArgumentNullExceptionIfNull(nameof(replaceWith)); - - var index = source.IndexOf(original); - if (index == -1) - { - source.Add(replaceWith); - } - else - { - source[index] = replaceWith; - } - } - - /// - /// Clears the collection if the number of items in the range is the same as the source collection. Otherwise a remove many operation is applied. - /// NB: This is because an observable change set may be a composite of multiple change sets in which case if one of them has clear operation applied it should not clear the entire result. - /// - /// The type of the item. - /// The source. - /// The change. - internal static void ClearOrRemoveMany(this IList source, Change change) - where T : notnull - { - // apply this to other operators - if (source.Count == change.Range.Count) - { - source.Clear(); - } - else - { - source.RemoveMany(change.Range); - } - } - - internal static bool MovedWithinRange(this Change source, int startIndex, int endIndex) - where T : notnull - { - if (source.Reason != ListChangeReason.Moved) - { - return false; - } - - var current = source.Item.CurrentIndex; - var previous = source.Item.PreviousIndex; - - return (current >= startIndex && current <= endIndex) || (previous >= startIndex && previous <= endIndex); - } - - private static void Clone(this IList source, Change item, IEqualityComparer equalityComparer) - where T : notnull - { - var changeAware = source as ChangeAwareList; - - switch (item.Reason) - { - case ListChangeReason.Add: - { - var change = item.Item; - var hasIndex = change.CurrentIndex >= 0; - if (hasIndex) - { - source.Insert(change.CurrentIndex, change.Current); - } - else - { - source.Add(change.Current); - } - - break; - } - - case ListChangeReason.AddRange: - { - source.AddOrInsertRange(item.Range, item.Range.Index); - break; - } - - case ListChangeReason.Clear: - { - source.ClearOrRemoveMany(item); - break; - } - - case ListChangeReason.Replace: - { - var change = item.Item; - if (change.CurrentIndex >= 0 && change.CurrentIndex == change.PreviousIndex) - { - source[change.CurrentIndex] = change.Current; - } - else - { - if (change.PreviousIndex == -1) - { - source.Remove(change.Previous.Value); - } - else - { - // is this best? or replace + move? - source.RemoveAt(change.PreviousIndex); - } - - if (change.CurrentIndex == -1) - { - source.Add(change.Current); - } - else - { - source.Insert(change.CurrentIndex, change.Current); - } - } - - break; - } - - case ListChangeReason.Refresh: - { - if (changeAware is not null) - { - changeAware.RefreshAt(item.Item.CurrentIndex); - } - else - { - source.RemoveAt(item.Item.CurrentIndex); - source.Insert(item.Item.CurrentIndex, item.Item.Current); - } - - break; - } - - case ListChangeReason.Remove: - { - var change = item.Item; - var hasIndex = change.CurrentIndex >= 0; - if (hasIndex) - { - source.RemoveAt(change.CurrentIndex); - } - else - { - var index = source.IndexOf(change.Current, equalityComparer); - if (index > -1) - { - source.RemoveAt(index); - } - } - - break; - } - - case ListChangeReason.RemoveRange: - { - // ignore this case because WhereReasonsAre removes the index [in which case call RemoveMany] - //// if (item.Range.Index < 0) - //// throw new UnspecifiedIndexException("ListChangeReason.RemoveRange should not have an index specified index"); - if (item.Range.Index >= 0 && (source is IExtendedList || source is List)) - { - source.RemoveRange(item.Range.Index, item.Range.Count); - } - else - { - source.RemoveMany(item.Range); - } - - break; - } - - case ListChangeReason.Moved: - { - var change = item.Item; - var hasIndex = change.CurrentIndex >= 0; - if (!hasIndex) - { - throw new UnspecifiedIndexException("Cannot move as an index was not specified"); - } - - if (source is IExtendedList extendedList) - { - extendedList.Move(change.PreviousIndex, change.CurrentIndex); - } - else if (source is ObservableCollection observableCollection) - { - observableCollection.Move(change.PreviousIndex, change.CurrentIndex); - } - else - { - // check this works whatever the index is - source.RemoveAt(change.PreviousIndex); - source.Insert(change.CurrentIndex, change.Current); - } - - break; - } - } - } - - /// - /// Removes the number of items, starting at the specified index. - /// - /// The type of the item. - /// The source. - /// The index. - /// The count. - /// Cannot remove range. - private static void RemoveRange(this IList source, int index, int count) - { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - - switch (source) - { - case List list: - list.RemoveRange(index, count); - break; - case IExtendedList list: - list.RemoveRange(index, count); - break; - default: - throw new NotSupportedException($"Cannot remove range from {source.GetType().FullName}"); - } - } -} +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Roland Pheasant licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +// ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else +namespace DynamicData; +#endif + +/// +/// Extensions to help with maintenance of a list. +/// +public static class ListEx +{ + /// + /// Adds the items to the specified list. + /// + /// The type of the item. + /// The source. + /// The items. + /// + /// source + /// or + /// items. + /// + public static void Add(this IList source, IEnumerable items) + { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(items); + + items.ForEach(source.Add); + } + + /// + /// Adds the range if a negative is specified, otherwise the range is added at the end of the list. + /// + /// The type of the item. + /// The source. + /// The items. + /// The index. + public static void AddOrInsertRange(this IList source, IEnumerable items, int index) + { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(items); + + switch (source) + { + case List list when index >= 0: + list.InsertRange(index, items); + break; + case List list: + list.AddRange(items); + break; + case IExtendedList extendedList when index >= 0: + extendedList.InsertRange(items, index); + break; + case IExtendedList extendedList: + extendedList.AddRange(items); + break; + default: + { + if (index >= 0) + { + // TODO: Why the hell reverse? Surely there must be as reason otherwise I would not have done it. + items.Reverse().ForEach(t => source.Insert(index, t)); + } + else + { + items.ForEach(source.Add); + } + + break; + } + } + } + + /// + /// Adds the range to the source list. + /// + /// The type of the item. + /// The source. + /// The items. + /// + /// source + /// or + /// items. + /// + public static void AddRange(this IList source, IEnumerable items) + { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(items); + + switch (source) + { + case List list: + list.AddRange(items); + break; + case IExtendedList extendedList: + extendedList.AddRange(items); + break; + default: + foreach (var t in items) + source.Add(t); + break; + } + } + + /// + /// Adds the range to the list. The starting range is at the specified index. + /// + /// The type of the item. + /// The source. + /// The items. + /// The index. + public static void AddRange(this IList source, IEnumerable items, int index) + { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(items); + + switch (source) + { + case List list: + list.InsertRange(index, items); + break; + case IExtendedList list: + list.InsertRange(items, index); + break; + default: + items.ForEach(source.Add); + break; + } + } + + /// + /// Performs a binary search on the specified collection. + /// + /// The type of the item. + /// The list to be searched. + /// The value to search for. + /// The index of the specified value in the specified array, if value is found; otherwise, a negative number. + public static int BinarySearch(this IList list, TItem value) => BinarySearch(list, value, Comparer.Default); + + /// + /// Performs a binary search on the specified collection. + /// + /// The type of the item. + /// The list to be searched. + /// The value to search for. + /// The comparer that is used to compare the value with the list items. + /// The index of the specified value in the specified array, if value is found; otherwise, a negative number. + public static int BinarySearch(this IList list, TItem value, IComparer comparer) + { + ArgumentExceptionHelper.ThrowIfNull(comparer); + + return list.BinarySearch(value, comparer.Compare); + } + + /// + /// Performs a binary search on the specified collection. + /// Thanks to https://stackoverflow.com/questions/967047/how-to-perform-a-binary-search-on-ilistt. + /// + /// The type of the item. + /// The type of the searched item. + /// The list to be searched. + /// The value to search for. + /// The comparer that is used to compare the value with the list items. + /// The index of the specified value in the specified array, if value is found; otherwise, a negative number. + public static int BinarySearch(this IList list, TSearch value, Func comparer) + { + ArgumentExceptionHelper.ThrowIfNull(list); + ArgumentExceptionHelper.ThrowIfNull(comparer); + + var lower = 0; + var upper = list.Count - 1; + + while (lower <= upper) + { + var middle = lower + ((upper - lower) / 2); + var comparisonResult = comparer(value, list[middle]); + if (comparisonResult < 0) + { + upper = middle - 1; + } + else if (comparisonResult > 0) + { + lower = middle + 1; + } + else + { + return middle; + } + } + + return ~lower; + } + + /// + /// Clones the list from the specified change set. + /// + /// The type of the item. + /// The source. + /// The changes. + /// + /// source + /// or + /// changes. + /// + public static void Clone(this IList source, IChangeSet changes) + where T : notnull => Clone(source, changes, null); + + /// + /// Clones the list from the specified change set. + /// + /// The type of the item. + /// The source. + /// The changes. + /// An equality comparer to match items in the changes. + /// + /// source + /// or + /// changes. + /// + public static void Clone(this IList source, IChangeSet changes, IEqualityComparer? equalityComparer) + where T : notnull => Clone(source, (IEnumerable>)changes, equalityComparer); + + /// + /// Clones the list from the specified enumerable of changes. + /// + /// The type of the item. + /// The source. + /// The changes. + /// An equality comparer to match items in the changes. + /// + /// source + /// or + /// changes. + /// + public static void Clone(this IList source, IEnumerable> changes, IEqualityComparer? equalityComparer) + where T : notnull + { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(changes); + + foreach (var item in changes) + { + Clone(source, item, equalityComparer ?? EqualityComparer.Default); + } + } + + /// + /// Finds the index of the current item using the specified equality comparer. + /// + /// The type of the item. + /// The source enumerable. + /// The item to get the index of. + /// The index. + public static int IndexOf(this IEnumerable source, T item) => IndexOf(source, item, EqualityComparer.Default); + + /// + /// Finds the index of the current item using the specified equality comparer. + /// + /// The type of the item. + /// The source enumerable. + /// The item to get the index of. + /// Use to determine object equality. + /// The index. + public static int IndexOf(this IEnumerable source, T item, IEqualityComparer equalityComparer) + { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(equalityComparer); + + var i = 0; + foreach (var candidate in source) + { + if (equalityComparer.Equals(item, candidate)) + { + return i; + } + + i++; + } + + return -1; + } + + /// + /// Lookups the item using the specified comparer. If matched, the item's index is also returned. + /// + /// The type of the item. + /// The source. + /// The item. + /// The equality comparer. + /// The index of the item if available. + public static ReactiveUI.Primitives.Optional> IndexOfOptional(this IEnumerable source, T item, IEqualityComparer? equalityComparer = null) + { + var comparer = equalityComparer ?? EqualityComparer.Default; + var index = source.IndexOf(item, comparer); + return index < 0 ? ReactiveUI.Primitives.Optional>.None : new ItemWithIndex(item, index); + } + + /// + /// Removes the items from the specified list. + /// + /// The type of the item. + /// The source. + /// The items. + /// + /// source + /// or + /// items. + /// + public static void Remove(this IList source, IEnumerable items) + { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(items); + + items.ForEach(t => source.Remove(t)); + } + + /// + /// Removes many items from the collection in an optimal way. + /// + /// The type of the item. + /// The source. + /// The items to remove. + public static void RemoveMany(this IList source, IEnumerable itemsToRemove) + { + /* + This may seem OTT but for large sets of data where there are many removes scattered + across the source collection IndexOf lookups can result in very slow updates + (especially for subsequent operators) + */ + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(itemsToRemove); + + var toRemoveArray = itemsToRemove.AsArray(); + + // match all indexes and and remove in reverse as it is more efficient + var toRemove = source.IndexOfMany(toRemoveArray).OrderByDescending(x => x.Index).ToArray(); + + // if there are duplicates, it could be that an item exists in the + // source collection more than once - in that case the fast remove + // would remove each instance + var hasDuplicates = toRemove.Duplicates(t => t.Item).Any(); + + if (hasDuplicates) + { + // Slow remove but safe + toRemoveArray.ForEach(t => source.Remove(t)); + } + else + { + // Fast remove because we know the index of all and we remove in order + toRemove.ForEach(t => source.RemoveAt(t.Index)); + } + } + + /// + /// Replaces the specified item. + /// + /// The type of the item. + /// The source. + /// The original. + /// The value to replace with. + /// source + /// or + /// items. + public static void Replace(this IList source, T original, T replaceWith) + { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(original); + ArgumentExceptionHelper.ThrowIfNull(replaceWith); + + var index = source.IndexOf(original); + if (index == -1) + { + throw new ArgumentException("Cannot find index of original item. Either it does not exist in the list or the hashcode has mutated"); + } + + source[index] = replaceWith; + } + + /// + /// Replaces the specified item. + /// + /// The type of item. + /// The source. + /// The item which is to be replaced. If not in the list and argument exception will be thrown. + /// The new item. + /// The equality comparer to be used to find the original item in the list. + /// source + /// or + /// items. + public static void Replace(this IList source, T original, T replaceWith, IEqualityComparer comparer) + { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(original); + ArgumentExceptionHelper.ThrowIfNull(replaceWith); + ArgumentExceptionHelper.ThrowIfNull(comparer); + + var index = source.IndexOf(original); + if (index == -1) + { + throw new ArgumentException("Cannot find index of original item. Either it does not exist in the list or the hashcode has mutated"); + } + + if (comparer.Equals(source[index], replaceWith)) + { + source[index] = replaceWith; + } + } + + /// + /// Replaces the item if found, otherwise the item is added to the list. + /// + /// The type of the item. + /// The source. + /// The original. + /// The value to replace with. + public static void ReplaceOrAdd(this IList source, T original, T replaceWith) + { + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(original); + ArgumentExceptionHelper.ThrowIfNull(replaceWith); + + var index = source.IndexOf(original); + if (index == -1) + { + source.Add(replaceWith); + } + else + { + source[index] = replaceWith; + } + } + + /// + /// Clears the collection if the number of items in the range is the same as the source collection. Otherwise a remove many operation is applied. + /// NB: This is because an observable change set may be a composite of multiple change sets in which case if one of them has clear operation applied it should not clear the entire result. + /// + /// The type of the item. + /// The source. + /// The change. + internal static void ClearOrRemoveMany(this IList source, Change change) + where T : notnull + { + // apply this to other operators + if (source.Count == change.Range.Count) + { + source.Clear(); + } + else + { + source.RemoveMany(change.Range); + } + } + + /// + /// Executes the MovedWithinRange operation. + /// + /// The type of the T value. + /// The source value. + /// The startIndex value. + /// The endIndex value. + /// The result of the operation. + internal static bool MovedWithinRange(this Change source, int startIndex, int endIndex) + where T : notnull + { + if (source.Reason != ListChangeReason.Moved) + { + return false; + } + + var current = source.Item.CurrentIndex; + var previous = source.Item.PreviousIndex; + + return (current >= startIndex && current <= endIndex) || (previous >= startIndex && previous <= endIndex); + } + + /// + /// Executes the Clone operation. + /// + /// The type of the T value. + /// The source value. + /// The item value. + /// The equalityComparer value. + private static void Clone(this IList source, Change item, IEqualityComparer equalityComparer) + where T : notnull + { + var changeAware = source as ChangeAwareList; + + switch (item.Reason) + { + case ListChangeReason.Add: + { + var change = item.Item; + var hasIndex = change.CurrentIndex >= 0; + if (hasIndex) + { + source.Insert(change.CurrentIndex, change.Current); + } + else + { + source.Add(change.Current); + } + + break; + } + + case ListChangeReason.AddRange: + { + source.AddOrInsertRange(item.Range, item.Range.Index); + break; + } + + case ListChangeReason.Clear: + { + source.ClearOrRemoveMany(item); + break; + } + + case ListChangeReason.Replace: + { + var change = item.Item; + if (change.CurrentIndex >= 0 && change.CurrentIndex == change.PreviousIndex) + { + source[change.CurrentIndex] = change.Current; + } + else + { + if (change.PreviousIndex == -1) + { + source.Remove(change.Previous.Value); + } + else + { + // is this best? or replace + move? + source.RemoveAt(change.PreviousIndex); + } + + if (change.CurrentIndex == -1) + { + source.Add(change.Current); + } + else + { + source.Insert(change.CurrentIndex, change.Current); + } + } + + break; + } + + case ListChangeReason.Refresh: + { + if (changeAware is not null) + { + changeAware.RefreshAt(item.Item.CurrentIndex); + } + else + { + source.RemoveAt(item.Item.CurrentIndex); + source.Insert(item.Item.CurrentIndex, item.Item.Current); + } + + break; + } + + case ListChangeReason.Remove: + { + var change = item.Item; + var hasIndex = change.CurrentIndex >= 0; + if (hasIndex) + { + source.RemoveAt(change.CurrentIndex); + } + else + { + var index = source.IndexOf(change.Current, equalityComparer); + if (index > -1) + { + source.RemoveAt(index); + } + } + + break; + } + + case ListChangeReason.RemoveRange: + { + // ignore this case because WhereReasonsAre removes the index [in which case call RemoveMany] + //// if (item.Range.Index < 0) + //// throw new UnspecifiedIndexException("ListChangeReason.RemoveRange should not have an index specified index"); + if (item.Range.Index >= 0 && (source is IExtendedList || source is List)) + { + source.RemoveRange(item.Range.Index, item.Range.Count); + } + else + { + source.RemoveMany(item.Range); + } + + break; + } + + case ListChangeReason.Moved: + { + var change = item.Item; + var hasIndex = change.CurrentIndex >= 0; + if (!hasIndex) + { + throw new UnspecifiedIndexException("Cannot move as an index was not specified"); + } + + if (source is IExtendedList extendedList) + { + extendedList.Move(change.PreviousIndex, change.CurrentIndex); + } + else if (source is ObservableCollection observableCollection) + { + observableCollection.Move(change.PreviousIndex, change.CurrentIndex); + } + else + { + // check this works whatever the index is + source.RemoveAt(change.PreviousIndex); + source.Insert(change.CurrentIndex, change.Current); + } + + break; + } + } + } + + /// + /// Removes the number of items, starting at the specified index. + /// + /// The type of the item. + /// The source. + /// The index. + /// The count. + /// Cannot remove range. + private static void RemoveRange(this IList source, int index, int count) + { + ArgumentExceptionHelper.ThrowIfNull(source); + + switch (source) + { + case List list: + list.RemoveRange(index, count); + break; + case IExtendedList list: + list.RemoveRange(index, count); + break; + default: + throw new NotSupportedException($"Cannot remove range from {source.GetType().FullName}"); + } + } +} diff --git a/src/DynamicData/List/ListFilterPolicy.cs b/src/DynamicData/List/ListFilterPolicy.cs index 8b347a765..943ead2cf 100644 --- a/src/DynamicData/List/ListFilterPolicy.cs +++ b/src/DynamicData/List/ListFilterPolicy.cs @@ -1,8 +1,13 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Specifies which filter strategy should be used when the filter predicate is changed. diff --git a/src/DynamicData/List/ObservableListEx.Adapt.cs b/src/DynamicData/List/ObservableListEx.Adapt.cs index 942d943c7..c33f3960e 100644 --- a/src/DynamicData/List/ObservableListEx.Adapt.cs +++ b/src/DynamicData/List/ObservableListEx.Adapt.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -24,26 +22,26 @@ namespace DynamicData; public static partial class ObservableListEx { /// - /// Injects a side effect into a changeset stream via an . + /// Injects a side effect into a changeset stream via an IChangeSetAdaptor<T>. /// The adaptor's Adapt method is invoked for each changeset before it is forwarded downstream unchanged. /// /// The type of items in the list. - /// The source to observe and adapt. - /// The adaptor whose Adapt method is invoked for each changeset. + /// The source IObservable<IChangeSet<T>> to observe and adapt. + /// The IChangeSetAdaptor<T> adaptor whose Adapt method is invoked for each changeset. /// A list changeset stream identical to the source, with the adaptor side effect applied. /// or is . /// /// - /// This is the primary extension point for custom UI binding adaptors (e.g., + /// This is the primary extension point for custom UI binding adaptors (e.g., Bind<T>(IObservable<IChangeSet<T>>, IObservableCollection<T>, BindingOptions) /// delegates to this operator). If the adaptor throws, the exception propagates downstream as OnError. /// /// - /// + /// Bind<T>(IObservable<IChangeSet<T>>, IObservableCollection<T>, BindingOptions) public static IObservable> Adapt(this IObservable> source, IChangeSetAdaptor adaptor) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - adaptor.ThrowArgumentNullExceptionIfNull(nameof(adaptor)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(adaptor); return Observable.Create>( observer => diff --git a/src/DynamicData/List/ObservableListEx.AddKey.cs b/src/DynamicData/List/ObservableListEx.AddKey.cs index 0ead13a87..c2e894bd6 100644 --- a/src/DynamicData/List/ObservableListEx.AddKey.cs +++ b/src/DynamicData/List/ObservableListEx.AddKey.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Linq; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -28,9 +26,9 @@ public static partial class ObservableListEx /// /// The type of items in the list. /// The type of the key. - /// The source to add keys to, converting to a cache changeset. - /// A function to extract a unique key from each item. - /// A cache changeset stream with keyed items. + /// The source IObservable<IChangeSet<TObject>> to add keys to, converting to a cache changeset. + /// A Func<T, TResult> function to extract a unique key from each item. + /// A cache IObservable<IChangeSet<TObject, TKey>> changeset stream with keyed items. /// or is . /// /// @@ -38,13 +36,13 @@ public static partial class ObservableListEx /// Use this when you need to transition from list-based pipelines to cache-based operators (Filter by key, Join, Group, etc.). /// /// - /// + /// ObservableCacheEx.RemoveKey<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>) public static IObservable> AddKey(this IObservable> source, Func keySelector) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - keySelector.ThrowArgumentNullExceptionIfNull(nameof(keySelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(keySelector); return source.Select(changes => new ChangeSet(new AddKeyEnumerator(changes, keySelector))); } diff --git a/src/DynamicData/List/ObservableListEx.And.cs b/src/DynamicData/List/ObservableListEx.And.cs index 2dee36d90..764233ad9 100644 --- a/src/DynamicData/List/ObservableListEx.And.cs +++ b/src/DynamicData/List/ObservableListEx.And.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -28,8 +26,8 @@ public static partial class ObservableListEx /// Only items present in ALL sources appear in the result. /// /// The type of items in the lists. - /// The first source to intersect. - /// The additional changeset streams to intersect with. + /// The first source IObservable<IChangeSet<T>> to intersect. + /// The additional IObservable<IChangeSet<T>> changeset streams to intersect with. /// A list changeset stream containing items that exist in every source. /// is . /// @@ -47,50 +45,66 @@ public static partial class ObservableListEx /// /// Worth noting: Item identity uses object equality, not position. Duplicate items in a single source are reference-counted independently. /// - /// - /// - /// - /// + /// Or<T>(IObservable<IChangeSet<T>>, IObservable<IChangeSet<T>>[]) + /// Except<T>(IObservable<IChangeSet<T>>, IObservable<IChangeSet<T>>[]) + /// Xor<T>(IObservable<IChangeSet<T>>, IObservable<IChangeSet<T>>[]) + /// ObservableCacheEx.And<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IObservable<IChangeSet<TObject, TKey>>[]) public static IObservable> And(this IObservable> source, params IObservable>[] others) where T : notnull { - others.ThrowArgumentNullExceptionIfNull(nameof(others)); + ArgumentExceptionHelper.ThrowIfNull(others); return source.Combine(CombineOperator.And, others); } - /// - /// A of changeset streams to intersect. + /// + /// Provides an overload of Combine for the supplied arguments. + /// + /// The type of the T value. + /// A ICollection<T> of changeset streams to intersect. + /// The resulting observable sequence. /// - /// + /// This overload follows the same core behavior as the related overload. /// This overload accepts a pre-built collection of sources instead of a params array. /// public static IObservable> And(this ICollection>> sources) where T : notnull => sources.Combine(CombineOperator.And); - /// - /// An of changeset streams. Sources can be added or removed dynamically. + /// + /// Provides an overload of Combine for the supplied arguments. + /// + /// The type of the T value. + /// An IObservableList<T> of changeset streams. Sources can be added or removed dynamically. + /// The resulting observable sequence. /// - /// + /// This overload follows the same core behavior as the related overload. /// This overload supports dynamic source management: adding or removing changeset streams from the observable list triggers re-evaluation. /// public static IObservable> And(this IObservableList>> sources) where T : notnull => sources.Combine(CombineOperator.And); - /// - /// An of . Each inner list's changes are connected automatically. + /// + /// Provides an overload of Combine for the supplied arguments. + /// + /// The type of the T value. + /// An IObservableList<IObservableList<T>> of IObservableList<IObservableList<T>>. Each inner list's changes are connected automatically. + /// The resulting observable sequence. /// - /// - /// This overload accepts instances directly, calling Connect() internally. + /// This overload follows the same core behavior as the related overload. + /// This overload accepts IObservableList<T> instances directly, calling Connect() internally. /// public static IObservable> And(this IObservableList> sources) where T : notnull => sources.Combine(CombineOperator.And); - /// - /// An of . Each inner list's changes are connected automatically. + /// + /// Provides an overload of Combine for the supplied arguments. + /// + /// The type of the T value. + /// An IObservableList<ISourceList<T>> of ISourceList<T>. Each inner list's changes are connected automatically. + /// The resulting observable sequence. /// - /// - /// This overload accepts instances directly, calling Connect() internally. + /// This overload follows the same core behavior as the related overload. + /// This overload accepts ISourceList<T> instances directly, calling Connect() internally. /// public static IObservable> And(this IObservableList> sources) where T : notnull => sources.Combine(CombineOperator.And); diff --git a/src/DynamicData/List/ObservableListEx.AsObservableList.cs b/src/DynamicData/List/ObservableListEx.AsObservableList.cs index 478ae5b63..471219da6 100644 --- a/src/DynamicData/List/ObservableListEx.AsObservableList.cs +++ b/src/DynamicData/List/ObservableListEx.AsObservableList.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -24,40 +22,40 @@ namespace DynamicData; public static partial class ObservableListEx { /// - /// Wraps a as a read-only , hiding mutation methods. + /// Wraps a ISourceList<T> as a read-only IObservableList<T>, hiding mutation methods. /// /// The type of items in the list. - /// The mutable source list to wrap. + /// The ISourceList<T> mutable source list to wrap. /// A read-only observable list that mirrors the source. /// is . public static IObservableList AsObservableList(this ISourceList source) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new AnonymousObservableList(source); } /// - /// Materializes a changeset stream into a read-only . + /// Materializes a changeset stream into a read-only IObservableList<T>. /// The list is kept in sync with the source stream for the lifetime of the subscription. /// /// The type of items in the list. - /// The source to materialize into a read-only list. + /// The source IObservable<IChangeSet<T>> to materialize into a read-only list. /// A read-only observable list reflecting the current state of the stream. /// is . /// /// - /// This is the primary way to multicast a changeset pipeline. Materializing once into an , + /// This is the primary way to multicast a changeset pipeline. Materializing once into an IObservableList<T>, /// then calling Connect() on the result for each downstream consumer, ensures the upstream operators are evaluated only once /// regardless of how many subscribers consume the result. /// /// - /// + /// AsObservableList<T>(ISourceList<T>) public static IObservableList AsObservableList(this IObservable> source) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new AnonymousObservableList(source); } diff --git a/src/DynamicData/List/ObservableListEx.AutoRefresh.cs b/src/DynamicData/List/ObservableListEx.AutoRefresh.cs index d5e0660f8..40f03e7b3 100644 --- a/src/DynamicData/List/ObservableListEx.AutoRefresh.cs +++ b/src/DynamicData/List/ObservableListEx.AutoRefresh.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; +#if REACTIVE_SHIM +using DynamicData.Reactive.Binding; +#else using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -28,7 +26,7 @@ public static partial class ObservableListEx /// changes when any property changes, causing downstream operators to re-evaluate. /// /// The type of items, which must implement . - /// The source to monitor for property-driven refresh signals. + /// The source IObservable<IChangeSet<TObject>> to monitor for property-driven refresh signals. /// An optional buffer duration to batch multiple refresh signals into a single changeset. /// An optional throttle applied to each item's property change notifications. /// The scheduler for throttle and buffer timing. Defaults to . @@ -36,8 +34,8 @@ public static partial class ObservableListEx /// is . /// /// - /// Wraps using WhenAnyPropertyChanged() as the re-evaluator. - /// Pair with or + /// Wraps AutoRefreshOnObservable<TObject, TAny> using WhenAnyPropertyChanged() as the re-evaluator. + /// Pair with Filter<T>(IObservable<IChangeSet<T>>, Func<T, bool>) or Sort<T>(IObservable<IChangeSet<T>>, IComparer<T>, SortOptions, IObservable<Unit>?, IObservable<IComparer<T>>?, int) /// to get reactive re-evaluation on property changes. /// /// @@ -50,14 +48,14 @@ public static partial class ObservableListEx /// /// Worth noting: Each item generates a subscription. For large lists with frequent property changes, use and to reduce churn. /// - /// - /// - /// - /// + /// AutoRefresh<TObject, TProperty>(IObservable<IChangeSet<TObject>>, Expression<Func<TObject, TProperty>>, TimeSpan?, TimeSpan?, IScheduler?) + /// AutoRefreshOnObservable<TObject, TAny>(IObservable<IChangeSet<TObject>>, Func<TObject, IObservable<TAny>>, TimeSpan?, IScheduler?) + /// SuppressRefresh<T>(IObservable<IChangeSet<T>>) + /// ObservableCacheEx.AutoRefresh<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, TimeSpan?, TimeSpan?, IScheduler?) public static IObservable> AutoRefresh(this IObservable> source, TimeSpan? changeSetBuffer = null, TimeSpan? propertyChangeThrottle = null, IScheduler? scheduler = null) where TObject : INotifyPropertyChanged { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.AutoRefreshOnObservable( t => @@ -78,12 +76,20 @@ public static IObservable> AutoRefresh(this IObserv /// and emits Refresh changes when that property changes, causing downstream operators to re-evaluate. More efficient than /// the all-properties overload when only one property (of type ) affects downstream behavior. /// - /// + /// The type of the TObject value. + /// The type of the TProperty value. + /// This overload follows the same core behavior as the related overload. + /// The source value. + /// The propertyAccessor value. + /// The changeSetBuffer value. + /// The propertyChangeThrottle value. + /// The scheduler value. + /// The resulting observable sequence. public static IObservable> AutoRefresh(this IObservable> source, Expression> propertyAccessor, TimeSpan? changeSetBuffer = null, TimeSpan? propertyChangeThrottle = null, IScheduler? scheduler = null) where TObject : INotifyPropertyChanged { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - propertyAccessor.ThrowArgumentNullExceptionIfNull(nameof(propertyAccessor)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(propertyAccessor); return source.AutoRefreshOnObservable( t => diff --git a/src/DynamicData/List/ObservableListEx.AutoRefreshOnObservable.cs b/src/DynamicData/List/ObservableListEx.AutoRefreshOnObservable.cs index dc3ede92c..58658fe13 100644 --- a/src/DynamicData/List/ObservableListEx.AutoRefreshOnObservable.cs +++ b/src/DynamicData/List/ObservableListEx.AutoRefreshOnObservable.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -29,15 +27,15 @@ public static partial class ObservableListEx /// /// The type of items in the list. /// The type emitted by the re-evaluator observable (value is ignored). - /// The source to monitor for observable-driven refresh signals. - /// A factory that, given an item, returns an observable whose emissions trigger a Refresh for that item. + /// The source IObservable<IChangeSet<TObject>> to monitor for observable-driven refresh signals. + /// A Func<T, TResult> factory that, given an item, returns an observable whose emissions trigger a Refresh for that item. /// An optional buffer duration to batch refresh signals into a single changeset. /// The for buffering. /// A list changeset stream with additional Refresh changes injected when per-item observables fire. /// or is . /// /// - /// This is the general-purpose refresh mechanism. + /// This is the general-purpose refresh mechanism. AutoRefresh<TObject>(IObservable<IChangeSet<TObject>>, TimeSpan?, TimeSpan?, IScheduler?) /// is a convenience wrapper that uses WhenAnyPropertyChanged() as the re-evaluator. /// /// @@ -49,14 +47,14 @@ public static partial class ObservableListEx /// Re-evaluator firesThe item's current index is looked up and a Refresh change is emitted. /// /// - /// - /// - /// + /// AutoRefresh<TObject>(IObservable<IChangeSet<TObject>>, TimeSpan?, TimeSpan?, IScheduler?) + /// SuppressRefresh<T>(IObservable<IChangeSet<T>>) + /// ObservableCacheEx.AutoRefreshOnObservable<TObject, TKey, TAny>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, IObservable<TAny>>, TimeSpan?, IScheduler?) public static IObservable> AutoRefreshOnObservable(this IObservable> source, Func> reevaluator, TimeSpan? changeSetBuffer = null, IScheduler? scheduler = null) where TObject : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - reevaluator.ThrowArgumentNullExceptionIfNull(nameof(reevaluator)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(reevaluator); return new AutoRefresh(source, reevaluator, changeSetBuffer, scheduler).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.Bind.cs b/src/DynamicData/List/ObservableListEx.Bind.cs index fe14bece5..15b26aa0b 100644 --- a/src/DynamicData/List/ObservableListEx.Bind.cs +++ b/src/DynamicData/List/ObservableListEx.Bind.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -24,17 +22,17 @@ namespace DynamicData; public static partial class ObservableListEx { /// - /// Applies changeset mutations to a target for UI data binding. + /// Applies changeset mutations to a target IObservableCollection<T> for UI data binding. /// /// The type of items in the list. - /// The source to bind to a collection. - /// The target collection to keep in sync. + /// The source IObservable<IChangeSet<T>> to bind to a collection. + /// The IObservableCollection<T> target collection to keep in sync. /// When a changeset exceeds this many changes, the collection is reset instead of applying individual changes. /// A continuation of the source changeset stream (allows further chaining). /// or is . /// /// - /// Delegates to with an internal collection adaptor. + /// Delegates to Adapt<T>(IObservable<IChangeSet<T>>, IChangeSetAdaptor<T>) with an internal collection adaptor. /// Each changeset is applied to the target collection on the calling thread. For UI binding, ensure the source is /// observed on the UI thread (e.g., via ObserveOn). /// @@ -49,16 +47,16 @@ public static partial class ObservableListEx /// RefreshDepends on the adaptor implementation. /// /// - /// - /// - /// - /// - /// + /// Bind<T>(IObservable<IChangeSet<T>>, IObservableCollection<T>, BindingOptions) + /// Bind<T>(IObservable<IChangeSet<T>>, out ReadOnlyObservableCollection<T>, int) + /// Clone<T>(IObservable<IChangeSet<T>>, IList<T>) + /// Adapt<T>(IObservable<IChangeSet<T>>, IChangeSetAdaptor<T>) + /// ObservableCacheEx.Bind<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IObservableCollection<TObject>, int) public static IObservable> Bind(this IObservable> source, IObservableCollection targetCollection, int resetThreshold = BindingOptions.DefaultResetThreshold) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - targetCollection.ThrowArgumentNullExceptionIfNull(nameof(targetCollection)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(targetCollection); // if user has not specified different defaults, use system wide defaults instead. // This is a hack to retro fit system wide defaults which override the hard coded defaults above @@ -74,31 +72,41 @@ public static IObservable> Bind(this IObservable> /// /// Binds the source changeset stream to , with fine-grained control over reset threshold and other behaviors. /// - /// + /// The type of the T value. + /// This overload follows the same core behavior as the related overload. + /// The source value. + /// The targetCollection value. + /// The options value. + /// The resulting observable sequence. public static IObservable> Bind(this IObservable> source, IObservableCollection targetCollection, BindingOptions options) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - targetCollection.ThrowArgumentNullExceptionIfNull(nameof(targetCollection)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(targetCollection); var adaptor = new ObservableCollectionAdaptor(targetCollection, options); return source.Adapt(adaptor); } /// - /// Constructs a and binds the changeset stream to it. + /// Constructs a ReadOnlyObservableCollection<T> and binds the changeset stream to it. /// Use this overload when you need a read-only view (typically for UI binding) without managing the backing collection yourself. /// The created collection is returned via the output parameter. /// - /// + /// The type of the T value. + /// This overload follows the same core behavior as the related overload. + /// The source value. + /// The readOnlyObservableCollection value. + /// The resetThreshold value. + /// The resulting observable sequence. /// - /// + /// This overload follows the same core behavior as the related overload. /// The created collection is backed by an internal ObservableCollectionExtended<T>. Callers receive only the read-only wrapper. /// public static IObservable> Bind(this IObservable> source, out ReadOnlyObservableCollection readOnlyObservableCollection, int resetThreshold = BindingOptions.DefaultResetThreshold) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); // if user has not specified different defaults, use system wide defaults instead. // This is a hack to retro fit system wide defaults which override the hard coded defaults above @@ -111,19 +119,24 @@ public static IObservable> Bind(this IObservable> } /// - /// Constructs a and binds the changeset stream to it, + /// Constructs a ReadOnlyObservableCollection<T> and binds the changeset stream to it, /// with fine-grained control over reset threshold and other behaviors. /// The created collection is returned via the output parameter. /// - /// + /// The type of the T value. + /// This overload follows the same core behavior as the related overload. + /// The source value. + /// The readOnlyObservableCollection value. + /// The options value. + /// The resulting observable sequence. /// - /// + /// This overload follows the same core behavior as the related overload. /// The created collection is backed by an internal ObservableCollectionExtended<T>. Callers receive only the read-only wrapper. /// public static IObservable> Bind(this IObservable> source, out ReadOnlyObservableCollection readOnlyObservableCollection, BindingOptions options) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); var target = new ObservableCollectionExtended(); var result = new ReadOnlyObservableCollection(target); @@ -131,17 +144,22 @@ public static IObservable> Bind(this IObservable> readOnlyObservableCollection = result; return source.Adapt(adaptor); } - #if SUPPORTS_BINDINGLIST + /// - /// Binds the source changeset stream to a WinForms , keeping in sync. + /// Binds the source changeset stream to a WinForms BindingList<T>, keeping in sync. /// - /// + /// The type of the T value. + /// This overload follows the same core behavior as the related overload. + /// The source value. + /// The bindingList value. + /// The resetThreshold value. + /// The resulting observable sequence. public static IObservable> Bind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this IObservable> source, BindingList bindingList, int resetThreshold = BindingOptions.DefaultResetThreshold) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - bindingList.ThrowArgumentNullExceptionIfNull(nameof(bindingList)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(bindingList); return source.Adapt(new BindingListAdaptor(bindingList, resetThreshold)); } diff --git a/src/DynamicData/List/ObservableListEx.BufferIf.cs b/src/DynamicData/List/ObservableListEx.BufferIf.cs index 37224290f..d97e8032e 100644 --- a/src/DynamicData/List/ObservableListEx.BufferIf.cs +++ b/src/DynamicData/List/ObservableListEx.BufferIf.cs @@ -1,53 +1,74 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. /// public static partial class ObservableListEx { - /// + /// + /// Provides an overload of BufferIf for the supplied arguments. + /// + /// The type of the T value. + /// The source value. + /// The pauseIfTrueSelector value. + /// The scheduler value. + /// The resulting observable sequence. /// - /// + /// This overload follows the same core behavior as the related overload. /// This overload starts unpaused and has no timeout. /// public static IObservable> BufferIf(this IObservable> source, IObservable pauseIfTrueSelector, IScheduler? scheduler = null) where T : notnull => BufferIf(source, pauseIfTrueSelector, false, scheduler); - /// + /// + /// Provides an overload of BufferIf for the supplied arguments. + /// + /// The type of the T value. + /// The source value. + /// The pauseIfTrueSelector value. + /// The initialPauseState value. + /// The scheduler value. + /// The resulting observable sequence. /// - /// + /// This overload follows the same core behavior as the related overload. /// This overload allows setting the initial pause state but has no timeout. /// public static IObservable> BufferIf(this IObservable> source, IObservable pauseIfTrueSelector, bool initialPauseState, IScheduler? scheduler = null) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - pauseIfTrueSelector.ThrowArgumentNullExceptionIfNull(nameof(pauseIfTrueSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(pauseIfTrueSelector); return BufferIf(source, pauseIfTrueSelector, initialPauseState, null, scheduler); } - /// + /// + /// Provides an overload of BufferIf for the supplied arguments. + /// + /// The type of the T value. + /// The source value. + /// The pauseIfTrueSelector value. + /// The timeOut value. + /// The scheduler value. + /// The resulting observable sequence. /// - /// + /// This overload follows the same core behavior as the related overload. /// This overload starts unpaused and accepts a timeout but not an explicit initial pause state. /// public static IObservable> BufferIf(this IObservable> source, IObservable pauseIfTrueSelector, TimeSpan? timeOut, IScheduler? scheduler = null) @@ -57,8 +78,8 @@ public static IObservable> BufferIf(this IObservable /// The type of items in the list. - /// The source to conditionally buffer. - /// An of that controls buffering: pauses (buffers), resumes (flushes). + /// The source IObservable<IChangeSet<T>> to conditionally buffer. + /// An IObservable<bool> of that controls buffering: pauses (buffers), resumes (flushes). /// The initial pause state. When , buffering starts immediately. /// An optional maximum duration to keep the buffer open. After this time, the buffer is flushed regardless of pause state. /// The for timeout scheduling. @@ -84,8 +105,8 @@ public static IObservable> BufferIf(this IObservable> BufferIf(this IObservable> source, IObservable pauseIfTrueSelector, bool initialPauseState, TimeSpan? timeOut, IScheduler? scheduler = null) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - pauseIfTrueSelector.ThrowArgumentNullExceptionIfNull(nameof(pauseIfTrueSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(pauseIfTrueSelector); return new BufferIf(source, pauseIfTrueSelector, initialPauseState, timeOut, scheduler).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.BufferInitial.cs b/src/DynamicData/List/ObservableListEx.BufferInitial.cs index 17ad6d43b..2db1b3e34 100644 --- a/src/DynamicData/List/ObservableListEx.BufferInitial.cs +++ b/src/DynamicData/List/ObservableListEx.BufferInitial.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -27,7 +18,7 @@ public static partial class ObservableListEx /// Buffers changesets during an initial time window, then emits a single combined changeset and passes through subsequent changes. /// /// The type of items in the list. - /// The source to buffer during the initial loading period. + /// The source IObservable<IChangeSet<TObject>> to buffer during the initial loading period. /// The time period (measured from first emission) during which changes are buffered. /// The for timing the buffer window. /// A list changeset stream where the initial burst is combined into one changeset. @@ -37,8 +28,8 @@ public static partial class ObservableListEx /// After this initial window, subsequent changesets pass through immediately. /// /// - /// - /// + /// DeferUntilLoaded<T>(IObservable<IChangeSet<T>>) + /// FlattenBufferResult<T>(IObservable<IList<IChangeSet<T>>>) public static IObservable> BufferInitial(this IObservable> source, TimeSpan initialBuffer, IScheduler? scheduler = null) where TObject : notnull => source.DeferUntilLoaded().Publish( shared => diff --git a/src/DynamicData/List/ObservableListEx.Cast.cs b/src/DynamicData/List/ObservableListEx.Cast.cs index 7c0f9aad7..dadbc1117 100644 --- a/src/DynamicData/List/ObservableListEx.Cast.cs +++ b/src/DynamicData/List/ObservableListEx.Cast.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -27,15 +18,15 @@ public static partial class ObservableListEx /// Casts each item in the changeset from object to using a direct cast. /// /// The target type to cast to. - /// The source of object items. + /// The source IObservable<IChangeSet<object>> of object items. /// A list changeset stream of cast items. /// is . - /// - /// + /// Cast<TSource, TDestination>(IObservable<IChangeSet<TSource>>, Func<TSource, TDestination>) + /// CastToObject<T>(IObservable<IChangeSet<T>>) public static IObservable> Cast(this IObservable> source) where TDestination : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Select(changes => changes.Transform(t => (TDestination)t)); } @@ -45,20 +36,19 @@ public static IObservable> Cast(this IObs /// /// The source item type. /// The destination item type. - /// The source to cast. - /// A function to convert each item from to . + /// The source IObservable<IChangeSet<TSource>> to cast. + /// A Func<T, TResult> function to convert each item from to . /// A list changeset stream of converted items. /// or is . - /// Use this overload when type inference requires explicit specification of both source and destination types. Alternatively, call first, then the single-type-parameter overload. - /// - /// + /// Use this overload when type inference requires explicit specification of both source and destination types. Alternatively, call CastToObject<T> first, then the single-type-parameter Cast<TDestination> overload. + /// Cast<TDestination>(IObservable<IChangeSet<object>>) + /// Transform<TSource, TDestination>(IObservable<IChangeSet<TSource>>, Func<TSource, TDestination>, bool) public static IObservable> Cast(this IObservable> source, Func conversionFactory) where TSource : notnull where TDestination : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - - conversionFactory.ThrowArgumentNullExceptionIfNull(nameof(conversionFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(conversionFactory); return source.Select(changes => changes.Transform(conversionFactory)); } diff --git a/src/DynamicData/List/ObservableListEx.CastToObject.cs b/src/DynamicData/List/ObservableListEx.CastToObject.cs index 4695297da..6e0601e46 100644 --- a/src/DynamicData/List/ObservableListEx.CastToObject.cs +++ b/src/DynamicData/List/ObservableListEx.CastToObject.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -24,12 +15,16 @@ namespace DynamicData; public static partial class ObservableListEx { /// - /// Casts each item in the changeset to object. Typically used before to work around type inference limitations. + /// Casts each item in the changeset to object. Typically used before Cast<TDestination>(IObservable<IChangeSet<object>>) to work around type inference limitations. /// /// The source item type (must be a reference type). - /// The source to cast to object. + /// The source IObservable<IChangeSet<T>> to cast to object. /// A list changeset stream of object items. - /// + /// Cast<TDestination>(IObservable<IChangeSet<object>>) public static IObservable> CastToObject(this IObservable> source) - where T : class => source.Select(changes => changes.Transform(t => (object)t)); + where T : class + { + ArgumentExceptionHelper.ThrowIfNull(source); + return source.Select(changes => changes.Transform(t => (object)t)); + } } diff --git a/src/DynamicData/List/ObservableListEx.Clone.cs b/src/DynamicData/List/ObservableListEx.Clone.cs index 2588c8927..fb2742b74 100644 --- a/src/DynamicData/List/ObservableListEx.Clone.cs +++ b/src/DynamicData/List/ObservableListEx.Clone.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -27,19 +25,19 @@ public static partial class ObservableListEx /// Applies each changeset to the target list as a side effect, keeping it synchronized with the source. /// /// The type of items in the list. - /// The source to clone. - /// The target list to clone changes into. + /// The source IObservable<IChangeSet<T>> to clone. + /// The IList<T> target list to clone changes into. /// A continuation of the source changeset stream. /// is . /// - /// Lower-level than . Uses .Clone() to apply all changeset operations directly. + /// Lower-level than Bind<T>(IObservable<IChangeSet<T>>, IObservableCollection<T>, int). Uses IList<T>.Clone() to apply all changeset operations directly. /// - /// - /// + /// Bind<T>(IObservable<IChangeSet<T>>, IObservableCollection<T>, int) + /// PopulateInto<T>(IObservable<IChangeSet<T>>, ISourceList<T>) public static IObservable> Clone(this IObservable> source, IList target) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Do(target.Clone); } diff --git a/src/DynamicData/List/ObservableListEx.Combine.cs b/src/DynamicData/List/ObservableListEx.Combine.cs index 13a617439..82dd3f381 100644 --- a/src/DynamicData/List/ObservableListEx.Combine.cs +++ b/src/DynamicData/List/ObservableListEx.Combine.cs @@ -1,41 +1,59 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; using DynamicData.Cache.Internal; +#endif +#if REACTIVE_SHIM +using DynamicData.Reactive.List.Internal; +#else using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. /// public static partial class ObservableListEx { + /// + /// Executes the Combine operation. + /// + /// The type of the T value. + /// The sources value. + /// The type value. + /// The result of the operation. private static IObservable> Combine(this ICollection>> sources, CombineOperator type) where T : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return new Combiner(sources, type).Run(); } + /// + /// Executes the Combine operation. + /// + /// The type of the T value. + /// The source value. + /// The type value. + /// The others value. + /// The result of the operation. private static IObservable> Combine(this IObservable> source, CombineOperator type, params IObservable>[] others) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - others.ThrowArgumentNullExceptionIfNull(nameof(others)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(others); if (others.Length == 0) { @@ -46,10 +64,17 @@ private static IObservable> Combine(this IObservable(items, type).Run(); } + /// + /// Executes the Combine operation. + /// + /// The type of the T value. + /// The sources value. + /// The type value. + /// The result of the operation. private static IObservable> Combine(this IObservableList> sources, CombineOperator type) where T : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return Observable.Create>( observer => @@ -60,10 +85,17 @@ private static IObservable> Combine(this IObservableList + /// Executes the Combine operation. + /// + /// The type of the T value. + /// The sources value. + /// The type value. + /// The result of the operation. private static IObservable> Combine(this IObservableList> sources, CombineOperator type) where T : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return Observable.Create>( observer => @@ -74,10 +106,17 @@ private static IObservable> Combine(this IObservableList + /// Executes the Combine operation. + /// + /// The type of the T value. + /// The sources value. + /// The type value. + /// The result of the operation. private static IObservable> Combine(this IObservableList>> sources, CombineOperator type) where T : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return new DynamicCombiner(sources, type).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.Convert.cs b/src/DynamicData/List/ObservableListEx.Convert.cs index 4380c296d..1d3af140b 100644 --- a/src/DynamicData/List/ObservableListEx.Convert.cs +++ b/src/DynamicData/List/ObservableListEx.Convert.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -29,17 +20,16 @@ public static partial class ObservableListEx /// /// The type of items in the list. /// The type of the destination items. - /// The source to convert. - /// The conversion factory. + /// The source IObservable<IChangeSet<TObject>> to convert. + /// The Func<T, TResult> conversion factory. /// An observable which emits the change set. [Obsolete("Prefer Cast as it is does the same thing but is semantically correct")] public static IObservable> Convert(this IObservable> source, Func conversionFactory) where TObject : notnull where TDestination : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - - conversionFactory.ThrowArgumentNullExceptionIfNull(nameof(conversionFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(conversionFactory); return source.Select(changes => changes.Transform(conversionFactory)); } diff --git a/src/DynamicData/List/ObservableListEx.DeferUntilLoaded.cs b/src/DynamicData/List/ObservableListEx.DeferUntilLoaded.cs index 1e6b5d037..2d1b0ee88 100644 --- a/src/DynamicData/List/ObservableListEx.DeferUntilLoaded.cs +++ b/src/DynamicData/List/ObservableListEx.DeferUntilLoaded.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -27,7 +25,7 @@ public static partial class ObservableListEx /// Defers downstream delivery until the source emits its first changeset, then forwards all subsequent changesets. /// /// The type of the object. - /// The source to defer until the first changeset arrives. + /// The source IObservable<IChangeSet<T>> to defer until the first changeset arrives. /// A list changeset stream that begins emitting only after the source has produced its first changeset. /// is . /// @@ -36,25 +34,30 @@ public static partial class ObservableListEx /// the initial data and all subsequent changesets. This is useful when downstream consumers should not receive an empty initial state. /// /// - /// - /// + /// SkipInitial<T>(IObservable<IChangeSet<T>>) + /// StartWithEmpty<T>(IObservable<IChangeSet<T>>) public static IObservable> DeferUntilLoaded(this IObservable> source) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new DeferUntilLoaded(source).Run(); } - /// + /// + /// Provides an overload of DeferUntilLoaded for the supplied arguments. + /// + /// The type of the T value. + /// The source value. + /// The resulting observable sequence. /// - /// + /// This overload follows the same core behavior as the related overload. /// Convenience overload that calls source.Connect().DeferUntilLoaded(). /// public static IObservable> DeferUntilLoaded(this IObservableList source) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Connect().DeferUntilLoaded(); } diff --git a/src/DynamicData/List/ObservableListEx.DisposeMany.cs b/src/DynamicData/List/ObservableListEx.DisposeMany.cs index ec463f21d..0f7187779 100644 --- a/src/DynamicData/List/ObservableListEx.DisposeMany.cs +++ b/src/DynamicData/List/ObservableListEx.DisposeMany.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -28,7 +26,7 @@ public static partial class ObservableListEx /// All remaining tracked items are disposed when the stream finalizes (OnCompleted, OnError, or subscription disposal). /// /// The type of the object. - /// The source to track for disposal on removal. + /// The source IObservable<IChangeSet<T>> to track for disposal on removal. /// A continuation of the source changeset stream with disposal side effects applied. /// is . /// @@ -47,13 +45,13 @@ public static partial class ObservableListEx /// /// Worth noting: Disposal happens after the changeset is delivered downstream, so subscribers see the change before items are disposed. /// - /// - /// - /// + /// OnItemRemoved<T>(IObservable<IChangeSet<T>>, Action<T>, bool) + /// SubscribeMany<T>(IObservable<IChangeSet<T>>, Func<T, IDisposable>) + /// ObservableCacheEx.DisposeMany<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>) public static IObservable> DisposeMany(this IObservable> source) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new DisposeMany(source).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.DistinctValues.cs b/src/DynamicData/List/ObservableListEx.DistinctValues.cs index 39181d6c8..35fa24102 100644 --- a/src/DynamicData/List/ObservableListEx.DistinctValues.cs +++ b/src/DynamicData/List/ObservableListEx.DistinctValues.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -28,8 +26,8 @@ public static partial class ObservableListEx /// /// The type of items in the source list. /// The type of distinct values produced. - /// The source to extract distinct values. - /// A function that extracts the value to track from each source item. + /// The source IObservable<IChangeSet<TObject>> to extract distinct values. + /// A Func<T, TResult> function that extracts the value to track from each source item. /// A list changeset stream of distinct values. /// or is . /// @@ -46,14 +44,13 @@ public static partial class ObservableListEx /// ClearAll reference counts cleared. Remove emitted for every tracked distinct value. /// /// - /// + /// ObservableCacheEx.DistinctValues<TObject, TKey, TValue>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, TValue>) public static IObservable> DistinctValues(this IObservable> source, Func valueSelector) where TObject : notnull where TValue : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - - valueSelector.ThrowArgumentNullExceptionIfNull(nameof(valueSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(valueSelector); return new Distinct(source, valueSelector).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.Except.cs b/src/DynamicData/List/ObservableListEx.Except.cs index 7c19912d8..b4f7d2fc0 100644 --- a/src/DynamicData/List/ObservableListEx.Except.cs +++ b/src/DynamicData/List/ObservableListEx.Except.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -28,8 +26,8 @@ public static partial class ObservableListEx /// Items present in the first source but not in any of the are included in the result. /// /// The type of the item. - /// The primary from which other streams are subtracted. - /// The other changeset streams to exclude from the result. + /// The primary IObservable<IChangeSet<T>> from which other streams are subtracted. + /// The other IObservable<IChangeSet<T>> changeset streams to exclude from the result. /// A list changeset stream containing items from that are not in any of . /// is . /// @@ -48,48 +46,68 @@ public static partial class ObservableListEx /// MovedIgnored by the set logic (no positional semantics). /// RefreshForwarded if the item is currently in the result set. /// - /// Worth noting: Unlike , the first source is asymmetric: only its items can appear in the result. + /// Worth noting: Unlike Or<T>(IObservable<IChangeSet<T>>, IObservable<IChangeSet<T>>[]), the first source is asymmetric: only its items can appear in the result. /// - /// - /// - /// - /// + /// And<T>(IObservable<IChangeSet<T>>, IObservable<IChangeSet<T>>[]) + /// Or<T>(IObservable<IChangeSet<T>>, IObservable<IChangeSet<T>>[]) + /// Xor<T>(IObservable<IChangeSet<T>>, IObservable<IChangeSet<T>>[]) + /// ObservableCacheEx.Except<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IObservable<IChangeSet<TObject, TKey>>[]) public static IObservable> Except(this IObservable> source, params IObservable>[] others) where T : notnull { - others.ThrowArgumentNullExceptionIfNull(nameof(others)); + ArgumentExceptionHelper.ThrowIfNull(others); return source.Combine(CombineOperator.Except, others); } - /// + /// + /// Provides an overload of Combine for the supplied arguments. + /// + /// The type of the T value. + /// The sources value. + /// The resulting observable sequence. /// - /// + /// This overload follows the same core behavior as the related overload. /// Static overload accepting a pre-built collection of sources. The first item in the collection is the primary source. /// public static IObservable> Except(this ICollection>> sources) where T : notnull => sources.Combine(CombineOperator.Except); - /// + /// + /// Provides an overload of Combine for the supplied arguments. + /// + /// The type of the T value. + /// The sources value. + /// The resulting observable sequence. /// - /// - /// Dynamic overload: sources can be added or removed from the at runtime. The first source in the list acts as the primary. + /// This overload follows the same core behavior as the related overload. + /// Dynamic overload: sources can be added or removed from the IObservableList<T> at runtime. The first source in the list acts as the primary. /// public static IObservable> Except(this IObservableList>> sources) where T : notnull => sources.Combine(CombineOperator.Except); - /// + /// + /// Provides an overload of Combine for the supplied arguments. + /// + /// The type of the T value. + /// The sources value. + /// The resulting observable sequence. /// - /// - /// Dynamic overload accepting of . Each inner list's Connect() is used as a source. + /// This overload follows the same core behavior as the related overload. + /// Dynamic overload accepting IObservableList<T> of IObservableList<T>. Each inner list's Connect() is used as a source. /// public static IObservable> Except(this IObservableList> sources) where T : notnull => sources.Combine(CombineOperator.Except); - /// + /// + /// Provides an overload of Combine for the supplied arguments. + /// + /// The type of the T value. + /// The sources value. + /// The resulting observable sequence. /// - /// - /// Dynamic overload accepting of . Each inner list's Connect() is used as a source. + /// This overload follows the same core behavior as the related overload. + /// Dynamic overload accepting IObservableList<T> of ISourceList<T>. Each inner list's Connect() is used as a source. /// public static IObservable> Except(this IObservableList> sources) where T : notnull => sources.Combine(CombineOperator.Except); diff --git a/src/DynamicData/List/ObservableListEx.ExpireAfter.cs b/src/DynamicData/List/ObservableListEx.ExpireAfter.cs index 4df348372..a92c0da88 100644 --- a/src/DynamicData/List/ObservableListEx.ExpireAfter.cs +++ b/src/DynamicData/List/ObservableListEx.ExpireAfter.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -28,14 +19,14 @@ public static partial class ObservableListEx /// Returns an observable of the items that were expired and removed. /// /// The type of the item. - /// The source list to apply time-based expiration to. - /// A function returning the time-to-live for each item. Return for items that should never expire. + /// The ISourceList<T> source list to apply time-based expiration to. + /// A Func<T, TResult> function returning the time-to-live for each item. Return for items that should never expire. /// An optional polling interval to batch expiry checks. If omitted, a separate timer is created for each unique expiry time. /// The scheduler for scheduling expiry timers. Defaults to . /// An observable that emits collections of items each time expired items are removed from the source list. /// /// - /// This operator acts directly on an , not on a changeset stream. It monitors items as they are added, + /// This operator acts directly on an ISourceList<T>, not on a changeset stream. It monitors items as they are added, /// schedules their removal, and physically removes them from the source list when their time expires. /// /// @@ -44,8 +35,8 @@ public static partial class ObservableListEx /// /// Worth noting: The returned observable emits the expired items (not changesets). Subscribe to this observable to trigger the expiry mechanism; if not subscribed, no items will be removed. /// - /// - /// + /// LimitSizeTo<T>(ISourceList<T>, int, IScheduler?) + /// ToObservableChangeSet<T>(IObservable<T>, Func<T, TimeSpan?>, IScheduler?) public static IObservable> ExpireAfter( this ISourceList source, Func timeSelector, diff --git a/src/DynamicData/List/ObservableListEx.Filter.cs b/src/DynamicData/List/ObservableListEx.Filter.cs index 2165375b0..764e05237 100644 --- a/src/DynamicData/List/ObservableListEx.Filter.cs +++ b/src/DynamicData/List/ObservableListEx.Filter.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -28,8 +19,8 @@ public static partial class ObservableListEx /// Only items satisfying are included downstream. /// /// The type of items in the list. - /// The source to filter. - /// A predicate that determines which items are included. Items returning appear downstream; items returning are excluded. + /// The source IObservable<IChangeSet<T>> to filter. + /// A Func<T, TResult> predicate that determines which items are included. Items returning appear downstream; items returning are excluded. /// A list changeset stream containing only items that satisfy . /// Thrown when or is . /// @@ -50,12 +41,12 @@ public static partial class ObservableListEx /// RefreshThe predicate is re-evaluated. If the item now passes but previously did not, an Add is emitted. If it previously passed but no longer does, a Remove is emitted. If still passes, the Refresh is forwarded. If still fails, dropped. /// ClearAll downstream items are cleared. /// - /// Worth noting: Refresh events trigger re-evaluation, which can promote or demote items (turning a Refresh into an Add or Remove). Pair with for property-change-driven filtering. + /// Worth noting: Refresh events trigger re-evaluation, which can promote or demote items (turning a Refresh into an Add or Remove). Pair with AutoRefresh<TObject>(IObservable<IChangeSet<TObject>>, TimeSpan?, TimeSpan?, IScheduler?) for property-change-driven filtering. /// - /// - /// - /// - /// + /// Filter<T>(IObservable<IChangeSet<T>>, IObservable<Func<T, bool>>, ListFilterPolicy) + /// FilterOnObservable<TObject>(IObservable<IChangeSet<TObject>>, Func<TObject, IObservable<bool>>, TimeSpan?, IScheduler?) + /// AutoRefresh<TObject>(IObservable<IChangeSet<TObject>>, TimeSpan?, TimeSpan?, IScheduler?) + /// ObservableCacheEx.Filter<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, bool>, bool) public static IObservable> Filter( this IObservable> source, Func predicate) @@ -70,8 +61,8 @@ public static IObservable> Filter( /// When emits a new function, all items are re-evaluated. /// /// The type of the item. - /// The source to filter. - /// An that emits new predicate functions. Each emission triggers a full re-evaluation of all items. + /// The source IObservable<IChangeSet<T>> to filter. + /// An IObservable<Func<T, bool>> that emits new predicate functions. Each emission triggers a full re-evaluation of all items. /// The that controls re-filtering behavior when the predicate changes. /// A list changeset stream containing only items that satisfy the most recent predicate. /// @@ -92,14 +83,14 @@ public static IObservable> Filter( /// Worth noting: No items are included until emits its first function. /// /// or is . - /// - /// + /// Filter<T>(IObservable<IChangeSet<T>>, Func<T, bool>) + /// FilterOnObservable<TObject>(IObservable<IChangeSet<TObject>>, Func<TObject, IObservable<bool>>, TimeSpan?, IScheduler?) public static IObservable> Filter(this IObservable> source, IObservable> predicate, ListFilterPolicy filterPolicy = ListFilterPolicy.CalculateDiff) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); - predicate.ThrowArgumentNullExceptionIfNull(nameof(predicate)); + ArgumentExceptionHelper.ThrowIfNull(predicate); return new List.Internal.Filter.Dynamic(source, predicate, filterPolicy).Run(); } @@ -110,9 +101,9 @@ public static IObservable> Filter(this IObservable /// The type of the item. /// The type of state value required by . - /// The source to filter. - /// An stream of state values to be passed to . - /// A static predicate receiving the current state and an item, returning to include or to exclude. The function itself does not change; only the state value passed to it changes. + /// The source IObservable<IChangeSet<T>> to filter. + /// An IObservable<TState> stream of state values to be passed to . + /// A static Func<T, TResult> predicate receiving the current state and an item, returning to include or to exclude. The function itself does not change; only the state value passed to it changes. /// The that controls re-filtering behavior when the state changes. /// When (default), empty changesets are suppressed. Set to to publish empty changesets (useful for monitoring loading status). /// A list changeset stream containing only items satisfying with the current state. @@ -132,8 +123,8 @@ public static IObservable> Filter(this IObservableState changedAll items are re-evaluated with the new state value. The output is shaped by . /// /// - /// - /// + /// Filter<T>(IObservable<IChangeSet<T>>, Func<T, bool>) + /// Filter<T>(IObservable<IChangeSet<T>>, IObservable<Func<T, bool>>, ListFilterPolicy) public static IObservable> Filter( this IObservable> source, IObservable predicateState, diff --git a/src/DynamicData/List/ObservableListEx.FilterOnObservable.cs b/src/DynamicData/List/ObservableListEx.FilterOnObservable.cs index bd49bf1db..ca3146b56 100644 --- a/src/DynamicData/List/ObservableListEx.FilterOnObservable.cs +++ b/src/DynamicData/List/ObservableListEx.FilterOnObservable.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -24,11 +22,11 @@ namespace DynamicData; public static partial class ObservableListEx { /// - /// Filters each item using a per-item of that dynamically controls inclusion. + /// Filters each item using a per-item IObservable<T> of that dynamically controls inclusion. /// When an item's observable emits the item enters the result; when it emits the item is removed. /// /// The type of items in the list. - /// The source to filter by property value. + /// The source IObservable<IChangeSet<TObject>> to filter by property value. /// A function that returns an observable of for each item, controlling its inclusion. /// An optional throttle duration applied to each per-item observable to reduce re-evaluation frequency. /// The used when throttling. Defaults to the system default scheduler. @@ -52,14 +50,14 @@ public static partial class ObservableListEx /// Emits If currently included, a Remove is emitted downstream. /// /// - /// - /// - /// - /// + /// Filter<T>(IObservable<IChangeSet<T>>, Func<T, bool>) + /// Filter<T>(IObservable<IChangeSet<T>>, IObservable<Func<T, bool>>, ListFilterPolicy) + /// AutoRefresh<TObject>(IObservable<IChangeSet<TObject>>, TimeSpan?, TimeSpan?, IScheduler?) + /// ObservableCacheEx.FilterOnObservable<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, IObservable<bool>>, TimeSpan?, IScheduler?) public static IObservable> FilterOnObservable(this IObservable> source, Func> objectFilterObservable, TimeSpan? propertyChangedThrottle = null, IScheduler? scheduler = null) where TObject : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new FilterOnObservable(source, objectFilterObservable, propertyChangedThrottle, scheduler).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.FilterOnProperty.cs b/src/DynamicData/List/ObservableListEx.FilterOnProperty.cs index b9b297d77..a82032922 100644 --- a/src/DynamicData/List/ObservableListEx.FilterOnProperty.cs +++ b/src/DynamicData/List/ObservableListEx.FilterOnProperty.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; +#if REACTIVE_SHIM +using DynamicData.Reactive.List.Internal; +#else using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -28,26 +26,24 @@ public static partial class ObservableListEx /// /// The type of the object. Must implement . /// The type of the property. - /// The source to filter by property value. - /// selecting the property to monitor for changes. - /// A predicate evaluated against the item to determine inclusion. + /// The source IObservable<IChangeSet<TObject>> to filter by property value. + /// Expression<TDelegate> selecting the property to monitor for changes. + /// A Func<T, TResult> predicate evaluated against the item to determine inclusion. /// An optional throttle duration for property change notifications. /// The used when throttling. /// A list changeset stream of items satisfying the predicate, re-evaluated on property changes. /// - /// Deprecated. Use followed by instead. + /// Deprecated. Use AutoRefresh<TObject, TProperty>(IObservable<IChangeSet<TObject>>, Expression<Func<TObject, TProperty>>, TimeSpan?, TimeSpan?, IScheduler?) followed by Filter<T>(IObservable<IChangeSet<T>>, Func<T, bool>) instead. /// - /// - /// + /// AutoRefresh<TObject, TProperty>(IObservable<IChangeSet<TObject>>, Expression<Func<TObject, TProperty>>, TimeSpan?, TimeSpan?, IScheduler?) + /// Filter<T>(IObservable<IChangeSet<T>>, Func<T, bool>) [Obsolete("Use AutoRefresh(), followed by Filter() instead")] public static IObservable> FilterOnProperty(this IObservable> source, Expression> propertySelector, Func predicate, TimeSpan? propertyChangedThrottle = null, IScheduler? scheduler = null) where TObject : INotifyPropertyChanged { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - - propertySelector.ThrowArgumentNullExceptionIfNull(nameof(propertySelector)); - - predicate.ThrowArgumentNullExceptionIfNull(nameof(predicate)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(propertySelector); + ArgumentExceptionHelper.ThrowIfNull(predicate); return new FilterOnProperty(source, propertySelector, predicate, propertyChangedThrottle, scheduler).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.FlattenBufferResult.cs b/src/DynamicData/List/ObservableListEx.FlattenBufferResult.cs index 667a494ad..40f1fdf12 100644 --- a/src/DynamicData/List/ObservableListEx.FlattenBufferResult.cs +++ b/src/DynamicData/List/ObservableListEx.FlattenBufferResult.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -24,17 +15,21 @@ namespace DynamicData; public static partial class ObservableListEx { /// - /// Flattens buffered changesets (e.g. from ) back into single changesets. + /// Flattens buffered changesets back into single changesets. /// Empty buffers are dropped. /// /// The type of the item. - /// The of buffered changeset lists. + /// The IObservable<T> of buffered changeset lists. /// A list changeset stream with all buffered changes concatenated into single changesets. /// /// Use this after applying Observable.Buffer() to a changeset stream to re-merge the batched changesets into a single stream. /// - /// - /// + /// BufferIf<T>(IObservable<IChangeSet<T>>, IObservable<bool>, IScheduler?) + /// BufferInitial<T>(IObservable<IChangeSet<T>>, TimeSpan, IScheduler?) public static IObservable> FlattenBufferResult(this IObservable>> source) - where T : notnull => source.Where(x => x.Count != 0).Select(updates => new ChangeSet(updates.SelectMany(u => u))); + where T : notnull + { + ArgumentExceptionHelper.ThrowIfNull(source); + return source.Where(x => x.Count != 0).Select(updates => new ChangeSet(updates.SelectMany(u => u))); + } } diff --git a/src/DynamicData/List/ObservableListEx.ForEachChange.cs b/src/DynamicData/List/ObservableListEx.ForEachChange.cs index f4066e413..e850acac9 100644 --- a/src/DynamicData/List/ObservableListEx.ForEachChange.cs +++ b/src/DynamicData/List/ObservableListEx.ForEachChange.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -24,34 +15,33 @@ namespace DynamicData; public static partial class ObservableListEx { /// - /// Invokes once for every in each changeset. Range changes - /// (AddRange, RemoveRange, Clear) are delivered as a single ; they are not flattened into per-item changes. + /// Invokes once for every Change<T> in each changeset. Range changes + /// (AddRange, RemoveRange, Clear) are delivered as a single Change<T>; they are not flattened into per-item changes. /// The changeset is forwarded downstream unchanged. /// /// The type of items in the list. - /// The source to observe each change in. - /// The action invoked for each . + /// The source IObservable<IChangeSet<TObject>> to observe each change in. + /// The action invoked for each Change<T>. /// A continuation of the source changeset stream. /// or is . /// - /// This is a side-effect operator. It does not modify the changeset. If you need each individual item from range operations flattened out, use instead. + /// This is a side-effect operator. It does not modify the changeset. If you need each individual item from range operations flattened out, use ForEachItemChange<TObject>(IObservable<IChangeSet<TObject>>, Action<ItemChange<TObject>>) instead. /// /// EventBehavior - /// Add/Replace/Remove/Moved/RefreshCallback invoked with the (single-item change). Changeset forwarded. - /// AddRange/RemoveRange/ClearCallback invoked once with the containing the range (accessible via Range property). Changeset forwarded. + /// Add/Replace/Remove/Moved/RefreshCallback invoked with the Change<T> (single-item change). Changeset forwarded. + /// AddRange/RemoveRange/ClearCallback invoked once with the Change<T> containing the range (accessible via Range property). Changeset forwarded. /// OnErrorIf the callback throws, the exception propagates as OnError. /// /// - /// - /// - /// - /// + /// ForEachItemChange<TObject>(IObservable<IChangeSet<TObject>>, Action<ItemChange<TObject>>) + /// OnItemAdded<T>(IObservable<IChangeSet<T>>, Action<T>) + /// OnItemRemoved<T>(IObservable<IChangeSet<T>>, Action<T>, bool) + /// ObservableCacheEx.ForEachChange<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Action<Change<TObject, TKey>>) public static IObservable> ForEachChange(this IObservable> source, Action> action) where TObject : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - - action.ThrowArgumentNullExceptionIfNull(nameof(action)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(action); return source.Do(changes => changes.ForEach(action)); } diff --git a/src/DynamicData/List/ObservableListEx.ForEachItemChange.cs b/src/DynamicData/List/ObservableListEx.ForEachItemChange.cs index f39760fac..52093167d 100644 --- a/src/DynamicData/List/ObservableListEx.ForEachItemChange.cs +++ b/src/DynamicData/List/ObservableListEx.ForEachItemChange.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -24,27 +15,26 @@ namespace DynamicData; public static partial class ObservableListEx { /// - /// Invokes for every individual in each changeset. + /// Invokes for every individual ItemChange<TObject> in each changeset. /// Range changes are flattened into individual item changes first, so the callback only receives Add, Replace, Remove, and Refresh. /// /// The type of items in the list. - /// The source to observe each item-level change in. - /// The action invoked for each individual item change. + /// The source IObservable<IChangeSet<TObject>> to observe each item-level change in. + /// The Action<ItemChange<TObject>> action invoked for each individual item change. /// A continuation of the source changeset stream. /// or is . /// /// - /// Unlike , this operator flattens - /// AddRange, RemoveRange, and Clear into individual entries before invoking the callback. + /// Unlike ForEachChange<TObject>(IObservable<IChangeSet<TObject>>, Action<Change<TObject>>), this operator flattens + /// AddRange, RemoveRange, and Clear into individual ItemChange<TObject> entries before invoking the callback. /// /// - /// + /// ForEachChange<TObject>(IObservable<IChangeSet<TObject>>, Action<Change<TObject>>) public static IObservable> ForEachItemChange(this IObservable> source, Action> action) where TObject : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - - action.ThrowArgumentNullExceptionIfNull(nameof(action)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(action); return source.Do(changes => changes.Flatten().ForEach(action)); } diff --git a/src/DynamicData/List/ObservableListEx.GroupOn.cs b/src/DynamicData/List/ObservableListEx.GroupOn.cs index 180c98ff6..873b9b0b8 100644 --- a/src/DynamicData/List/ObservableListEx.GroupOn.cs +++ b/src/DynamicData/List/ObservableListEx.GroupOn.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; +#if REACTIVE_SHIM +using DynamicData.Reactive.List.Internal; +#else using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -24,15 +22,15 @@ namespace DynamicData; public static partial class ObservableListEx { /// - /// Groups source items by the value returned by . Each group is an + /// Groups source items by the value returned by . Each group is an IGroup<TObject, TGroup> /// containing an inner observable list of its members. /// /// The type of items in the list. /// The type of the group key. - /// The source to group. - /// A function that returns the group key for each item. - /// An optional of that forces all items to be re-evaluated against when it fires. Useful for time-based groupings (e.g., "Last Hour", "Today"). - /// A list changeset stream of objects, each containing the items belonging to that group. + /// The source IObservable<IChangeSet<TObject>> to group. + /// A Func<T, TResult> function that returns the group key for each item. + /// An optional IObservable<Unit> of that forces all items to be re-evaluated against when it fires. Useful for time-based groupings (e.g., "Last Hour", "Today"). + /// A list changeset stream of IGroup<TObject, TGroup> objects, each containing the items belonging to that group. /// or is . /// /// @@ -48,16 +46,15 @@ public static partial class ObservableListEx /// Regrouper firesAll items re-evaluated. Items that changed group key are moved between groups. Empty groups removed, new groups added. /// /// - /// - /// - /// + /// GroupOnProperty<TObject, TGroup>(IObservable<IChangeSet<TObject>>, Expression<Func<TObject, TGroup>>, TimeSpan?, IScheduler?) + /// GroupWithImmutableState<TObject, TGroupKey>(IObservable<IChangeSet<TObject>>, Func<TObject, TGroupKey>, IObservable<Unit>?) + /// Transform<TSource, TDestination>(IObservable<IChangeSet<TSource>>, Func<TSource, TDestination>, bool) public static IObservable>> GroupOn(this IObservable> source, Func groupSelector, IObservable? regrouper = null) where TObject : notnull where TGroup : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - - groupSelector.ThrowArgumentNullExceptionIfNull(nameof(groupSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(groupSelector); return new GroupOn(source, groupSelector, regrouper).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.GroupOnProperty.cs b/src/DynamicData/List/ObservableListEx.GroupOnProperty.cs index 4bf1bb98a..b17ccfc98 100644 --- a/src/DynamicData/List/ObservableListEx.GroupOnProperty.cs +++ b/src/DynamicData/List/ObservableListEx.GroupOnProperty.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; +#if REACTIVE_SHIM +using DynamicData.Reactive.List.Internal; +#else using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -29,11 +27,11 @@ public static partial class ObservableListEx /// /// The type of the object. Must implement . /// The type of the group key. - /// The source to group by property value. - /// selecting the property whose value determines the group key. + /// The source IObservable<IChangeSet<TObject>> to group by property value. + /// Expression<TDelegate> selecting the property whose value determines the group key. /// An optional throttle duration for property change notifications. /// The used when throttling. - /// A list changeset stream of objects. + /// A list changeset stream of IGroup<TObject, TGroup> objects. /// or is . /// /// @@ -41,16 +39,15 @@ public static partial class ObservableListEx /// Property changes trigger re-evaluation of the group key, potentially moving items between groups. /// /// - /// - /// - /// + /// GroupOn<TObject, TGroup>(IObservable<IChangeSet<TObject>>, Func<TObject, TGroup>, IObservable<Unit>?) + /// GroupOnPropertyWithImmutableState<TObject, TGroup>(IObservable<IChangeSet<TObject>>, Expression<Func<TObject, TGroup>>, TimeSpan?, IScheduler?) + /// AutoRefresh<TObject, TProperty>(IObservable<IChangeSet<TObject>>, Expression<Func<TObject, TProperty>>, TimeSpan?, TimeSpan?, IScheduler?) public static IObservable>> GroupOnProperty(this IObservable> source, Expression> propertySelector, TimeSpan? propertyChangedThrottle = null, IScheduler? scheduler = null) where TObject : INotifyPropertyChanged where TGroup : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - - propertySelector.ThrowArgumentNullExceptionIfNull(nameof(propertySelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(propertySelector); return new GroupOnProperty(source, propertySelector, propertyChangedThrottle, scheduler).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.GroupOnPropertyWithImmutableState.cs b/src/DynamicData/List/ObservableListEx.GroupOnPropertyWithImmutableState.cs index dad726079..182834fda 100644 --- a/src/DynamicData/List/ObservableListEx.GroupOnPropertyWithImmutableState.cs +++ b/src/DynamicData/List/ObservableListEx.GroupOnPropertyWithImmutableState.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; +#if REACTIVE_SHIM +using DynamicData.Reactive.List.Internal; +#else using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -29,29 +27,28 @@ public static partial class ObservableListEx /// /// The type of the object. Must implement . /// The type of the group key. - /// The source to group by property value with immutable snapshots. - /// selecting the property whose value determines the group key. + /// The source IObservable<IChangeSet<TObject>> to group by property value with immutable snapshots. + /// Expression<TDelegate> selecting the property whose value determines the group key. /// An optional throttle duration for property change notifications. /// The used when throttling. - /// A list changeset stream of immutable group snapshots. + /// A list changeset stream of List.IGrouping<TObject, TGroup> immutable group snapshots. /// or is . /// /// - /// Combines - /// with . - /// Unlike , + /// Combines AutoRefresh<TObject, TProperty>(IObservable<IChangeSet<TObject>>, Expression<Func<TObject, TProperty>>, TimeSpan?, TimeSpan?, IScheduler?) + /// with GroupWithImmutableState<TObject, TGroupKey>(IObservable<IChangeSet<TObject>>, Func<TObject, TGroupKey>, IObservable<Unit>?). + /// Unlike GroupOnProperty<TObject, TGroup>(IObservable<IChangeSet<TObject>>, Expression<Func<TObject, TGroup>>, TimeSpan?, IScheduler?), /// this produces immutable snapshots per group rather than live inner observable lists. /// /// - /// - /// + /// GroupOnProperty<TObject, TGroup>(IObservable<IChangeSet<TObject>>, Expression<Func<TObject, TGroup>>, TimeSpan?, IScheduler?) + /// GroupWithImmutableState<TObject, TGroupKey>(IObservable<IChangeSet<TObject>>, Func<TObject, TGroupKey>, IObservable<Unit>?) public static IObservable>> GroupOnPropertyWithImmutableState(this IObservable> source, Expression> propertySelector, TimeSpan? propertyChangedThrottle = null, IScheduler? scheduler = null) where TObject : INotifyPropertyChanged where TGroup : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - - propertySelector.ThrowArgumentNullExceptionIfNull(nameof(propertySelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(propertySelector); return new GroupOnPropertyWithImmutableState(source, propertySelector, propertyChangedThrottle, scheduler).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.GroupWithImmutableState.cs b/src/DynamicData/List/ObservableListEx.GroupWithImmutableState.cs index b607a56f4..5d3250878 100644 --- a/src/DynamicData/List/ObservableListEx.GroupWithImmutableState.cs +++ b/src/DynamicData/List/ObservableListEx.GroupWithImmutableState.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; +#if REACTIVE_SHIM +using DynamicData.Reactive.List.Internal; +#else using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -29,27 +27,27 @@ public static partial class ObservableListEx /// /// The type of items in the list. /// The type of the group key. - /// The source to group with immutable snapshots. - /// A function that returns the group key for each item. - /// An optional of that forces all items to be re-evaluated when it fires. - /// A list changeset stream of immutable snapshots. + /// The source IObservable<IChangeSet<TObject>> to group with immutable snapshots. + /// A Func<T, TResult> function that returns the group key for each item. + /// An optional IObservable<Unit> of that forces all items to be re-evaluated when it fires. + /// A list changeset stream of List.IGrouping<TObject, TGroupKey> immutable snapshots. /// or is . /// /// - /// Works like + /// Works like GroupOn<TObject, TGroup>(IObservable<IChangeSet<TObject>>, Func<TObject, TGroup>, IObservable<Unit>?) /// but each affected group emits a new immutable snapshot on every change rather than updating a live inner list. /// This is useful when consumers need thread-safe, point-in-time snapshots of each group. /// /// - /// - /// + /// GroupOn<TObject, TGroup>(IObservable<IChangeSet<TObject>>, Func<TObject, TGroup>, IObservable<Unit>?) + /// GroupOnPropertyWithImmutableState<TObject, TGroup>(IObservable<IChangeSet<TObject>>, Expression<Func<TObject, TGroup>>, TimeSpan?, IScheduler?) public static IObservable>> GroupWithImmutableState(this IObservable> source, Func groupSelectorKey, IObservable? regrouper = null) where TObject : notnull where TGroupKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); - groupSelectorKey.ThrowArgumentNullExceptionIfNull(nameof(groupSelectorKey)); + ArgumentExceptionHelper.ThrowIfNull(groupSelectorKey); return new GroupOnImmutable(source, groupSelectorKey, regrouper).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.LimitSizeTo.cs b/src/DynamicData/List/ObservableListEx.LimitSizeTo.cs index fd01dcfa6..b6da5a8d0 100644 --- a/src/DynamicData/List/ObservableListEx.LimitSizeTo.cs +++ b/src/DynamicData/List/ObservableListEx.LimitSizeTo.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -29,7 +27,7 @@ public static partial class ObservableListEx /// Returns an observable of the items that were removed. /// /// The type of the item. - /// The source list to apply size limits to. + /// The ISourceList<T> source list to apply size limits to. /// The maximum number of items allowed. Must be greater than zero. /// The scheduler for scheduling size checks. Defaults to . /// An observable that emits collections of items each time excess items are removed from the source list. @@ -37,17 +35,17 @@ public static partial class ObservableListEx /// is zero or negative. /// /// - /// This operator acts directly on an . It subscribes to the source's changes, + /// This operator acts directly on an ISourceList<T>. It subscribes to the source's changes, /// tracks insertion order using an internal Transform, and removes the oldest items when the size limit is exceeded. /// /// Worth noting: The returned observable emits the removed items (not changesets). Subscribe to this observable to activate the size-limiting mechanism. Removal is performed synchronously under a lock shared with the change tracking. /// - /// - /// + /// ExpireAfter<T>(ISourceList<T>, Func<T, TimeSpan?>, TimeSpan?, IScheduler?) + /// Top<T>(IObservable<IChangeSet<T>>, int) public static IObservable> LimitSizeTo(this ISourceList source, int sizeLimit, IScheduler? scheduler = null) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); if (sizeLimit <= 0) { diff --git a/src/DynamicData/List/ObservableListEx.MergeChangeSets.cs b/src/DynamicData/List/ObservableListEx.MergeChangeSets.cs index 4f58c05cc..905a5f92f 100644 --- a/src/DynamicData/List/ObservableListEx.MergeChangeSets.cs +++ b/src/DynamicData/List/ObservableListEx.MergeChangeSets.cs @@ -1,93 +1,97 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. /// public static partial class ObservableListEx { - /// + /// This overload follows the same core behavior as the related overload. /// /// Merges multiple list changeset streams from an observable-of-observables into a single unified changeset stream. - /// Unlike , list merging performs no key-based deduplication. + /// Unlike ObservableCacheEx.MergeChangeSets<TObject, TKey>(IObservable<IObservable<IChangeSet<TObject, TKey>>>, IEqualityComparer<TObject>), list merging performs no key-based deduplication. /// - /// The source of nested changeset observables. - /// An optional used by the merge tracker to compare items. + /// The type of the TObject value. + /// The source IObservable<T> of nested changeset observables. + /// An optional IEqualityComparer<TObject> used by the merge tracker to compare items. + /// The resulting observable sequence. public static IObservable> MergeChangeSets(this IObservable>> source, IEqualityComparer? equalityComparer = null) where TObject : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new MergeChangeSets(source, equalityComparer).Run(); } - /// + /// This overload follows the same core behavior as the related overload. /// /// Merges two list changeset streams into a single unified stream. /// - /// The first to merge. - /// The second to merge with. - /// An optional used to compare items. + /// The type of the TObject value. + /// The first IObservable<IChangeSet<TObject>> to merge. + /// The second IObservable<IChangeSet<TObject>> to merge with. + /// An optional IEqualityComparer<TObject> used to compare items. /// An optional for scheduling enumeration. /// When (default), the result completes when all sources complete. + /// The resulting observable sequence. public static IObservable> MergeChangeSets(this IObservable> source, IObservable> other, IEqualityComparer? equalityComparer = null, IScheduler? scheduler = null, bool completable = true) where TObject : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - other.ThrowArgumentNullExceptionIfNull(nameof(other)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(other); return new[] { source, other }.MergeChangeSets(equalityComparer, scheduler, completable); } - /// + /// This overload follows the same core behavior as the related overload. /// /// Merges the source list changeset stream with additional changeset streams into a single unified stream. /// - /// The primary source to merge. - /// The additional of list changeset streams to merge with. - /// An optional used to compare items. + /// The type of the TObject value. + /// The primary source IObservable<IChangeSet<TObject>> to merge. + /// The additional IEnumerable<T> of list changeset streams to merge with. + /// An optional IEqualityComparer<TObject> used to compare items. /// An optional for scheduling enumeration. /// When (default), the result completes when all sources complete. + /// The resulting observable sequence. public static IObservable> MergeChangeSets(this IObservable> source, IEnumerable>> others, IEqualityComparer? equalityComparer = null, IScheduler? scheduler = null, bool completable = true) where TObject : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - others.ThrowArgumentNullExceptionIfNull(nameof(others)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(others); return source.EnumerateOne().Concat(others).MergeChangeSets(equalityComparer, scheduler, completable); } /// /// Merges a collection of list changeset streams into a single unified changeset stream. - /// This is the canonical list MergeChangeSets overload: other overloads accepting , , or pair/params variants ultimately produce equivalent behavior. + /// This is the canonical list MergeChangeSets overload: other overloads accepting IObservable<T>, IObservableList<T>, or pair/params variants ultimately produce equivalent behavior. /// /// The type of items in the list. - /// The collection of list changeset streams to merge. - /// An optional used by the merge tracker to compare items. Defaults to when . + /// The IEnumerable<T> collection of list changeset streams to merge. + /// An optional IEqualityComparer<TObject> used by the merge tracker to compare items. Defaults to EqualityComparer<T>.Default when . /// An optional for scheduling enumeration. /// When (default), the result completes when all sources complete. /// A single list changeset stream containing all changes from all sources. /// is . /// /// - /// All changes from inner streams are forwarded to the output. There is no key-based deduplication (unlike ): if the same item appears in multiple inner streams, it will appear multiple times in the merged output. + /// All changes from inner streams are forwarded to the output. There is no key-based deduplication (unlike ObservableCacheEx.MergeChangeSets<TObject, TKey>(IObservable<IObservable<IChangeSet<TObject, TKey>>>, IEqualityComparer<TObject>)): if the same item appears in multiple inner streams, it will appear multiple times in the merged output. /// /// /// EventBehavior @@ -98,51 +102,59 @@ public static IObservable> MergeChangeSets(this IOb /// MovedIgnored. /// /// - /// - /// - /// - /// + /// MergeChangeSets<TObject>(IObservable<IObservable<IChangeSet<TObject>>>, IEqualityComparer<TObject>?) + /// MergeManyChangeSets<TObject, TDestination>(IObservable<IChangeSet<TObject>>, Func<TObject, IObservable<IChangeSet<TDestination>>>, IEqualityComparer<TDestination>?) + /// Or<T>(IObservable<IChangeSet<T>>, IObservable<IChangeSet<T>>[]) + /// ObservableCacheEx.MergeChangeSets<TObject, TKey>(IObservable<IObservable<IChangeSet<TObject, TKey>>>, IEqualityComparer<TObject>) public static IObservable> MergeChangeSets(this IEnumerable>> source, IEqualityComparer? equalityComparer = null, IScheduler? scheduler = null, bool completable = true) where TObject : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new MergeChangeSets(source, equalityComparer, completable, scheduler).Run(); } - /// + /// This overload follows the same core behavior as the related overload. /// - /// Merges list changeset streams from an into a single stream. Sources can be added or removed dynamically. + /// Merges list changeset streams from an IObservableList<T> into a single stream. Sources can be added or removed dynamically. /// + /// The type of the TObject value. + /// The source value. + /// The equalityComparer value. + /// The resulting observable sequence. public static IObservable> MergeChangeSets(this IObservableList>> source, IEqualityComparer? equalityComparer = null) where TObject : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Connect().MergeChangeSets(equalityComparer); } - /// + /// This overload follows the same core behavior as the related overload. /// /// Merges list changeset streams from a list-of-list-changeset-observables into a single stream. /// Each inner list changeset observable in the source list is merged, and parent item removal triggers child cleanup. /// + /// The type of the TObject value. + /// The source value. + /// The equalityComparer value. + /// The resulting observable sequence. public static IObservable> MergeChangeSets(this IObservable>>> source, IEqualityComparer? equalityComparer = null) where TObject : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.MergeManyChangeSets(static src => src, equalityComparer); } /// - /// Merges cache changeset streams from an into a single cache changeset stream. + /// Merges cache changeset streams from an IObservableList<T> into a single cache changeset stream. /// Uses to resolve conflicts when the same key appears in multiple child streams. /// /// The type of items in the list. /// The type of the object key. - /// The of cache changeset observables. - /// to resolve which value wins when the same key appears in multiple sources. + /// The IObservableList<T> of cache changeset observables. + /// IComparer<TObject> to resolve which value wins when the same key appears in multiple sources. /// A single cache changeset stream with key-based deduplication. /// is . /// @@ -157,60 +169,69 @@ public static IObservable> MergeChangeSets(this IOb /// Source list RemoveDisposes that source's subscription. All keys it contributed are removed. For keys also contributed by other sources, the next-best value (per ) is promoted as an Update, not an Add. /// /// - /// - /// + /// MergeChangeSets<TObject, TKey>(IObservableList<IObservable<IChangeSet<TObject, TKey>>>, IEqualityComparer<TObject>?, IComparer<TObject>?) + /// MergeManyChangeSets<TObject, TDestination, TDestinationKey>(IObservable<IChangeSet<TObject>>, Func<TObject, IObservable<IChangeSet<TDestination, TDestinationKey>>>, IEqualityComparer<TDestination>?, IComparer<TDestination>?) public static IObservable> MergeChangeSets(this IObservableList>> source, IComparer comparer) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Connect().MergeChangeSets(comparer); } - /// + /// This overload follows the same core behavior as the related overload. /// - /// Merges cache changeset streams from an into a single cache changeset stream, with optional equality and ordering comparers. + /// Merges cache changeset streams from an IObservableList<T> into a single cache changeset stream, with optional equality and ordering comparers. /// - /// The of cache changeset observables. - /// An optional to determine if two elements are the same. - /// An optional to resolve conflicts when the same key appears in multiple sources. + /// The type of the TObject value. + /// The type of the TKey value. + /// The IObservableList<T> of cache changeset observables. + /// An optional IEqualityComparer<TObject> to determine if two elements are the same. + /// An optional IComparer<TObject> to resolve conflicts when the same key appears in multiple sources. + /// The resulting observable sequence. public static IObservable> MergeChangeSets(this IObservableList>> source, IEqualityComparer? equalityComparer = null, IComparer? comparer = null) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Connect().MergeChangeSets(equalityComparer, comparer); } - /// + /// This overload follows the same core behavior as the related overload. /// /// Merges cache changeset streams from a list changeset of cache changeset observables, using a comparer for conflict resolution. /// - /// The source whose items are cache changeset observables. - /// to resolve which value wins when the same key appears in multiple sources. + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<T> whose items are cache changeset observables. + /// IComparer<TObject> to resolve which value wins when the same key appears in multiple sources. + /// The resulting observable sequence. public static IObservable> MergeChangeSets(this IObservable>>> source, IComparer comparer) where TObject : notnull where TKey : notnull { - comparer.ThrowArgumentNullExceptionIfNull(nameof(comparer)); + ArgumentExceptionHelper.ThrowIfNull(comparer); return source.MergeChangeSets(comparer); } - /// + /// This overload follows the same core behavior as the related overload. /// /// Merges cache changeset streams from a list changeset of cache changeset observables, with optional equality and ordering comparers. /// - /// The source whose items are cache changeset observables. - /// An optional to determine if two elements are the same. - /// An optional to resolve conflicts when the same key appears in multiple sources. + /// The type of the TObject value. + /// The type of the TKey value. + /// The source IObservable<T> whose items are cache changeset observables. + /// An optional IEqualityComparer<TObject> to determine if two elements are the same. + /// An optional IComparer<TObject> to resolve conflicts when the same key appears in multiple sources. + /// The resulting observable sequence. public static IObservable> MergeChangeSets(this IObservable>>> source, IEqualityComparer? equalityComparer = null, IComparer? comparer = null) where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.MergeManyChangeSets(static src => src, equalityComparer, comparer); } diff --git a/src/DynamicData/List/ObservableListEx.MergeMany.cs b/src/DynamicData/List/ObservableListEx.MergeMany.cs index 55dfa4f19..3b7125a12 100644 --- a/src/DynamicData/List/ObservableListEx.MergeMany.cs +++ b/src/DynamicData/List/ObservableListEx.MergeMany.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; +#if REACTIVE_SHIM +using DynamicData.Reactive.List.Internal; +#else using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -24,13 +22,13 @@ namespace DynamicData; public static partial class ObservableListEx { /// - /// Subscribes to a per-item observable for each item in the source and merges all emissions into a single stream. + /// Subscribes to a per-item observable for each item in the source and merges all emissions into a single IObservable<TDestination> stream. /// This is NOT a changeset operator: it returns a flat observable of values. /// /// The type of items in the source list. /// The type of values emitted by per-item observables. - /// The source whose items each produce an observable. - /// A function that returns an observable for each source item. + /// The source IObservable<IChangeSet<T>> whose items each produce an observable. + /// A Func<T, TResult> function that returns an observable for each source item. /// An observable that emits values from all per-item observables, merged together. /// or is . /// @@ -43,16 +41,15 @@ public static partial class ObservableListEx /// OnCompleted (source)Completes only after the source and all active inner observables have completed. /// /// - /// - /// - /// - /// + /// SubscribeMany<T>(IObservable<IChangeSet<T>>, Func<T, IDisposable>) + /// MergeManyChangeSets<TObject, TDestination>(IObservable<IChangeSet<TObject>>, Func<TObject, IObservable<IChangeSet<TDestination>>>, IEqualityComparer<TDestination>?) + /// WhenPropertyChanged<TObject, TValue>(IObservable<IChangeSet<TObject>>, Expression<Func<TObject, TValue>>, bool) + /// ObservableCacheEx.MergeMany<TObject, TKey, TDestination>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, IObservable<TDestination>>) public static IObservable MergeMany(this IObservable> source, Func> observableSelector) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); return new MergeMany(source, observableSelector).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.MergeManyChangeSets.cs b/src/DynamicData/List/ObservableListEx.MergeManyChangeSets.cs index e10118cef..e6fb3171c 100644 --- a/src/DynamicData/List/ObservableListEx.MergeManyChangeSets.cs +++ b/src/DynamicData/List/ObservableListEx.MergeManyChangeSets.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -29,9 +27,9 @@ public static partial class ObservableListEx /// /// The type of items in the source list. /// The type of items in the child changeset streams. - /// The source whose items each produce a child changeset stream. - /// A function that returns a child list changeset stream for each source item. - /// An optional used to compare child items. + /// The source IObservable<IChangeSet<TObject>> whose items each produce a child changeset stream. + /// A Func<T, TResult> function that returns a child list changeset stream for each source item. + /// An optional IEqualityComparer<TDestination> used to compare child items. /// A single list changeset stream containing all items from all child streams. /// or is . /// @@ -46,10 +44,10 @@ public static partial class ObservableListEx /// Remove/RemoveRange/ClearChild subscription disposed. All child items from that parent are removed. /// /// - /// - /// - /// - /// + /// MergeChangeSets<TObject>(IEnumerable<IObservable<IChangeSet<TObject>>>, IEqualityComparer<TObject>?, IScheduler?, bool) + /// MergeManyChangeSets<TObject, TDestination, TDestinationKey>(IObservable<IChangeSet<TObject>>, Func<TObject, IObservable<IChangeSet<TDestination, TDestinationKey>>>, IEqualityComparer<TDestination>?, IComparer<TDestination>?) + /// TransformMany<TDestination, TSource>(IObservable<IChangeSet<TSource>>, Func<TSource, IEnumerable<TDestination>>, IEqualityComparer<TDestination>?) + /// ObservableCacheEx.MergeManyChangeSets<TObject, TKey, TDestination, TDestinationKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, IObservable<IChangeSet<TDestination, TDestinationKey>>>, IEqualityComparer<TDestination>, IComparer<TDestination>) public static IObservable> MergeManyChangeSets(this IObservable> source, Func>> observableSelector, IEqualityComparer? equalityComparer = null) where TObject : notnull where TDestination : notnull @@ -74,24 +72,24 @@ public static IObservable> MergeManyChangeSetsThe type of items in the source list. /// The type of items in the child cache changeset streams. /// The type of the key in the child cache changesets. - /// The source whose items each produce a child changeset stream. - /// A function that returns a child cache changeset stream for each source item. - /// to resolve which value wins when the same key appears from multiple children. + /// The source IObservable<IChangeSet<TObject>> whose items each produce a child changeset stream. + /// A Func<T, TResult> function that returns a child cache changeset stream for each source item. + /// IComparer<TDestination> to resolve which value wins when the same key appears from multiple children. /// A single cache changeset stream with key-based deduplication. /// , , or is . /// - /// - /// Delegates to with a equality comparer. + /// This overload follows the same core behavior as the related overload. + /// Delegates to MergeManyChangeSets<TObject, TDestination, TDestinationKey>(IObservable<IChangeSet<TObject>>, Func<TObject, IObservable<IChangeSet<TDestination, TDestinationKey>>>, IEqualityComparer<TDestination>?, IComparer<TDestination>?) with a equality comparer. /// - /// + /// MergeManyChangeSets<TObject, TDestination, TDestinationKey>(IObservable<IChangeSet<TObject>>, Func<TObject, IObservable<IChangeSet<TDestination, TDestinationKey>>>, IEqualityComparer<TDestination>?, IComparer<TDestination>?) public static IObservable> MergeManyChangeSets(this IObservable> source, Func>> observableSelector, IComparer comparer) where TObject : notnull where TDestination : notnull where TDestinationKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); - comparer.ThrowArgumentNullExceptionIfNull(nameof(comparer)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); + ArgumentExceptionHelper.ThrowIfNull(comparer); return source.MergeManyChangeSets(observableSelector, equalityComparer: null, comparer: comparer); } @@ -103,10 +101,10 @@ public static IObservable> MergeManyCh /// The type of items in the source list. /// The type of items in the child cache changeset streams. /// The type of the key in the child cache changesets. - /// The source whose items each produce a child changeset stream. - /// A function that returns a child cache changeset stream for each source item. - /// An optional to determine if two elements are the same. - /// An optional to resolve conflicts when the same key appears from multiple children. + /// The source IObservable<IChangeSet<TObject>> whose items each produce a child changeset stream. + /// A Func<T, TResult> function that returns a child cache changeset stream for each source item. + /// An optional IEqualityComparer<TDestination> to determine if two elements are the same. + /// An optional IComparer<TDestination> to resolve conflicts when the same key appears from multiple children. /// A single cache changeset stream with key-based deduplication. /// or is . /// @@ -127,19 +125,19 @@ public static IObservable> MergeManyCh /// /// /// EventBehavior - /// OnErrorAn error from the source (parent) stream or from any child changeset stream terminates the entire output. Unlike , child errors are NOT swallowed. + /// OnErrorAn error from the source (parent) stream or from any child changeset stream terminates the entire output. Unlike MergeMany<T, TDestination>(IObservable<IChangeSet<T>>, Func<T, IObservable<TDestination>>), child errors are NOT swallowed. /// OnCompletedThe output completes when the source (parent) stream completes and all active child changeset streams have also completed. /// /// - /// - /// + /// MergeManyChangeSets<TObject, TDestination>(IObservable<IChangeSet<TObject>>, Func<TObject, IObservable<IChangeSet<TDestination>>>, IEqualityComparer<TDestination>?) + /// MergeChangeSets<TObject, TKey>(IObservableList<IObservable<IChangeSet<TObject, TKey>>>, IComparer<TObject>) public static IObservable> MergeManyChangeSets(this IObservable> source, Func>> observableSelector, IEqualityComparer? equalityComparer = null, IComparer? comparer = null) where TObject : notnull where TDestination : notnull where TDestinationKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - observableSelector.ThrowArgumentNullExceptionIfNull(nameof(observableSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(observableSelector); return new MergeManyCacheChangeSets(source, observableSelector, equalityComparer, comparer).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.NotEmpty.cs b/src/DynamicData/List/ObservableListEx.NotEmpty.cs index efc183cb6..71f9a308e 100644 --- a/src/DynamicData/List/ObservableListEx.NotEmpty.cs +++ b/src/DynamicData/List/ObservableListEx.NotEmpty.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -27,15 +18,15 @@ public static partial class ObservableListEx /// Suppresses empty changesets from the stream. Only changesets with at least one change are forwarded. /// /// The type of the item. - /// The source to suppress empty changesets. + /// The source IObservable<IChangeSet<T>> to suppress empty changesets. /// A list changeset stream with empty changesets filtered out. /// is . - /// - /// + /// StartWithEmpty<T>(IObservable<IChangeSet<T>>) + /// WhereReasonsAre<T>(IObservable<IChangeSet<T>>, ListChangeReason[]) public static IObservable> NotEmpty(this IObservable> source) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Where(s => s.Count != 0); } diff --git a/src/DynamicData/List/ObservableListEx.OnItemAdded.cs b/src/DynamicData/List/ObservableListEx.OnItemAdded.cs index f07beed67..e28b9bf00 100644 --- a/src/DynamicData/List/ObservableListEx.OnItemAdded.cs +++ b/src/DynamicData/List/ObservableListEx.OnItemAdded.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -28,8 +19,8 @@ public static partial class ObservableListEx /// Triggers on , , and the new item of . /// /// The type of items in the list. - /// The source to observe item additions in. - /// The action to invoke for each added item. + /// The source IObservable<IChangeSet<T>> to observe item additions in. + /// The Action<T> action to invoke for each added item. /// A continuation of the source changeset stream, with the side effect applied before forwarding. /// or is . /// @@ -44,15 +35,18 @@ public static partial class ObservableListEx /// OnErrorIf the callback throws, the exception propagates as OnError. /// /// - /// - /// - /// - /// + /// OnItemRemoved<T>(IObservable<IChangeSet<T>>, Action<T>, bool) + /// OnItemRefreshed<T>(IObservable<IChangeSet<T>>, Action<T>) + /// ForEachItemChange<TObject>(IObservable<IChangeSet<TObject>>, Action<ItemChange<TObject>>) + /// ObservableCacheEx.OnItemAdded<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Action<TObject>) public static IObservable> OnItemAdded( this IObservable> source, Action addAction) where T : notnull - => List.Internal.OnItemAdded.Create( - source: source, - addAction: addAction); + { + ArgumentExceptionHelper.ThrowIfNull(source); + return List.Internal.OnItemAdded.Create( + source: source, + addAction: addAction); + } } diff --git a/src/DynamicData/List/ObservableListEx.OnItemRefreshed.cs b/src/DynamicData/List/ObservableListEx.OnItemRefreshed.cs index 53824aba0..827ffab00 100644 --- a/src/DynamicData/List/ObservableListEx.OnItemRefreshed.cs +++ b/src/DynamicData/List/ObservableListEx.OnItemRefreshed.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -27,19 +18,22 @@ public static partial class ObservableListEx /// Invokes for every item with a change in the source stream. /// /// The type of items in the list. - /// The source to observe item refresh events in. - /// The action to invoke for each refreshed item. + /// The source IObservable<IChangeSet<T>> to observe item refresh events in. + /// The Action<T> action to invoke for each refreshed item. /// A continuation of the source changeset stream, with the side effect applied before forwarding. /// or is . - /// - /// - /// - /// + /// OnItemAdded<T>(IObservable<IChangeSet<T>>, Action<T>) + /// OnItemRemoved<T>(IObservable<IChangeSet<T>>, Action<T>, bool) + /// AutoRefresh<TObject>(IObservable<IChangeSet<TObject>>, TimeSpan?, TimeSpan?, IScheduler?) + /// ObservableCacheEx.OnItemRefreshed<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Action<TObject>) public static IObservable> OnItemRefreshed( this IObservable> source, Action refreshAction) where T : notnull - => List.Internal.OnItemRefreshed.Create( - source: source, - refreshAction: refreshAction); + { + ArgumentExceptionHelper.ThrowIfNull(source); + return List.Internal.OnItemRefreshed.Create( + source: source, + refreshAction: refreshAction); + } } diff --git a/src/DynamicData/List/ObservableListEx.OnItemRemoved.cs b/src/DynamicData/List/ObservableListEx.OnItemRemoved.cs index 7a63f9247..0f7a85e67 100644 --- a/src/DynamicData/List/ObservableListEx.OnItemRemoved.cs +++ b/src/DynamicData/List/ObservableListEx.OnItemRemoved.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -28,8 +19,8 @@ public static partial class ObservableListEx /// Triggers on , , , and the old item of . /// /// The type of items in the list. - /// The source to observe item removals in. - /// The action to invoke for each removed item. + /// The source IObservable<IChangeSet<T>> to observe item removals in. + /// The Action<T> action to invoke for each removed item. /// When (default), is also invoked for all remaining tracked items upon stream disposal, completion, or error. /// A continuation of the source changeset stream, with the side effect applied before forwarding. /// or is . @@ -50,17 +41,20 @@ public static partial class ObservableListEx /// /// Worth noting: When is (the default), disposing the subscription also invokes the callback for every item still in the list, not just items that were explicitly removed during the subscription. Exceptions in are not caught. /// - /// - /// - /// - /// + /// OnItemAdded<T>(IObservable<IChangeSet<T>>, Action<T>) + /// DisposeMany<T>(IObservable<IChangeSet<T>>) + /// SubscribeMany<T>(IObservable<IChangeSet<T>>, Func<T, IDisposable>) + /// ObservableCacheEx.OnItemRemoved<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Action<TObject>, bool) public static IObservable> OnItemRemoved( this IObservable> source, Action removeAction, bool invokeOnUnsubscribe = true) where T : notnull - => List.Internal.OnItemRemoved.Create( - source: source, - removeAction: removeAction, - invokeOnUnsubscribe: invokeOnUnsubscribe); + { + ArgumentExceptionHelper.ThrowIfNull(source); + return List.Internal.OnItemRemoved.Create( + source: source, + removeAction: removeAction, + invokeOnUnsubscribe: invokeOnUnsubscribe); + } } diff --git a/src/DynamicData/List/ObservableListEx.Or.cs b/src/DynamicData/List/ObservableListEx.Or.cs index b47acd986..6a1d4a783 100644 --- a/src/DynamicData/List/ObservableListEx.Or.cs +++ b/src/DynamicData/List/ObservableListEx.Or.cs @@ -1,33 +1,34 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. /// public static partial class ObservableListEx { - /// + /// This overload follows the same core behavior as the related overload. /// /// Applies a logical OR (union) between a pre-built collection of list changeset sources. Items present in any source are included. /// - /// + /// The type of the T value. + /// The sources value. + /// The resulting observable sequence. + /// ObservableCacheEx.Or<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IObservable<IChangeSet<TObject, TKey>>[]) public static IObservable> Or(this ICollection>> sources) where T : notnull => sources.Combine(CombineOperator.Or); @@ -36,8 +37,8 @@ public static IObservable> Or(this ICollection /// The type of the item. - /// The primary source to union. - /// The other changeset streams to combine with. + /// The primary source IObservable<IChangeSet<T>> to union. + /// The other IObservable<IChangeSet<T>> changeset streams to combine with. /// A list changeset stream containing items that exist in at least one source. /// is . /// @@ -54,36 +55,45 @@ public static IObservable> Or(this ICollectionMovedIgnored. /// /// - /// - /// - /// - /// + /// And<T>(IObservable<IChangeSet<T>>, IObservable<IChangeSet<T>>[]) + /// Except<T>(IObservable<IChangeSet<T>>, IObservable<IChangeSet<T>>[]) + /// Xor<T>(IObservable<IChangeSet<T>>, IObservable<IChangeSet<T>>[]) + /// MergeChangeSets<TObject>(IEnumerable<IObservable<IChangeSet<TObject>>>, IEqualityComparer<TObject>?, IScheduler?, bool) public static IObservable> Or(this IObservable> source, params IObservable>[] others) where T : notnull { - others.ThrowArgumentNullExceptionIfNull(nameof(others)); + ArgumentExceptionHelper.ThrowIfNull(others); return source.Combine(CombineOperator.Or, others); } - /// + /// This overload follows the same core behavior as the related overload. /// - /// Dynamic OR: sources can be added or removed from the at runtime. + /// Dynamic OR: sources can be added or removed from the IObservableList<T> at runtime. /// + /// The type of the T value. + /// The sources value. + /// The resulting observable sequence. public static IObservable> Or(this IObservableList>> sources) where T : notnull => sources.Combine(CombineOperator.Or); - /// + /// This overload follows the same core behavior as the related overload. /// - /// Dynamic OR accepting of . Each inner list's Connect() is used as a source. + /// Dynamic OR accepting IObservableList<T> of IObservableList<T>. Each inner list's Connect() is used as a source. /// + /// The type of the T value. + /// The sources value. + /// The resulting observable sequence. public static IObservable> Or(this IObservableList> sources) where T : notnull => sources.Combine(CombineOperator.Or); - /// + /// This overload follows the same core behavior as the related overload. /// - /// Dynamic OR accepting of . Each inner list's Connect() is used as a source. + /// Dynamic OR accepting IObservableList<T> of ISourceList<T>. Each inner list's Connect() is used as a source. /// + /// The type of the T value. + /// The sources value. + /// The resulting observable sequence. public static IObservable> Or(this IObservableList> sources) where T : notnull => sources.Combine(CombineOperator.Or); } diff --git a/src/DynamicData/List/ObservableListEx.Page.cs b/src/DynamicData/List/ObservableListEx.Page.cs index da8dc2c3d..436ec7b61 100644 --- a/src/DynamicData/List/ObservableListEx.Page.cs +++ b/src/DynamicData/List/ObservableListEx.Page.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -27,9 +25,9 @@ public static partial class ObservableListEx /// Applies page-based windowing to the source list. Only items within the current page (determined by page number and page size from ) are included downstream. /// /// The type of the item. - /// The source to page. + /// The source IObservable<IChangeSet<T>> to page. /// An observable of controlling which page to display (page number and page size). - /// An stream containing only items within the current page window. + /// An IPageChangeSet<T> stream containing only items within the current page window. /// or is . /// /// @@ -39,14 +37,14 @@ public static partial class ObservableListEx /// /// Worth noting: Duplicate items are removed from the result via Distinct() using the default equality comparer for , regardless of source order. The source should ideally be sorted before paging, since list order determines which items fall within each page window. /// - /// - /// - /// + /// Virtualise<T>(IObservable<IChangeSet<T>>, IObservable<IVirtualRequest>) + /// Top<T>(IObservable<IChangeSet<T>>, int) + /// Sort<T>(IObservable<IChangeSet<T>>, IComparer<T>, SortOptions, IObservable<Unit>?, IObservable<IComparer<T>>?, int) public static IObservable> Page(this IObservable> source, IObservable requests) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - requests.ThrowArgumentNullExceptionIfNull(nameof(requests)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(requests); return new Pager(source, requests).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.PopulateInto.cs b/src/DynamicData/List/ObservableListEx.PopulateInto.cs index d5431ff4a..969744086 100644 --- a/src/DynamicData/List/ObservableListEx.PopulateInto.cs +++ b/src/DynamicData/List/ObservableListEx.PopulateInto.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -24,24 +22,24 @@ namespace DynamicData; public static partial class ObservableListEx { /// - /// Subscribes to the source changeset stream and pipes all changes into the . + /// Subscribes to the source changeset stream and pipes all changes into the ISourceList<T>. /// /// The type of the object. - /// The source to pipe into a target list. - /// The destination to receive all changes. + /// The source IObservable<IChangeSet<T>> to pipe into a target list. + /// The destination ISourceList<T> to receive all changes. /// An representing the subscription. Dispose to stop piping changes. /// or is . /// /// Each changeset is applied to the destination using Clone() inside an Edit() call, producing a single batch update per changeset. /// - /// - /// - /// + /// Clone<T>(IObservable<IChangeSet<T>>, IList<T>) + /// Bind<T>(IObservable<IChangeSet<T>>, IObservableCollection<T>, BindingOptions) + /// ObservableCacheEx.PopulateInto<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, ISourceCache<TObject, TKey>) public static IDisposable PopulateInto(this IObservable> source, ISourceList destination) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - destination.ThrowArgumentNullExceptionIfNull(nameof(destination)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(destination); return source.Subscribe(changes => destination.Edit(updater => updater.Clone(changes))); } diff --git a/src/DynamicData/List/ObservableListEx.QueryWhenChanged.cs b/src/DynamicData/List/ObservableListEx.QueryWhenChanged.cs index 0e9984894..45831cdeb 100644 --- a/src/DynamicData/List/ObservableListEx.QueryWhenChanged.cs +++ b/src/DynamicData/List/ObservableListEx.QueryWhenChanged.cs @@ -1,22 +1,25 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; using DynamicData.Binding; -using DynamicData.Cache.Internal; +#endif +#if REACTIVE_SHIM +using DynamicData.Reactive.List.Internal; +#else using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -25,48 +28,48 @@ public static partial class ObservableListEx { /// /// Emits a projected value from the current list snapshot after every changeset. - /// The receives an representing the current state. + /// The receives an IReadOnlyCollection<T> representing the current state. /// /// The type of items in the list. /// The type of the projected result. - /// The source to project on each change. - /// A function projecting the current list snapshot to a result value. + /// The source IObservable<IChangeSet<TObject>> to project on each change. + /// A Func<T, TResult> function projecting the current list snapshot to a result value. /// An observable emitting the projected value after each changeset. /// or is . /// - /// Delegates to and applies via Select. + /// Delegates to QueryWhenChanged<T>(IObservable<IChangeSet<T>>) and applies via Select. /// - /// - /// - /// + /// QueryWhenChanged<T>(IObservable<IChangeSet<T>>) + /// ToCollection<TObject>(IObservable<IChangeSet<TObject>>) + /// ObservableCacheEx.QueryWhenChanged<TObject, TKey, TDestination>(IObservable<IChangeSet<TObject, TKey>>, Func<IQuery<TObject, TKey>, TDestination>) public static IObservable QueryWhenChanged(this IObservable> source, Func, TDestination> resultSelector) where TObject : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - resultSelector.ThrowArgumentNullExceptionIfNull(nameof(resultSelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(resultSelector); return source.QueryWhenChanged().Select(resultSelector); } /// - /// Emits an snapshot of the current list state after every changeset. + /// Emits an IReadOnlyCollection<T> snapshot of the current list state after every changeset. /// Maintains an internal list updated by cloning each changeset. /// /// The type of items in the list. - /// The source to project on each change. - /// An observable emitting the full list snapshot as after each change. + /// The source IObservable<IChangeSet<T>> to project on each change. + /// An observable emitting the full list snapshot as IReadOnlyCollection<T> after each change. /// is . /// /// This is a non-changeset operator. It emits the entire collection state on each change, not incremental diffs. - /// Worth noting: A new snapshot is emitted on every changeset, which can be chatty. The collection is rebuilt by cloning each changeset into an internal list. For sorted output, use . + /// Worth noting: A new snapshot is emitted on every changeset, which can be chatty. The collection is rebuilt by cloning each changeset into an internal list. For sorted output, use ToSortedCollection<TObject, TSortKey>(IObservable<IChangeSet<TObject>>, Func<TObject, TSortKey>, SortDirection). /// - /// - /// - /// + /// QueryWhenChanged<TObject, TDestination>(IObservable<IChangeSet<TObject>>, Func<IReadOnlyCollection<TObject>, TDestination>) + /// ToCollection<TObject>(IObservable<IChangeSet<TObject>>) + /// ToSortedCollection<TObject, TSortKey>(IObservable<IChangeSet<TObject>>, Func<TObject, TSortKey>, SortDirection) public static IObservable> QueryWhenChanged(this IObservable> source) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new QueryWhenChanged(source).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.RefCount.cs b/src/DynamicData/List/ObservableListEx.RefCount.cs index 4e8f03a10..656150e2f 100644 --- a/src/DynamicData/List/ObservableListEx.RefCount.cs +++ b/src/DynamicData/List/ObservableListEx.RefCount.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -24,21 +22,21 @@ namespace DynamicData; public static partial class ObservableListEx { /// - /// Reference-counted materialization of the source changeset stream into an . + /// Reference-counted materialization of the source changeset stream into an IObservableList<T>. /// The shared list is created on the first subscriber and disposed when the last subscriber unsubscribes. /// /// The type of the item. - /// The source to share via reference counting. - /// A list changeset stream backed by a shared, reference-counted . + /// The source IObservable<IChangeSet<T>> to share via reference counting. + /// A list changeset stream backed by a shared, reference-counted IObservableList<T>. /// is . /// /// Equivalent to Publish().RefCount() for changeset streams. The underlying list is created lazily on first subscription. /// - /// + /// AsObservableList<T>(IObservable<IChangeSet<T>>) public static IObservable> RefCount(this IObservable> source) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new RefCount(source).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.RemoveIndex.cs b/src/DynamicData/List/ObservableListEx.RemoveIndex.cs index 9f0e434f7..fa569ede2 100644 --- a/src/DynamicData/List/ObservableListEx.RemoveIndex.cs +++ b/src/DynamicData/List/ObservableListEx.RemoveIndex.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -27,17 +18,17 @@ public static partial class ObservableListEx /// Strips index information from all changes in the stream. /// /// The type of the object. - /// The source to strip index information. + /// The source IObservable<IChangeSet<T>> to strip index information. /// A list changeset stream with all index values removed from changes. /// is . /// /// Removes index positions from every change in each changeset. This is useful when downstream operators do not require or support index-based operations. /// - /// + /// ChangeSetEx.YieldWithoutIndex<T>(IEnumerable<Change<T>>) public static IObservable> RemoveIndex(this IObservable> source) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Select(changes => new ChangeSet(changes.YieldWithoutIndex())); } diff --git a/src/DynamicData/List/ObservableListEx.Reverse.cs b/src/DynamicData/List/ObservableListEx.Reverse.cs index 757954e35..c56cca121 100644 --- a/src/DynamicData/List/ObservableListEx.Reverse.cs +++ b/src/DynamicData/List/ObservableListEx.Reverse.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Linq; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -27,18 +25,18 @@ public static partial class ObservableListEx /// Reverses the order of items in the changeset stream by transforming all indices: new_index = length - old_index - 1. /// /// The type of the item. - /// The source to reverse. + /// The source IObservable<IChangeSet<T>> to reverse. /// A list changeset stream with all index positions reversed. /// is . /// /// This is a pure index transformation. The items themselves are unchanged; only their positional indices are inverted. /// - /// + /// Sort<T>(IObservable<IChangeSet<T>>, IComparer<T>, SortOptions, IObservable<Unit>?, IObservable<IComparer<T>>?, int) public static IObservable> Reverse(this IObservable> source) where T : notnull { var reverser = new Reverser(); - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.Select(changes => new ChangeSet(reverser.Reverse(changes))); } diff --git a/src/DynamicData/List/ObservableListEx.SkipInitial.cs b/src/DynamicData/List/ObservableListEx.SkipInitial.cs index a7d50fbf0..107bdb1d8 100644 --- a/src/DynamicData/List/ObservableListEx.SkipInitial.cs +++ b/src/DynamicData/List/ObservableListEx.SkipInitial.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -28,7 +19,7 @@ public static partial class ObservableListEx /// Internally defers until loaded, then skips the first emission. /// /// The type of the object. - /// The source to skip the initial changeset. + /// The source IObservable<IChangeSet<T>> to skip the initial changeset. /// A list changeset stream that omits the initial snapshot. /// is . /// @@ -37,15 +28,15 @@ public static partial class ObservableListEx /// initial snapshot, those items are silently dropped while downstream consumers remain unaware of them. /// Any later Refresh, Replace, Remove, or Moved change targeting one of those /// dropped items will throw because the downstream collection has no record of them. Only use this against - /// a source you know starts empty (for example, a that has not yet been populated). + /// a source you know starts empty (for example, a ISourceList<T> that has not yet been populated). /// /// - /// - /// + /// DeferUntilLoaded<T>(IObservable<IChangeSet<T>>) + /// StartWithEmpty<T>(IObservable<IChangeSet<T>>) public static IObservable> SkipInitial(this IObservable> source) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.DeferUntilLoaded().Skip(1); } diff --git a/src/DynamicData/List/ObservableListEx.Sort.cs b/src/DynamicData/List/ObservableListEx.Sort.cs index c0c67e484..710cb2d1d 100644 --- a/src/DynamicData/List/ObservableListEx.Sort.cs +++ b/src/DynamicData/List/ObservableListEx.Sort.cs @@ -1,22 +1,25 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; using DynamicData.Binding; -using DynamicData.Cache.Internal; +#endif +#if REACTIVE_SHIM +using DynamicData.Reactive.List.Internal; +#else using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -27,11 +30,11 @@ public static partial class ObservableListEx /// Sorts the list using the specified comparer, maintaining a sorted output that incrementally updates as items change. /// /// The type of the item. - /// The source to sort. - /// The used for sorting. + /// The source IObservable<IChangeSet<T>> to sort. + /// The IComparer<T> used for sorting. /// The for improved performance when sorted values are immutable. - /// An optional of that forces a full re-sort when it fires. Required when sorted property values are mutable. - /// An optional of that replaces the comparer, triggering a full re-sort. + /// An optional IObservable<Unit> of that forces a full re-sort when it fires. Required when sorted property values are mutable. + /// An optional IObservable<IComparer<T>> of IComparer<T> that replaces the comparer, triggering a full re-sort. /// When the number of changes exceeds this threshold, a full reset is performed instead of incremental updates. Default is 50. /// A list changeset stream with items in sorted order. /// or is . @@ -49,38 +52,40 @@ public static partial class ObservableListEx /// Comparer changedFull re-sort of all items. /// Re-sort signalFull re-sort using the current comparer. /// - /// Worth noting: is faster but requires that the values being sorted on never mutate. If they do, use the signal or . + /// Worth noting: is faster but requires that the values being sorted on never mutate. If they do, use the signal or AutoRefresh<TObject>(IObservable<IChangeSet<TObject>>, TimeSpan?, TimeSpan?, IScheduler?). /// - /// - /// - /// - /// + /// Sort<T>(IObservable<IChangeSet<T>>, IObservable<IComparer<T>>, SortOptions, IObservable<Unit>?, int) + /// Page<T>(IObservable<IChangeSet<T>>, IObservable<IPageRequest>) + /// Bind<T>(IObservable<IChangeSet<T>>, IObservableCollection<T>, BindingOptions) + /// ObservableCacheEx.Sort<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IComparer<TObject>, SortOptimisations, int) public static IObservable> Sort(this IObservable> source, IComparer comparer, SortOptions options = SortOptions.None, IObservable? resort = null, IObservable>? comparerChanged = null, int resetThreshold = 50) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - comparer.ThrowArgumentNullExceptionIfNull(nameof(comparer)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(comparer); return new Sort(source, comparer, options, resort, comparerChanged, resetThreshold).Run(); } - /// + /// This overload follows the same core behavior as the related overload. /// /// Sorts the list using an observable comparer. The initial comparer is taken from the first emission; subsequent emissions trigger a full re-sort. /// + /// The type of the T value. + /// The resulting observable sequence. /// - /// Until emits its first comparer, items are sorted using . Downstream still receives changesets immediately; the initial ordering is whatever produces, then a full re-sort happens once the first comparer arrives. + /// Until emits its first comparer, items are sorted using Comparer<T>.Default. Downstream still receives changesets immediately; the initial ordering is whatever Comparer<T>.Default produces, then a full re-sort happens once the first comparer arrives. /// - /// The source to sort. - /// An of that emits comparers. The first emission provides the initial sort order; subsequent emissions trigger re-sorts. + /// The source IObservable<IChangeSet<T>> to sort. + /// An IObservable<IComparer<T>> of IComparer<T> that emits comparers. The first emission provides the initial sort order; subsequent emissions trigger re-sorts. /// for controlling sort behavior. - /// An optional of to force a re-sort with the current comparer. + /// An optional IObservable<Unit> of to force a re-sort with the current comparer. /// The threshold for triggering a full reset instead of incremental updates. public static IObservable> Sort(this IObservable> source, IObservable> comparerChanged, SortOptions options = SortOptions.None, IObservable? resort = null, int resetThreshold = 50) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - comparerChanged.ThrowArgumentNullExceptionIfNull(nameof(comparerChanged)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(comparerChanged); return new Sort(source, null, options, resort, comparerChanged, resetThreshold).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.StartWithEmpty.cs b/src/DynamicData/List/ObservableListEx.StartWithEmpty.cs index 91c7100e0..d92f1ce2f 100644 --- a/src/DynamicData/List/ObservableListEx.StartWithEmpty.cs +++ b/src/DynamicData/List/ObservableListEx.StartWithEmpty.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -27,11 +18,16 @@ public static partial class ObservableListEx /// Prepends an empty changeset to the source stream. Useful for initializing downstream consumers that expect an initial emission. /// /// The type of item. - /// The source to prepend an empty changeset to. + /// The source IObservable<IChangeSet<T>> to prepend an empty changeset to. /// A list changeset stream that begins with an empty changeset. - /// - /// - /// + /// DeferUntilLoaded<T>(IObservable<IChangeSet<T>>) + /// SkipInitial<T>(IObservable<IChangeSet<T>>) + /// ObservableCacheEx.StartWithEmpty<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>) public static IObservable> StartWithEmpty(this IObservable> source) - where T : notnull => source.StartWith(ChangeSet.Empty); + where T : notnull + { + ArgumentExceptionHelper.ThrowIfNull(source); + + return source.StartWith(ChangeSet.Empty); + } } diff --git a/src/DynamicData/List/ObservableListEx.SubscribeMany.cs b/src/DynamicData/List/ObservableListEx.SubscribeMany.cs index 6f4745a2a..01d1f3d37 100644 --- a/src/DynamicData/List/ObservableListEx.SubscribeMany.cs +++ b/src/DynamicData/List/ObservableListEx.SubscribeMany.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -29,7 +27,7 @@ public static partial class ObservableListEx /// The changeset is forwarded downstream unmodified. /// /// The type of the object. - /// The source to create a subscription for each item in. + /// The source IObservable<IChangeSet<T>> to create a subscription for each item in. /// A function that creates an for each item. /// A continuation of the source changeset stream with per-item subscriptions managed as a side effect. /// or is . @@ -43,15 +41,15 @@ public static partial class ObservableListEx /// OnError/OnCompleted/DisposalAll active subscriptions are disposed. /// /// - /// - /// - /// - /// + /// MergeMany<T, TDestination>(IObservable<IChangeSet<T>>, Func<T, IObservable<TDestination>>) + /// DisposeMany<T>(IObservable<IChangeSet<T>>) + /// OnItemRemoved<T>(IObservable<IChangeSet<T>>, Action<T>, bool) + /// ObservableCacheEx.SubscribeMany<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, Func<TObject, IDisposable>) public static IObservable> SubscribeMany(this IObservable> source, Func subscriptionFactory) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - subscriptionFactory.ThrowArgumentNullExceptionIfNull(nameof(subscriptionFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(subscriptionFactory); return new SubscribeMany(source, subscriptionFactory).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.SuppressRefresh.cs b/src/DynamicData/List/ObservableListEx.SuppressRefresh.cs index ac040d07f..0e71455a3 100644 --- a/src/DynamicData/List/ObservableListEx.SuppressRefresh.cs +++ b/src/DynamicData/List/ObservableListEx.SuppressRefresh.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -27,10 +18,10 @@ public static partial class ObservableListEx /// Suppresses all changes from the stream. All other change reasons pass through. /// /// The type of the object. - /// The source to strip refresh events. + /// The source IObservable<IChangeSet<T>> to strip refresh events. /// A list changeset stream with Refresh changes removed. - /// - /// + /// WhereReasonsAreNot<T>(IObservable<IChangeSet<T>>, ListChangeReason[]) + /// AutoRefresh<TObject>(IObservable<IChangeSet<TObject>>, TimeSpan?, TimeSpan?, IScheduler?) public static IObservable> SuppressRefresh(this IObservable> source) where T : notnull => source.WhereReasonsAreNot(ListChangeReason.Refresh); } diff --git a/src/DynamicData/List/ObservableListEx.Switch.cs b/src/DynamicData/List/ObservableListEx.Switch.cs index 0df10e44b..d00f6b414 100644 --- a/src/DynamicData/List/ObservableListEx.Switch.cs +++ b/src/DynamicData/List/ObservableListEx.Switch.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -24,21 +22,21 @@ namespace DynamicData; public static partial class ObservableListEx { /// - /// Subscribes to the latest inner , switching to each new source and clearing the result when switching. - /// This is the changeset-aware equivalent of Rx's , which cannot be applied directly to changeset streams. + /// Subscribes to the latest inner IObservableList<T>, switching to each new source and clearing the result when switching. + /// This is the changeset-aware equivalent of Rx's Observable.Switch<TSource>(IObservable<IObservable<TSource>>), which cannot be applied directly to changeset streams. /// /// The type of the object. - /// An observable that emits instances. Each emission triggers a switch to the new list. + /// An observable that emits IObservableList<T> instances. Each emission triggers a switch to the new list. /// A list changeset stream reflecting the most recently received inner list. /// is . /// - /// Convenience overload that calls Connect() on each inner list, then delegates to . + /// Convenience overload that calls Connect() on each inner list, then delegates to Switch<T>(IObservable<IObservable<IChangeSet<T>>>). /// - /// + /// Switch<T>(IObservable<IObservable<IChangeSet<T>>>) public static IObservable> Switch(this IObservable> sources) where T : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return sources.Select(cache => cache.Connect()).Switch(); } @@ -48,7 +46,7 @@ public static IObservable> Switch(this IObservable /// The type of the object. - /// An of changeset streams. The operator subscribes to the latest inner stream. + /// An IObservable<T> of IObservable<T> changeset streams. The operator subscribes to the latest inner stream. /// A list changeset stream reflecting the most recently received inner changeset stream. /// is . /// @@ -57,11 +55,11 @@ public static IObservable> Switch(this IObservableSwitch(). /// /// - /// + /// Switch<T>(IObservable<IObservableList<T>>) public static IObservable> Switch(this IObservable>> sources) where T : notnull { - sources.ThrowArgumentNullExceptionIfNull(nameof(sources)); + ArgumentExceptionHelper.ThrowIfNull(sources); return new Switch(sources).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.ToCollection.cs b/src/DynamicData/List/ObservableListEx.ToCollection.cs index 072451c92..2c43ab29f 100644 --- a/src/DynamicData/List/ObservableListEx.ToCollection.cs +++ b/src/DynamicData/List/ObservableListEx.ToCollection.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -24,14 +22,14 @@ namespace DynamicData; public static partial class ObservableListEx { /// - /// Emits the full collection as an after every changeset. Equivalent to QueryWhenChanged(items => items). + /// Emits the full collection as an IReadOnlyCollection<T> after every changeset. Equivalent to QueryWhenChanged(items => items). /// /// The type of items in the list. - /// The source to materialize into a collection on each change. + /// The source IObservable<IChangeSet<TObject>> to materialize into a collection on each change. /// An observable emitting the full collection snapshot after each change. - /// - /// - /// + /// QueryWhenChanged<T>(IObservable<IChangeSet<T>>) + /// ToSortedCollection<TObject, TSortKey>(IObservable<IChangeSet<TObject>>, Func<TObject, TSortKey>, SortDirection) + /// ObservableCacheEx.ToCollection<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>) public static IObservable> ToCollection(this IObservable> source) where TObject : notnull => source.QueryWhenChanged(items => items); } diff --git a/src/DynamicData/List/ObservableListEx.ToObservableChangeSet.cs b/src/DynamicData/List/ObservableListEx.ToObservableChangeSet.cs index 1c465dddc..a6a09890f 100644 --- a/src/DynamicData/List/ObservableListEx.ToObservableChangeSet.cs +++ b/src/DynamicData/List/ObservableListEx.ToObservableChangeSet.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -24,11 +15,11 @@ namespace DynamicData; public static partial class ObservableListEx { /// - /// Bridges an into the DynamicData world by converting each emitted item into a list changeset. + /// Bridges an IObservable<T> into the DynamicData world by converting each emitted item into a list changeset. /// Each emission becomes an Add operation in the resulting changeset stream. /// /// The type of the object. - /// The source to convert into a changeset stream. + /// The source IObservable<T> to convert into a changeset stream. /// An optional for time-based operations (expiry, size limiting). /// A list changeset stream where each source emission is an Add. /// is . @@ -40,8 +31,8 @@ public static partial class ObservableListEx /// /// Worth noting: Source completion and errors are propagated. The internal list is disposed on unsubscribe. /// - /// - /// + /// ToObservableChangeSet<T>(IObservable<T>, Func<T, TimeSpan?>, int, IScheduler?) + /// ToObservableChangeSet<T>(IObservable<IEnumerable<T>>, IScheduler?) public static IObservable> ToObservableChangeSet( this IObservable source, IScheduler? scheduler = null) @@ -52,14 +43,16 @@ public static IObservable> ToObservableChangeSet( limitSizeTo: -1, scheduler: scheduler); - /// + /// This overload follows the same core behavior as the related overload. /// - /// Bridges an into a list changeset stream with per-item time-based expiry. + /// Bridges an IObservable<T> into a list changeset stream with per-item time-based expiry. /// Expired items are automatically removed. /// - /// The source to convert into a changeset stream. - /// A function returning the time-to-live for each item. Return for non-expiring items. + /// The type of the T value. + /// The source IObservable<T> to convert into a changeset stream. + /// A Func<T, TResult> function returning the time-to-live for each item. Return for non-expiring items. /// An optional for expiry timers. + /// The resulting observable sequence. public static IObservable> ToObservableChangeSet( this IObservable source, Func expireAfter, @@ -71,14 +64,16 @@ public static IObservable> ToObservableChangeSet( limitSizeTo: -1, scheduler: scheduler); - /// + /// This overload follows the same core behavior as the related overload. /// - /// Bridges an into a list changeset stream with FIFO size limiting. + /// Bridges an IObservable<T> into a list changeset stream with FIFO size limiting. /// When the list exceeds , the oldest items are removed. /// - /// The source to convert into a changeset stream. + /// The type of the T value. + /// The source IObservable<T> to convert into a changeset stream. /// The maximum list size. Supply -1 to disable size limiting. /// An optional for scheduling removals. + /// The resulting observable sequence. public static IObservable> ToObservableChangeSet( this IObservable source, int limitSizeTo, @@ -90,14 +85,16 @@ public static IObservable> ToObservableChangeSet( limitSizeTo: limitSizeTo, scheduler: scheduler); - /// + /// This overload follows the same core behavior as the related overload. /// - /// Bridges an into a list changeset stream with both time-based expiry and FIFO size limiting. + /// Bridges an IObservable<T> into a list changeset stream with both time-based expiry and FIFO size limiting. /// - /// The source to convert into a changeset stream. - /// A function returning the time-to-live for each item. Return for non-expiring items. + /// The type of the T value. + /// The source IObservable<T> to convert into a changeset stream. + /// A Func<T, TResult> function returning the time-to-live for each item. Return for non-expiring items. /// The maximum list size. Supply -1 to disable size limiting. /// An optional for expiry timers and size-limit checks. + /// The resulting observable sequence. public static IObservable> ToObservableChangeSet( this IObservable source, Func? expireAfter, @@ -110,13 +107,15 @@ public static IObservable> ToObservableChangeSet( limitSizeTo: limitSizeTo, scheduler: scheduler); - /// + /// This overload follows the same core behavior as the related overload. /// - /// Bridges an of batches into a list changeset stream. + /// Bridges an IObservable<T> of IEnumerable<T> batches into a list changeset stream. /// Each emitted batch becomes an AddRange. /// - /// The source of to convert into a changeset stream. + /// The type of the T value. + /// The source IObservable<IEnumerable<T>> of IEnumerable<T> to convert into a changeset stream. /// An optional for time-based operations. + /// The resulting observable sequence. public static IObservable> ToObservableChangeSet( this IObservable> source, IScheduler? scheduler = null) @@ -127,13 +126,15 @@ public static IObservable> ToObservableChangeSet( limitSizeTo: -1, scheduler: scheduler); - /// + /// This overload follows the same core behavior as the related overload. /// - /// Bridges an of batches into a list changeset stream with FIFO size limiting. + /// Bridges an IObservable<T> of IEnumerable<T> batches into a list changeset stream with FIFO size limiting. /// - /// The source of to convert into a changeset stream. + /// The type of the T value. + /// The source IObservable<IEnumerable<T>> of IEnumerable<T> to convert into a changeset stream. /// The maximum list size. Oldest items are removed when the limit is exceeded. /// An optional for scheduling removals. + /// The resulting observable sequence. public static IObservable> ToObservableChangeSet( this IObservable> source, int limitSizeTo, @@ -145,13 +146,15 @@ public static IObservable> ToObservableChangeSet( limitSizeTo: limitSizeTo, scheduler: scheduler); - /// + /// This overload follows the same core behavior as the related overload. /// - /// Bridges an of batches into a list changeset stream with time-based expiry. + /// Bridges an IObservable<T> of IEnumerable<T> batches into a list changeset stream with time-based expiry. /// - /// The source of to convert into a changeset stream. - /// A function returning the time-to-live for each item. Return for non-expiring items. + /// The type of the T value. + /// The source IObservable<IEnumerable<T>> of IEnumerable<T> to convert into a changeset stream. + /// A Func<T, TResult> function returning the time-to-live for each item. Return for non-expiring items. /// An optional for expiry timers. + /// The resulting observable sequence. public static IObservable> ToObservableChangeSet( this IObservable> source, Func expireAfter, @@ -163,14 +166,16 @@ public static IObservable> ToObservableChangeSet( limitSizeTo: -1, scheduler: scheduler); - /// + /// This overload follows the same core behavior as the related overload. /// - /// Bridges an of batches into a list changeset stream with both time-based expiry and FIFO size limiting. + /// Bridges an IObservable<T> of IEnumerable<T> batches into a list changeset stream with both time-based expiry and FIFO size limiting. /// - /// The source of to convert into a changeset stream. - /// A function returning the time-to-live for each item. Return for non-expiring items. + /// The type of the T value. + /// The source IObservable<IEnumerable<T>> of IEnumerable<T> to convert into a changeset stream. + /// A Func<T, TResult> function returning the time-to-live for each item. Return for non-expiring items. /// The maximum list size. Oldest items removed when exceeded. /// An optional for expiry timers and size-limit checks. + /// The resulting observable sequence. public static IObservable> ToObservableChangeSet( this IObservable> source, Func? expireAfter, diff --git a/src/DynamicData/List/ObservableListEx.ToSortedCollection.cs b/src/DynamicData/List/ObservableListEx.ToSortedCollection.cs index 860079901..7d6c53e4d 100644 --- a/src/DynamicData/List/ObservableListEx.ToSortedCollection.cs +++ b/src/DynamicData/List/ObservableListEx.ToSortedCollection.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Binding; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -24,30 +22,30 @@ namespace DynamicData; public static partial class ObservableListEx { /// - /// Emits a sorted after every changeset, sorted by the value returned by . + /// Emits a sorted IReadOnlyCollection<T> after every changeset, sorted by the value returned by . /// /// The type of items in the list. /// The type of the sort key. - /// The source to materialize into a sorted collection on each change. - /// A function extracting the sort key from each item. + /// The source IObservable<IChangeSet<TObject>> to materialize into a sorted collection on each change. + /// A Func<T, TResult> function extracting the sort key from each item. /// The sort direction. Defaults to ascending. /// An observable emitting a sorted collection snapshot after each change. - /// - /// - /// - /// + /// ToCollection<TObject>(IObservable<IChangeSet<TObject>>) + /// ToSortedCollection<TObject>(IObservable<IChangeSet<TObject>>, IComparer<TObject>) + /// QueryWhenChanged<T>(IObservable<IChangeSet<T>>) + /// ObservableCacheEx.ToSortedCollection<TObject, TKey> public static IObservable> ToSortedCollection(this IObservable> source, Func sort, SortDirection sortOrder = SortDirection.Ascending) where TObject : notnull => source.QueryWhenChanged(query => sortOrder == SortDirection.Ascending ? new ReadOnlyCollectionLight(query.OrderBy(sort)) : new ReadOnlyCollectionLight(query.OrderByDescending(sort))); /// - /// Emits a sorted after every changeset, sorted using the specified . + /// Emits a sorted IReadOnlyCollection<T> after every changeset, sorted using the specified . /// /// The type of items in the list. - /// The source to materialize into a sorted collection on each change. - /// The used for sorting. + /// The source IObservable<IChangeSet<TObject>> to materialize into a sorted collection on each change. + /// The IComparer<TObject> used for sorting. /// An observable emitting a sorted collection snapshot after each change. - /// - /// + /// ToSortedCollection<TObject, TSortKey>(IObservable<IChangeSet<TObject>>, Func<TObject, TSortKey>, SortDirection) + /// ToCollection<TObject>(IObservable<IChangeSet<TObject>>) public static IObservable> ToSortedCollection(this IObservable> source, IComparer comparer) where TObject : notnull => source.QueryWhenChanged( query => diff --git a/src/DynamicData/List/ObservableListEx.Top.cs b/src/DynamicData/List/ObservableListEx.Top.cs index 17a128f99..2e689e986 100644 --- a/src/DynamicData/List/ObservableListEx.Top.cs +++ b/src/DynamicData/List/ObservableListEx.Top.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -27,7 +18,7 @@ public static partial class ObservableListEx /// Takes the first items from the source list. Implemented as Virtualise with a fixed window starting at index 0. /// /// The type of the item. - /// The source to take the top items. + /// The source IObservable<IChangeSet<T>> to take the top items. /// The maximum number of items to include. Must be greater than zero. /// A virtual changeset stream containing at most items from the beginning of the source. /// is . @@ -35,13 +26,13 @@ public static partial class ObservableListEx /// /// The source should ideally be sorted before applying Top, since list order determines which items appear. /// - /// - /// - /// + /// Virtualise<T>(IObservable<IChangeSet<T>>, IObservable<IVirtualRequest>) + /// Page<T>(IObservable<IChangeSet<T>>, IObservable<IPageRequest>) + /// Sort<T>(IObservable<IChangeSet<T>>, IComparer<T>, SortOptions, IObservable<Unit>?, IObservable<IComparer<T>>?, int) public static IObservable> Top(this IObservable> source, int numberOfItems) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); if (numberOfItems <= 0) { diff --git a/src/DynamicData/List/ObservableListEx.Transform.cs b/src/DynamicData/List/ObservableListEx.Transform.cs index 64a856a24..6f201a4d7 100644 --- a/src/DynamicData/List/ObservableListEx.Transform.cs +++ b/src/DynamicData/List/ObservableListEx.Transform.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -28,8 +26,8 @@ public static partial class ObservableListEx /// /// The type of the source items. /// The type of the destination items. - /// The source to transform. - /// The transform function applied to each item. + /// The source IObservable<IChangeSet<TSource>> to transform. + /// The Func<T, TResult> transform function applied to each item. /// When , Refresh events re-invoke the factory and emit an update. When (the default), Refresh is forwarded without re-transforming. /// A list changeset stream of transformed items. /// @@ -41,7 +39,7 @@ public static partial class ObservableListEx /// EventBehavior /// AddThe factory is called and an Add is emitted at the same index. /// AddRangeThe factory is called for each item. An AddRange is emitted at the same start index. - /// ReplaceThe factory is called for the new item. A Replace is emitted at the same index. The previous transformed value is available to overloads that accept . + /// ReplaceThe factory is called for the new item. A Replace is emitted at the same index. The previous transformed value is available to overloads that accept Optional<TDestination>. /// RemoveA Remove is emitted (no factory call). /// RemoveRangeA RemoveRange is emitted. /// MovedA Moved is emitted with updated indices (no factory call). Throws if the source change has no index information. @@ -52,61 +50,79 @@ public static partial class ObservableListEx /// Worth noting: By default, Refresh does NOT re-transform the item (it just forwards the signal). Set to if you need the factory re-invoked on Refresh. Add operations with out-of-bounds indices silently append to the end. /// /// or is . - /// - /// - /// - /// + /// TransformAsync<TSource, TDestination>(IObservable<IChangeSet<TSource>>, Func<TSource, Task<TDestination>>, bool) + /// TransformMany<TDestination, TSource>(IObservable<IChangeSet<TSource>>, Func<TSource, IEnumerable<TDestination>>, IEqualityComparer<TDestination>?) + /// Convert<TSource, TDestination>(IObservable<IChangeSet<TSource>>, Func<TSource, TDestination>) + /// ObservableCacheEx.Transform<TDestination, TSource, TKey>(IObservable<IChangeSet<TSource, TKey>>, Func<TSource, TDestination>, bool) public static IObservable> Transform(this IObservable> source, Func transformFactory, bool transformOnRefresh = false) where TSource : notnull where TDestination : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); return source.Transform((t, _, _) => transformFactory(t), transformOnRefresh); } - /// + /// This overload follows the same core behavior as the related overload. /// /// Projects each item using a transform function that also receives the item's index. /// + /// The type of the TSource value. + /// The type of the TDestination value. + /// The source value. + /// The transformFactory value. + /// The transformOnRefresh value. + /// The resulting observable sequence. public static IObservable> Transform(this IObservable> source, Func transformFactory, bool transformOnRefresh = false) where TSource : notnull where TDestination : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); return source.Transform((t, _, idx) => transformFactory(t, idx), transformOnRefresh); } - /// + /// This overload follows the same core behavior as the related overload. /// /// Projects each item using a transform function that also receives the previously transformed value (if any). /// Type arguments must be specified explicitly as type inference fails for this overload. /// - public static IObservable> Transform(this IObservable> source, Func, TDestination> transformFactory, bool transformOnRefresh = false) + /// The type of the TSource value. + /// The type of the TDestination value. + /// The source value. + /// The transformFactory value. + /// The transformOnRefresh value. + /// The resulting observable sequence. + public static IObservable> Transform(this IObservable> source, Func, TDestination> transformFactory, bool transformOnRefresh = false) where TSource : notnull where TDestination : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); return source.Transform((t, previous, _) => transformFactory(t, previous), transformOnRefresh); } - /// + /// This overload follows the same core behavior as the related overload. /// /// Projects each item using a transform function that receives the source item, the previously transformed value, and the index. /// Type arguments must be specified explicitly as type inference fails for this overload. /// - public static IObservable> Transform(this IObservable> source, Func, int, TDestination> transformFactory, bool transformOnRefresh = false) + /// The type of the TSource value. + /// The type of the TDestination value. + /// The source value. + /// The transformFactory value. + /// The transformOnRefresh value. + /// The resulting observable sequence. + public static IObservable> Transform(this IObservable> source, Func, int, TDestination> transformFactory, bool transformOnRefresh = false) where TSource : notnull where TDestination : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); return new Transformer(source, transformFactory, transformOnRefresh).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.TransformAsync.cs b/src/DynamicData/List/ObservableListEx.TransformAsync.cs index 998eb1419..c589f3627 100644 --- a/src/DynamicData/List/ObservableListEx.TransformAsync.cs +++ b/src/DynamicData/List/ObservableListEx.TransformAsync.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -24,17 +22,17 @@ namespace DynamicData; public static partial class ObservableListEx { /// - /// Projects each item to a new form using an async transform function. Behaves like but the factory returns a . + /// Projects each item to a new form using an async transform function. Behaves like Transform<TSource, TDestination>(IObservable<IChangeSet<TSource>>, Func<TSource, TDestination>, bool) but the factory returns a Task<T>. /// /// The type of the source items. /// The type of the destination items. - /// The source to transform asynchronously. - /// An async function that transforms each source item. + /// The source IObservable<IChangeSet<TSource>> to transform asynchronously. + /// An Func<T, TResult> async function that transforms each source item. /// When , Refresh events re-invoke the factory. /// A list changeset stream of asynchronously transformed items. /// or is . /// - /// Change handling is identical to the synchronous except the factory is awaited. Operations are serialized per changeset via a semaphore. + /// Change handling is identical to the synchronous Transform<TSource, TDestination>(IObservable<IChangeSet<TSource>>, Func<TSource, TDestination>, bool) except the factory is awaited. Operations are serialized per changeset via a semaphore. /// /// EventBehavior /// Add/AddRangeThe async factory is awaited for each item. An Add/AddRange is emitted with the transformed results. @@ -48,9 +46,9 @@ public static partial class ObservableListEx /// /// Worth noting: All async transforms within a single changeset are serialized (not parallel). Each changeset is fully processed before the next begins. By default, Refresh does NOT re-transform. /// - /// - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] + /// Transform<TSource, TDestination>(IObservable<IChangeSet<TSource>>, Func<TSource, TDestination>, bool) + /// ObservableCacheEx.TransformAsync<TDestination, TSource, TKey>(IObservable<IChangeSet<TSource, TKey>>, Func<TSource, Task<TDestination>>, IObservable<Func<TSource, TKey, bool>>) + [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformAsync( this IObservable> source, Func> transformFactory, @@ -58,17 +56,23 @@ public static IObservable> TransformAsync((t, _, _) => transformFactory(t), transformOnRefresh); } - /// + /// This overload follows the same core behavior as the related overload. /// /// Async transform overload receiving the source item and its index. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] + /// The type of the TSource value. + /// The type of the TDestination value. + /// The source value. + /// The transformFactory value. + /// The transformOnRefresh value. + /// The resulting observable sequence. + [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformAsync( this IObservable> source, Func> transformFactory, @@ -76,44 +80,56 @@ public static IObservable> TransformAsync((t, _, i) => transformFactory(t, i), transformOnRefresh); } - /// + /// This overload follows the same core behavior as the related overload. /// /// Async transform overload receiving the source item and the previously transformed value. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] + /// The type of the TSource value. + /// The type of the TDestination value. + /// The source value. + /// The transformFactory value. + /// The transformOnRefresh value. + /// The resulting observable sequence. + [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformAsync( this IObservable> source, - Func, Task> transformFactory, + Func, Task> transformFactory, bool transformOnRefresh = false) where TSource : notnull where TDestination : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); return source.TransformAsync((t, d, _) => transformFactory(t, d), transformOnRefresh); } - /// + /// This overload follows the same core behavior as the related overload. /// /// Async transform overload receiving the source item, previously transformed value, and index. This is the terminal overload that all other TransformAsync overloads delegate to. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] + /// The type of the TSource value. + /// The type of the TDestination value. + /// The source value. + /// The transformFactory value. + /// The transformOnRefresh value. + /// The resulting observable sequence. + [SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")] public static IObservable> TransformAsync( this IObservable> source, - Func, int, Task> transformFactory, + Func, int, Task> transformFactory, bool transformOnRefresh = false) where TSource : notnull where TDestination : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(transformFactory); return new TransformAsync(source, transformFactory, transformOnRefresh).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.TransformMany.cs b/src/DynamicData/List/ObservableListEx.TransformMany.cs index 6b3e3e327..416666d8c 100644 --- a/src/DynamicData/List/ObservableListEx.TransformMany.cs +++ b/src/DynamicData/List/ObservableListEx.TransformMany.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -29,9 +27,9 @@ public static partial class ObservableListEx /// /// The type of the destination items. /// The type of the source items. - /// The source to expand each item into multiple children. - /// A function that returns the child items for each source item. - /// An optional used during Replace to determine which child items changed between old and new parent values. + /// The source IObservable<IChangeSet<TSource>> to expand each item into multiple children. + /// A Func<T, TResult> function that returns the child items for each source item. + /// An optional IEqualityComparer<TDestination> used during Replace to determine which child items changed between old and new parent values. /// A list changeset stream of all child items from all source items. /// or is . /// @@ -43,39 +41,57 @@ public static partial class ObservableListEx /// RefreshChildren re-expanded and diffed. /// /// - /// - /// - /// + /// Transform<TSource, TDestination>(IObservable<IChangeSet<TSource>>, Func<TSource, TDestination>, bool) + /// MergeManyChangeSets<TObject, TDestination>(IObservable<IChangeSet<TObject>>, Func<TObject, IObservable<IChangeSet<TDestination>>>, IEqualityComparer<TDestination>?) + /// ObservableCacheEx.TransformMany<TDestination, TDestinationKey, TSource, TSourceKey>(IObservable<IChangeSet<TSource, TSourceKey>>, Func<TSource, IEnumerable<TDestination>>, Func<TDestination, TDestinationKey>) public static IObservable> TransformMany(this IObservable> source, Func> manySelector, IEqualityComparer? equalityComparer = null) where TDestination : notnull where TSource : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - manySelector.ThrowArgumentNullExceptionIfNull(nameof(manySelector)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(manySelector); return new TransformMany(source, manySelector, equalityComparer).Run(); } - /// + /// This overload follows the same core behavior as the related overload. /// - /// Flattens each source item into children from an . The collection is observed for subsequent changes. + /// Flattens each source item into children from an ObservableCollection<T>. The collection is observed for subsequent changes. /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The source value. + /// The manySelector value. + /// The equalityComparer value. + /// The resulting observable sequence. public static IObservable> TransformMany(this IObservable> source, Func> manySelector, IEqualityComparer? equalityComparer = null) where TDestination : notnull where TSource : notnull => new TransformMany(source, manySelector, equalityComparer).Run(); - /// + /// This overload follows the same core behavior as the related overload. /// - /// Flattens each source item into children from a . The collection is observed for subsequent changes. + /// Flattens each source item into children from a ReadOnlyObservableCollection<T>. The collection is observed for subsequent changes. /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The source value. + /// The manySelector value. + /// The equalityComparer value. + /// The resulting observable sequence. public static IObservable> TransformMany(this IObservable> source, Func> manySelector, IEqualityComparer? equalityComparer = null) where TDestination : notnull where TSource : notnull => new TransformMany(source, manySelector, equalityComparer).Run(); - /// + /// This overload follows the same core behavior as the related overload. /// - /// Flattens each source item into children from an . The inner list is observed for subsequent changes. + /// Flattens each source item into children from an IObservableList<T>. The inner list is observed for subsequent changes. /// + /// The type of the TDestination value. + /// The type of the TSource value. + /// The source value. + /// The manySelector value. + /// The equalityComparer value. + /// The resulting observable sequence. public static IObservable> TransformMany(this IObservable> source, Func> manySelector, IEqualityComparer? equalityComparer = null) where TDestination : notnull where TSource : notnull => new TransformMany(source, manySelector, equalityComparer).Run(); diff --git a/src/DynamicData/List/ObservableListEx.Virtualise.cs b/src/DynamicData/List/ObservableListEx.Virtualise.cs index a2d2ec09d..69f00ef28 100644 --- a/src/DynamicData/List/ObservableListEx.Virtualise.cs +++ b/src/DynamicData/List/ObservableListEx.Virtualise.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -28,24 +26,23 @@ public static partial class ObservableListEx /// Only items within the window are included downstream. /// /// The type of the item. - /// The source to virtualize. + /// The source IObservable<IChangeSet<T>> to virtualize. /// An observable of specifying the start index and size of the window. - /// An stream containing only items within the current virtual window. + /// An IVirtualChangeSet<T> stream containing only items within the current virtual window. /// or is . /// /// - /// Like but uses absolute start index and size instead of page number and page size. + /// Like Page<T>(IObservable<IChangeSet<T>>, IObservable<IPageRequest>) but uses absolute start index and size instead of page number and page size. /// Internally maintains the full source list and recalculates the window on each change or request. /// /// - /// - /// + /// Page<T>(IObservable<IChangeSet<T>>, IObservable<IPageRequest>) + /// Top<T>(IObservable<IChangeSet<T>>, int) public static IObservable> Virtualise(this IObservable> source, IObservable requests) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - - requests.ThrowArgumentNullExceptionIfNull(nameof(requests)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(requests); return new Virtualiser(source, requests).Run(); } diff --git a/src/DynamicData/List/ObservableListEx.WhenAnyPropertyChanged.cs b/src/DynamicData/List/ObservableListEx.WhenAnyPropertyChanged.cs index a6529e3f3..95dd20ac8 100644 --- a/src/DynamicData/List/ObservableListEx.WhenAnyPropertyChanged.cs +++ b/src/DynamicData/List/ObservableListEx.WhenAnyPropertyChanged.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; +#if REACTIVE_SHIM +using DynamicData.Reactive.Binding; +#else using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -26,24 +24,24 @@ public static partial class ObservableListEx /// /// Watches all items in the source list and emits the item when any of its properties change. /// Requires to implement . - /// This is NOT a changeset operator: it returns a flat . + /// This is NOT a changeset operator: it returns a flat IObservable<T>. /// /// The type of the object. Must implement . - /// The source to observe property changes on items in. + /// The source IObservable<IChangeSet<TObject>> to observe property changes on items in. /// An optional list of property names to monitor. If empty, all property changes are observed. /// An observable emitting the item whenever any monitored property changes. /// is . /// - /// Implemented via . Subscriptions are managed per item: created on add, disposed on remove. + /// Implemented via MergeMany<T, TDestination>(IObservable<IChangeSet<T>>, Func<T, IObservable<TDestination>>). Subscriptions are managed per item: created on add, disposed on remove. /// - /// - /// - /// - /// + /// WhenPropertyChanged<TObject, TValue>(IObservable<IChangeSet<TObject>>, Expression<Func<TObject, TValue>>, bool) + /// WhenValueChanged<TObject, TValue>(IObservable<IChangeSet<TObject>>, Expression<Func<TObject, TValue>>, bool) + /// AutoRefresh<TObject>(IObservable<IChangeSet<TObject>>, TimeSpan?, TimeSpan?, IScheduler?) + /// ObservableCacheEx.WhenAnyPropertyChanged<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, string[]) public static IObservable WhenAnyPropertyChanged(this IObservable> source, params string[] propertiesToMonitor) where TObject : INotifyPropertyChanged { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return source.MergeMany(t => t.WhenAnyPropertyChanged(propertiesToMonitor)); } diff --git a/src/DynamicData/List/ObservableListEx.WhenPropertyChanged.cs b/src/DynamicData/List/ObservableListEx.WhenPropertyChanged.cs index 4a91bd035..e27d42ee4 100644 --- a/src/DynamicData/List/ObservableListEx.WhenPropertyChanged.cs +++ b/src/DynamicData/List/ObservableListEx.WhenPropertyChanged.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; +#if REACTIVE_SHIM +using DynamicData.Reactive.Binding; +#else using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -24,28 +22,28 @@ namespace DynamicData; public static partial class ObservableListEx { /// - /// Watches a specific property on all items in the source list and emits a (item + value pair) when it changes. + /// Watches a specific property on all items in the source list and emits a PropertyValue<TObject, TValue> (item + value pair) when it changes. /// Requires to implement . - /// This is NOT a changeset operator: it returns a flat . + /// This is NOT a changeset operator: it returns a flat IObservable<T>. /// /// The type of item. Must implement . /// The type of the property value. - /// The source to observe a specific property on items in. - /// An expression selecting the property to observe. + /// The source IObservable<IChangeSet<TObject>> to observe a specific property on items in. + /// An Expression<TDelegate> expression selecting the property to observe. /// When (default), the current value is emitted immediately upon subscribing to each item. - /// An observable emitting whenever the property changes on any tracked item. + /// An observable emitting PropertyValue<TObject, TValue> whenever the property changes on any tracked item. /// or is . /// - /// Implemented via . + /// Implemented via MergeMany<T, TDestination>(IObservable<IChangeSet<T>>, Func<T, IObservable<TDestination>>). /// - /// - /// - /// + /// WhenValueChanged<TObject, TValue>(IObservable<IChangeSet<TObject>>, Expression<Func<TObject, TValue>>, bool) + /// WhenAnyPropertyChanged<TObject>(IObservable<IChangeSet<TObject>>, string[]) + /// ObservableCacheEx.WhenPropertyChanged<TObject, TKey, TValue>(IObservable<IChangeSet<TObject, TKey>>, Expression<Func<TObject, TValue>>, bool) public static IObservable> WhenPropertyChanged(this IObservable> source, Expression> propertyAccessor, bool notifyOnInitialValue = true) where TObject : INotifyPropertyChanged { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - propertyAccessor.ThrowArgumentNullExceptionIfNull(nameof(propertyAccessor)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(propertyAccessor); var factory = propertyAccessor.GetFactory(); return source.MergeMany(t => factory(t, notifyOnInitialValue)); diff --git a/src/DynamicData/List/ObservableListEx.WhenValueChanged.cs b/src/DynamicData/List/ObservableListEx.WhenValueChanged.cs index 0328218fc..f11aafdca 100644 --- a/src/DynamicData/List/ObservableListEx.WhenValueChanged.cs +++ b/src/DynamicData/List/ObservableListEx.WhenValueChanged.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; +#if REACTIVE_SHIM +using DynamicData.Reactive.Binding; +#else using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -26,23 +24,23 @@ public static partial class ObservableListEx /// /// Watches a specific property on all items and emits just the property value (without the sender) when it changes. /// Requires to implement . - /// This is NOT a changeset operator: it returns a flat . + /// This is NOT a changeset operator: it returns a flat IObservable<T>. /// /// The type of item. Must implement . /// The type of the property value. - /// The source to observe a specific property value on items in. - /// An expression selecting the property to observe. + /// The source IObservable<IChangeSet<TObject>> to observe a specific property value on items in. + /// An Expression<TDelegate> expression selecting the property to observe. /// When (default), the current value is emitted immediately upon subscribing to each item. /// An observable emitting the property value whenever it changes on any tracked item. /// or is . - /// - /// - /// + /// WhenPropertyChanged<TObject, TValue>(IObservable<IChangeSet<TObject>>, Expression<Func<TObject, TValue>>, bool) + /// WhenAnyPropertyChanged<TObject>(IObservable<IChangeSet<TObject>>, string[]) + /// ObservableCacheEx.WhenValueChanged<TObject, TKey, TValue>(IObservable<IChangeSet<TObject, TKey>>, Expression<Func<TObject, TValue>>, bool) public static IObservable WhenValueChanged(this IObservable> source, Expression> propertyAccessor, bool notifyOnInitialValue = true) where TObject : INotifyPropertyChanged { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - propertyAccessor.ThrowArgumentNullExceptionIfNull(nameof(propertyAccessor)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(propertyAccessor); var factory = propertyAccessor.GetFactory(); return source.MergeMany(t => factory(t, notifyOnInitialValue).Select(pv => pv.Value)); diff --git a/src/DynamicData/List/ObservableListEx.WhereReasonsAre.cs b/src/DynamicData/List/ObservableListEx.WhereReasonsAre.cs index c2e61653d..8c0a873ce 100644 --- a/src/DynamicData/List/ObservableListEx.WhereReasonsAre.cs +++ b/src/DynamicData/List/ObservableListEx.WhereReasonsAre.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -28,22 +19,23 @@ public static partial class ObservableListEx /// Index information is stripped from the output because removing some changes invalidates the original index positions. /// /// The type of the item. - /// The source to filter by change reason. + /// The source IObservable<IChangeSet<T>> to filter by change reason. /// The change reasons to include. Must specify at least one. /// A list changeset stream containing only changes with the specified reasons. /// is . /// is empty. /// - /// Filters individual changes within each changeset. If filtering removes all changes from a changeset, the empty changeset is suppressed via . + /// Filters individual changes within each changeset. If filtering removes all changes from a changeset, the empty changeset is suppressed via NotEmpty<T>(IObservable<IChangeSet<T>>). /// Worth noting: Filtering out Remove changes can cause downstream operators to accumulate items indefinitely (memory leak). Index information is stripped because removing some changes invalidates the original index positions. /// - /// - /// - /// + /// WhereReasonsAreNot<T>(IObservable<IChangeSet<T>>, ListChangeReason[]) + /// SuppressRefresh<T>(IObservable<IChangeSet<T>>) + /// ChangeSetEx.YieldWithoutIndex<T>(IEnumerable<Change<T>>) public static IObservable> WhereReasonsAre(this IObservable> source, params ListChangeReason[] reasons) where T : notnull { - reasons.ThrowArgumentNullExceptionIfNull(nameof(reasons)); + ArgumentExceptionHelper.ThrowIfNull(reasons); + ArgumentExceptionHelper.ThrowIfNull(source); if (reasons.Length == 0) { diff --git a/src/DynamicData/List/ObservableListEx.WhereReasonsAreNot.cs b/src/DynamicData/List/ObservableListEx.WhereReasonsAreNot.cs index aabb2b3c2..b490a271c 100644 --- a/src/DynamicData/List/ObservableListEx.WhereReasonsAreNot.cs +++ b/src/DynamicData/List/ObservableListEx.WhereReasonsAreNot.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -29,7 +20,7 @@ public static partial class ObservableListEx /// The exception is when only is excluded, since removing Refresh does not affect index calculations. /// /// The type of the item. - /// The source to filter by excluding change reasons. + /// The source IObservable<IChangeSet<T>> to filter by excluding change reasons. /// The change reasons to exclude. Must specify at least one. /// A list changeset stream with the specified change reasons removed. /// is . @@ -40,13 +31,14 @@ public static partial class ObservableListEx /// indices are preserved, since removing Refresh does not affect index calculations. /// /// - /// - /// - /// + /// WhereReasonsAre<T>(IObservable<IChangeSet<T>>, ListChangeReason[]) + /// SuppressRefresh<T>(IObservable<IChangeSet<T>>) + /// ChangeSetEx.YieldWithoutIndex<T>(IEnumerable<Change<T>>) public static IObservable> WhereReasonsAreNot(this IObservable> source, params ListChangeReason[] reasons) where T : notnull { - reasons.ThrowArgumentNullExceptionIfNull(nameof(reasons)); + ArgumentExceptionHelper.ThrowIfNull(reasons); + ArgumentExceptionHelper.ThrowIfNull(source); if (reasons.Length == 0) { diff --git a/src/DynamicData/List/ObservableListEx.Xor.cs b/src/DynamicData/List/ObservableListEx.Xor.cs index b63b5c6d0..462206421 100644 --- a/src/DynamicData/List/ObservableListEx.Xor.cs +++ b/src/DynamicData/List/ObservableListEx.Xor.cs @@ -1,22 +1,20 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. @@ -28,8 +26,8 @@ public static partial class ObservableListEx /// Items present in exactly one source are included in the result. /// /// The type of the item. - /// The primary source to exclusively combine. - /// The other changeset streams to combine with. + /// The primary source IObservable<IChangeSet<T>> to exclusively combine. + /// The other IObservable<IChangeSet<T>> changeset streams to combine with. /// A list changeset stream containing items that exist in exactly one source. /// is . /// @@ -47,43 +45,55 @@ public static partial class ObservableListEx /// MovedIgnored. /// /// - /// - /// - /// - /// + /// And<T>(IObservable<IChangeSet<T>>, IObservable<IChangeSet<T>>[]) + /// Or<T>(IObservable<IChangeSet<T>>, IObservable<IChangeSet<T>>[]) + /// Except<T>(IObservable<IChangeSet<T>>, IObservable<IChangeSet<T>>[]) + /// ObservableCacheEx.Xor<TObject, TKey>(IObservable<IChangeSet<TObject, TKey>>, IObservable<IChangeSet<TObject, TKey>>[]) public static IObservable> Xor(this IObservable> source, params IObservable>[] others) where T : notnull { - others.ThrowArgumentNullExceptionIfNull(nameof(others)); + ArgumentExceptionHelper.ThrowIfNull(others); return source.Combine(CombineOperator.Xor, others); } - /// + /// This overload follows the same core behavior as the related overload. /// /// Applies a logical XOR between a pre-built collection of list changeset sources. /// + /// The type of the T value. + /// The sources value. + /// The resulting observable sequence. public static IObservable> Xor(this ICollection>> sources) where T : notnull => sources.Combine(CombineOperator.Xor); - /// + /// This overload follows the same core behavior as the related overload. /// - /// Dynamic XOR: sources can be added or removed from the at runtime. + /// Dynamic XOR: sources can be added or removed from the IObservableList<T> at runtime. /// + /// The type of the T value. + /// The sources value. + /// The resulting observable sequence. public static IObservable> Xor(this IObservableList>> sources) where T : notnull => sources.Combine(CombineOperator.Xor); - /// + /// This overload follows the same core behavior as the related overload. /// - /// Dynamic XOR accepting of . Each inner list's Connect() is used as a source. + /// Dynamic XOR accepting IObservableList<T> of IObservableList<T>. Each inner list's Connect() is used as a source. /// + /// The type of the T value. + /// The sources value. + /// The resulting observable sequence. public static IObservable> Xor(this IObservableList> sources) where T : notnull => sources.Combine(CombineOperator.Xor); - /// + /// This overload follows the same core behavior as the related overload. /// - /// Dynamic XOR accepting of . Each inner list's Connect() is used as a source. + /// Dynamic XOR accepting IObservableList<T> of ISourceList<T>. Each inner list's Connect() is used as a source. /// + /// The type of the T value. + /// The sources value. + /// The resulting observable sequence. public static IObservable> Xor(this IObservableList> sources) where T : notnull => sources.Combine(CombineOperator.Xor); } diff --git a/src/DynamicData/List/ObservableListEx.cs b/src/DynamicData/List/ObservableListEx.cs index ffe07170a..730c4dd8f 100644 --- a/src/DynamicData/List/ObservableListEx.cs +++ b/src/DynamicData/List/ObservableListEx.cs @@ -1,22 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // 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.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using DynamicData.Binding; -using DynamicData.Cache.Internal; -using DynamicData.List.Internal; -using DynamicData.List.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Extensions for ObservableList. diff --git a/src/DynamicData/List/PageChangeSet.cs b/src/DynamicData/List/PageChangeSet.cs index c746ca0e3..5ee4e93a3 100644 --- a/src/DynamicData/List/PageChangeSet.cs +++ b/src/DynamicData/List/PageChangeSet.cs @@ -1,42 +1,93 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Collections; +using DynamicData.Reactive.Operators; +#else using DynamicData.Operators; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif +/// +/// Provides members for the PageChangeSet class. +/// +/// The type of the T value. +/// The virtualChangeSet value. +/// The response value. internal sealed class PageChangeSet(IChangeSet virtualChangeSet, IPageResponse response) : IPageChangeSet where T : notnull { + /// + /// The _virtualChangeSet field. + /// private readonly IChangeSet _virtualChangeSet = virtualChangeSet ?? throw new ArgumentNullException(nameof(virtualChangeSet)); + /// + /// Gets the Count value. + /// public int Count => _virtualChangeSet.Count; + /// + /// Gets the Refreshes value. + /// public int Refreshes => _virtualChangeSet.Refreshes; + /// + /// Gets the Response value. + /// public IPageResponse Response { get; } = response ?? throw new ArgumentNullException(nameof(response)); + /// + /// Gets the Adds value. + /// int IChangeSet.Adds => _virtualChangeSet.Adds; + /// + /// Gets or sets the Capacity value. + /// int IChangeSet.Capacity { get => _virtualChangeSet.Capacity; set => _virtualChangeSet.Capacity = value; } + /// + /// Gets the Moves value. + /// int IChangeSet.Moves => _virtualChangeSet.Moves; + /// + /// Gets the Removes value. + /// int IChangeSet.Removes => _virtualChangeSet.Removes; + /// + /// Gets the Replaced value. + /// int IChangeSet.Replaced => _virtualChangeSet.Replaced; + /// + /// Gets the TotalChanges value. + /// int IChangeSet.TotalChanges => _virtualChangeSet.TotalChanges; + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. public IEnumerator> GetEnumerator() => _virtualChangeSet.GetEnumerator(); + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/DynamicData/List/RangeChange.cs b/src/DynamicData/List/RangeChange.cs index 4abef195e..a9c25491e 100644 --- a/src/DynamicData/List/RangeChange.cs +++ b/src/DynamicData/List/RangeChange.cs @@ -2,10 +2,12 @@ // 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.Collections; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Multiple change container. @@ -13,6 +15,9 @@ namespace DynamicData; /// The type of the item. public sealed class RangeChange : IEnumerable { + /// + /// The _items field. + /// private readonly List _items; /// @@ -60,6 +65,7 @@ private RangeChange() public void Add(T item) => _items.Add(item); /// + /// The result of the operation. public IEnumerator GetEnumerator() => _items.GetEnumerator(); /// @@ -84,5 +90,6 @@ private RangeChange() public override string ToString() => $"Range<{typeof(T).Name}>. Count={Count}"; /// + /// The result of the operation. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/DynamicData/List/SortException.cs b/src/DynamicData/List/SortException.cs index 42da97c8a..bd2070abe 100644 --- a/src/DynamicData/List/SortException.cs +++ b/src/DynamicData/List/SortException.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Thrown when an exception occurs within the sort operators. diff --git a/src/DynamicData/List/SortOptions.cs b/src/DynamicData/List/SortOptions.cs index 14beb63d7..25c9fada2 100644 --- a/src/DynamicData/List/SortOptions.cs +++ b/src/DynamicData/List/SortOptions.cs @@ -1,8 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Options for sorting. diff --git a/src/DynamicData/List/SourceList.cs b/src/DynamicData/List/SourceList.cs index 3db42bc11..dc903e2f3 100644 --- a/src/DynamicData/List/SourceList.cs +++ b/src/DynamicData/List/SourceList.cs @@ -3,14 +3,20 @@ // See the LICENSE file in the project root for full license information. using System.Diagnostics; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else using DynamicData.List.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// An editable observable list. @@ -20,41 +26,76 @@ namespace DynamicData; public sealed class SourceList : ISourceList where T : notnull { - private readonly ISubject> _changes = new Subject>(); + /// + /// The _changes field. + /// + private readonly Signal> _changes = new(); - private readonly Subject> _changesPreview = new(); + /// + /// The _changesPreview field. + /// + private readonly Signal> _changesPreview = new(); + /// + /// The _cleanUp field. + /// private readonly IDisposable _cleanUp; - private readonly Lazy> _countChanged = new(() => new Subject()); + /// + /// The _countChanged field. + /// + private readonly Lazy> _countChanged = new(() => new Signal()); -#if NET9_0_OR_GREATER + /// + /// The _locker field. + /// private readonly Lock _locker = new(); -#else - private readonly object _locker = new(); -#endif + /// + /// The _readerWriter field. + /// private readonly ReaderWriter _readerWriter = new(); + /// + /// The _notifications field. + /// + private readonly DeliveryQueue _notifications; + + /// + /// The _editLevel field. + /// private int _editLevel; + /// + /// The _currentVersion field. + /// + private long _currentVersion; + + /// + /// The _currentDeliveryVersion field. + /// + private long _currentDeliveryVersion; + + /// + /// The _isDisposed field. + /// + private bool _isDisposed; + /// /// Initializes a new instance of the class. /// /// The source. public SourceList(IObservable>? source = null) { + _notifications = new DeliveryQueue(_locker, new ListUpdateObserver(this)); + var loader = source is null ? Disposable.Empty : LoadFromSource(source); _cleanUp = Disposable.Create( () => { loader.Dispose(); - OnCompleted(); - if (_countChanged.IsValueCreated) - { - _countChanged.Value.OnCompleted(); - } + NotifyCompleted(); }); } @@ -66,37 +107,60 @@ public SourceList(IObservable>? source = null) Observable.Create( observer => { - lock (_locker) + using var readLock = _notifications.AcquireReadLock(); + + if (_isDisposed) { - var source = _countChanged.Value.StartWith(_readerWriter.Count).DistinctUntilChanged(); - return source.SubscribeSafe(observer); + observer.OnNext(_readerWriter.Count); + observer.OnCompleted(); + return Disposable.Empty; } + + var snapshotVersion = _currentVersion; + var countChanged = readLock.HasPending + ? _countChanged.Value.SkipWhile(_ => Volatile.Read(ref _currentDeliveryVersion) <= snapshotVersion) + : _countChanged.Value; + + var source = countChanged.StartWith(_readerWriter.Count).DistinctUntilChanged(); + return source.SubscribeSafe(observer); }); /// public IReadOnlyList Items => _readerWriter.Items; /// + /// The predicate value. + /// The result of the operation. public IObservable> Connect(Func? predicate = null) { var observable = Observable.Create>( observer => { - lock (_locker) + using var readLock = _notifications.AcquireReadLock(); + + if (_readerWriter.Items.Length > 0) { - if (_readerWriter.Items.Length > 0) - { - observer.OnNext( - new ChangeSet - { - new(ListChangeReason.AddRange, _readerWriter.Items, 0) - }); - } - - var source = _changes.Finally(observer.OnCompleted); - - return source.SubscribeSafe(observer); + observer.OnNext( + new ChangeSet + { + new(ListChangeReason.AddRange, _readerWriter.Items, 0) + }); } + + if (_isDisposed) + { + observer.OnCompleted(); + return Disposable.Empty; + } + + var snapshotVersion = _currentVersion; + var changes = readLock.HasPending + ? _changes.SkipWhile(_ => Volatile.Read(ref _currentDeliveryVersion) <= snapshotVersion) + : (IObservable>)_changes; + + var source = changes.Finally(observer.OnCompleted); + + return source.SubscribeSafe(observer); }); if (predicate is not null) @@ -110,43 +174,90 @@ public IObservable> Connect(Func? predicate = null) /// public void Dispose() { + using (var notifications = _notifications.AcquireLock()) + { + if (_isDisposed) + { + return; + } + + _isDisposed = true; + } + _cleanUp.Dispose(); + + if (_notifications.IsDeliveringOnCurrentThread) + { + return; + } + + SpinWait spinner = default; + while (!_notifications.IsTerminated) + { + spinner.SpinOnce(); + } + + _notifications.Dispose(); _changesPreview.Dispose(); + _changes.Dispose(); + if (_countChanged.IsValueCreated) + { + _countChanged.Value.Dispose(); + } } /// + /// The updateAction value. public void Edit(Action> updateAction) { - updateAction.ThrowArgumentNullExceptionIfNull(nameof(updateAction)); + ArgumentExceptionHelper.ThrowIfNull(updateAction); - lock (_locker) + using var notifications = _notifications.AcquireLock(); + + if (_isDisposed) { - IChangeSet? changes = null; + throw new ObjectDisposedException(nameof(SourceList)); + } - _editLevel++; + IChangeSet? changes = null; - if (_editLevel == 1) - { - changes = _changesPreview.HasObservers ? _readerWriter.WriteWithPreview(updateAction, InvokeNextPreview) : _readerWriter.Write(updateAction); - } - else - { - _readerWriter.WriteNested(updateAction); - } + _editLevel++; - _editLevel--; + if (_editLevel == 1) + { + changes = _changesPreview.HasObservers ? _readerWriter.WriteWithPreview(updateAction, InvokeNextPreview) : _readerWriter.Write(updateAction); + } + else + { + _readerWriter.WriteNested(updateAction); + } - if (changes is not null && _editLevel == 0) - { - InvokeNext(changes); - } + _editLevel--; + + if (changes is not null && changes.Count != 0 && _editLevel == 0) + { + notifications.EnqueueNext(new ListUpdate(changes, _readerWriter.Count, ++_currentVersion)); } } /// + /// The predicate value. + /// The result of the operation. public IObservable> Preview(Func? predicate = null) { - IObservable> observable = _changesPreview; + var observable = Observable.Create>( + observer => + { + using var readLock = _notifications.AcquireReadLock(); + + if (_isDisposed) + { + observer.OnCompleted(); + return Disposable.Empty; + } + + return _changesPreview.SubscribeSafe(observer); + }); if (predicate is not null) { @@ -156,54 +267,126 @@ public IObservable> Preview(Func? predicate = null) return observable; } - private void InvokeNext(IChangeSet changes) + /// + /// Executes the InvokeNextPreview operation. + /// + /// The changes value. + private void InvokeNextPreview(IChangeSet changes) { - if (changes.Count == 0) + if (changes.Count == 0 || _notifications.IsTerminated) { return; } lock (_locker) { - _changes.OnNext(changes); - - if (_countChanged.IsValueCreated) - { - _countChanged.Value.OnNext(_readerWriter.Count); - } + _changesPreview.OnNext(changes); } } - private void InvokeNextPreview(IChangeSet changes) + /// + /// Executes the LoadFromSource operation. + /// + /// The source value. + /// The result of the operation. + private IDisposable LoadFromSource(IObservable> source) => + source.Subscribe( + changes => + { + using var notifications = _notifications.AcquireLock(); + + if (_isDisposed) + { + return; + } + + var capturedChanges = _readerWriter.Write(changes); + if (capturedChanges.Count != 0) + { + notifications.EnqueueNext(new ListUpdate(capturedChanges, _readerWriter.Count, ++_currentVersion)); + } + }, + NotifyError, + NotifyCompleted); + + /// + /// Executes the NotifyCompleted operation. + /// + private void NotifyCompleted() { - if (changes.Count == 0) - { - return; - } + using var notifications = _notifications.AcquireLock(); + notifications.EnqueueCompleted(); + } - lock (_locker) - { - _changesPreview.OnNext(changes); - } + /// + /// Executes the NotifyError operation. + /// + /// The exception value. + private void NotifyError(Exception exception) + { + using var notifications = _notifications.AcquireLock(); + notifications.EnqueueError(exception); } - private IDisposable LoadFromSource(IObservable> source) => source.Synchronize(_locker).Finally(OnCompleted).Select(_readerWriter.Write).Subscribe(InvokeNext, OnError, OnCompleted); + /// + /// The notification payload for list delivery. Null Changes = count-only notification. + /// + /// The Changes value. + /// The Count value. + /// The Version value. + private readonly record struct ListUpdate(IChangeSet? Changes, int Count, long Version = 0); - private void OnCompleted() + /// + /// Observer that dispatches items to the list's downstream subjects. + /// + /// The source list value. + private sealed class ListUpdateObserver(SourceList sourceList) : IObserver { - lock (_locker) + /// + /// Executes the OnNext operation. + /// + /// The value value. + public void OnNext(ListUpdate value) { - _changesPreview.OnCompleted(); - _changes.OnCompleted(); + if (value.Changes is not null) + { + Volatile.Write(ref sourceList._currentDeliveryVersion, value.Version); + sourceList._changes.OnNext(value.Changes); + } + + if (sourceList._countChanged.IsValueCreated) + { + sourceList._countChanged.Value.OnNext(value.Count); + } } - } - private void OnError(Exception exception) - { - lock (_locker) + /// + /// Executes the OnError operation. + /// + /// The error value. + public void OnError(Exception error) { - _changesPreview.OnError(exception); - _changes.OnError(exception); + sourceList._changesPreview.OnError(error); + sourceList._changes.OnError(error); + + if (sourceList._countChanged.IsValueCreated) + { + sourceList._countChanged.Value.OnError(error); + } + } + + /// + /// Executes the OnCompleted operation. + /// + public void OnCompleted() + { + sourceList._changesPreview.OnCompleted(); + sourceList._changes.OnCompleted(); + + if (sourceList._countChanged.IsValueCreated) + { + sourceList._countChanged.Value.OnCompleted(); + } } } } diff --git a/src/DynamicData/List/SourceListEditConvenienceEx.cs b/src/DynamicData/List/SourceListEditConvenienceEx.cs index 7c08529f8..a3ce89d45 100644 --- a/src/DynamicData/List/SourceListEditConvenienceEx.cs +++ b/src/DynamicData/List/SourceListEditConvenienceEx.cs @@ -1,11 +1,20 @@ // Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +using DynamicData.Reactive.List.Internal; +#else using DynamicData.List.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Convenience methods for a source list. @@ -21,7 +30,7 @@ public static class SourceListEditConvenienceEx public static void Add(this ISourceList source, T item) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(list => list.Add(item)); } @@ -35,7 +44,8 @@ public static void Add(this ISourceList source, T item) public static void AddRange(this ISourceList source, IEnumerable items) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(items); source.Edit(list => list.AddRange(items)); } @@ -48,7 +58,7 @@ public static void AddRange(this ISourceList source, IEnumerable items) public static void Clear(this ISourceList source) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(list => list.Clear()); } @@ -64,8 +74,8 @@ public static void Clear(this ISourceList source) public static void EditDiff(this ISourceList source, IEnumerable allItems, IEqualityComparer? equalityComparer = null) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - allItems.ThrowArgumentNullExceptionIfNull(nameof(allItems)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(allItems); var editDiff = new EditDiff(source, equalityComparer); editDiff.Edit(allItems); @@ -81,13 +91,13 @@ public static void EditDiff(this ISourceList source, IEnumerable allIte public static void Insert(this ISourceList source, int index, T item) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(list => list.Insert(index, item)); } /// - /// Inserts the elements of a collection into the at the specified index. + /// Inserts the elements of a collection into the IExtendedList<T> at the specified index. /// /// The item type. /// The source. @@ -96,7 +106,7 @@ public static void Insert(this ISourceList source, int index, T item) public static void InsertRange(this ISourceList source, IEnumerable items, int index) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(list => list.AddRange(items, index)); } @@ -111,7 +121,7 @@ public static void InsertRange(this ISourceList source, IEnumerable ite public static void Move(this ISourceList source, int original, int destination) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(list => list.Move(original, destination)); } @@ -127,7 +137,7 @@ public static bool Remove(this ISourceList source, T item) where T : notnull { var removed = false; - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(list => removed = list.Remove(item)); return removed; @@ -142,7 +152,7 @@ public static bool Remove(this ISourceList source, T item) public static void RemoveAt(this ISourceList source, int index) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(list => list.RemoveAt(index)); } @@ -156,24 +166,24 @@ public static void RemoveAt(this ISourceList source, int index) public static void RemoveMany(this ISourceList source, IEnumerable itemsToRemove) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(list => list.RemoveMany(itemsToRemove)); } /// - /// Removes a range of elements from the . + /// Removes a range of elements from the ISourceList<T>. /// /// The item type. /// The source. /// The zero-based starting index of the range of elements to remove. /// The number of elements to remove. /// is less than 0.-or- is less than 0. - /// and do not denote a valid range of elements in the . + /// and do not denote a valid range of elements in the List<T>. public static void RemoveRange(this ISourceList source, int index, int count) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(list => list.RemoveRange(index, count)); } @@ -188,7 +198,7 @@ public static void RemoveRange(this ISourceList source, int index, int cou public static void Replace(this ISourceList source, T original, T destination) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(list => list.Replace(original, destination)); } @@ -203,7 +213,7 @@ public static void Replace(this ISourceList source, T original, T destinat public static void ReplaceAt(this ISourceList source, int index, T item) where T : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); source.Edit(list => list[index] = item); } diff --git a/src/DynamicData/List/SourceListEx.cs b/src/DynamicData/List/SourceListEx.cs index 78ca4d971..c38b22982 100644 --- a/src/DynamicData/List/SourceListEx.cs +++ b/src/DynamicData/List/SourceListEx.cs @@ -1,9 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Source list extensions. @@ -23,8 +27,8 @@ public static IObservable> Cast( where TSource : notnull where TDestination : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); - conversionFactory.ThrowArgumentNullExceptionIfNull(nameof(conversionFactory)); + ArgumentExceptionHelper.ThrowIfNull(source); + ArgumentExceptionHelper.ThrowIfNull(conversionFactory); return source.Connect().Cast(conversionFactory); } diff --git a/src/DynamicData/List/Tests/ChangeSetAggregator.cs b/src/DynamicData/List/Tests/ChangeSetAggregator.cs index afac4458b..2180fdfd3 100644 --- a/src/DynamicData/List/Tests/ChangeSetAggregator.cs +++ b/src/DynamicData/List/Tests/ChangeSetAggregator.cs @@ -2,11 +2,12 @@ // 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.Reactive.Disposables; -using System.Reactive.Linq; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive.Tests; +#else namespace DynamicData.Tests; +#endif /// /// Aggregates all events and statistics for a change set to help assertions when testing. @@ -15,8 +16,14 @@ namespace DynamicData.Tests; public class ChangeSetAggregator : IDisposable where TObject : notnull { + /// + /// The _disposer field. + /// private readonly IDisposable _disposer; + /// + /// The _isDisposed field. + /// private bool _isDisposed; /// @@ -25,6 +32,8 @@ public class ChangeSetAggregator : IDisposable /// The source. public ChangeSetAggregator(IObservable> source) { + ArgumentExceptionHelper.ThrowIfNull(source); + var published = source.Publish(); Data = published.AsObservableList(); diff --git a/src/DynamicData/List/Tests/ListTextEx.cs b/src/DynamicData/List/Tests/ListTextEx.cs index 7913113bf..cb771ee5d 100644 --- a/src/DynamicData/List/Tests/ListTextEx.cs +++ b/src/DynamicData/List/Tests/ListTextEx.cs @@ -1,9 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive.Tests; +#else namespace DynamicData.Tests; +#endif /// /// Test extensions. diff --git a/src/DynamicData/List/UnspecifiedIndexException.cs b/src/DynamicData/List/UnspecifiedIndexException.cs index 643fa9f62..21fbab3ef 100644 --- a/src/DynamicData/List/UnspecifiedIndexException.cs +++ b/src/DynamicData/List/UnspecifiedIndexException.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for full license information. // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Thrown when an index is expected but not specified. diff --git a/src/DynamicData/List/VirtualChangeSet.cs b/src/DynamicData/List/VirtualChangeSet.cs index 086490a36..bedaab07f 100644 --- a/src/DynamicData/List/VirtualChangeSet.cs +++ b/src/DynamicData/List/VirtualChangeSet.cs @@ -2,39 +2,85 @@ // 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.Collections; - // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif +/// +/// Provides members for the VirtualChangeSet class. +/// +/// The type of the T value. +/// The virtualChangeSet value. +/// The response value. internal sealed class VirtualChangeSet(IChangeSet virtualChangeSet, IVirtualResponse response) : IVirtualChangeSet where T : notnull { + /// + /// The _virtualChangeSet field. + /// private readonly IChangeSet _virtualChangeSet = virtualChangeSet ?? throw new ArgumentNullException(nameof(virtualChangeSet)); + /// + /// Gets the Refreshes value. + /// public int Refreshes => _virtualChangeSet.Refreshes; + /// + /// Gets the Response value. + /// public IVirtualResponse Response { get; } = response ?? throw new ArgumentNullException(nameof(response)); + /// + /// Gets the Adds value. + /// int IChangeSet.Adds => _virtualChangeSet.Adds; + /// + /// Gets or sets the Capacity value. + /// int IChangeSet.Capacity { get => _virtualChangeSet.Capacity; set => _virtualChangeSet.Capacity = value; } + /// + /// Gets the Count value. + /// int IChangeSet.Count => _virtualChangeSet.Count; + /// + /// Gets the Moves value. + /// int IChangeSet.Moves => _virtualChangeSet.Moves; + /// + /// Gets the Removes value. + /// int IChangeSet.Removes => _virtualChangeSet.Removes; + /// + /// Gets the Replaced value. + /// int IChangeSet.Replaced => _virtualChangeSet.Replaced; + /// + /// Gets the TotalChanges value. + /// int IChangeSet.TotalChanges => _virtualChangeSet.TotalChanges; + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. public IEnumerator> GetEnumerator() => _virtualChangeSet.GetEnumerator(); + /// + /// Executes the GetEnumerator operation. + /// + /// The result of the operation. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/DynamicData/ObservableChangeSet.cs b/src/DynamicData/ObservableChangeSet.cs index e9106b3e3..9649ca2ea 100644 --- a/src/DynamicData/ObservableChangeSet.cs +++ b/src/DynamicData/ObservableChangeSet.cs @@ -1,11 +1,13 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM -using System.Reactive.Disposables; -using System.Reactive.Linq; +namespace DynamicData.Reactive; +#else namespace DynamicData; +#endif /// /// Creation methods for observable change sets. @@ -24,8 +26,8 @@ public static IObservable> Create(Func< where TObject : notnull where TKey : notnull { - subscribe.ThrowArgumentNullExceptionIfNull(nameof(subscribe)); - keySelector.ThrowArgumentNullExceptionIfNull(nameof(keySelector)); + ArgumentExceptionHelper.ThrowIfNull(subscribe); + ArgumentExceptionHelper.ThrowIfNull(keySelector); return Create( cache => @@ -48,14 +50,15 @@ public static IObservable> Create(Func< where TObject : notnull where TKey : notnull { - subscribe.ThrowArgumentNullExceptionIfNull(nameof(subscribe)); - keySelector.ThrowArgumentNullExceptionIfNull(nameof(keySelector)); + ArgumentExceptionHelper.ThrowIfNull(subscribe); + ArgumentExceptionHelper.ThrowIfNull(keySelector); return Observable.Create>( observer => { var cache = new SourceCache(keySelector); var disposable = new SingleAssignmentDisposable(); + var responder = cache.Connect().SubscribeSafe(observer); try { @@ -66,7 +69,7 @@ public static IObservable> Create(Func< observer.OnError(e); } - return new CompositeDisposable(disposable, Disposable.Create(observer.OnCompleted), cache.Connect().SubscribeSafe(observer), cache); + return new CompositeDisposable(disposable, Disposable.Create(observer.OnCompleted), responder, cache); }); } @@ -82,8 +85,8 @@ public static IObservable> Create(Func< where TObject : notnull where TKey : notnull { - subscribe.ThrowArgumentNullExceptionIfNull(nameof(subscribe)); - keySelector.ThrowArgumentNullExceptionIfNull(nameof(keySelector)); + ArgumentExceptionHelper.ThrowIfNull(subscribe); + ArgumentExceptionHelper.ThrowIfNull(keySelector); return Create(async (cache, _) => await subscribe(cache).ConfigureAwait(false), keySelector); } @@ -100,8 +103,8 @@ public static IObservable> Create(Func< where TObject : notnull where TKey : notnull { - subscribe.ThrowArgumentNullExceptionIfNull(nameof(subscribe)); - keySelector.ThrowArgumentNullExceptionIfNull(nameof(keySelector)); + ArgumentExceptionHelper.ThrowIfNull(subscribe); + ArgumentExceptionHelper.ThrowIfNull(keySelector); return Observable.Create>( async (observer, ct) => @@ -135,8 +138,8 @@ public static IObservable> Create(Func< where TObject : notnull where TKey : notnull { - subscribe.ThrowArgumentNullExceptionIfNull(nameof(subscribe)); - keySelector.ThrowArgumentNullExceptionIfNull(nameof(keySelector)); + ArgumentExceptionHelper.ThrowIfNull(subscribe); + ArgumentExceptionHelper.ThrowIfNull(keySelector); return Create((cache, _) => subscribe(cache), keySelector); } @@ -153,8 +156,8 @@ public static IObservable> Create(Func< where TObject : notnull where TKey : notnull { - subscribe.ThrowArgumentNullExceptionIfNull(nameof(subscribe)); - keySelector.ThrowArgumentNullExceptionIfNull(nameof(keySelector)); + ArgumentExceptionHelper.ThrowIfNull(subscribe); + ArgumentExceptionHelper.ThrowIfNull(keySelector); return Observable.Create>( async (observer, ct) => @@ -197,8 +200,8 @@ public static IObservable> Create(Func< where TObject : notnull where TKey : notnull { - subscribe.ThrowArgumentNullExceptionIfNull(nameof(subscribe)); - keySelector.ThrowArgumentNullExceptionIfNull(nameof(keySelector)); + ArgumentExceptionHelper.ThrowIfNull(subscribe); + ArgumentExceptionHelper.ThrowIfNull(keySelector); return Observable.Create>( async observer => @@ -231,8 +234,8 @@ public static IObservable> Create(Func< where TObject : notnull where TKey : notnull { - subscribe.ThrowArgumentNullExceptionIfNull(nameof(subscribe)); - keySelector.ThrowArgumentNullExceptionIfNull(nameof(keySelector)); + ArgumentExceptionHelper.ThrowIfNull(subscribe); + ArgumentExceptionHelper.ThrowIfNull(keySelector); return Observable.Create>( async (observer, ct) => @@ -262,7 +265,7 @@ public static IObservable> Create(Func< public static IObservable> Create(Func, Action> subscribe) where T : notnull { - subscribe.ThrowArgumentNullExceptionIfNull(nameof(subscribe)); + ArgumentExceptionHelper.ThrowIfNull(subscribe); return Create( list => @@ -281,13 +284,14 @@ public static IObservable> Create(Func, Action> public static IObservable> Create(Func, IDisposable> subscribe) where T : notnull { - subscribe.ThrowArgumentNullExceptionIfNull(nameof(subscribe)); + ArgumentExceptionHelper.ThrowIfNull(subscribe); return Observable.Create>( observer => { var list = new SourceList(); IDisposable? disposeAction = null; + var responder = list.Connect().SubscribeSafe(observer); try { @@ -299,7 +303,7 @@ public static IObservable> Create(Func, IDisposa } return new CompositeDisposable( - list.Connect().SubscribeSafe(observer), + responder, list, Disposable.Create( () => @@ -319,7 +323,7 @@ public static IObservable> Create(Func, IDisposa public static IObservable> Create(Func, Task> subscribe) where T : notnull { - subscribe.ThrowArgumentNullExceptionIfNull(nameof(subscribe)); + ArgumentExceptionHelper.ThrowIfNull(subscribe); return Create((list, _) => subscribe(list)); } @@ -333,7 +337,7 @@ public static IObservable> Create(Func, Task> Create(Func, CancellationToken, Task> subscribe) where T : notnull { - subscribe.ThrowArgumentNullExceptionIfNull(nameof(subscribe)); + ArgumentExceptionHelper.ThrowIfNull(subscribe); return Observable.Create>( async (observer, ct) => @@ -374,7 +378,7 @@ public static IObservable> Create(Func, Cancella public static IObservable> Create(Func, Task> subscribe) where T : notnull { - subscribe.ThrowArgumentNullExceptionIfNull(nameof(subscribe)); + ArgumentExceptionHelper.ThrowIfNull(subscribe); return Create(async (list, _) => await subscribe(list).ConfigureAwait(false)); } @@ -388,7 +392,7 @@ public static IObservable> Create(Func, Task> Create(Func, CancellationToken, Task> subscribe) where T : notnull { - subscribe.ThrowArgumentNullExceptionIfNull(nameof(subscribe)); + ArgumentExceptionHelper.ThrowIfNull(subscribe); return Observable.Create>( async (observer, ct) => @@ -428,7 +432,7 @@ public static IObservable> Create(Func, Cancella public static IObservable> Create(Func, Task> subscribe) where T : notnull { - subscribe.ThrowArgumentNullExceptionIfNull(nameof(subscribe)); + ArgumentExceptionHelper.ThrowIfNull(subscribe); return Observable.Create>( async observer => @@ -458,7 +462,7 @@ public static IObservable> Create(Func, Task> su public static IObservable> Create(Func, CancellationToken, Task> subscribe) where T : notnull { - subscribe.ThrowArgumentNullExceptionIfNull(nameof(subscribe)); + ArgumentExceptionHelper.ThrowIfNull(subscribe); return Observable.Create>( async (observer, ct) => diff --git a/src/DynamicData/Platforms/net45/PFilter.cs b/src/DynamicData/Platforms/net45/PFilter.cs index f4aff797e..33ffe099c 100644 --- a/src/DynamicData/Platforms/net45/PFilter.cs +++ b/src/DynamicData/Platforms/net45/PFilter.cs @@ -3,17 +3,37 @@ // See the LICENSE file in the project root for full license information. #if P_LINQ -using System.Reactive.Linq; +#if REACTIVE_SHIM + +using DynamicData.Reactive.Cache.Internal; +#else using DynamicData.Cache.Internal; +#endif // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive.PLinq +#else namespace DynamicData.PLinq +#endif { - internal sealed class PFilter(IObservable> source, Func filter, ParallelisationOptions parallelisationOptions) +/// +/// Provides members for the PFilter class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. +/// The filter value. +/// The parallelisationOptions value. +internal sealed class PFilter(IObservable> source, Func filter, ParallelisationOptions parallelisationOptions) where TObject : notnull where TKey : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { @@ -21,10 +41,23 @@ public IObservable> Run() => Observable.Create filter, ParallelisationOptions parallelisationOptions) : AbstractFilter(new ChangeAwareCache(), filter) +/// +/// Provides members for the PLinqFilteredUpdater class. +/// +/// The filter value. +/// The parallelisationOptions value. +private sealed class PLinqFilteredUpdater(Func filter, ParallelisationOptions parallelisationOptions) : AbstractFilter(new ChangeAwareCache(), filter) { + /// + /// The _parallelisationOptions field. + /// private readonly ParallelisationOptions _parallelisationOptions = parallelisationOptions; + /// + /// Executes the GetChangesWithFilter operation. + /// + /// The updates value. + /// The result of the operation. protected override IEnumerable GetChangesWithFilter(ChangeSet updates) { if (updates.ShouldParallelise(_parallelisationOptions)) @@ -35,7 +68,13 @@ protected override IEnumerable GetChangesWithFilter(ChangeSet< return updates.Select(u => new UpdateWithFilter(Filter(u.Current), u)).ToArray(); } - protected override IEnumerable> Refresh(IEnumerable> items, Func, Optional>> factory) + /// + /// Executes the Refresh operation. + /// + /// The items value. + /// The factory value. + /// The result of the operation. + protected override IEnumerable> Refresh(IEnumerable> items, Func, ReactiveUI.Primitives.Optional>> factory) { var keyValuePairs = items as KeyValuePair[] ?? items.ToArray(); diff --git a/src/DynamicData/Platforms/net45/PSubscribeMany.cs b/src/DynamicData/Platforms/net45/PSubscribeMany.cs index 4c9e4be35..435c3330c 100644 --- a/src/DynamicData/Platforms/net45/PSubscribeMany.cs +++ b/src/DynamicData/Platforms/net45/PSubscribeMany.cs @@ -3,20 +3,40 @@ // See the LICENSE file in the project root for full license information. #if P_LINQ -using System.Reactive.Disposables; -using System.Reactive.Linq; // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive.PLinq +#else namespace DynamicData.PLinq +#endif { - internal sealed class PSubscribeMany(IObservable> source, Func subscriptionFactory, ParallelisationOptions parallelisationOptions) +/// +/// Provides members for the PSubscribeMany class. +/// +/// The type of the TObject value. +/// The type of the TKey value. +/// The source value. +/// The subscriptionFactory value. +/// The parallelisationOptions value. +internal sealed class PSubscribeMany(IObservable> source, Func subscriptionFactory, ParallelisationOptions parallelisationOptions) where TObject : notnull where TKey : notnull { + /// + /// The _source field. + /// private readonly IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); + /// + /// The _subscriptionFactory field. + /// private readonly Func _subscriptionFactory = subscriptionFactory ?? throw new ArgumentNullException(nameof(subscriptionFactory)); + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>( observer => { diff --git a/src/DynamicData/Platforms/net45/PTransform.cs b/src/DynamicData/Platforms/net45/PTransform.cs index a9871c335..3bcec44b6 100644 --- a/src/DynamicData/Platforms/net45/PTransform.cs +++ b/src/DynamicData/Platforms/net45/PTransform.cs @@ -3,20 +3,37 @@ // See the LICENSE file in the project root for full license information. #if P_LINQ -using System.Reactive.Linq; // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive.PLinq +#else namespace DynamicData.PLinq +#endif { - internal sealed class PTransform( +/// +/// Provides members for the PTransform class. +/// +/// The type of the TDestination value. +/// The type of the TSource value. +/// The type of the TKey value. +/// The source value. +/// The transformFactory value. +/// The parallelisationOptions value. +/// The exceptionCallback value. +internal sealed class PTransform( IObservable> source, - Func, TKey, TDestination> transformFactory, + Func, TKey, TDestination> transformFactory, ParallelisationOptions parallelisationOptions, Action>? exceptionCallback = null) where TDestination : notnull where TSource : notnull where TKey : notnull { + /// + /// Executes the Run operation. + /// + /// The result of the operation. public IObservable> Run() => Observable.Create>(observer => { @@ -25,7 +42,13 @@ public IObservable> Run() => return transformer.NotEmpty().SubscribeSafe(observer); }); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0305:Simplify collection initialization", Justification = "A collection initializer is not equivalent to a .ToArray() call for a ParallelQuery. This change actually introduces a race-condition exception.")] + /// + /// Executes the DoTransform operation. + /// + /// The cache value. + /// The changes value. + /// The result of the operation. + [SuppressMessage("Style", "IDE0305:Simplify collection initialization", Justification = "A collection initializer is not equivalent to a .ToArray() call for a ParallelQuery. This change actually introduces a race-condition exception.")] private ChangeSet DoTransform(ChangeAwareCache cache, IChangeSet changes) { var transformed = changes.ShouldParallelise(parallelisationOptions) @@ -35,6 +58,11 @@ private ChangeSet DoTransform(ChangeAwareCache + /// Executes the ToDestination operation. + /// + /// The change value. + /// The result of the operation. private TransformResult ToDestination(Change change) { try @@ -59,6 +87,12 @@ private TransformResult ToDestination(Change change) } } + /// + /// Executes the ProcessUpdates operation. + /// + /// The cache value. + /// The transformedItems value. + /// The result of the operation. private ChangeSet ProcessUpdates(ChangeAwareCache cache, IEnumerable transformedItems) { foreach (var result in transformedItems) @@ -92,8 +126,16 @@ private ChangeSet ProcessUpdates(ChangeAwareCache +/// Represents the TransformResult value. +/// +private readonly struct TransformResult { + /// + /// Initializes a new instance of the struct. + /// + /// The change value. + /// The destination value. public TransformResult(in Change change, TDestination destination) : this() { @@ -103,15 +145,24 @@ public TransformResult(in Change change, TDestination destination Key = change.Key; } + /// + /// Initializes a new instance of the struct. + /// + /// The change value. public TransformResult(in Change change) : this() { Change = change; - Destination = Optional.None; + Destination = ReactiveUI.Primitives.Optional.None; Success = true; Key = change.Key; } + /// + /// Initializes a new instance of the struct. + /// + /// The change value. + /// The error value. public TransformResult(in Change change, Exception error) : this() { @@ -121,14 +172,29 @@ public TransformResult(in Change change, Exception error) Key = change.Key; } + /// + /// Gets the Change value. + /// public Change Change { get; } + /// + /// Gets the Error value. + /// public Exception? Error { get; } + /// + /// Gets the Success value. + /// public bool Success { get; } - public Optional Destination { get; } + /// + /// Gets the Destination value. + /// + public ReactiveUI.Primitives.Optional Destination { get; } + /// + /// Gets the Key value. + /// public TKey Key { get; } } } diff --git a/src/DynamicData/Platforms/net45/ParallelEx.cs b/src/DynamicData/Platforms/net45/ParallelEx.cs index 3d29d2b96..defde0461 100644 --- a/src/DynamicData/Platforms/net45/ParallelEx.cs +++ b/src/DynamicData/Platforms/net45/ParallelEx.cs @@ -4,13 +4,25 @@ #if P_LINQ // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive.PLinq +#else namespace DynamicData.PLinq +#endif { - /// - /// Parallelisation extensions for DynamicData. - /// - internal static class ParallelEx +/// +/// Parallelisation extensions for DynamicData. +/// +internal static class ParallelEx { + /// + /// Executes the Parallelise operation. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The option value. + /// The result of the operation. internal static ParallelQuery> Parallelise(this IChangeSet source, ParallelisationOptions option) where TObject : notnull where TKey : notnull => option.Type switch @@ -20,6 +32,14 @@ internal static ParallelQuery> Parallelise( _ => throw new ArgumentException("Should not parallelise! Call ShouldParallelise() first"), }; + /// + /// Executes the Parallelise operation. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The option value. + /// The result of the operation. internal static ParallelQuery> Parallelise(this IEnumerable> source, ParallelisationOptions option) where TKey : notnull => option.Type switch { @@ -28,6 +48,13 @@ internal static ParallelQuery> Parallelise throw new ArgumentException("Should not parallelise! Call ShouldParallelise() first"), }; + /// + /// Executes the Parallelise operation. + /// + /// The type of the T value. + /// The source value. + /// The option value. + /// The result of the operation. internal static IEnumerable Parallelise(this IEnumerable source, ParallelisationOptions option) { switch (option.Type) @@ -59,10 +86,26 @@ internal static IEnumerable Parallelise(this IEnumerable source, Parall } } + /// + /// Executes the ShouldParallelise operation. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The option value. + /// The result of the operation. internal static bool ShouldParallelise(this IChangeSet source, ParallelisationOptions option) where TObject : notnull where TKey : notnull => (option.Type == ParallelType.Parallelise || option.Type == ParallelType.Ordered) && (option.Threshold >= 0 && source.Count >= option.Threshold); + /// + /// Executes the ShouldParallelise operation. + /// + /// The type of the TObject value. + /// The type of the TKey value. + /// The source value. + /// The option value. + /// The result of the operation. internal static bool ShouldParallelise(this IEnumerable> source, ParallelisationOptions option) where TKey : notnull => (option.Type == ParallelType.Parallelise || option.Type == ParallelType.Ordered) && (option.Threshold >= 0 && source.Skip(option.Threshold).Any()); } diff --git a/src/DynamicData/Platforms/net45/ParallelOperators.cs b/src/DynamicData/Platforms/net45/ParallelOperators.cs index b79624fca..2003b4d7c 100644 --- a/src/DynamicData/Platforms/net45/ParallelOperators.cs +++ b/src/DynamicData/Platforms/net45/ParallelOperators.cs @@ -5,12 +5,16 @@ #if P_LINQ // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive.PLinq +#else namespace DynamicData.PLinq +#endif { - /// - /// PLinq operators or Net4 and Net45 only. - /// - public static class ParallelOperators +/// +/// PLinq operators or Net4 and Net45 only. +/// +public static class ParallelOperators { /// /// Filters the stream using the specified predicate. @@ -26,7 +30,7 @@ public static IObservable> Filter(this where TObject : notnull where TKey : notnull { - source.ThrowArgumentNullExceptionIfNull(nameof(source)); + ArgumentExceptionHelper.ThrowIfNull(source); return new PFilter(source, filter, parallelisationOptions).Run(); } @@ -50,9 +54,9 @@ public static IObservable> SubscribeMany(source, (t, _) => subscriptionFactory(t), parallelisationOptions).Run(); } @@ -76,9 +80,9 @@ public static IObservable> SubscribeMany(source, subscriptionFactory, parallelisationOptions).Run(); } @@ -103,9 +107,9 @@ public static IObservable> Transform(source, (t, _, k) => transformFactory(t, k), parallelisationOptions).Run(); } @@ -154,10 +158,10 @@ public static IObservable> TransformSafe(source, (t, _, _) => transformFactory(t), parallelisationOptions, errorHandler).Run(); } diff --git a/src/DynamicData/Platforms/net45/ParallelType.cs b/src/DynamicData/Platforms/net45/ParallelType.cs index 1ef290a51..2f0e0da09 100644 --- a/src/DynamicData/Platforms/net45/ParallelType.cs +++ b/src/DynamicData/Platforms/net45/ParallelType.cs @@ -4,12 +4,16 @@ #if P_LINQ // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive.PLinq +#else namespace DynamicData.PLinq +#endif { - /// - /// The type of parallelisation. - /// - public enum ParallelType +/// +/// The type of parallelisation. +/// +public enum ParallelType { /// /// No parallelisation will take place. diff --git a/src/DynamicData/Platforms/net45/ParallelisationOptions.cs b/src/DynamicData/Platforms/net45/ParallelisationOptions.cs index 72011b438..6d6ed4f59 100644 --- a/src/DynamicData/Platforms/net45/ParallelisationOptions.cs +++ b/src/DynamicData/Platforms/net45/ParallelisationOptions.cs @@ -4,18 +4,22 @@ #if P_LINQ // ReSharper disable once CheckNamespace +#if REACTIVE_SHIM +namespace DynamicData.Reactive.PLinq +#else namespace DynamicData.PLinq +#endif { - /// - /// Options to specify parallelisation of stream operations. Only applicable for .Net4 and .Net45 builds. - /// - /// - /// Initializes a new instance of the class. - /// - /// The type of parallel operation. - /// The threshold before making the operation parallel. - /// The maximum degrees of parallelism. - public class ParallelisationOptions(ParallelType type = ParallelType.None, int threshold = 0, int maxDegreeOfParallelisation = 0) +/// +/// Options to specify parallelisation of stream operations. Only applicable for .Net4 and .Net45 builds. +/// +/// +/// Initializes a new instance of the class. +/// +/// The type of parallel operation. +/// The threshold before making the operation parallel. +/// The maximum degrees of parallelism. +public class ParallelisationOptions(ParallelType type = ParallelType.None, int threshold = 0, int maxDegreeOfParallelisation = 0) { /// /// The default parallelisation options. diff --git a/src/DynamicData/Polyfills/AllowNullAttribute.cs b/src/DynamicData/Polyfills/AllowNullAttribute.cs new file mode 100644 index 000000000..b14f9a67d --- /dev/null +++ b/src/DynamicData/Polyfills/AllowNullAttribute.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Roland Pheasant licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +// Polyfill implementation adapted from SimonCropp/Polyfill (https://github.com/SimonCropp/Polyfill). +#if !NETCOREAPP3_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER +namespace System.Diagnostics.CodeAnalysis; + +/// Specifies that is allowed as an input even if the corresponding type disallows it. +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property)] +internal sealed class AllowNullAttribute : Attribute; +#endif diff --git a/src/DynamicData/Polyfills/ArgumentExceptionHelper.cs b/src/DynamicData/Polyfills/ArgumentExceptionHelper.cs new file mode 100644 index 000000000..0136871e1 --- /dev/null +++ b/src/DynamicData/Polyfills/ArgumentExceptionHelper.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Roland Pheasant licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else + +namespace DynamicData; +#endif + +/// +/// Polyfill for ArgumentNullException.ThrowIfNull on target frameworks (net462-net481) that predate it. +/// On net8.0 and later this type is not compiled; consuming projects alias the ArgumentExceptionHelper +/// identifier directly to so the call sites bind to the BCL method. +/// +[ExcludeFromCodeCoverage] +internal static class ArgumentExceptionHelper +{ + /// Throws an if is . + /// The reference type argument to validate as non-null. + /// The name of the parameter with which corresponds. + public static void ThrowIfNull( + [NotNull] object? argument, + [CallerArgumentExpression(nameof(argument))] string? paramName = null) + { + if (argument is not null) + { + return; + } + + throw new ArgumentNullException(paramName); + } +} diff --git a/src/DynamicData/Polyfills/ArgumentOutOfRangeExceptionHelper.cs b/src/DynamicData/Polyfills/ArgumentOutOfRangeExceptionHelper.cs new file mode 100644 index 000000000..a97f3823f --- /dev/null +++ b/src/DynamicData/Polyfills/ArgumentOutOfRangeExceptionHelper.cs @@ -0,0 +1,83 @@ +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Roland Pheasant licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. +#if REACTIVE_SHIM + +namespace DynamicData.Reactive; +#else + +namespace DynamicData; +#endif + +/// Polyfill for modern guard helpers on target frameworks that predate them. +[ExcludeFromCodeCoverage] +internal static class ArgumentOutOfRangeExceptionHelper +{ + /// Throws when is negative. + /// The value to validate. + /// The parameter name. + public static void ThrowIfNegative(int value, [CallerArgumentExpression(nameof(value))] string? paramName = null) + { + if (value >= 0) + { + return; + } + + throw new ArgumentOutOfRangeException(paramName, value, null); + } + + /// Throws when is negative or zero. + /// The value to validate. + /// The parameter name. + public static void ThrowIfNegativeOrZero(int value, [CallerArgumentExpression(nameof(value))] string? paramName = null) + { + if (value > 0) + { + return; + } + + throw new ArgumentOutOfRangeException(paramName, value, null); + } + + /// Throws when is less than . + /// The value to validate. + /// The lower bound. + /// The parameter name. + public static void ThrowIfLessThan(int value, int other, [CallerArgumentExpression(nameof(value))] string? paramName = null) + { + if (value >= other) + { + return; + } + + throw new ArgumentOutOfRangeException(paramName, value, null); + } + + /// Throws when is less than . + /// The value to validate. + /// The lower bound. + /// The parameter name. + public static void ThrowIfLessThan(TimeSpan value, TimeSpan other, [CallerArgumentExpression(nameof(value))] string? paramName = null) + { + if (value >= other) + { + return; + } + + throw new ArgumentOutOfRangeException(paramName, value, null); + } + + /// Throws when is less than or equal to . + /// The value to validate. + /// The lower bound. + /// The parameter name. + public static void ThrowIfLessThanOrEqual(TimeSpan value, TimeSpan other, [CallerArgumentExpression(nameof(value))] string? paramName = null) + { + if (value > other) + { + return; + } + + throw new ArgumentOutOfRangeException(paramName, value, null); + } +} diff --git a/src/DynamicData/Polyfills/CallerArgumentExpressionAttribute.cs b/src/DynamicData/Polyfills/CallerArgumentExpressionAttribute.cs new file mode 100644 index 000000000..c89d0cb23 --- /dev/null +++ b/src/DynamicData/Polyfills/CallerArgumentExpressionAttribute.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Roland Pheasant licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +// Polyfill implementation adapted from SimonCropp/Polyfill (https://github.com/SimonCropp/Polyfill). +#if !NET5_0_OR_GREATER +using System.Diagnostics; + +namespace System.Runtime.CompilerServices; + +/// Indicates that a parameter captures the expression passed for another parameter as a string. +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage(AttributeTargets.Parameter)] +internal sealed class CallerArgumentExpressionAttribute : Attribute +{ + /// Initializes a new instance of the class. + /// The name of the parameter whose expression should be captured. + public CallerArgumentExpressionAttribute(string parameterName) => + ParameterName = parameterName; + + /// Gets the name of the parameter whose expression should be captured. + public string ParameterName { get; } +} +#endif diff --git a/src/DynamicData/Polyfills/CancellationTokenPolyfillExtensions.cs b/src/DynamicData/Polyfills/CancellationTokenPolyfillExtensions.cs new file mode 100644 index 000000000..a98322436 --- /dev/null +++ b/src/DynamicData/Polyfills/CancellationTokenPolyfillExtensions.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Roland Pheasant licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +// Polyfill implementation adapted from SimonCropp/Polyfill (https://github.com/SimonCropp/Polyfill). +#if !NETCOREAPP3_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER +namespace System.Threading; + +/// Polyfill extensions for on frameworks without UnsafeRegister. +internal static class CancellationTokenPolyfillExtensions +{ + /// Polyfill registration operations for a cancellation token. + extension(CancellationToken token) + { + /// Registers a delegate that is invoked when the token is cancelled, without capturing the execution context. + /// The delegate to invoke on cancellation. + /// The state passed to . + /// A registration that can be disposed to remove the callback. + public CancellationTokenRegistration UnsafeRegister(Action callback, object? state) => + token.Register(callback, state, useSynchronizationContext: false); + + /// Registers a delegate that is invoked with the triggering token when the token is cancelled, without capturing the execution context. + /// The delegate to invoke on cancellation, receiving the state and the triggering token. + /// The state passed to . + /// A registration that can be disposed to remove the callback. + public CancellationTokenRegistration UnsafeRegister(Action callback, object? state) => + token.Register( + static boxed => + { + var (inner, innerState, innerToken) = ((Action Callback, object? State, CancellationToken Token))boxed!; + inner(innerState, innerToken); + }, + (callback, state, token), + useSynchronizationContext: false); + } +} +#endif diff --git a/src/DynamicData/Polyfills/CancellationTokenSourcePolyfillExtensions.cs b/src/DynamicData/Polyfills/CancellationTokenSourcePolyfillExtensions.cs new file mode 100644 index 000000000..20974a5c6 --- /dev/null +++ b/src/DynamicData/Polyfills/CancellationTokenSourcePolyfillExtensions.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Roland Pheasant licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +// Polyfill implementation adapted from SimonCropp/Polyfill (https://github.com/SimonCropp/Polyfill). +#if !NET8_0_OR_GREATER +namespace System.Threading; + +/// Polyfill extensions for on frameworks without the .NET 8 async cancellation API. +internal static class CancellationTokenSourcePolyfillExtensions +{ + /// Polyfill operations for a cancellation token source. + extension(CancellationTokenSource source) + { + /// Communicates a request for cancellation, completing synchronously (no asynchronous callback draining on this framework). + /// A completed task representing the cancellation request. + public Task CancelAsync() + { + source.Cancel(); + return Task.CompletedTask; + } + } +} +#endif diff --git a/src/DynamicData/Polyfills/CompilerFeatureRequiredAttribute.cs b/src/DynamicData/Polyfills/CompilerFeatureRequiredAttribute.cs index cdcff56c9..ee0ce6737 100644 --- a/src/DynamicData/Polyfills/CompilerFeatureRequiredAttribute.cs +++ b/src/DynamicData/Polyfills/CompilerFeatureRequiredAttribute.cs @@ -1,23 +1,31 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +// Polyfill implementation adapted from SimonCropp/Polyfill (https://github.com/SimonCropp/Polyfill). #if !NET7_0_OR_GREATER +using System.Diagnostics; + namespace System.Runtime.CompilerServices; -// Allows use of the C#11 `required` keyword, internally within this library, when targeting frameworks older than .NET 7. +/// Indicates that compiler support for a particular feature is required for the location where it is applied. +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] -internal sealed class CompilerFeatureRequiredAttribute(string featureName) - : Attribute +internal sealed class CompilerFeatureRequiredAttribute : Attribute { - public const string RefStructs - = nameof(RefStructs); + /// The used for the ref structs C# feature. + public const string RefStructs = nameof(RefStructs); - public const string RequiredMembers - = nameof(RequiredMembers); + /// The used for the required members C# feature. + public const string RequiredMembers = nameof(RequiredMembers); - public string FeatureName { get; } = featureName; + /// Initializes a new instance of the class. + /// The name of the required compiler feature. + public CompilerFeatureRequiredAttribute(string featureName) => + FeatureName = featureName; - public bool IsOptional { get; init; } + /// Gets the name of the required compiler feature. + public string FeatureName { get; } } #endif diff --git a/src/DynamicData/Polyfills/DynamicallyAccessedMembersAttribute.cs b/src/DynamicData/Polyfills/DynamicallyAccessedMembersAttribute.cs index 7c591011b..75aa51ae0 100644 --- a/src/DynamicData/Polyfills/DynamicallyAccessedMembersAttribute.cs +++ b/src/DynamicData/Polyfills/DynamicallyAccessedMembersAttribute.cs @@ -1,39 +1,115 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. #if !NET5_0_OR_GREATER namespace System.Diagnostics.CodeAnalysis; +/// +/// Defines values for the DynamicallyAccessedMemberTypes enumeration. +/// [Flags] internal enum DynamicallyAccessedMemberTypes { + /// + /// The All value. + /// All = -1, + + /// + /// The None value. + /// None = 0, + + /// + /// The PublicParameterlessConstructor value. + /// PublicParameterlessConstructor = 1, + + /// + /// The PublicConstructors value. + /// PublicConstructors = 3, + + /// + /// The NonPublicConstructors value. + /// NonPublicConstructors = 4, + + /// + /// The PublicMethods value. + /// PublicMethods = 8, + + /// + /// The NonPublicMethods value. + /// NonPublicMethods = 16, + + /// + /// The PublicFields value. + /// PublicFields = 32, + + /// + /// The NonPublicFields value. + /// NonPublicFields = 64, + + /// + /// The PublicNestedTypes value. + /// PublicNestedTypes = 128, + + /// + /// The NonPublicNestedTypes value. + /// NonPublicNestedTypes = 256, + + /// + /// The PublicProperties value. + /// PublicProperties = 512, + + /// + /// The NonPublicProperties value. + /// NonPublicProperties = 1024, + + /// + /// The PublicEvents value. + /// PublicEvents = 2048, + + /// + /// The NonPublicEvents value. + /// NonPublicEvents = 4096, + + /// + /// The Interfaces value. + /// Interfaces = 8192 } +/// +/// Provides members for the DynamicallyAccessedMembersAttribute class. +/// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, Inherited = false)] internal sealed class DynamicallyAccessedMembersAttribute : Attribute { + /// + /// Initializes a new instance of the class. + /// + /// The member types which are dynamically accessed. public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) { MemberTypes = memberTypes; } + /// + /// Gets the MemberTypes value. + /// public DynamicallyAccessedMemberTypes MemberTypes { get; } } #endif diff --git a/src/DynamicData/Polyfills/EnumEx.cs b/src/DynamicData/Polyfills/EnumEx.cs index 1e590a8b3..0f70588b6 100644 --- a/src/DynamicData/Polyfills/EnumEx.cs +++ b/src/DynamicData/Polyfills/EnumEx.cs @@ -1,17 +1,33 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. namespace System; +/// +/// Provides members for the EnumEx class. +/// internal static class EnumEx { #if NET5_0_OR_GREATER + + /// + /// Executes the IsDefined operation. + /// + /// The type of the TEnum value. + /// The value value. + /// The result of the operation. public static bool IsDefined(TEnum value) where TEnum : struct, Enum => Enum.IsDefined(value); #else - public static bool IsDefined(TEnum value) + /// + /// Executes the IsDefined operation. + /// + /// The type of the TEnum value. + /// The value value. + /// The result of the operation. +public static bool IsDefined(TEnum value) where TEnum : struct, Enum => Enum.IsDefined(typeof(TEnum), value); #endif diff --git a/src/DynamicData/Polyfills/IsExternalInit.cs b/src/DynamicData/Polyfills/IsExternalInit.cs index 4542c2eed..c36a21ddf 100644 --- a/src/DynamicData/Polyfills/IsExternalInit.cs +++ b/src/DynamicData/Polyfills/IsExternalInit.cs @@ -1,10 +1,19 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. -#if !NETCOREAPP +// Polyfill implementation adapted from SimonCropp/Polyfill (https://github.com/SimonCropp/Polyfill). +#if !NET5_0_OR_GREATER +using System.Diagnostics; + namespace System.Runtime.CompilerServices; -// Allows use of the C#11 `init` keyword, internally within this library, when targeting frameworks older than .NET 5. -internal sealed class IsExternalInit; +/// Reserved for the compiler to track metadata for -only members; not for use in source. +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[SuppressMessage( + "Design", + "SST1436:Add members to this type or remove it; an empty type is rarely intentional", + Justification = "Compiler-recognized marker type for init-only members; it must remain empty.")] +internal static class IsExternalInit; #endif diff --git a/src/DynamicData/Polyfills/ListEnsureCapacity.cs b/src/DynamicData/Polyfills/ListEnsureCapacity.cs index 671b66760..b336215db 100644 --- a/src/DynamicData/Polyfills/ListEnsureCapacity.cs +++ b/src/DynamicData/Polyfills/ListEnsureCapacity.cs @@ -1,13 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. #if !NETCOREAPP namespace System.Collections.Generic; +/// +/// Provides members for the ListEnsureCapacity class. +/// internal static class ListEnsureCapacity { - public static void EnsureCapacity(this List list, int capacity) + /// + /// Executes the EnsureCapacity operation. + /// + /// The type of the T value. + /// The list value. + /// The capacity value. +public static void EnsureCapacity(this List list, int capacity) { if (list.Capacity < capacity) list.Capacity = capacity; diff --git a/src/DynamicData/Polyfills/MemberNotNullWhenAttribute.cs b/src/DynamicData/Polyfills/MemberNotNullWhenAttribute.cs new file mode 100644 index 000000000..1d8b3c08d --- /dev/null +++ b/src/DynamicData/Polyfills/MemberNotNullWhenAttribute.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Roland Pheasant licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +// Polyfill implementation adapted from SimonCropp/Polyfill (https://github.com/SimonCropp/Polyfill). +#if !NET5_0_OR_GREATER +namespace System.Diagnostics.CodeAnalysis; + +/// Specifies that the listed members are not when the method returns the given value. +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage( + AttributeTargets.Method | AttributeTargets.Property, + Inherited = false, + AllowMultiple = true)] +internal sealed class MemberNotNullWhenAttribute : Attribute +{ + /// Initializes a new instance of the class. + /// The return value condition for which the members are not . + /// The field and property members that are promised to be not-. + public MemberNotNullWhenAttribute(bool returnValue, params string[] members) + { + ReturnValue = returnValue; + Members = members; + } + + /// Gets the return value condition for which the members are not . + public bool ReturnValue { get; } + + /// Gets the field and property member names that are promised to be not-. + public string[] Members { get; } +} +#endif diff --git a/src/DynamicData/Polyfills/NotNullAttribute.cs b/src/DynamicData/Polyfills/NotNullAttribute.cs new file mode 100644 index 000000000..9cce49350 --- /dev/null +++ b/src/DynamicData/Polyfills/NotNullAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Roland Pheasant licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +// Polyfill implementation adapted from SimonCropp/Polyfill (https://github.com/SimonCropp/Polyfill). +#if !NETCOREAPP3_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER +namespace System.Diagnostics.CodeAnalysis; + +/// Specifies that an output is not even if the corresponding type allows it. +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage( + AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)] +internal sealed class NotNullAttribute : Attribute; +#endif diff --git a/src/DynamicData/Polyfills/NotNullWhenAttribute.cs b/src/DynamicData/Polyfills/NotNullWhenAttribute.cs new file mode 100644 index 000000000..75bda35c3 --- /dev/null +++ b/src/DynamicData/Polyfills/NotNullWhenAttribute.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Roland Pheasant licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +// Polyfill implementation adapted from SimonCropp/Polyfill (https://github.com/SimonCropp/Polyfill). +#if !NETCOREAPP3_0_OR_GREATER && !NETSTANDARD2_1_OR_GREATER +namespace System.Diagnostics.CodeAnalysis; + +/// Specifies that when a method returns , the parameter is not . +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage(AttributeTargets.Parameter)] +internal sealed class NotNullWhenAttribute : Attribute +{ + /// Initializes a new instance of the class. + /// The return value condition for which the parameter is not . + public NotNullWhenAttribute(bool returnValue) => + ReturnValue = returnValue; + + /// Gets the return value condition for which the parameter is not . + public bool ReturnValue { get; } +} +#endif diff --git a/src/DynamicData/Polyfills/RequiredMemberAttribute.cs b/src/DynamicData/Polyfills/RequiredMemberAttribute.cs index b1bbc836d..80ac1b32b 100644 --- a/src/DynamicData/Polyfills/RequiredMemberAttribute.cs +++ b/src/DynamicData/Polyfills/RequiredMemberAttribute.cs @@ -1,14 +1,22 @@ -// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. // Roland Pheasant licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +// Polyfill implementation adapted from SimonCropp/Polyfill (https://github.com/SimonCropp/Polyfill). #if !NET7_0_OR_GREATER -using System.ComponentModel; +using System.Diagnostics; namespace System.Runtime.CompilerServices; -// Allows use of the C#11 `required` keyword, internally within this library, when targeting frameworks older than .NET 7. -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, Inherited = false)] -[EditorBrowsable(EditorBrowsableState.Never)] +/// Indicates that a type or member is required by the compiler and must be initialized. +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage( + AttributeTargets.Class | + AttributeTargets.Struct | + AttributeTargets.Field | + AttributeTargets.Property, + AllowMultiple = false, + Inherited = false)] internal sealed class RequiredMemberAttribute : Attribute; #endif diff --git a/src/DynamicData/Polyfills/SetsRequiredMembersAttribute.cs b/src/DynamicData/Polyfills/SetsRequiredMembersAttribute.cs new file mode 100644 index 000000000..a9f18d6b9 --- /dev/null +++ b/src/DynamicData/Polyfills/SetsRequiredMembersAttribute.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Roland Pheasant licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +// Polyfill implementation adapted from SimonCropp/Polyfill (https://github.com/SimonCropp/Polyfill). +#if !NET7_0_OR_GREATER +namespace System.Diagnostics.CodeAnalysis; + +/// Specifies that a constructor sets all required members, satisfying the compiler's required-member checks. +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)] +internal sealed class SetsRequiredMembersAttribute : Attribute; +#endif diff --git a/src/DynamicData/Polyfills/TaskCompletionSource.cs b/src/DynamicData/Polyfills/TaskCompletionSource.cs new file mode 100644 index 000000000..904202264 --- /dev/null +++ b/src/DynamicData/Polyfills/TaskCompletionSource.cs @@ -0,0 +1,48 @@ +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Roland Pheasant licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +// Polyfill implementation adapted from SimonCropp/Polyfill (https://github.com/SimonCropp/Polyfill). +#if !NET5_0_OR_GREATER + +namespace System.Threading.Tasks; + +/// Polyfill for the non-generic introduced in .NET 5, backed by a TaskCompletionSource<TResult>. +[SuppressMessage("Performance", "CA1812", Justification = "Broadcast polyfill; not instantiated in every consuming leaf.")] +internal sealed class TaskCompletionSource +{ + /// The underlying generic completion source that backs this non-generic facade. + private readonly TaskCompletionSource _inner; + + /// Initializes a new instance of the class. + public TaskCompletionSource() => _inner = new(); + + /// Transitions the underlying task to the state. + public void SetResult() => _inner.SetResult(true); + + /// Attempts to transition the underlying task to the state. + /// if the operation was successful; otherwise . + public bool TrySetResult() => _inner.TrySetResult(true); + + /// Transitions the underlying task to the state with the specified exception. + /// The exception to bind to the task. + public void SetException(Exception exception) => _inner.SetException(exception); + + /// Attempts to transition the underlying task to the state with the specified exception. + /// The exception to bind to the task. + /// if the operation was successful; otherwise . + public bool TrySetException(Exception exception) => _inner.TrySetException(exception); + + /// Transitions the underlying task to the state. + public void SetCanceled() => _inner.TrySetCanceled(); + + /// Attempts to transition the underlying task to the state. + /// if the operation was successful; otherwise . + public bool TrySetCanceled() => _inner.TrySetCanceled(); + + /// Attempts to transition the underlying task to the state for the specified token. + /// The token associated with the cancellation. + /// if the operation was successful; otherwise . + public bool TrySetCanceled(CancellationToken cancellationToken) => _inner.TrySetCanceled(cancellationToken); +} +#endif diff --git a/src/DynamicData/Polyfills/TaskPolyfillExtensions.cs b/src/DynamicData/Polyfills/TaskPolyfillExtensions.cs new file mode 100644 index 000000000..c97f40bd6 --- /dev/null +++ b/src/DynamicData/Polyfills/TaskPolyfillExtensions.cs @@ -0,0 +1,70 @@ +// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved. +// Roland Pheasant licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +// Polyfill implementation adapted from SimonCropp/Polyfill (https://github.com/SimonCropp/Polyfill). +#if !NET6_0_OR_GREATER +namespace System.Threading.Tasks; + +/// Polyfill extensions for on frameworks without the .NET 6 WaitAsync overloads. +internal static class TaskPolyfillExtensions +{ + /// Polyfill awaiting operations for a task. + extension(Task task) + { + /// Gets a task that completes with , or faults when the timeout elapses or the token is cancelled. + /// The timeout after which the returned task faults, or for no timeout. + /// A token that cancels the wait. + /// A task that mirrors subject to the timeout and cancellation. + public async Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken) + { + await WaitForCompletionAsync(task, timeout, cancellationToken).ConfigureAwait(false); + await task.ConfigureAwait(false); + } + } + + /// Polyfill awaiting operations for a task that produces a result. + extension(Task task) + { + /// Gets a task that completes with the same result as , or faults when the timeout elapses or the token is cancelled. + /// The timeout after which the returned task faults, or for no timeout. + /// A token that cancels the wait. + /// A task that mirrors subject to the timeout and cancellation. + public async Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken) + { + await WaitForCompletionAsync(task, timeout, cancellationToken).ConfigureAwait(false); + return await task.ConfigureAwait(false); + } + } + + /// Waits for the task to complete, timeout, or cancellation. + /// The task to observe. + /// The timeout after which the wait fails, or for no timeout. + /// A token that cancels the wait. + /// A task that completes when the wait condition is resolved. + private static async Task WaitForCompletionAsync(Task task, TimeSpan timeout, CancellationToken cancellationToken) + { + if (task.IsCompleted) + { + return; + } + + TaskCompletionSource signal = new(TaskCreationOptions.RunContinuationsAsynchronously); + using var linked = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + if (timeout != Timeout.InfiniteTimeSpan) + { + linked.CancelAfter(timeout); + } + + using (linked.Token.Register(static state => ((TaskCompletionSource)state).TrySetResult(true), signal)) + { + var completed = await Task.WhenAny(task, signal.Task).ConfigureAwait(false); + if (completed != task) + { + cancellationToken.ThrowIfCancellationRequested(); + throw new TimeoutException(); + } + } + } +} +#endif