Skip to content

Commit 2401dab

Browse files
committed
Break ObservableListEx.cs into per-family partial classes
Splits the 2900-line ObservableListEx.cs into 17 smaller partial-class files grouped by operator family. Each method (and all of its overloads) lives in exactly one file. The class declaration is changed to partial; no code, comments, or XML documentation is added, removed, or otherwise modified. All 2218 tests pass.
1 parent 335ecbc commit 2401dab

18 files changed

Lines changed: 3329 additions & 2929 deletions
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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.ComponentModel;
7+
using System.Diagnostics.CodeAnalysis;
8+
using System.Linq.Expressions;
9+
using System.Reactive;
10+
using System.Reactive.Concurrency;
11+
using System.Reactive.Disposables;
12+
using System.Reactive.Linq;
13+
using DynamicData.Binding;
14+
using DynamicData.Cache.Internal;
15+
using DynamicData.List.Internal;
16+
using DynamicData.List.Linq;
17+
18+
// ReSharper disable once CheckNamespace
19+
namespace DynamicData;
20+
21+
/// <summary>
22+
/// ObservableList extensions for Adapt.
23+
/// </summary>
24+
public static partial class ObservableListEx
25+
{
26+
/// <summary>
27+
/// Injects a side effect into a changeset stream via an <see cref="IChangeSetAdaptor{T}"/>.
28+
/// The adaptor's <c>Adapt</c> method is invoked for each changeset before it is forwarded downstream unchanged.
29+
/// </summary>
30+
/// <typeparam name="T">The type of items in the list.</typeparam>
31+
/// <param name="source">The source <see cref="IObservable{IChangeSet{T}}"/> to observe and adapt.</param>
32+
/// <param name="adaptor">The <see cref="IChangeSetAdaptor{T}"/> adaptor whose <c>Adapt</c> method is invoked for each changeset.</param>
33+
/// <returns>A list changeset stream identical to the source, with the adaptor side effect applied.</returns>
34+
/// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="adaptor"/> is <see langword="null"/>.</exception>
35+
/// <remarks>
36+
/// <para>
37+
/// This is the primary extension point for custom UI binding adaptors (e.g., <see cref="Bind{T}(IObservable{IChangeSet{T}}, IObservableCollection{T}, BindingOptions)"/>
38+
/// delegates to this operator). If the adaptor throws, the exception propagates downstream as <c>OnError</c>.
39+
/// </para>
40+
/// </remarks>
41+
/// <seealso cref="Bind{T}(IObservable{IChangeSet{T}}, IObservableCollection{T}, BindingOptions)"/>
42+
public static IObservable<IChangeSet<T>> Adapt<T>(this IObservable<IChangeSet<T>> source, IChangeSetAdaptor<T> adaptor)
43+
where T : notnull
44+
{
45+
source.ThrowArgumentNullExceptionIfNull(nameof(source));
46+
adaptor.ThrowArgumentNullExceptionIfNull(nameof(adaptor));
47+
48+
return Observable.Create<IChangeSet<T>>(
49+
observer =>
50+
{
51+
var locker = InternalEx.NewLock();
52+
return source.Synchronize(locker).Select(
53+
changes =>
54+
{
55+
adaptor.Adapt(changes);
56+
return changes;
57+
}).SubscribeSafe(observer);
58+
});
59+
}
60+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
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.ComponentModel;
7+
using System.Diagnostics.CodeAnalysis;
8+
using System.Linq.Expressions;
9+
using System.Reactive;
10+
using System.Reactive.Concurrency;
11+
using System.Reactive.Disposables;
12+
using System.Reactive.Linq;
13+
using DynamicData.Binding;
14+
using DynamicData.Cache.Internal;
15+
using DynamicData.List.Internal;
16+
using DynamicData.List.Linq;
17+
18+
// ReSharper disable once CheckNamespace
19+
namespace DynamicData;
20+
21+
/// <summary>
22+
/// ObservableList extensions for AutoRefresh.
23+
/// </summary>
24+
public static partial class ObservableListEx
25+
{
26+
/// <summary>
27+
/// Monitors all properties on each item (via <see cref="INotifyPropertyChanged"/>) and emits <b>Refresh</b>
28+
/// changes when any property changes, causing downstream operators to re-evaluate.
29+
/// </summary>
30+
/// <typeparam name="TObject">The type of items, which must implement <see cref="INotifyPropertyChanged"/>.</typeparam>
31+
/// <param name="source">The source <see cref="IObservable{IChangeSet{TObject}}"/> to monitor for property-driven refresh signals.</param>
32+
/// <param name="changeSetBuffer">An optional <see cref="TimeSpan"/> buffer duration to batch multiple refresh signals into a single changeset.</param>
33+
/// <param name="propertyChangeThrottle">An optional <see cref="TimeSpan"/> throttle applied to each item's property change notifications.</param>
34+
/// <param name="scheduler">The scheduler for throttle and buffer timing. Defaults to <see cref="GlobalConfig.DefaultScheduler"/>.</param>
35+
/// <returns>A list changeset stream with additional <b>Refresh</b> changes injected when properties change.</returns>
36+
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception>
37+
/// <remarks>
38+
/// <para>
39+
/// Wraps <see cref="AutoRefreshOnObservable{TObject, TAny}"/> using <c>WhenAnyPropertyChanged()</c> as the re-evaluator.
40+
/// Pair with <see cref="Filter{T}(IObservable{IChangeSet{T}}, Func{T, bool})"/> or <see cref="Sort{T}(IObservable{IChangeSet{T}}, IComparer{T}, SortOptions, IObservable{Unit}?, IObservable{IComparer{T}}?, int)"/>
41+
/// to get reactive re-evaluation on property changes.
42+
/// </para>
43+
/// <list type="table">
44+
/// <listheader><term>Event</term><description>Behavior</description></listheader>
45+
/// <item><term>Add/AddRange</term><description>Subscribes to <c>PropertyChanged</c> on each new item. The original change is forwarded.</description></item>
46+
/// <item><term>Replace</term><description>Unsubscribes from the old item, subscribes to the new. The original change is forwarded.</description></item>
47+
/// <item><term>Remove/RemoveRange/Clear</term><description>Unsubscribes from removed items. The original change is forwarded.</description></item>
48+
/// <item><term>Moved/Refresh</term><description>Forwarded unchanged.</description></item>
49+
/// <item><term>Property changes</term><description>A <b>Refresh</b> change is emitted for the item whose property changed.</description></item>
50+
/// </list>
51+
/// <para><b>Worth noting:</b> Each item generates a subscription. For large lists with frequent property changes, use <paramref name="changeSetBuffer"/> and <paramref name="propertyChangeThrottle"/> to reduce churn.</para>
52+
/// </remarks>
53+
/// <seealso cref="AutoRefresh{TObject, TProperty}(IObservable{IChangeSet{TObject}}, Expression{Func{TObject, TProperty}}, TimeSpan?, TimeSpan?, IScheduler?)"/>
54+
/// <seealso cref="AutoRefreshOnObservable{TObject, TAny}(IObservable{IChangeSet{TObject}}, Func{TObject, IObservable{TAny}}, TimeSpan?, IScheduler?)"/>
55+
/// <seealso cref="SuppressRefresh{T}(IObservable{IChangeSet{T}})"/>
56+
/// <seealso cref="ObservableCacheEx.AutoRefresh{TObject, TKey}(IObservable{IChangeSet{TObject, TKey}}, TimeSpan?, TimeSpan?, IScheduler?)"/>
57+
public static IObservable<IChangeSet<TObject>> AutoRefresh<TObject>(this IObservable<IChangeSet<TObject>> source, TimeSpan? changeSetBuffer = null, TimeSpan? propertyChangeThrottle = null, IScheduler? scheduler = null)
58+
where TObject : INotifyPropertyChanged
59+
{
60+
source.ThrowArgumentNullExceptionIfNull(nameof(source));
61+
62+
return source.AutoRefreshOnObservable(
63+
t =>
64+
{
65+
if (propertyChangeThrottle is null)
66+
{
67+
return t.WhenAnyPropertyChanged();
68+
}
69+
70+
return t.WhenAnyPropertyChanged().Throttle(propertyChangeThrottle.Value, scheduler ?? GlobalConfig.DefaultScheduler);
71+
},
72+
changeSetBuffer,
73+
scheduler);
74+
}
75+
76+
/// <summary>
77+
/// Monitors a single property (selected by <paramref name="propertyAccessor"/>) on each item via <see cref="INotifyPropertyChanged"/>
78+
/// and emits <b>Refresh</b> changes when that property changes, causing downstream operators to re-evaluate. More efficient than
79+
/// the all-properties overload when only one property (of type <typeparamref name="TProperty"/>) affects downstream behavior.
80+
/// </summary>
81+
/// <inheritdoc cref="AutoRefresh{TObject}(IObservable{IChangeSet{TObject}}, TimeSpan?, TimeSpan?, IScheduler?)"/>
82+
public static IObservable<IChangeSet<TObject>> AutoRefresh<TObject, TProperty>(this IObservable<IChangeSet<TObject>> source, Expression<Func<TObject, TProperty>> propertyAccessor, TimeSpan? changeSetBuffer = null, TimeSpan? propertyChangeThrottle = null, IScheduler? scheduler = null)
83+
where TObject : INotifyPropertyChanged
84+
{
85+
source.ThrowArgumentNullExceptionIfNull(nameof(source));
86+
propertyAccessor.ThrowArgumentNullExceptionIfNull(nameof(propertyAccessor));
87+
88+
return source.AutoRefreshOnObservable(
89+
t =>
90+
{
91+
if (propertyChangeThrottle is null)
92+
{
93+
return t.WhenPropertyChanged(propertyAccessor, false);
94+
}
95+
96+
return t.WhenPropertyChanged(propertyAccessor, false).Throttle(propertyChangeThrottle.Value, scheduler ?? GlobalConfig.DefaultScheduler);
97+
},
98+
changeSetBuffer,
99+
scheduler);
100+
}
101+
102+
/// <summary>
103+
/// Monitors each item with a custom observable and emits <b>Refresh</b> changes whenever that observable fires,
104+
/// causing downstream operators (Filter, Sort, Group) to re-evaluate.
105+
/// </summary>
106+
/// <typeparam name="TObject">The type of items in the list.</typeparam>
107+
/// <typeparam name="TAny">The type emitted by the re-evaluator observable (value is ignored).</typeparam>
108+
/// <param name="source">The source <see cref="IObservable{IChangeSet{TObject}}"/> to monitor for observable-driven refresh signals.</param>
109+
/// <param name="reevaluator">A <see cref="Func{T, TResult}"/> factory that, given an item, returns an observable whose emissions trigger a <b>Refresh</b> for that item.</param>
110+
/// <param name="changeSetBuffer">An optional <see cref="TimeSpan"/> buffer duration to batch refresh signals into a single changeset.</param>
111+
/// <param name="scheduler">The <see cref="IScheduler"/> for buffering.</param>
112+
/// <returns>A list changeset stream with additional <b>Refresh</b> changes injected when per-item observables fire.</returns>
113+
/// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="reevaluator"/> is <see langword="null"/>.</exception>
114+
/// <remarks>
115+
/// <para>
116+
/// This is the general-purpose refresh mechanism. <see cref="AutoRefresh{TObject}(IObservable{IChangeSet{TObject}}, TimeSpan?, TimeSpan?, IScheduler?)"/>
117+
/// is a convenience wrapper that uses <c>WhenAnyPropertyChanged()</c> as the re-evaluator.
118+
/// </para>
119+
/// <list type="table">
120+
/// <listheader><term>Event</term><description>Behavior</description></listheader>
121+
/// <item><term>Add/AddRange</term><description>Subscribes to the re-evaluator observable for each new item. The original change is forwarded.</description></item>
122+
/// <item><term>Replace</term><description>Unsubscribes from the old item's observable, subscribes to the new. The original change is forwarded.</description></item>
123+
/// <item><term>Remove/RemoveRange/Clear</term><description>Unsubscribes from removed items. The original change is forwarded.</description></item>
124+
/// <item><term>Moved/Refresh</term><description>Forwarded unchanged.</description></item>
125+
/// <item><term>Re-evaluator fires</term><description>The item's current index is looked up and a <b>Refresh</b> change is emitted.</description></item>
126+
/// </list>
127+
/// </remarks>
128+
/// <seealso cref="AutoRefresh{TObject}(IObservable{IChangeSet{TObject}}, TimeSpan?, TimeSpan?, IScheduler?)"/>
129+
/// <seealso cref="SuppressRefresh{T}(IObservable{IChangeSet{T}})"/>
130+
/// <seealso cref="ObservableCacheEx.AutoRefreshOnObservable{TObject, TKey, TAny}(IObservable{IChangeSet{TObject, TKey}}, Func{TObject, IObservable{TAny}}, TimeSpan?, IScheduler?)"/>
131+
public static IObservable<IChangeSet<TObject>> AutoRefreshOnObservable<TObject, TAny>(this IObservable<IChangeSet<TObject>> source, Func<TObject, IObservable<TAny>> reevaluator, TimeSpan? changeSetBuffer = null, IScheduler? scheduler = null)
132+
where TObject : notnull
133+
{
134+
source.ThrowArgumentNullExceptionIfNull(nameof(source));
135+
reevaluator.ThrowArgumentNullExceptionIfNull(nameof(reevaluator));
136+
137+
return new AutoRefresh<TObject, TAny>(source, reevaluator, changeSetBuffer, scheduler).Run();
138+
}
139+
}

0 commit comments

Comments
 (0)