Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/DynamicData/Cache/ObservableCacheEx.Adapt.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved.
// Roland Pheasant licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
using DynamicData.Binding;
using DynamicData.Cache;
using DynamicData.Cache.Internal;

// ReSharper disable once CheckNamespace

namespace DynamicData;

/// <summary>
/// Extensions for dynamic data.
/// </summary>
public static partial class ObservableCacheEx
{
/// <summary>
/// Injects a side effect into the changeset stream by calling <paramref name="adaptor"/>.<see cref="IChangeSetAdaptor{TObject, TKey}.Adapt(IChangeSet{TObject, TKey})"/>
/// for every changeset, then forwarding it downstream unchanged.
/// </summary>
/// <typeparam name="TObject">The type of items in the cache.</typeparam>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <param name="source">The source <see cref="IObservable{IChangeSet{TObject, TKey}}"/> to observe and adapt.</param>

Check warning on line 34 in src/DynamicData/Cache/ObservableCacheEx.Adapt.cs

View workflow job for this annotation

GitHub Actions / build

Type parameter declaration must be an identifier not a type. See also error CS0081.

Check warning on line 34 in src/DynamicData/Cache/ObservableCacheEx.Adapt.cs

View workflow job for this annotation

GitHub Actions / build

XML comment has syntactically incorrect cref attribute 'IObservable{IChangeSet{TObject, TKey}}'

Check warning on line 34 in src/DynamicData/Cache/ObservableCacheEx.Adapt.cs

View workflow job for this annotation

GitHub Actions / build

XML comment has syntactically incorrect cref attribute 'IObservable{IChangeSet{TObject, TKey}}'
/// <param name="adaptor">The <see cref="IChangeSetAdaptor{TObject, TKey}"/> whose Adapt method is called for each changeset.</param>
/// <returns>An observable that emits the same changesets as <paramref name="source"/>, after the adaptor has processed each one.</returns>
/// <remarks>
/// <para>
/// This is a thin wrapper around Rx's <c>Do</c> operator. The adaptor receives each changeset
/// as a side effect; the changeset itself is forwarded downstream unmodified.
/// </para>
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="adaptor"/> is <see langword="null"/>.</exception>
/// <seealso cref="Adapt{TObject, TKey}(IObservable{ISortedChangeSet{TObject, TKey}}, ISortedChangeSetAdaptor{TObject, TKey})"/>
/// <seealso cref="Bind{TObject, TKey}(IObservable{IChangeSet{TObject, TKey}}, IObservableCollection{TObject}, IObservableCollectionAdaptor{TObject, TKey})"/>
public static IObservable<IChangeSet<TObject, TKey>> Adapt<TObject, TKey>(this IObservable<IChangeSet<TObject, TKey>> source, IChangeSetAdaptor<TObject, TKey> adaptor)
where TObject : notnull
where TKey : notnull
{
source.ThrowArgumentNullExceptionIfNull(nameof(source));
adaptor.ThrowArgumentNullExceptionIfNull(nameof(adaptor));

return source.Do(adaptor.Adapt);
}

/// <inheritdoc cref="Adapt{TObject, TKey}(IObservable{IChangeSet{TObject, TKey}}, IChangeSetAdaptor{TObject, TKey})"/>
/// <param name="source">The source <see cref="IObservable{ISortedChangeSet{TObject, TKey}}"/> to observe and adapt.</param>
/// <param name="adaptor">The <see cref="ISortedChangeSetAdaptor{TObject, TKey}"/> whose Adapt method is called for each changeset.</param>
/// <remarks>This overload operates on <see cref="ISortedChangeSet{TObject, TKey}"/>. Delegates to Rx's <c>Do</c> operator.</remarks>
public static IObservable<IChangeSet<TObject, TKey>> Adapt<TObject, TKey>(this IObservable<ISortedChangeSet<TObject, TKey>> source, ISortedChangeSetAdaptor<TObject, TKey> adaptor)
where TObject : notnull
where TKey : notnull
{
source.ThrowArgumentNullExceptionIfNull(nameof(source));
adaptor.ThrowArgumentNullExceptionIfNull(nameof(adaptor));

return source.Do(adaptor.Adapt);
}
}
33 changes: 33 additions & 0 deletions src/DynamicData/Cache/ObservableCacheEx.AdaptSelector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved.
// Roland Pheasant licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
using DynamicData.Binding;
using DynamicData.Cache;
using DynamicData.Cache.Internal;

// ReSharper disable once CheckNamespace

namespace DynamicData;

/// <summary>
/// Extensions for dynamic data.
/// </summary>
public static partial class ObservableCacheEx
{
// TODO: Apply the Adapter to more places
private static Func<TObject, TKey, TResult> AdaptSelector<TObject, TKey, TResult>(Func<TObject, TResult> other)
where TObject : notnull
where TKey : notnull
where TResult : notnull => (obj, _) => other(obj);
}
113 changes: 113 additions & 0 deletions src/DynamicData/Cache/ObservableCacheEx.AddOrUpdate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved.
// Roland Pheasant licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
using DynamicData.Binding;
using DynamicData.Cache;
using DynamicData.Cache.Internal;

// ReSharper disable once CheckNamespace

namespace DynamicData;

/// <summary>
/// Extensions for dynamic data.
/// </summary>
public static partial class ObservableCacheEx
{
/// <summary>
/// Adds or updates the cache with the specified item, producing a changeset with a single <b>Add</b>
/// (if the key is new) or <b>Update</b> (if the key already exists).
/// </summary>
/// <typeparam name="TObject">The type of the object.</typeparam>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <param name="source">The <see cref="ISourceCache{TObject, TKey}"/> to add or update items in.</param>
/// <param name="item">The <typeparamref name="TObject"/> item to add or update.</param>
/// <remarks>
/// <para>Convenience method that wraps a single-item mutation inside <see cref="ISourceCache{TObject,TKey}.Edit"/>.</para>
/// <list type="table">
/// <listheader><term>Event</term><description>Behavior</description></listheader>
/// <item><term>Add</term><description>Produced when the key does not already exist in the cache.</description></item>
/// <item><term>Update</term><description>Produced when the key already exists. The previous value is included in the changeset.</description></item>
/// <item><term>Remove</term><description>Not produced by this method.</description></item>
/// <item><term>Refresh</term><description>Not produced by this method.</description></item>
/// </list>
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception>
/// <seealso cref="EditDiff{TObject, TKey}(ISourceCache{TObject, TKey}, IEnumerable{TObject}, IEqualityComparer{TObject})"/>
/// <seealso cref="Remove{TObject, TKey}(ISourceCache{TObject, TKey}, TObject)"/>
public static void AddOrUpdate<TObject, TKey>(this ISourceCache<TObject, TKey> source, TObject item)
where TObject : notnull
where TKey : notnull
{
source.ThrowArgumentNullExceptionIfNull(nameof(source));

source.Edit(updater => updater.AddOrUpdate(item));
}

/// <inheritdoc cref="AddOrUpdate{TObject, TKey}(ISourceCache{TObject, TKey}, TObject)"/>
/// <param name="source">The <see cref="ISourceCache{TObject, TKey}"/> to add or update items in.</param>
/// <param name="item">The <typeparamref name="TObject"/> item to add or update.</param>
/// <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>
/// <remarks>This overload uses <paramref name="equalityComparer"/> to suppress no-op updates when the new value equals the existing one.</remarks>
public static void AddOrUpdate<TObject, TKey>(this ISourceCache<TObject, TKey> source, TObject item, IEqualityComparer<TObject> equalityComparer)
where TObject : notnull
where TKey : notnull
{
source.ThrowArgumentNullExceptionIfNull(nameof(source));

source.Edit(updater => updater.AddOrUpdate(item, equalityComparer));
}

/// <inheritdoc cref="AddOrUpdate{TObject, TKey}(ISourceCache{TObject, TKey}, TObject)"/>
/// <param name="source">The <see cref="ISourceCache{TObject, TKey}"/> to add or update items in.</param>
/// <param name="items">The <see cref="IEnumerable{TObject}"/> of items to add or update.</param>
/// <remarks>Batch overload. All items are added/updated inside a single <see cref="ISourceCache{TObject,TKey}.Edit"/> call, producing one changeset.</remarks>
public static void AddOrUpdate<TObject, TKey>(this ISourceCache<TObject, TKey> source, IEnumerable<TObject> items)
where TObject : notnull
where TKey : notnull
{
source.ThrowArgumentNullExceptionIfNull(nameof(source));

source.Edit(updater => updater.AddOrUpdate(items));
}

/// <inheritdoc cref="AddOrUpdate{TObject, TKey}(ISourceCache{TObject, TKey}, TObject)"/>
/// <param name="source">The <see cref="ISourceCache{TObject, TKey}"/> to add or update items in.</param>
/// <param name="items">The <see cref="IEnumerable{TObject}"/> of items to add or update.</param>
/// <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>
/// <remarks>Batch overload with equality comparison. All items are added/updated inside a single <see cref="ISourceCache{TObject,TKey}.Edit"/> call.</remarks>
public static void AddOrUpdate<TObject, TKey>(this ISourceCache<TObject, TKey> source, IEnumerable<TObject> items, IEqualityComparer<TObject> equalityComparer)
where TObject : notnull
where TKey : notnull
{
source.ThrowArgumentNullExceptionIfNull(nameof(source));

source.Edit(updater => updater.AddOrUpdate(items, equalityComparer));
}

/// <inheritdoc cref="AddOrUpdate{TObject, TKey}(ISourceCache{TObject, TKey}, TObject)"/>
/// <param name="source">The <see cref="IIntermediateCache{TObject, TKey}"/> to add or update items in.</param>
/// <param name="item">The <typeparamref name="TObject"/> item to add or update.</param>
/// <param name="key">The <typeparamref name="TKey"/> key to associate with the item.</param>
/// <remarks>This overload operates on <see cref="IIntermediateCache{TObject, TKey}"/>, which requires an explicit key parameter.</remarks>
public static void AddOrUpdate<TObject, TKey>(this IIntermediateCache<TObject, TKey> source, TObject item, TKey key)
where TObject : notnull
where TKey : notnull
{
source.ThrowArgumentNullExceptionIfNull(nameof(source));
item.ThrowArgumentNullExceptionIfNull(nameof(item));

source.Edit(updater => updater.AddOrUpdate(item, key));
}
}
121 changes: 121 additions & 0 deletions src/DynamicData/Cache/ObservableCacheEx.And.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright (c) 2011-2025 Roland Pheasant. All rights reserved.
// Roland Pheasant licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
using DynamicData.Binding;
using DynamicData.Cache;
using DynamicData.Cache.Internal;

// ReSharper disable once CheckNamespace

namespace DynamicData;

/// <summary>
/// Extensions for dynamic data.
/// </summary>
public static partial class ObservableCacheEx
{
/// <summary>
/// Applied a logical And operator between the collections i.e items which are in all of the
/// sources are included.
/// </summary>
/// <typeparam name="TObject">The type of the object.</typeparam>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <param name="source">The source <see cref="IObservable{IChangeSet{TObject, TKey}}"/> to combine.</param>
/// <param name="others">The additional <see cref="IObservable{IChangeSet{TObject, TKey}}"/> streams to combine with.</param>
/// <returns>An observable which emits change sets.</returns>
/// <exception cref="ArgumentNullException">source or others.</exception>
/// <seealso cref="ObservableListEx.And"/>
public static IObservable<IChangeSet<TObject, TKey>> And<TObject, TKey>(this IObservable<IChangeSet<TObject, TKey>> source, params IObservable<IChangeSet<TObject, TKey>>[] others)
where TObject : notnull
where TKey : notnull
{
source.ThrowArgumentNullExceptionIfNull(nameof(source));

return others is null || others.Length == 0
? throw new ArgumentNullException(nameof(others))
: source.Combine(CombineOperator.And, others);
}

/// <summary>
/// Applied a logical And operator between the collections i.e items which are in all of the sources are included.
/// </summary>
/// <typeparam name="TObject">The type of the object.</typeparam>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <param name="sources">The <see cref="ICollection{T}"/> of streams to combine.</param>
/// <returns>An observable which emits change sets.</returns>
/// <exception cref="ArgumentNullException">
/// source
/// or
/// others.
/// </exception>
public static IObservable<IChangeSet<TObject, TKey>> And<TObject, TKey>(this ICollection<IObservable<IChangeSet<TObject, TKey>>> sources)
where TObject : notnull
where TKey : notnull
{
sources.ThrowArgumentNullExceptionIfNull(nameof(sources));

return sources.Combine(CombineOperator.And);
}

/// <summary>
/// Dynamically apply a logical And operator between the items in the outer observable list.
/// Items which are in all of the sources are included in the result.
/// </summary>
/// <typeparam name="TObject">The type of the object.</typeparam>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <param name="sources">The <see cref="IObservableList{T}"/> of streams to combine.</param>
/// <returns>An observable which emits change sets.</returns>
public static IObservable<IChangeSet<TObject, TKey>> And<TObject, TKey>(this IObservableList<IObservable<IChangeSet<TObject, TKey>>> sources)
where TObject : notnull
where TKey : notnull
{
sources.ThrowArgumentNullExceptionIfNull(nameof(sources));

return sources.Combine(CombineOperator.And);
}

/// <summary>
/// Dynamically apply a logical And operator between the items in the outer observable list.
/// Items which are in all of the sources are included in the result.
/// </summary>
/// <typeparam name="TObject">The type of the object.</typeparam>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <param name="sources">The <see cref="IObservableList{IObservableCache{TObject, TKey}}"/> of changeset streams to combine.</param>
/// <returns>An observable which emits change sets.</returns>
public static IObservable<IChangeSet<TObject, TKey>> And<TObject, TKey>(this IObservableList<IObservableCache<TObject, TKey>> sources)
where TObject : notnull
where TKey : notnull
{
sources.ThrowArgumentNullExceptionIfNull(nameof(sources));

return sources.Combine(CombineOperator.And);
}

/// <summary>
/// Dynamically apply a logical And operator between the items in the outer observable list.
/// Items which are in all of the sources are included in the result.
/// </summary>
/// <typeparam name="TObject">The type of the object.</typeparam>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <param name="sources">The <see cref="IObservableList{ISourceCache{TObject, TKey}}"/> of changeset streams to combine.</param>
/// <returns>An observable which emits change sets.</returns>
public static IObservable<IChangeSet<TObject, TKey>> And<TObject, TKey>(this IObservableList<ISourceCache<TObject, TKey>> sources)
where TObject : notnull
where TKey : notnull
{
sources.ThrowArgumentNullExceptionIfNull(nameof(sources));

return sources.Combine(CombineOperator.And);
}
}
Loading
Loading