Skip to content

Commit ab5bd6b

Browse files
authored
Split ObservableCacheEx.cs into per-family partial classes (#1095)
* Break ObservableCacheEx.cs into per-family partial classes Splits the 6800-line ObservableCacheEx.cs into 24 smaller partial-class files grouped by operator family. Each method (and all of its overloads) lives in exactly one file. No code, comments, or XML documentation is added, removed, or otherwise modified; this is a pure file reorganization. All 2218 tests pass. * Break ObservableCacheEx.cs into per-family partial classes Splits the monolithic ObservableCacheEx.cs into 19 smaller partial-class files grouped by operator family. The two pre-existing partials (ObservableCacheEx.SortAndBind.cs, ObservableCacheEx.VirtualiseAndPage.cs) are untouched. Each method (and all of its overloads) lives in exactly one file. No code, XML documentation, comments, preprocessor directives, or constants are added, removed, or otherwise modified. The split was generated programmatically with byte-level per-method equality checks against the original. * Alphabetize members within new ObservableCacheEx partial files Sorts members alphabetically by name within each new partial file. Overloads of the same name preserve their original declaration order. Constants sort before methods. Pre-existing partials (SortAndBind, VirtualiseAndPage) are not modified. * Split ObservableCacheEx.cs partials into one file per operator (overload set) Addresses PR review feedback: 1. ONE FILE PER OPERATOR NAME (one overload set per file). The previous split into 19 family files is replaced with 103 per-operator partial files, matching the existing convention set by ObservableCacheEx.SortAndBind.cs and ObservableCacheEx.VirtualiseAndPage.cs. 2. BARE ObservableCacheEx.cs FILE restored to carry the canonical class-level XML documentation. All partials carry the same canonical class summary ('Extensions for dynamic data.') so SA1601 is satisfied and there are no divergent per-file class docs. SortAndBind.cs and VirtualiseAndPage.cs were also updated for consistency. 3. PRIVATE HELPERS placed AFTER all public members within their containing file. Each private helper lives in the alphabetically-first operator file that calls it: - Combine -> And.cs (also called by Except, Or, Xor) - ForForced -> Transform.cs (also called by TransformSafe) - AdaptSelector -> Group.cs (also called by GroupOnObservable) - OnChangeAction -> OnItemAdded.cs (also called by OnItem* family) - TrueFor -> TrueForAll.cs (also called by TrueForAny) - CreateChangeSetTransformer -> TransformManyAsync.cs (also called by TransformManySafeAsync) - DefaultResortOnSourceRefresh const -> MergeManyChangeSets.cs - DefaultSortResetThreshold const -> Sort.cs The byte content of every method body is preserved (verified programmatically). #if/#endif preprocessor regions (SUPPORTS_BINDINGLIST in Bind.cs, SUPPORTS_ASYNC_DISPOSABLE around AsyncDisposeMany) are reconstructed in the new files. * Extract shared private helpers into per-helper partial files Per Jake's review feedback, private helpers used by multiple operators get their own ObservableCacheEx.{HelperName}.cs file, matching the per-operator pattern established for the public surface. Combine -> ObservableCacheEx.Combine.cs (from And.cs) AdaptSelector -> ObservableCacheEx.AdaptSelector.cs (from Group.cs) OnChangeAction -> ObservableCacheEx.OnChangeAction.cs (from OnItemAdded.cs) ForForced -> ObservableCacheEx.ForForced.cs (from Transform.cs) CreateChangeSetTransformer -> ObservableCacheEx.CreateChangeSetTransformer.cs (from TransformManyAsync.cs) TrueFor -> ObservableCacheEx.TrueFor.cs (from TrueForAll.cs) DefaultSortResetThreshold const moved to ObservableCacheEx.cs (the core file). Audit found it is used by both Sort and SortBy, contrary to the original PR body. AsyncDisposeMany #if SUPPORTS_ASYNC_DISPOSABLE wrapping replaced with a project-level Compile Remove. The file body is unconditionally compiled on supported platforms and excluded entirely on unsupported ones (net4*). All extractions are byte-preserving moves with no functional change. Builds clean on all target frameworks (netstandard2.0, net462, net6-net10). Targeted tests pass.
1 parent e6d4e44 commit ab5bd6b

113 files changed

Lines changed: 9748 additions & 6803 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved.
2+
// Roland Pheasant licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for full license information.
4+
5+
using System.Collections.ObjectModel;
6+
using System.Collections.Specialized;
7+
using System.ComponentModel;
8+
using System.Diagnostics.CodeAnalysis;
9+
using System.Linq.Expressions;
10+
using System.Reactive;
11+
using System.Reactive.Concurrency;
12+
using System.Reactive.Disposables;
13+
using System.Reactive.Linq;
14+
using System.Runtime.CompilerServices;
15+
using DynamicData.Binding;
16+
using DynamicData.Cache;
17+
using DynamicData.Cache.Internal;
18+
19+
// ReSharper disable once CheckNamespace
20+
21+
namespace DynamicData;
22+
23+
/// <summary>
24+
/// Extensions for dynamic data.
25+
/// </summary>
26+
public static partial class ObservableCacheEx
27+
{
28+
/// <summary>
29+
/// Injects a side effect into the changeset stream by calling <paramref name="adaptor"/>.<see cref="IChangeSetAdaptor{TObject, TKey}.Adapt(IChangeSet{TObject, TKey})"/>
30+
/// for every changeset, then forwarding it downstream unchanged.
31+
/// </summary>
32+
/// <typeparam name="TObject">The type of items in the cache.</typeparam>
33+
/// <typeparam name="TKey">The type of the key.</typeparam>
34+
/// <param name="source">The source <see cref="IObservable{IChangeSet{TObject, TKey}}"/> to observe and adapt.</param>
35+
/// <param name="adaptor">The <see cref="IChangeSetAdaptor{TObject, TKey}"/> whose Adapt method is called for each changeset.</param>
36+
/// <returns>An observable that emits the same changesets as <paramref name="source"/>, after the adaptor has processed each one.</returns>
37+
/// <remarks>
38+
/// <para>
39+
/// This is a thin wrapper around Rx's <c>Do</c> operator. The adaptor receives each changeset
40+
/// as a side effect; the changeset itself is forwarded downstream unmodified.
41+
/// </para>
42+
/// </remarks>
43+
/// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="adaptor"/> is <see langword="null"/>.</exception>
44+
/// <seealso cref="Adapt{TObject, TKey}(IObservable{ISortedChangeSet{TObject, TKey}}, ISortedChangeSetAdaptor{TObject, TKey})"/>
45+
/// <seealso cref="Bind{TObject, TKey}(IObservable{IChangeSet{TObject, TKey}}, IObservableCollection{TObject}, IObservableCollectionAdaptor{TObject, TKey})"/>
46+
public static IObservable<IChangeSet<TObject, TKey>> Adapt<TObject, TKey>(this IObservable<IChangeSet<TObject, TKey>> source, IChangeSetAdaptor<TObject, TKey> adaptor)
47+
where TObject : notnull
48+
where TKey : notnull
49+
{
50+
source.ThrowArgumentNullExceptionIfNull(nameof(source));
51+
adaptor.ThrowArgumentNullExceptionIfNull(nameof(adaptor));
52+
53+
return source.Do(adaptor.Adapt);
54+
}
55+
56+
/// <inheritdoc cref="Adapt{TObject, TKey}(IObservable{IChangeSet{TObject, TKey}}, IChangeSetAdaptor{TObject, TKey})"/>
57+
/// <param name="source">The source <see cref="IObservable{ISortedChangeSet{TObject, TKey}}"/> to observe and adapt.</param>
58+
/// <param name="adaptor">The <see cref="ISortedChangeSetAdaptor{TObject, TKey}"/> whose Adapt method is called for each changeset.</param>
59+
/// <remarks>This overload operates on <see cref="ISortedChangeSet{TObject, TKey}"/>. Delegates to Rx's <c>Do</c> operator.</remarks>
60+
public static IObservable<IChangeSet<TObject, TKey>> Adapt<TObject, TKey>(this IObservable<ISortedChangeSet<TObject, TKey>> source, ISortedChangeSetAdaptor<TObject, TKey> adaptor)
61+
where TObject : notnull
62+
where TKey : notnull
63+
{
64+
source.ThrowArgumentNullExceptionIfNull(nameof(source));
65+
adaptor.ThrowArgumentNullExceptionIfNull(nameof(adaptor));
66+
67+
return source.Do(adaptor.Adapt);
68+
}
69+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved.
2+
// Roland Pheasant licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for full license information.
4+
5+
using System.Collections.ObjectModel;
6+
using System.Collections.Specialized;
7+
using System.ComponentModel;
8+
using System.Diagnostics.CodeAnalysis;
9+
using System.Linq.Expressions;
10+
using System.Reactive;
11+
using System.Reactive.Concurrency;
12+
using System.Reactive.Disposables;
13+
using System.Reactive.Linq;
14+
using System.Runtime.CompilerServices;
15+
using DynamicData.Binding;
16+
using DynamicData.Cache;
17+
using DynamicData.Cache.Internal;
18+
19+
// ReSharper disable once CheckNamespace
20+
21+
namespace DynamicData;
22+
23+
/// <summary>
24+
/// Extensions for dynamic data.
25+
/// </summary>
26+
public static partial class ObservableCacheEx
27+
{
28+
// TODO: Apply the Adapter to more places
29+
private static Func<TObject, TKey, TResult> AdaptSelector<TObject, TKey, TResult>(Func<TObject, TResult> other)
30+
where TObject : notnull
31+
where TKey : notnull
32+
where TResult : notnull => (obj, _) => other(obj);
33+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved.
2+
// Roland Pheasant licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for full license information.
4+
5+
using System.Collections.ObjectModel;
6+
using System.Collections.Specialized;
7+
using System.ComponentModel;
8+
using System.Diagnostics.CodeAnalysis;
9+
using System.Linq.Expressions;
10+
using System.Reactive;
11+
using System.Reactive.Concurrency;
12+
using System.Reactive.Disposables;
13+
using System.Reactive.Linq;
14+
using System.Runtime.CompilerServices;
15+
using DynamicData.Binding;
16+
using DynamicData.Cache;
17+
using DynamicData.Cache.Internal;
18+
19+
// ReSharper disable once CheckNamespace
20+
21+
namespace DynamicData;
22+
23+
/// <summary>
24+
/// Extensions for dynamic data.
25+
/// </summary>
26+
public static partial class ObservableCacheEx
27+
{
28+
/// <summary>
29+
/// Adds or updates the cache with the specified item, producing a changeset with a single <b>Add</b>
30+
/// (if the key is new) or <b>Update</b> (if the key already exists).
31+
/// </summary>
32+
/// <typeparam name="TObject">The type of the object.</typeparam>
33+
/// <typeparam name="TKey">The type of the key.</typeparam>
34+
/// <param name="source">The <see cref="ISourceCache{TObject, TKey}"/> to add or update items in.</param>
35+
/// <param name="item">The <typeparamref name="TObject"/> item to add or update.</param>
36+
/// <remarks>
37+
/// <para>Convenience method that wraps a single-item mutation inside <see cref="ISourceCache{TObject,TKey}.Edit"/>.</para>
38+
/// <list type="table">
39+
/// <listheader><term>Event</term><description>Behavior</description></listheader>
40+
/// <item><term>Add</term><description>Produced when the key does not already exist in the cache.</description></item>
41+
/// <item><term>Update</term><description>Produced when the key already exists. The previous value is included in the changeset.</description></item>
42+
/// <item><term>Remove</term><description>Not produced by this method.</description></item>
43+
/// <item><term>Refresh</term><description>Not produced by this method.</description></item>
44+
/// </list>
45+
/// </remarks>
46+
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception>
47+
/// <seealso cref="EditDiff{TObject, TKey}(ISourceCache{TObject, TKey}, IEnumerable{TObject}, IEqualityComparer{TObject})"/>
48+
/// <seealso cref="Remove{TObject, TKey}(ISourceCache{TObject, TKey}, TObject)"/>
49+
public static void AddOrUpdate<TObject, TKey>(this ISourceCache<TObject, TKey> source, TObject item)
50+
where TObject : notnull
51+
where TKey : notnull
52+
{
53+
source.ThrowArgumentNullExceptionIfNull(nameof(source));
54+
55+
source.Edit(updater => updater.AddOrUpdate(item));
56+
}
57+
58+
/// <inheritdoc cref="AddOrUpdate{TObject, TKey}(ISourceCache{TObject, TKey}, TObject)"/>
59+
/// <param name="source">The <see cref="ISourceCache{TObject, TKey}"/> to add or update items in.</param>
60+
/// <param name="item">The <typeparamref name="TObject"/> item to add or update.</param>
61+
/// <param name="equalityComparer">The <see cref="IEqualityComparer{TObject}"/> used to determine whether a new item is the same as an existing cached item. When equal, the update is skipped.</param>
62+
/// <remarks>This overload uses <paramref name="equalityComparer"/> to suppress no-op updates when the new value equals the existing one.</remarks>
63+
public static void AddOrUpdate<TObject, TKey>(this ISourceCache<TObject, TKey> source, TObject item, IEqualityComparer<TObject> equalityComparer)
64+
where TObject : notnull
65+
where TKey : notnull
66+
{
67+
source.ThrowArgumentNullExceptionIfNull(nameof(source));
68+
69+
source.Edit(updater => updater.AddOrUpdate(item, equalityComparer));
70+
}
71+
72+
/// <inheritdoc cref="AddOrUpdate{TObject, TKey}(ISourceCache{TObject, TKey}, TObject)"/>
73+
/// <param name="source">The <see cref="ISourceCache{TObject, TKey}"/> to add or update items in.</param>
74+
/// <param name="items">The <see cref="IEnumerable{TObject}"/> of items to add or update.</param>
75+
/// <remarks>Batch overload. All items are added/updated inside a single <see cref="ISourceCache{TObject,TKey}.Edit"/> call, producing one changeset.</remarks>
76+
public static void AddOrUpdate<TObject, TKey>(this ISourceCache<TObject, TKey> source, IEnumerable<TObject> items)
77+
where TObject : notnull
78+
where TKey : notnull
79+
{
80+
source.ThrowArgumentNullExceptionIfNull(nameof(source));
81+
82+
source.Edit(updater => updater.AddOrUpdate(items));
83+
}
84+
85+
/// <inheritdoc cref="AddOrUpdate{TObject, TKey}(ISourceCache{TObject, TKey}, TObject)"/>
86+
/// <param name="source">The <see cref="ISourceCache{TObject, TKey}"/> to add or update items in.</param>
87+
/// <param name="items">The <see cref="IEnumerable{TObject}"/> of items to add or update.</param>
88+
/// <param name="equalityComparer">The <see cref="IEqualityComparer{TObject}"/> used to determine whether a new item is the same as an existing cached item. When equal, the update is skipped.</param>
89+
/// <remarks>Batch overload with equality comparison. All items are added/updated inside a single <see cref="ISourceCache{TObject,TKey}.Edit"/> call.</remarks>
90+
public static void AddOrUpdate<TObject, TKey>(this ISourceCache<TObject, TKey> source, IEnumerable<TObject> items, IEqualityComparer<TObject> equalityComparer)
91+
where TObject : notnull
92+
where TKey : notnull
93+
{
94+
source.ThrowArgumentNullExceptionIfNull(nameof(source));
95+
96+
source.Edit(updater => updater.AddOrUpdate(items, equalityComparer));
97+
}
98+
99+
/// <inheritdoc cref="AddOrUpdate{TObject, TKey}(ISourceCache{TObject, TKey}, TObject)"/>
100+
/// <param name="source">The <see cref="IIntermediateCache{TObject, TKey}"/> to add or update items in.</param>
101+
/// <param name="item">The <typeparamref name="TObject"/> item to add or update.</param>
102+
/// <param name="key">The <typeparamref name="TKey"/> key to associate with the item.</param>
103+
/// <remarks>This overload operates on <see cref="IIntermediateCache{TObject, TKey}"/>, which requires an explicit key parameter.</remarks>
104+
public static void AddOrUpdate<TObject, TKey>(this IIntermediateCache<TObject, TKey> source, TObject item, TKey key)
105+
where TObject : notnull
106+
where TKey : notnull
107+
{
108+
source.ThrowArgumentNullExceptionIfNull(nameof(source));
109+
item.ThrowArgumentNullExceptionIfNull(nameof(item));
110+
111+
source.Edit(updater => updater.AddOrUpdate(item, key));
112+
}
113+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved.
2+
// Roland Pheasant licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for full license information.
4+
5+
using System.Collections.ObjectModel;
6+
using System.Collections.Specialized;
7+
using System.ComponentModel;
8+
using System.Diagnostics.CodeAnalysis;
9+
using System.Linq.Expressions;
10+
using System.Reactive;
11+
using System.Reactive.Concurrency;
12+
using System.Reactive.Disposables;
13+
using System.Reactive.Linq;
14+
using System.Runtime.CompilerServices;
15+
using DynamicData.Binding;
16+
using DynamicData.Cache;
17+
using DynamicData.Cache.Internal;
18+
19+
// ReSharper disable once CheckNamespace
20+
21+
namespace DynamicData;
22+
23+
/// <summary>
24+
/// Extensions for dynamic data.
25+
/// </summary>
26+
public static partial class ObservableCacheEx
27+
{
28+
/// <summary>
29+
/// Applied a logical And operator between the collections i.e items which are in all of the
30+
/// sources are included.
31+
/// </summary>
32+
/// <typeparam name="TObject">The type of the object.</typeparam>
33+
/// <typeparam name="TKey">The type of the key.</typeparam>
34+
/// <param name="source">The source <see cref="IObservable{IChangeSet{TObject, TKey}}"/> to combine.</param>
35+
/// <param name="others">The additional <see cref="IObservable{IChangeSet{TObject, TKey}}"/> streams to combine with.</param>
36+
/// <returns>An observable which emits change sets.</returns>
37+
/// <exception cref="ArgumentNullException">source or others.</exception>
38+
/// <seealso cref="ObservableListEx.And"/>
39+
public static IObservable<IChangeSet<TObject, TKey>> And<TObject, TKey>(this IObservable<IChangeSet<TObject, TKey>> source, params IObservable<IChangeSet<TObject, TKey>>[] others)
40+
where TObject : notnull
41+
where TKey : notnull
42+
{
43+
source.ThrowArgumentNullExceptionIfNull(nameof(source));
44+
45+
return others is null || others.Length == 0
46+
? throw new ArgumentNullException(nameof(others))
47+
: source.Combine(CombineOperator.And, others);
48+
}
49+
50+
/// <summary>
51+
/// Applied a logical And operator between the collections i.e items which are in all of the sources are included.
52+
/// </summary>
53+
/// <typeparam name="TObject">The type of the object.</typeparam>
54+
/// <typeparam name="TKey">The type of the key.</typeparam>
55+
/// <param name="sources">The <see cref="ICollection{T}"/> of streams to combine.</param>
56+
/// <returns>An observable which emits change sets.</returns>
57+
/// <exception cref="ArgumentNullException">
58+
/// source
59+
/// or
60+
/// others.
61+
/// </exception>
62+
public static IObservable<IChangeSet<TObject, TKey>> And<TObject, TKey>(this ICollection<IObservable<IChangeSet<TObject, TKey>>> sources)
63+
where TObject : notnull
64+
where TKey : notnull
65+
{
66+
sources.ThrowArgumentNullExceptionIfNull(nameof(sources));
67+
68+
return sources.Combine(CombineOperator.And);
69+
}
70+
71+
/// <summary>
72+
/// Dynamically apply a logical And operator between the items in the outer observable list.
73+
/// Items which are in all of the sources are included in the result.
74+
/// </summary>
75+
/// <typeparam name="TObject">The type of the object.</typeparam>
76+
/// <typeparam name="TKey">The type of the key.</typeparam>
77+
/// <param name="sources">The <see cref="IObservableList{T}"/> of streams to combine.</param>
78+
/// <returns>An observable which emits change sets.</returns>
79+
public static IObservable<IChangeSet<TObject, TKey>> And<TObject, TKey>(this IObservableList<IObservable<IChangeSet<TObject, TKey>>> sources)
80+
where TObject : notnull
81+
where TKey : notnull
82+
{
83+
sources.ThrowArgumentNullExceptionIfNull(nameof(sources));
84+
85+
return sources.Combine(CombineOperator.And);
86+
}
87+
88+
/// <summary>
89+
/// Dynamically apply a logical And operator between the items in the outer observable list.
90+
/// Items which are in all of the sources are included in the result.
91+
/// </summary>
92+
/// <typeparam name="TObject">The type of the object.</typeparam>
93+
/// <typeparam name="TKey">The type of the key.</typeparam>
94+
/// <param name="sources">The <see cref="IObservableList{IObservableCache{TObject, TKey}}"/> of changeset streams to combine.</param>
95+
/// <returns>An observable which emits change sets.</returns>
96+
public static IObservable<IChangeSet<TObject, TKey>> And<TObject, TKey>(this IObservableList<IObservableCache<TObject, TKey>> sources)
97+
where TObject : notnull
98+
where TKey : notnull
99+
{
100+
sources.ThrowArgumentNullExceptionIfNull(nameof(sources));
101+
102+
return sources.Combine(CombineOperator.And);
103+
}
104+
105+
/// <summary>
106+
/// Dynamically apply a logical And operator between the items in the outer observable list.
107+
/// Items which are in all of the sources are included in the result.
108+
/// </summary>
109+
/// <typeparam name="TObject">The type of the object.</typeparam>
110+
/// <typeparam name="TKey">The type of the key.</typeparam>
111+
/// <param name="sources">The <see cref="IObservableList{ISourceCache{TObject, TKey}}"/> of changeset streams to combine.</param>
112+
/// <returns>An observable which emits change sets.</returns>
113+
public static IObservable<IChangeSet<TObject, TKey>> And<TObject, TKey>(this IObservableList<ISourceCache<TObject, TKey>> sources)
114+
where TObject : notnull
115+
where TKey : notnull
116+
{
117+
sources.ThrowArgumentNullExceptionIfNull(nameof(sources));
118+
119+
return sources.Combine(CombineOperator.And);
120+
}
121+
}

0 commit comments

Comments
 (0)