diff --git a/.github/skills/add-documentation/SKILL.md b/.github/skills/add-documentation/SKILL.md
new file mode 100644
index 000000000..69ac28012
--- /dev/null
+++ b/.github/skills/add-documentation/SKILL.md
@@ -0,0 +1,269 @@
+---
+name: add-documentation
+description: Use when adding or updating XML documentation for any public API in DynamicData, including new operators, changed behavior, batch documentation passes, or PR reviewer requests for improved docs. Covers extension methods, public classes, interfaces, enums, and any member visible in the published API reference.
+---
+
+# Add Documentation
+
+## Overview
+
+Write publication-quality XML documentation for DynamicData public APIs. Every behavioral claim must be traced from the implementation source, not guessed from naming conventions. The documentation will be extracted as HTML and published on the ReactiveUI docs site.
+
+## When to Use
+
+- New operator or public API added
+- Existing operator's behavior changed
+- Batch documentation pass across a file
+- PR reviewer requests improved documentation
+- Any public type, interface, enum, or member needs docs
+
+## Core Rule: Additive Only
+
+Never remove existing useful information from comments. After making changes, diff against main to confirm nothing substantive was lost.
+
+## Unified Template
+
+Start from this template for any primary overload. Delete sections that do not apply.
+
+```xml
+///
+/// [1-3 sentences: what it does, why you'd use it. No behavioral details.]
+///
+/// The type of items.
+/// The type of the key.
+/// The source to [verb: filter, transform, sort, etc.].
+/// An that determines sort order.
+/// [What it emits and what each emission represents.]
+/// Thrown when is null.
+///
+/// [When to use. How it differs from alternatives. Multiple paragraphs fine.]
+///
+///
+///
+///
+///
+///
+///
+/// EventBehavior
+///
+/// - Add[specific outcome, bold output reasons]
+/// - Update[specific outcome]
+/// - Remove[specific outcome, mention cleanup]
+/// - Refresh[specific outcome, trace carefully]
+///
+/// - AddRange[specific outcome]
+/// - Replace[list equivalent of Update]
+/// - RemoveRange[specific outcome]
+/// - Moved[specific outcome]
+/// - Clear[specific outcome]
+///
+/// - OnError[forwarded? swallowed? conditional?]
+/// - OnCompleted[immediate? waits for children?]
+///
+///
+///
+/// Source changeset handling (parent events):
+///
+/// EventBehavior
+/// - Add[subscribes to child / creates state]
+/// - Update[disposes old, subscribes to new]
+/// - Remove[disposes, emits downstream removes]
+/// - Refresh[no effect / re-evaluates]
+///
+/// Per-item observable handling:
+///
+/// EmissionBehavior
+/// - First value[what appears downstream]
+/// - Subsequent values[updates? replacements?]
+/// - Error[terminates? swallowed?]
+/// - Completed[freezes? removed?]
+///
+///
+/// Worth noting: [Non-obvious behavior, edge cases, disposal semantics.]
+///
+///
+///
+///
+///
+```
+
+For **secondary overloads** in an overload set:
+```xml
+
+///
+/// A that [specific difference].
+/// This overload [what differs]. Delegates to .
+
+
+
+
+```
+
+## Process
+
+### 1. Analyze the Implementation
+
+Read the actual source code. Trace each code path for every change reason the operator handles.
+
+Key questions:
+- What does each change reason produce downstream? Name the exact output.
+- Are there conditional outcomes? (e.g., Filter's Update has four possible results)
+- Does the operator create per-item subscriptions? When created/disposed?
+- Are errors from child subscriptions forwarded or swallowed?
+- Does OnCompleted wait for child subscriptions?
+
+Refresh behavior varies significantly between operators: some re-evaluate (Filter), some forward as-is (Transform by default), some drop (FilterImmutable), some convert to other change types. But all change reasons deserve the same careful tracing.
+
+### 2. Choose What to Include
+
+| The method... | Template sections to use |
+|---|---|
+| Extends `IObservable`, produces `IObservable` | Single event table (cache or list rows) |
+| Has multiple input sources or per-item observables | Multi-source tables (parent + child) |
+| Extends `ISourceCache`/`ISourceList`, returns void | Single table, framed as "produced" |
+| Produces non-changeset output (`IObservable`, `bool`, etc.) | Single table, framed as subscription lifecycle |
+| Is a class, interface, enum, or non-extension member | Summary, params, returns, remarks (no event table) |
+
+### 3. Apply Quality Rules
+
+**Params**: Every `` must read as natural English with the type linked via `` woven into the sentence. No type is exempt from linking (including enums, `Optional`, `Change`, `IChangeSet`, standard library types like `IComparer`, `TimeSpan`, `IScheduler`). Use `` / `` / `` for C# keywords.
+
+Param writing rules:
+- **Reads as English.** Read it aloud. If it sounds wrong, it IS wrong.
+- **Starts with an article** ("The", "A", "An") or a condition ("When", "If").
+- **Links the actual parameter type** from the method signature. Use ``. Get the generic type arguments right (e.g., `IComparer{TObject}` not `IComparer{T}` when the param type is `IComparer`).
+- **Describes the purpose**, not just the type. Every param description must answer: "what does the caller use this for?"
+- **No redundancy with the type name.** The type `IChangeSet` already says "changeset", so don't add "changeset stream" after it. The type `IObservable` already says "observable", so don't add "observable" after it. The type `IComparer` already says "comparer", so don't add "comparer" after it.
+- **No double articles.** "The source [type] the left stream" has two articles fighting each other.
+- **Combined nested crefs** for observable changeset types. Write `IObservable{IChangeSet{TObject, TKey}}` as one cref. NEVER split into `IObservable{T}` + "of" + `IChangeSet{...}`.
+- **For deeply nested generics** (3+ nesting levels), use `{T}` in the cref and describe the actual type in prose.
+- **For `params` array parameters**, do not include `[]` in the cref.
+- **For `Optional.None`**, use ``.
+
+```xml
+
+
+
+/// The source of .
+
+
+/// The source changeset stream.
+
+
+/// The .
+
+
+/// The comparer used for sorting.
+
+
+/// The source the left input.
+
+
+
+
+/// The source to filter.
+/// The source to transform.
+/// The source to bind.
+/// The to add items to.
+
+
+/// The left to join.
+/// The right to join.
+
+
+/// The that will receive the changes.
+/// The output that will be populated with the results.
+
+
+/// The that determines sort order.
+/// An optional for determining item equality.
+
+
+/// A that projects each source item into a destination item.
+/// A predicate that determines which items to include.
+/// A that extracts the join key from each right item.
+
+
+/// An optional for scheduling expiry timers.
+/// The that controls reset threshold and binding behavior.
+
+
+/// When , re-invokes the transform factory on Refresh changes instead of forwarding them.
+/// When , invokes the callback for all tracked items when the subscription is disposed.
+```
+
+**SeeAlso**: Bidirectional for overload sets. Link safe/async/immutable variants, similar operators, complementary operators, commonly confused operators.
+
+**Type references**: All types use ``, including enums, structs, and standard library types. Method/event/property names use `...`. Internal types must not appear; describe behavior instead.
+
+**Tone**: No em dashes. No emoji. No filler words (comprehensive, robust, seamlessly, leverage, utilize, facilitate). Be specific: "an **Update** is emitted" not "the change is propagated". Use "Worth noting" for non-obvious behavior.
+
+### 4. Verify
+
+```bash
+dotnet build src/DynamicData/DynamicData.csproj --no-restore -c Release --framework net9.0
+```
+- 0 errors, no new CS1574 warnings
+- No em dashes (Unicode 0x2014)
+- No "The source." remaining
+- Spot-check 3-5 operators against implementation
+- Diff against main: confirm nothing lost
+
+## Before and After
+
+**BEFORE** (old-style):
+```xml
+///
+/// Filters the specified source.
+///
+/// The source.
+/// The filter.
+/// An observable which emits change sets.
+```
+
+**AFTER** (publication-quality):
+```xml
+///
+/// Filters items from the source changeset stream using a static predicate.
+/// Only items satisfying are included downstream.
+///
+/// The source of .
+/// A predicate. Items returning true are included.
+/// A changeset stream containing only items satisfying .
+///
+/// Use this overload when the predicate is fixed for the subscription's lifetime.
+///
+/// EventBehavior
+/// - AddPredicate evaluated. If passes, Add emitted. Otherwise dropped.
+/// - UpdateRe-evaluated. Both pass: Update. New passes only: Add. Old passed only: Remove. Neither: dropped.
+/// - RemoveIf downstream, Remove emitted. Otherwise dropped.
+/// - RefreshRe-evaluated. Now passes: Add. Still passes: Refresh. No longer passes: Remove. Still fails: dropped.
+/// - OnErrorForwarded.
+/// - OnCompletedForwarded.
+///
+/// Worth noting: Refresh re-evaluation can promote or demote items.
+///
+///
+///
+///
+```
+
+## Batch Application
+
+1. Dispatch parallel **read-only** agents to analyze implementations (group by family)
+2. Apply edits via **single agent or yourself** (multiple agents editing one file clobber each other)
+3. Build, audit metrics, spot-check accuracy
+4. Diff against main for lost details
+
+## Common Mistakes
+
+| Mistake | Fix |
+|---------|-----|
+| Guessing behavior from method name | Read the implementation |
+| `IComparer` for a type | `` |
+| Referencing internal types | Describe behavior instead |
+| One-way seealso links | Bidirectional |
+| Multiple agents editing same file | One editor at a time |
+| Removing existing information | Additive only; diff against main |
+| Only cache rows for list operators | List needs AddRange, RemoveRange, Moved, Clear |
+| Params missing type links | Every param links its type |
diff --git a/src/DynamicData/Cache/ObservableCacheEx.SortAndBind.cs b/src/DynamicData/Cache/ObservableCacheEx.SortAndBind.cs
index 06c14e0d4..9ce7130f2 100644
--- a/src/DynamicData/Cache/ObservableCacheEx.SortAndBind.cs
+++ b/src/DynamicData/Cache/ObservableCacheEx.SortAndBind.cs
@@ -18,9 +18,10 @@ public static partial class ObservableCacheEx
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// The resulting read only observable collection.
+ /// The source to sort and bind.
+ /// The output that will be populated with the sorted results.
/// An observable which will emit change sets.
+ /// Creates a and delegates to .
public static IObservable> Bind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>(
this IObservable>> source,
out ReadOnlyObservableCollection readOnlyObservableCollection)
@@ -38,10 +39,11 @@ public static partial class ObservableCacheEx
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// The resulting read only observable collection.
- /// Bind and sort default options.
+ /// The source to sort and bind.
+ /// The output that will be populated with the sorted results.
+ /// The with default settings.
/// An observable which will emit change sets.
+ /// Creates a and delegates to .
public static IObservable> Bind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>(
this IObservable>> source,
out ReadOnlyObservableCollection readOnlyObservableCollection,
@@ -60,9 +62,10 @@ public static partial class ObservableCacheEx
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// The list to bind to.
+ /// The source to sort and bind.
+ /// The 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>(
this IObservable>> source,
IList targetList)
@@ -75,10 +78,11 @@ public static partial class ObservableCacheEx
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// The list to bind to.
- /// Bind and sort default options.
+ /// The source to sort and bind.
+ /// The to bind sorted results to.
+ /// The with default settings.
/// An observable which will emit change sets.
+ /// This overload accepts to control reset threshold behavior.
public static IObservable> Bind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>(
this IObservable>> source,
IList targetList,
@@ -92,9 +96,10 @@ public static partial class ObservableCacheEx
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// The resulting read only observable collection.
+ /// The source to sort and bind.
+ /// The output that will be populated with the sorted results.
/// An observable which will emit change sets.
+ /// Creates a and delegates to .
public static IObservable> Bind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>(
this IObservable>> source,
out ReadOnlyObservableCollection readOnlyObservableCollection)
@@ -112,10 +117,11 @@ public static partial class ObservableCacheEx
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// The resulting read only observable collection.
- /// Bind and sort default options.
+ /// The source to sort and bind.
+ /// The output that will be populated with the sorted results.
+ /// The with default settings.
/// An observable which will emit change sets.
+ /// Creates a and delegates to .
public static IObservable> Bind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>(
this IObservable>> source,
out ReadOnlyObservableCollection readOnlyObservableCollection,
@@ -134,9 +140,10 @@ public static partial class ObservableCacheEx
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// The list to bind to.
+ /// The source to sort and bind.
+ /// The 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>(
this IObservable>> source,
IList targetList)
@@ -149,10 +156,11 @@ public static partial class ObservableCacheEx
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// The list to bind to.
- /// Bind and sort default options.
+ /// The source to sort and bind.
+ /// The to bind sorted results to.
+ /// The with default settings.
/// An observable which will emit change sets.
+ /// This overload accepts to control reset threshold behavior.
public static IObservable> Bind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>(
this IObservable>> source,
IList targetList,
@@ -161,14 +169,8 @@ public static partial class ObservableCacheEx
where TKey : notnull =>
new BindVirtualized(source, targetList, options).Run();
- ///
- /// Bind sorted data to the specified collection, for an object which implements IComparable>.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// The list to bind to.
- /// An observable which will emit change sets.
+ ///
+ /// This overload uses for types implementing .
public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>(
this IObservable> source,
IList targetList)
@@ -176,15 +178,8 @@ public static partial class ObservableCacheEx
where TKey : notnull =>
source.SortAndBind(targetList, DynamicDataOptions.SortAndBind);
- ///
- /// Bind sorted data to the specified collection, for an object which implements IComparable>.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// The list to bind to.
- /// Bind and sort default options.
- /// An observable which will emit change sets.
+ ///
+ /// This overload uses for types implementing .
public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>(
this IObservable> source,
IList targetList,
@@ -193,15 +188,7 @@ public static partial class ObservableCacheEx
where TKey : notnull =>
source.SortAndBind(targetList, Comparer.Default, options);
- ///
- /// Bind sorted data to the specified collection.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// The list to bind to.
- /// The comparer to order the resulting dataset.
- /// An observable which will emit change sets.
+ ///
public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>(
this IObservable> source,
IList targetList,
@@ -211,15 +198,34 @@ public static partial class ObservableCacheEx
source.SortAndBind(targetList, comparer, DynamicDataOptions.SortAndBind);
///
- /// Bind sorted data to the specified collection.
+ /// Sorts the source changeset using and applies incremental changes
+ /// directly to , keeping it sorted in-place.
+ /// Combines the behavior of Sort and Bind into a single optimized step.
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// The list to bind to.
- /// The comparer to order the resulting dataset.
- /// Bind and sort default options.
+ /// 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 controlling reset threshold and initial capacity.
/// An observable which will emit change sets.
+ ///
+ ///
+ /// This operator is the preferred replacement for the .Sort().Bind() chain.
+ /// It applies sort logic and collection mutations in a single pass, avoiding intermediate allocations.
+ ///
+ ///
+ /// EventBehavior
+ /// - AddItem inserted at the correct sorted position in .
+ /// - UpdateOld item removed and new item inserted at its sorted position.
+ /// - RemoveItem removed from .
+ /// - RefreshSort position is re-evaluated. If the position changed, the item is moved in-place.
+ /// - OnErrorForwarded to the downstream observer.
+ /// - OnCompletedForwarded to the downstream observer.
+ ///
+ /// 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.
+ ///
+ ///
public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>(
this IObservable> source,
IList targetList,
@@ -229,15 +235,7 @@ public static partial class ObservableCacheEx
where TKey : notnull =>
new SortAndBind(source, comparer, options, targetList).Run();
- ///
- /// Bind sorted data to the specified collection, using an observable of comparers to switch sort order.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// The list to bind to.
- /// An observable of comparers which enables the sort order to be changed.>
- /// An observable which will emit change sets.
+ ///
public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>(
this IObservable> source,
IList targetList,
@@ -247,15 +245,33 @@ public static partial class ObservableCacheEx
source.SortAndBind(targetList, comparerChanged, DynamicDataOptions.SortAndBind);
///
- /// Bind sorted data to the specified collection, using an observable of comparers to switch sort order.
+ /// Sorts the source changeset and applies incremental changes directly to ,
+ /// re-sorting when the comparer observable emits a new comparer.
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// The list to bind to.
- /// An observable of comparers which enables the sort order to be changed.>
- /// Bind and sort default options.
+ /// 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 controlling reset threshold and initial capacity.
/// An observable which will emit change sets.
+ ///
+ ///
+ /// When emits a new comparer, all items are re-sorted and the target list is updated.
+ /// No data is emitted until the first comparer arrives.
+ ///
+ ///
+ /// EventBehavior
+ /// - AddItem inserted at the correct sorted position in .
+ /// - UpdateOld item removed and new item inserted at its sorted position.
+ /// - RemoveItem removed from .
+ /// - RefreshSort position is re-evaluated. If the position changed, the item is moved in-place.
+ /// - Comparer changedFull re-sort of all items. The target list is updated to reflect the new order.
+ /// - OnErrorForwarded to the downstream observer.
+ ///
+ /// 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 .
+ ///
+ ///
public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>(
this IObservable> source,
IList targetList,
@@ -265,14 +281,8 @@ public static partial class ObservableCacheEx
where TKey : notnull =>
new SortAndBind(source, comparerChanged, options, targetList).Run();
- ///
- /// Bind sorted data to the specified readonly observable collection for an object which implements IComparable>.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// The resulting read only observable collection.
- /// An observable which will emit change sets.
+ ///
+ /// This overload uses for types implementing .
public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>(
this IObservable> source,
out ReadOnlyObservableCollection readOnlyObservableCollection)
@@ -280,15 +290,8 @@ public static partial class ObservableCacheEx
where TKey : notnull =>
source.SortAndBind(out readOnlyObservableCollection, Comparer.Default, DynamicDataOptions.SortAndBind);
- ///
- /// Bind sorted data to the specified readonly observable collection for an object which implements IComparable>.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// The resulting read only observable collection.
- /// Bind and sort default options.
- /// An observable which will emit change sets.
+ ///
+ /// This overload uses for types implementing .
public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>(
this IObservable> source,
out ReadOnlyObservableCollection readOnlyObservableCollection,
@@ -297,15 +300,7 @@ public static partial class ObservableCacheEx
where TKey : notnull =>
source.SortAndBind(out readOnlyObservableCollection, Comparer.Default, options);
- ///
- /// Bind sorted data to the specified readonly observable collection.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// The resulting read only observable collection.
- /// The comparer to order the resulting dataset.
- /// An observable which will emit change sets.
+ ///
public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>(
this IObservable> source,
out ReadOnlyObservableCollection readOnlyObservableCollection,
@@ -314,16 +309,11 @@ public static partial class ObservableCacheEx
where TKey : notnull =>
source.SortAndBind(out readOnlyObservableCollection, comparer, DynamicDataOptions.SortAndBind);
- ///
- /// Bind sorted data to the specified readonly observable collection.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// The resulting read only observable collection.
- /// The comparer to order the resulting dataset.
- /// Bind and sort default options.
- /// An observable which will emit change sets.
+ ///
+ /// The source to sort and bind.
+ /// The output that will be populated with the sorted results.
+ /// The that determines sort order.
+ /// The controlling reset threshold and initial capacity.
public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>(
this IObservable> source,
out ReadOnlyObservableCollection readOnlyObservableCollection,
@@ -342,15 +332,7 @@ public static partial class ObservableCacheEx
return new SortAndBind(source, comparer, options, observableCollection).Run();
}
- ///
- /// Bind sorted data to the specified readonly observable collection, using an observable of comparers to switch sort order.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// The resulting read only observable collection.
- /// An observable of comparers which enables the sort order to be changed.
- /// An observable which will emit change sets.
+ ///
public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>(
this IObservable> source,
out ReadOnlyObservableCollection readOnlyObservableCollection,
@@ -359,16 +341,11 @@ public static partial class ObservableCacheEx
where TKey : notnull =>
source.SortAndBind(out readOnlyObservableCollection, comparerChanged, DynamicDataOptions.SortAndBind);
- ///
- /// Bind sorted data to the specified readonly observable collection, using an observable of comparers to switch sort order.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// The resulting read only observable collection.
- /// An observable of comparers which enables the sort order to be changed.>
- /// Bind and sort default options.
- /// An observable which will emit change sets.
+ ///
+ /// 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.
+ /// The controlling reset threshold and initial capacity.
public static IObservable> SortAndBind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TObject, TKey>(
this IObservable> source,
out ReadOnlyObservableCollection readOnlyObservableCollection,
diff --git a/src/DynamicData/Cache/ObservableCacheEx.VirtualiseAndPage.cs b/src/DynamicData/Cache/ObservableCacheEx.VirtualiseAndPage.cs
index d4a5f060c..d5a4bd979 100644
--- a/src/DynamicData/Cache/ObservableCacheEx.VirtualiseAndPage.cs
+++ b/src/DynamicData/Cache/ObservableCacheEx.VirtualiseAndPage.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved.
+// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved.
// Roland Pheasant licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.
@@ -12,16 +12,8 @@ namespace DynamicData;
///
public static partial class ObservableCacheEx
{
- ///
- /// Sort and virtualize the underlying data from the specified source.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// The comparer to order the resulting dataset.
- /// The virtualizing requests.
- /// An observable which will emit virtual change sets.
- /// source.
+ ///
+ /// This overload uses default .
public static IObservable>> SortAndVirtualize(this IObservable> source,
IComparer comparer,
IObservable virtualRequests)
@@ -29,16 +21,8 @@ public static IObservable>> So
where TKey : notnull =>
source.SortAndVirtualize(comparer, virtualRequests, new SortAndVirtualizeOptions());
- ///
- /// Sort and virtualize the underlying data from the specified source.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// An observable of comparers which enables the sort order to be changed.>
- /// The virtualizing requests.
- /// An observable which will emit virtual change sets.
- /// source.
+ ///
+ /// This overload uses default .
public static IObservable>> SortAndVirtualize(
this IObservable> source,
IObservable> comparerChanged,
@@ -53,16 +37,35 @@ public static IObservable>> So
}
///
- /// Sort and virtualize the underlying data from the specified source.
+ /// Sorts unsorted data using , then returns only items within the
+ /// virtual window defined by .
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// The comparer to order the resulting dataset.
- /// The virtualizing requests.
- /// Addition optimization options for virtualization.
+ /// The source to paginate.
+ /// The that determines sort order.
+ /// The that controls which window of sorted items to include.
+ /// The for controlling virtualization behavior.
/// An observable which will emit virtual change sets.
/// source.
+ ///
+ ///
+ /// Combines sorting and index-based windowing. Only items within the current virtual window are emitted.
+ /// Use the observable comparer overload if you need to change sort order at runtime.
+ ///
+ ///
+ /// EventBehavior
+ /// - AddIf the new item's sorted position falls within the window, an Add is emitted. Items pushed out of the window produce a Remove.
+ /// - UpdateIf the updated item is within the window, an Update is emitted. Sort position changes may cause items to enter or leave the window.
+ /// - RemoveIf the removed item was within the window, a Remove is emitted. Items shifted into the window produce an Add.
+ /// - RefreshSort position is re-evaluated. Window membership may change.
+ /// - OnErrorForwarded to the downstream observer.
+ /// - OnCompletedForwarded to the downstream observer.
+ ///
+ /// Worth noting: No data is emitted until produces its first value. Changing the window can cause a full recalculation of visible items.
+ ///
+ ///
+ ///
public static IObservable>> SortAndVirtualize(
this IObservable> source,
IComparer comparer,
@@ -78,16 +81,35 @@ public static IObservable>> So
}
///
- /// Sort and virtualize the underlying data from the specified source.
+ /// Sorts unsorted data, then returns only the items within the virtual window defined by
+ /// (start index + size). Re-sorts when the comparer observable emits.
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// An observable of comparers which enables the sort order to be changed.>
- /// The virtualizing requests.
- /// Addition optimization options for virtualization.
+ /// The source to paginate.
+ /// An that emits new comparers to re-sort with.
+ /// The that controls which window of sorted items to include.
+ /// The for controlling virtualization behavior.
/// An observable which will emit virtual change sets.
/// source.
+ ///
+ ///
+ /// Combines sorting and index-based windowing in a single operator. Only items within the
+ /// current virtual window are emitted downstream. The window is defined by a start index and size.
+ ///
+ ///
+ /// EventBehavior
+ /// - AddIf the new item's sorted position falls within the window, an Add is emitted. Items pushed out of the window produce a Remove.
+ /// - UpdateIf the updated item is within the window, an Update is emitted. Sort position changes may cause items to enter or leave the window.
+ /// - RemoveIf the removed item was within the window, a Remove is emitted. Items shifted into the window produce an Add.
+ /// - RefreshSort position is re-evaluated. Window membership may change.
+ /// - OnErrorForwarded to the downstream observer.
+ /// - OnCompletedForwarded to the downstream observer.
+ ///
+ /// 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.
+ ///
+ ///
+ ///
public static IObservable>> SortAndVirtualize(
this IObservable> source,
IObservable> comparerChanged,
@@ -107,8 +129,8 @@ public static IObservable>> So
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// The virtualising requests.
+ /// The source to paginate.
+ /// The that controls which window of sorted items to include.
/// An observable which will emit virtual change sets.
/// source.
[Obsolete(Constants.VirtualizeIsObsolete)]
@@ -123,16 +145,25 @@ public static IObservable> Virtualise
- /// Limits the size of the result set to the specified number, ordering by the comparer.
+ /// Returns the top items from the source, sorted by .
+ /// Equivalent to SortAndVirtualize with a fixed window starting at index 0.
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// The comparer.
- /// The size.
+ /// The source to limit.
+ /// The 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
+ /// 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.
+ ///
+ ///
public static IObservable>> Top(this IObservable> source, IComparer comparer, int size)
where TObject : notnull
where TKey : notnull
@@ -153,8 +184,8 @@ public static IObservable>> To
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// The size.
+ /// The source to paginate.
+ /// The maximum number of items to include.
/// An observable which will emit virtual change sets.
/// source.
/// size;Size should be greater than zero.
@@ -173,16 +204,8 @@ public static IObservable> Top(t
return new Virtualise(source, Observable.Return(new VirtualRequest(0, size))).Run();
}
- ///
- /// Sort and page the underlying data from the specified source.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// The comparer to order the resulting dataset.
- /// The virtualizing requests.
- /// An observable which will emit virtual change sets.
- /// source.
+ ///
+ /// This overload uses default .
public static IObservable>> SortAndPage(this IObservable> source,
IComparer comparer,
IObservable pageRequests)
@@ -190,16 +213,8 @@ public static IObservable>> SortA
where TKey : notnull =>
source.SortAndPage(comparer, pageRequests, new SortAndPageOptions());
- ///
- /// Sort and page the underlying data from the specified source.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// An observable of comparers which enables the sort order to be changed.>
- /// The virtualizing requests.
- /// An observable which will emit virtual change sets.
- /// source.
+ ///
+ /// This overload uses default .
public static IObservable>> SortAndPage(
this IObservable> source,
IObservable> comparerChanged,
@@ -214,16 +229,34 @@ public static IObservable>> SortA
}
///
- /// Sort and page the underlying data from the specified source.
+ /// Sorts unsorted data using , then pages the result using
+ /// .
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// The comparer to order the resulting dataset.
- /// The virtualizing requests.
- /// Addition optimization options for virtualization.
- /// An observable which will emit virtual change sets.
+ /// The source to paginate.
+ /// The that determines sort order.
+ /// The that controls which page of sorted items to include.
+ /// The for controlling paging behavior.
+ /// An observable which will emit paged change sets.
/// source.
+ ///
+ ///
+ /// Combines sorting and page-based windowing. Only items on the current page are emitted.
+ /// Use the observable comparer overload if you need to change sort order at runtime.
+ ///
+ ///
+ /// EventBehavior
+ /// - AddIf the new item's sorted position falls on the current page, an Add is emitted. Items pushed off the page produce a Remove.
+ /// - UpdateIf the updated item is on the current page, an Update is emitted. Sort position changes may move items on or off the page.
+ /// - RemoveIf the removed item was on the current page, a Remove is emitted. Items shifted onto the page produce an Add.
+ /// - RefreshSort position is re-evaluated. Page membership may change.
+ /// - OnErrorForwarded to the downstream observer.
+ /// - OnCompletedForwarded to the downstream observer.
+ ///
+ /// 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.
+ ///
+ ///
public static IObservable>> SortAndPage(
this IObservable> source,
IComparer comparer,
@@ -239,16 +272,34 @@ public static IObservable>> SortA
}
///
- /// Sort and page the underlying data from the specified source.
+ /// Sorts unsorted data, then pages the result using page number and page size from
+ /// . Re-sorts when the comparer observable emits.
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// An observable of comparers which enables the sort order to be changed.>
- /// The virtualizing requests.
- /// Addition optimization options for virtualization.
- /// An observable which will emit virtual change sets.
+ /// The source to paginate.
+ /// An that emits new comparers to re-sort with.
+ /// The that controls which page of sorted items to include.
+ /// The for controlling paging behavior.
+ /// An observable which will emit paged change sets.
/// source.
+ ///
+ ///
+ /// Combines sorting and page-based windowing in a single operator. Only items on the current page
+ /// are emitted downstream. The page is defined by a 1-based page number and page size.
+ ///
+ ///
+ /// EventBehavior
+ /// - AddIf the new item's sorted position falls on the current page, an Add is emitted. Items pushed off the page produce a Remove.
+ /// - UpdateIf the updated item is on the current page, an Update is emitted. Sort position changes may move items on or off the page.
+ /// - RemoveIf the removed item was on the current page, a Remove is emitted. Items shifted onto the page produce an Add.
+ /// - RefreshSort position is re-evaluated. Page membership may change.
+ /// - OnErrorForwarded to the downstream observer.
+ /// - OnCompletedForwarded to the downstream observer.
+ ///
+ /// 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.
+ ///
+ ///
public static IObservable>> SortAndPage(
this IObservable> source,
IObservable> comparerChanged,
@@ -268,8 +319,8 @@ public static IObservable>> SortA
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// The page requests.
+ /// The source to paginate.
+ /// The 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)
diff --git a/src/DynamicData/Cache/ObservableCacheEx.cs b/src/DynamicData/Cache/ObservableCacheEx.cs
index 4edf35285..e532d15d1 100644
--- a/src/DynamicData/Cache/ObservableCacheEx.cs
+++ b/src/DynamicData/Cache/ObservableCacheEx.cs
@@ -28,18 +28,32 @@ public static partial class ObservableCacheEx
private const bool DefaultResortOnSourceRefresh = true;
///
- /// Inject side effects into the stream using the specified adaptor.
+ /// Injects a side effect into the changeset stream by calling .
+ /// for every changeset, then forwarding it downstream unchanged.
///
- /// The type of the object.
+ /// The type of items in the cache.
/// The type of the key.
- /// The source.
- /// The adaptor.
- /// An observable which will emit change sets.
- ///
- /// source
- /// or
- /// destination.
- ///
+ /// The source to observe and adapt.
+ /// The whose Adapt method is called for each changeset.
+ /// An observable that emits the same changesets as , after the adaptor has processed each one.
+ ///
+ ///
+ /// This is a thin wrapper around Rx's Do operator. The adaptor receives each changeset
+ /// as a side effect; the changeset itself is forwarded downstream unmodified.
+ ///
+ ///
+ /// EventBehavior
+ /// - AddPassed to the adaptor, then forwarded.
+ /// - UpdatePassed to the adaptor, then forwarded.
+ /// - RemovePassed to the adaptor, then forwarded.
+ /// - RefreshPassed to the adaptor, then forwarded.
+ /// - OnErrorForwarded to the downstream observer. The adaptor is not called.
+ /// - OnCompletedForwarded to the downstream observer.
+ ///
+ ///
+ /// or is .
+ ///
+ ///
public static IObservable> Adapt(this IObservable> source, IChangeSetAdaptor adaptor)
where TObject : notnull
where TKey : notnull
@@ -50,19 +64,10 @@ public static IObservable> Adapt(this I
return source.Do(adaptor.Adapt);
}
- ///
- /// Inject side effects into the stream using the specified sorted adaptor.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// The adaptor.
- /// An observable which will emit change sets.
- ///
- /// source
- /// or
- /// destination.
- ///
+ ///
+ /// 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.
public static IObservable> Adapt(this IObservable> source, ISortedChangeSetAdaptor adaptor)
where TObject : notnull
where TKey : notnull
@@ -74,13 +79,26 @@ public static IObservable> Adapt(this I
}
///
- /// Adds or updates the cache with the specified item.
+ /// Adds or updates the cache with the specified item, producing a changeset with a single Add
+ /// (if the key is new) or Update (if the key already exists).
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// The item.
- /// source.
+ /// The to add or update items in.
+ /// The item to add or update.
+ ///
+ /// Convenience method that wraps a single-item mutation inside .
+ ///
+ /// EventBehavior
+ /// - AddProduced when the key does not already exist in the cache.
+ /// - UpdateProduced when the key already exists. The previous value is included in the changeset.
+ /// - RemoveNot produced by this method.
+ /// - RefreshNot produced by this method.
+ ///
+ ///
+ /// is .
+ ///
+ ///
public static void AddOrUpdate(this ISourceCache source, TObject item)
where TObject : notnull
where TKey : notnull
@@ -90,15 +108,11 @@ public static void AddOrUpdate(this ISourceCache s
source.Edit(updater => updater.AddOrUpdate(item));
}
- ///
- /// Adds or updates the cache with the specified item.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// The item.
- /// The equality comparer used to determine whether a new item is the same as an existing cached item.
- /// source.
+ ///
+ /// The 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.
+ /// 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
@@ -108,16 +122,10 @@ public static void AddOrUpdate(this ISourceCache s
source.Edit(updater => updater.AddOrUpdate(item, equalityComparer));
}
- ///
- ///
- /// Adds or updates the cache with the specified items.
- ///
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// The items.
- /// source.
+ ///
+ /// 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.
public static void AddOrUpdate(this ISourceCache source, IEnumerable items)
where TObject : notnull
where TKey : notnull
@@ -127,17 +135,11 @@ public static void AddOrUpdate(this ISourceCache s
source.Edit(updater => updater.AddOrUpdate(items));
}
- ///
- ///
- /// Adds or updates the cache with the specified items.
- ///
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// The items.
- /// The equality comparer used to determine whether a new item is the same as an existing cached item.
- /// source.
+ ///
+ /// 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.
public static void AddOrUpdate(this ISourceCache source, IEnumerable items, IEqualityComparer equalityComparer)
where TObject : notnull
where TKey : notnull
@@ -147,15 +149,11 @@ public static void AddOrUpdate(this ISourceCache s
source.Edit(updater => updater.AddOrUpdate(items, equalityComparer));
}
- ///
- /// Adds or updates the cache with the specified item / key pair.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source cache.
- /// The item to add or update.
- /// The key to add or update.
- /// source.
+ ///
+ /// The 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.
public static void AddOrUpdate(this IIntermediateCache source, TObject item, TKey key)
where TObject : notnull
where TKey : notnull
@@ -172,10 +170,11 @@ public static void AddOrUpdate(this IIntermediateCache
/// The type of the object.
/// The type of the key.
- /// The source.
- /// The others.
+ /// The source to combine.
+ /// The additional streams to combine with.
/// An observable which emits change sets.
/// source or others.
+ ///
public static IObservable> And(this IObservable> source, params IObservable>[] others)
where TObject : notnull
where TKey : notnull
@@ -192,7 +191,7 @@ public static IObservable> And(this IOb
///
/// The type of the object.
/// The type of the key.
- /// The source.
+ /// The of streams to combine.
/// An observable which emits change sets.
///
/// source
@@ -214,7 +213,7 @@ public static IObservable> And(this ICo
///
/// The type of the object.
/// The type of the key.
- /// The source.
+ /// The of streams to combine.
/// An observable which emits change sets.
public static IObservable> And(this IObservableList>> sources)
where TObject : notnull
@@ -231,7 +230,7 @@ public static IObservable> And(this IOb
///
/// The type of the object.
/// The type of the key.
- /// The source.
+ /// The of changeset streams to combine.
/// An observable which emits change sets.
public static IObservable> And(this IObservableList> sources)
where TObject : notnull
@@ -248,7 +247,7 @@ public static IObservable> And(this IOb
///
/// The type of the object.
/// The type of the key.
- /// The source.
+ /// The of changeset streams to combine.
/// An observable which emits change sets.
public static IObservable> And(this IObservableList> sources)
where TObject : notnull
@@ -260,13 +259,14 @@ public static IObservable> And(this IOb
}
///
- /// Converts the source to an read only observable cache.
+ /// Wraps an in a read-only facade, hiding the mutable API.
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// An observable cache.
- /// source.
+ /// The to operate on.
+ /// A read-only .
+ /// is .
+ ///
public static IObservableCache AsObservableCache(this IObservableCache source)
where TObject : notnull
where TKey : notnull
@@ -277,14 +277,24 @@ public static IObservableCache AsObservableCache(t
}
///
- /// Converts the source to a readonly observable cache.
+ /// Materializes a changeset stream into a queryable, read-only .
+ /// 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.
- /// if set to true all methods are synchronised. There is no need to apply locking when the consumer can be sure the read / write operations are already synchronised.
- /// An observable cache.
- /// source.
+ /// The source 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.
+ ///
+ ///
+ /// 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.
+ ///
+ /// is .
+ ///
+ ///
public static IObservableCache AsObservableCache(this IObservable> source, bool applyLocking = true)
where TObject : notnull
where TKey : notnull
@@ -302,34 +312,48 @@ public static IObservableCache AsObservableCache(t
#if SUPPORTS_ASYNC_DISPOSABLE
///
///
- /// Automatically disposes items within the source collection, upon removal of the collection or teardown of the operator.
- ///
- ///
- /// Individual items are disposed after removal or replacement changes have been sent downstream.
- /// All items previously-published on the stream are disposed after the stream finalizes.
- /// This includes both upstream completion or failure, or downstream un-subscription.
+ /// Disposes items implementing or when they are removed or replaced,
+ /// and disposes all tracked items when the stream completes, errors, or the subscription is disposed.
///
///
- /// Disposal is supported for both and items.
- /// Items implementing neither of these interfaces are unaffected by this operator.
+ /// Individual items are disposed after the changeset has been forwarded downstream, so downstream operators
+ /// see the removal before disposal occurs. Items implementing neither disposal interface are ignored.
///
///
- /// The type of items in the source collection.
- /// The type of key values used to uniquely identify items in the source collection.
- /// A stream of changes from the source collection.
+ /// The type of items in the cache.
+ /// The type of the key.
+ /// The source to track for async disposal on removal.
///
///
- /// An action to be invoked upon each subscription to this operator, allowing the consumer access to the "disposalsCompleted" stream for that subscription.
+ /// Invoked once per subscription, providing an that signals when all
+ /// calls have finished. The signal emits a single value
+ /// and then completes.
+ ///
+ ///
+ /// This is delivered on a separate channel from the main changeset stream so it can be observed even
+ /// if the source stream errors.
///
+ ///
+ /// A stream that forwards all changesets from unchanged.
+ ///
///
- /// The "disposalsCompleted" stream allows the consumer to properly observe the asynchronous disposal of any items that are disposed by the operator. This stream will emit a single value, and then complete, upon successfull completion of all invocations performed by the operator.
+ /// Change reason handling:
+ ///
+ /// EventBehavior
+ /// - AddTracks the item. No disposal.
+ /// - UpdateDisposes the previous value (if it differs by reference from the current). Tracks the new value.
+ /// - RemoveDisposes the removed item.
+ /// - RefreshPassed through. No disposal.
+ ///
///
///
- /// Providing these notifications within a downstream channel separate from the main collection change stream ensures that these notifications can be observed even in the event of a failure within the operator, or within stream.
+ /// On stream completion, error, or subscription disposal, all items still in the cache are disposed.
+ /// items are disposed synchronously; items
+ /// are dispatched via the signal.
///
- ///
- /// A stream containing copies of all changes observed from .
- /// Throws for and .
+ ///
+ /// or is .
+ ///
public static IObservable> AsyncDisposeMany(
this IObservable> source,
Action> disposalsCompletedAccessor)
@@ -345,11 +369,12 @@ public static IObservable> AsyncDisposeMany
/// The object of the change set.
/// The key of the change set.
- /// The source observable.
- /// Batch up changes by specifying the buffer. This greatly increases performance when many elements have successive property changes.
- /// When observing on multiple property changes, apply a throttle to prevent excessive refresh invocations.
- /// The scheduler.
+ /// The source 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.
+ ///
public static IObservable> AutoRefresh(this IObservable> source, TimeSpan? changeSetBuffer = null, TimeSpan? propertyChangeThrottle = null, IScheduler? scheduler = null)
where TObject : INotifyPropertyChanged
where TKey : notnull
@@ -376,11 +401,11 @@ public static IObservable> AutoRefresh(
/// The object of the change set.
/// The key of the change set.
/// The type of the property.
- /// The source observable.
- /// Specify a property to observe changes. When it changes a Refresh is invoked.
- /// Batch up changes by specifying the buffer. This greatly increases performance when many elements have successive property changes.
- /// When observing on multiple property changes, apply a throttle to prevent excessive refresh invocations.
- /// The scheduler.
+ /// The source to monitor for property-driven refresh signals.
+ /// A 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.
/// An observable change set with additional refresh changes.
public static IObservable> AutoRefresh(this IObservable> source, Expression> propertyAccessor, TimeSpan? changeSetBuffer = null, TimeSpan? propertyChangeThrottle = null, IScheduler? scheduler = null)
where TObject : INotifyPropertyChanged
@@ -408,11 +433,12 @@ public static IObservable> AutoRefreshThe object of the change set.
/// The key of the change set.
/// The type of evaluation.
- /// The source observable change set.
- /// An observable which acts on items within the collection and produces a value when the item should be refreshed.
- /// Batch up changes by specifying the buffer. This greatly increases performance when many elements require a refresh.
- /// The scheduler.
+ /// 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.
+ /// 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.
+ ///
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);
@@ -423,11 +449,14 @@ public static IObservable> AutoRefreshOnObservableThe object of the change set.
/// The key of the change set.
/// The type of evaluation.
- /// The source observable change set.
- /// An observable which acts on items within the collection and produces a value when the item should be refreshed.
- /// Batch up changes by specifying the buffer. This greatly increases performance when many elements require a refresh.
- /// The scheduler.
+ /// 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.
+ /// 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.
+ ///
+ /// Worth noting: Per-item observable errors are silently ignored (not forwarded to the downstream observer). Only source stream errors propagate.
+ ///
public static IObservable> AutoRefreshOnObservable(this IObservable> source, Func> reevaluator, TimeSpan? changeSetBuffer = null, IScheduler? scheduler = null)
where TObject : notnull
where TKey : notnull
@@ -439,17 +468,34 @@ public static IObservable> AutoRefreshOnObservable
- /// Batches the updates for the specified time period.
+ /// Collects changesets emitted within a time window and merges them into a single changeset.
+ /// Uses Rx's Buffer operator followed by .
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// The time span.
- /// The scheduler.
- /// An observable which emits change sets.
- /// source
- /// or
- /// scheduler.
+ /// The source to batch.
+ /// The time window for batching.
+ /// The scheduler for timing. Defaults to .
+ /// An observable that emits merged changesets, one per time window.
+ ///
+ ///
+ /// All changesets received during the time window are concatenated into a single changeset.
+ /// This is useful for reducing UI update frequency when the source emits many rapid changes.
+ ///
+ ///
+ /// EventBehavior
+ /// - AddBuffered and included in the merged changeset at the end of the time window.
+ /// - UpdateBuffered and included in the merged changeset.
+ /// - RemoveBuffered and included in the merged changeset.
+ /// - RefreshBuffered and included in the merged changeset.
+ /// - OnErrorForwarded to the downstream observer.
+ /// - OnCompletedAny remaining buffered changes are flushed, then completion is forwarded.
+ ///
+ /// 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 .
+ ///
+ ///
public static IObservable> Batch(this IObservable> source, TimeSpan timeSpan, IScheduler? scheduler = null)
where TObject : notnull
where TKey : notnull
@@ -459,66 +505,55 @@ public static IObservable> Batch(this I
return source.Buffer(timeSpan, scheduler ?? GlobalConfig.DefaultScheduler).FlattenBufferResult();
}
- ///
- /// Batches the underlying updates if a pause signal (i.e when the buffer selector return true) has been received.
- /// When a resume signal has been received the batched updates will be fired.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// When true, observable begins to buffer and when false, window closes and buffered result if notified.
- /// The scheduler.
- /// An observable which emits change sets.
- /// source.
+ ///
+ /// 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);
- ///
- /// Batches the underlying updates if a pause signal (i.e when the buffer selector return true) has been received.
- /// When a resume signal has been received the batched updates will be fired.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// When true, observable begins to buffer and when false, window closes and buffered result if notified.
- /// if set to true [initial pause state].
- /// The scheduler.
- /// An observable which emits change sets.
- /// source.
+ ///
+ /// 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();
- ///
- /// Batches the underlying updates if a pause signal (i.e when the buffer selector return true) has been received.
- /// When a resume signal has been received the batched updates will be fired.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// When true, observable begins to buffer and when false, window closes and buffered result if notified.
- /// Specify a time to ensure the buffer window does not stay open for too long. On completion buffering will cease.
- /// The scheduler.
- /// An observable which emits change sets.
- /// source.
+ ///
+ /// 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
where TKey : notnull => BatchIf(source, pauseIfTrueSelector, false, timeOut, scheduler);
///
- /// Batches the underlying updates if a pause signal (i.e when the buffer selector return true) has been received.
- /// When a resume signal has been received the batched updates will be fired.
+ /// Conditionally buffers changesets while a pause signal is active, then flushes all buffered
+ /// changes as a single merged changeset when the signal resumes.
///
/// The type of the object.
/// The type of the key.
- /// The source.
- /// When true, observable begins to buffer and when false, window closes and buffered result if notified.
- /// if set to true [initial pause state].
- /// Specify a time to ensure the buffer window does not stay open for too long. On completion buffering will cease.
- /// The scheduler.
- /// An observable which emits change sets.
- /// source.
+ /// The source to conditionally buffer.
+ /// An 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.
+ /// An observable that emits changesets, buffered or passthrough depending on pause state.
+ ///
+ ///
+ /// While paused, incoming changesets are accumulated. On resume (or timeout), all buffered changesets
+ /// are merged into a single changeset and emitted. While not paused, changesets pass through immediately.
+ ///
+ ///
+ /// EventBehavior
+ /// - AddBuffered while paused; forwarded immediately while active.
+ /// - UpdateBuffered while paused; forwarded immediately while active.
+ /// - RemoveBuffered while paused; forwarded immediately while active.
+ /// - RefreshBuffered while paused; forwarded immediately while active.
+ /// - OnErrorForwarded to the downstream observer. Buffered data is lost.
+ /// - OnCompletedForwarded. Any remaining buffered data is flushed before completion.
+ ///
+ /// 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 .
+ ///
+ ///
public static IObservable> BatchIf(this IObservable> source, IObservable pauseIfTrueSelector, bool initialPauseState = false, TimeSpan? timeOut = null, IScheduler? scheduler = null)
where TObject : notnull
where TKey : notnull
@@ -529,19 +564,13 @@ public static IObservable> BatchIf(this
return new BatchIf(source, pauseIfTrueSelector, timeOut, initialPauseState, scheduler: scheduler).Run();
}
- ///
- /// Batches the underlying updates if a pause signal (i.e when the buffer selector return true) has been received.
- /// When a resume signal has been received the batched updates will be fired.
- ///
- /// The type of the object.
- /// The type of the key.
- /// The source.
- /// When true, observable begins to buffer and when false, window closes and buffered result if notified.
- /// if set to true [initial pause state].
- /// Specify a time observable. The buffer will be emptied each time the timer produces a value and when it completes. On completion buffering will cease.
- /// The scheduler.
- /// An observable which emits change sets.
- /// source.
+ ///
+ /// The source to conditionally buffer.
+ /// An 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 for scheduling work.
+ /// This overload accepts an explicit timer observable instead of a timeout.
public static IObservable> BatchIf(this IObservable> source, IObservable