From b583685be76ccf6f5ccb817e3854e04d6671a398 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 23 Sep 2022 11:54:37 -0400 Subject: [PATCH 1/3] feat(feeds): Use KeyEquality for entity tracking in ListFeed --- .../Collections/ItemComparer.T.cs | 43 ++++++++++++++++--- .../Collections/ItemComparer.cs | 11 ++++- .../Tracking/CollectionAnalyzer.T.cs | 2 - .../Core/ListFeed.T.Internal.cs | 30 +++++++++++++ .../Core/ListFeed.T.cs | 2 +- .../Operators/FeedToListFeedAdapter.cs | 4 +- .../Operators/WhereListFeed.cs | 4 +- .../Sources/PaginatedListFeed.cs | 5 ++- 8 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 src/Uno.Extensions.Reactive/Core/ListFeed.T.Internal.cs diff --git a/src/Uno.Extensions.Reactive/Collections/ItemComparer.T.cs b/src/Uno.Extensions.Reactive/Collections/ItemComparer.T.cs index 893de37468..9a8edf71c9 100644 --- a/src/Uno.Extensions.Reactive/Collections/ItemComparer.T.cs +++ b/src/Uno.Extensions.Reactive/Collections/ItemComparer.T.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Uno.Extensions.Equality; namespace Uno.Extensions.Reactive.Collections; @@ -20,13 +21,43 @@ namespace Uno.Extensions.Reactive.Collections; /// The typical usage of this structure is for item tracking in collections. /// We first try to match the items using the comparer to detect insertions and removes. /// When 2 items are consider as not equals, we generate some add / remove collection changed args. -/// If they are considered as equals by the comparer, they are compared using teh comparer. -/// If **not** equals (i.e. 2 **distinct version** of the **same entity**, a replace collection changed args is generated, +/// If they are considered as equals by the comparer, they are compared using the comparer. +/// If **not** equals (i.e. 2 **distinct version** of the **same entity**), a replace collection changed args is generated, /// so the binding engine kicks-in to update the properties of the item. -/// If equals (i.e. 2 **instances** of the **same version** of the **same entity** (all properties are equals) no event is raised at all. +/// If equals (i.e. 2 **instances** of the **same version** of the **same entity**), all properties are equals no event is raised at all. /// /// -/// For both comparer, prefer to provide `null` than . -/// This allow user of this struct to use fast-paths when possible. +/// The common usages are:
+/// * If the is an immutable record, with a notion of key +/// (so a new instance of the record, with the same key, is created on each update, and a new immutable collection is created using that new instance) +/// use the as and the for . +/// * For immutable records that does not have a notion of key / version, +/// use the as and keep the for `null`. +/// * For an entity that as no notion of key (only relies on instance equality), +/// use `null` for both and . +///
+/// The general rules are: +/// * The should be more restrictive than the comparer. +/// * If there is no notion of key/version for your items, the should be `null` (rely only on for tracking). +/// * If the is `null` (lets to the list the responsibility to choose the right comparer, +/// so usually ref-equals for classes, deep-equals for records and ValueTypes), the should also be `null`. +/// * Do not use the same comparer for both +/// * Prefer to keep `null` than , it allows user of this struct to use fast-paths when possible. +/// (so the should also be null then). ///
-internal record struct ItemComparer(IEqualityComparer? Entity, IEqualityComparer? Version); +internal record struct ItemComparer(IEqualityComparer? Entity, IEqualityComparer? Version) +{ + public static ItemComparer Null => new(); + + public static ItemComparer Default => new(null, null); + + // A bool which indicates that we went trough a constructor instead of using default(ItemComparer) (both comparers can still be null) + public bool IsSet { get; init; } = true; + public bool IsNull => !IsSet; + + public static explicit operator ItemComparer(ItemComparer typed) + => new(typed.Entity?.ToEqualityComparer(), typed.Version?.ToEqualityComparer()) { IsSet = typed.IsSet }; + + public static explicit operator ItemComparer(ItemComparer untyped) + => new(untyped.Entity?.ToEqualityComparer(), untyped.Version?.ToEqualityComparer()) { IsSet = untyped.IsSet }; +} diff --git a/src/Uno.Extensions.Reactive/Collections/ItemComparer.cs b/src/Uno.Extensions.Reactive/Collections/ItemComparer.cs index 98f3525fb4..ec887953c0 100644 --- a/src/Uno.Extensions.Reactive/Collections/ItemComparer.cs +++ b/src/Uno.Extensions.Reactive/Collections/ItemComparer.cs @@ -29,4 +29,13 @@ namespace Uno.Extensions.Reactive.Collections; /// For both comparer, prefer to provide `null` than . /// This allow user of this struct to use fast-paths when possible. /// -internal record struct ItemComparer(IEqualityComparer? Entity, IEqualityComparer? Version); +internal record struct ItemComparer(IEqualityComparer? Entity, IEqualityComparer? Version) +{ + public static ItemComparer Null => new(); + + public static ItemComparer Default => new(null, null); + + // A bool which indicates that we went trough a constructor instead of using default(ItemComparer) (both comparers can still be null) + public bool IsSet { get; init; } = true; + public bool IsNull => !IsSet; +} diff --git a/src/Uno.Extensions.Reactive/Collections/Tracking/CollectionAnalyzer.T.cs b/src/Uno.Extensions.Reactive/Collections/Tracking/CollectionAnalyzer.T.cs index f734e14125..af29b18ad2 100644 --- a/src/Uno.Extensions.Reactive/Collections/Tracking/CollectionAnalyzer.T.cs +++ b/src/Uno.Extensions.Reactive/Collections/Tracking/CollectionAnalyzer.T.cs @@ -15,8 +15,6 @@ namespace Uno.Extensions.Collections.Tracking; /// internal class CollectionAnalyzer : CollectionAnalyzer { - public static CollectionAnalyzer Default { get; } = new(default); - private readonly IEqualityComparer? _comparer; private readonly ComparerRef? _versionComparer; diff --git a/src/Uno.Extensions.Reactive/Core/ListFeed.T.Internal.cs b/src/Uno.Extensions.Reactive/Core/ListFeed.T.Internal.cs new file mode 100644 index 0000000000..0298a04560 --- /dev/null +++ b/src/Uno.Extensions.Reactive/Core/ListFeed.T.Internal.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Uno.Extensions.Collections.Tracking; +using Uno.Extensions.Equality; +using Uno.Extensions.Reactive.Collections; + +namespace Uno.Extensions.Reactive; + +static partial class ListFeed +{ + /// + /// The default comparer to use in feed when not configured by user. + /// + internal static ItemComparer DefaultComparer { get; } = KeyEqualityComparer.Find() is {} keyComparer + ? new (keyComparer, EqualityComparer.Default) + : ItemComparer.Default; // No comparers, use default behavior of the list + + internal static ItemComparer GetComparer(ItemComparer comparer) + => comparer is { IsNull: true } ? DefaultComparer : comparer; + + /// + /// The default analyzer that should be used in feeds that should not offer a way to customize the , + /// like all operators. + /// + internal static CollectionAnalyzer DefaultAnalyzer { get; } = new(DefaultComparer); + + internal static CollectionAnalyzer GetAnalyzer(ItemComparer comparer) + => comparer is { IsNull: true } ? DefaultAnalyzer : new(comparer); +} diff --git a/src/Uno.Extensions.Reactive/Core/ListFeed.T.cs b/src/Uno.Extensions.Reactive/Core/ListFeed.T.cs index 6d2ae17dac..fc1c29423f 100644 --- a/src/Uno.Extensions.Reactive/Core/ListFeed.T.cs +++ b/src/Uno.Extensions.Reactive/Core/ListFeed.T.cs @@ -14,7 +14,7 @@ namespace Uno.Extensions.Reactive; /// Provides a set of static methods to create and manipulate . /// /// The type of the items. -public static class ListFeed +public static partial class ListFeed { /// /// Creates a custom feed from a raw sequence of . diff --git a/src/Uno.Extensions.Reactive/Operators/FeedToListFeedAdapter.cs b/src/Uno.Extensions.Reactive/Operators/FeedToListFeedAdapter.cs index ed10c0bf3c..323988df6b 100644 --- a/src/Uno.Extensions.Reactive/Operators/FeedToListFeedAdapter.cs +++ b/src/Uno.Extensions.Reactive/Operators/FeedToListFeedAdapter.cs @@ -37,8 +37,8 @@ public FeedToListFeedAdapter( { _source = source; _toImmutable = toImmutable; - _itemComparer = itemComparer; - _analyzer = new(itemComparer); + _itemComparer = ListFeed.GetComparer(itemComparer); + _analyzer = ListFeed.GetAnalyzer(itemComparer); } /// diff --git a/src/Uno.Extensions.Reactive/Operators/WhereListFeed.cs b/src/Uno.Extensions.Reactive/Operators/WhereListFeed.cs index d2e0573ba7..4840a0b66f 100644 --- a/src/Uno.Extensions.Reactive/Operators/WhereListFeed.cs +++ b/src/Uno.Extensions.Reactive/Operators/WhereListFeed.cs @@ -10,9 +10,9 @@ namespace Uno.Extensions.Reactive.Operators; -internal class WhereListFeed : IListFeed +internal sealed class WhereListFeed : IListFeed { - private static readonly CollectionAnalyzer _analyzer = CollectionAnalyzer.Default; + private static readonly CollectionAnalyzer _analyzer = ListFeed.DefaultAnalyzer; private readonly IListFeed _parent; private readonly Predicate _predicate; diff --git a/src/Uno.Extensions.Reactive/Sources/PaginatedListFeed.cs b/src/Uno.Extensions.Reactive/Sources/PaginatedListFeed.cs index 00f270dbf2..195d824385 100644 --- a/src/Uno.Extensions.Reactive/Sources/PaginatedListFeed.cs +++ b/src/Uno.Extensions.Reactive/Sources/PaginatedListFeed.cs @@ -7,6 +7,7 @@ using Uno.Extensions.Collections; using Uno.Extensions.Collections.Facades.Differential; using Uno.Extensions.Collections.Tracking; +using Uno.Extensions.Reactive.Collections; using Uno.Extensions.Reactive.Core; using Uno.Extensions.Reactive.Utils; @@ -18,11 +19,11 @@ internal class PaginatedListFeed : IListFeed, IRefreshabl private readonly GetPage _getPage; private readonly CollectionAnalyzer _diffAnalyzer; - public PaginatedListFeed(TCursor firstPage, GetPage getPage) + public PaginatedListFeed(TCursor firstPage, GetPage getPage, ItemComparer itemComparer = default) { _firstPage = firstPage; _getPage = getPage; - _diffAnalyzer = new CollectionAnalyzer(default); + _diffAnalyzer = ListFeed.GetAnalyzer(itemComparer); } /// From d8e664579afc4312797897d020fa056d7326964d Mon Sep 17 00:00:00 2001 From: David Date: Fri, 23 Sep 2022 11:59:49 -0400 Subject: [PATCH 2/3] test(feeds): Test entity traking in ListFeeds --- .../ConstraintParts/Items.cs | 3 + .../ConstraintParts/ItemsChanged.cs | 10 ++ .../Operators/Given_FeedToListFeedAdapter.cs | 104 ++++++++++++++++++ 3 files changed, 117 insertions(+) diff --git a/src/Uno.Extensions.Reactive.Testing/ConstraintParts/Items.cs b/src/Uno.Extensions.Reactive.Testing/ConstraintParts/Items.cs index 4434b0fad7..6656bdac6f 100644 --- a/src/Uno.Extensions.Reactive.Testing/ConstraintParts/Items.cs +++ b/src/Uno.Extensions.Reactive.Testing/ConstraintParts/Items.cs @@ -38,6 +38,9 @@ public static ItemsChanged Remove(int at, IEnumerable items) public static ItemsChanged Replace(int at, IEnumerable oldItems, IEnumerable newItems) => ItemsChanged.Replace(at, oldItems, newItems); + public static ItemsChanged Replace(int at, T oldItem, T newItem) + => ItemsChanged.Replace(at, oldItem, newItem); + public static ItemsChanged Move(int from, int to, params T[] items) => ItemsChanged.Move(from, to, items); diff --git a/src/Uno.Extensions.Reactive.Testing/ConstraintParts/ItemsChanged.cs b/src/Uno.Extensions.Reactive.Testing/ConstraintParts/ItemsChanged.cs index 4a680c2453..fee356a98d 100644 --- a/src/Uno.Extensions.Reactive.Testing/ConstraintParts/ItemsChanged.cs +++ b/src/Uno.Extensions.Reactive.Testing/ConstraintParts/ItemsChanged.cs @@ -30,6 +30,9 @@ public static ItemsChanged Remove(int index, IEnumerable items) public static ItemsChanged Replace(int index, IEnumerable oldItems, IEnumerable newItems) => new(RichNotifyCollectionChangedEventArgs.ReplaceSome(oldItems.ToList(), newItems.ToList(), index)); + public static ItemsChanged Replace(int index, T oldItem, T newItem) + => new(RichNotifyCollectionChangedEventArgs.Replace(oldItem, newItem, index)); + public static ItemsChanged Move(int oldIndex, int newIndex, params T[] items) => new(RichNotifyCollectionChangedEventArgs.MoveSome(items.ToList(), oldIndex, newIndex)); @@ -42,9 +45,16 @@ public static ItemsChanged Reset(IEnumerable oldItems, IEnumerable newI public static ItemsChanged Reset(IEnumerable newItems) => new(RichNotifyCollectionChangedEventArgs.Reset(null, newItems.ToList())); + public static ItemsChanged operator &(ItemsChanged left, ItemsChanged right) + => new(left._expectedArgs.Concat(right._expectedArgs).ToImmutableList()); + internal ItemsChanged(params RichNotifyCollectionChangedEventArgs[] expectedArgs) => _expectedArgs = expectedArgs.ToImmutableList(); + internal ItemsChanged(IImmutableList expectedArgs) + => _expectedArgs = expectedArgs.ToImmutableList(); + + /// public override void Assert(ChangeCollection actual) { diff --git a/src/Uno.Extensions.Reactive.Tests/Operators/Given_FeedToListFeedAdapter.cs b/src/Uno.Extensions.Reactive.Tests/Operators/Given_FeedToListFeedAdapter.cs index cd716715f1..0492c27318 100644 --- a/src/Uno.Extensions.Reactive.Tests/Operators/Given_FeedToListFeedAdapter.cs +++ b/src/Uno.Extensions.Reactive.Tests/Operators/Given_FeedToListFeedAdapter.cs @@ -2,10 +2,14 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; +using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Uno.Extensions.Equality; +using Uno.Extensions.Reactive.Operators; using Uno.Extensions.Reactive.Testing; namespace Uno.Extensions.Reactive.Tests.Operators; @@ -36,4 +40,104 @@ await result.Should().BeAsync(r => r .Message(Changed.Data, Data.None, Error.No, Progress.Final) ); } + + [TestMethod] + public async Task When_KeyEquatableNoComparerAndUpdate_Then_TrackItemsUsingKeyEquality() + { + var original = new MyKeyedRecord[] { new(1, 1), new(2, 1), new(3, 1), new(4, 1) }; + var updated = new MyKeyedRecord[] { new(1, 1), new(2, 2), new(4, 1), new(5, 1) }; + + async IAsyncEnumerable> GetSource([EnumeratorCancellation] CancellationToken ct = default) + { + yield return original.ToImmutableList(); + yield return updated.ToImmutableList(); + } + + var source = Feed.AsyncEnumerable(GetSource); + var sut = new FeedToListFeedAdapter(source); + var result = sut.Record(); + + await result.Should().BeAsync(r => r + .Message(m => m + .Current(Items.Some(original), Error.No, Progress.Final) + .Changed(Items.Reset(original))) + .Message(m => m + .Current(Items.Some(updated), Error.No, Progress.Final) + .Changed(Items.Replace(1, new MyKeyedRecord(2,1), new MyKeyedRecord(2,2)) + & Items.Remove(2, new MyKeyedRecord(3,1)) + & Items.Add(3, new MyKeyedRecord(5,1)))) + ); + } + + [TestMethod] + public async Task When_NotKeyEquatableNoComparerAndUpdate_Then_TrackItemsUsingKeyEquality() + { + var original = new MyNotKeyedRecord[] { new(1, 1), new(2, 1), new(3, 1), new(4, 1) }; + var updated = new MyNotKeyedRecord[] { new(1, 1), new(2, 2), new(4, 1), new(5, 1) }; + + async IAsyncEnumerable> GetSource([EnumeratorCancellation] CancellationToken ct = default) + { + yield return original.ToImmutableList(); + yield return updated.ToImmutableList(); + } + + var source = Feed.AsyncEnumerable(GetSource); + var sut = new FeedToListFeedAdapter(source); + var result = sut.Record(); + + await result.Should().BeAsync(r => r + .Message(m => m + .Current(Items.Some(original), Error.No, Progress.Final) + .Changed(Items.Reset(original))) + .Message(m => m + .Current(Items.Some(updated), Error.No, Progress.Final) + .Changed(Items.Remove(1, new MyNotKeyedRecord(2, 1), new MyNotKeyedRecord(3, 1)) + & Items.Add(1, new MyNotKeyedRecord(2, 2)) + & Items.Add(3, new MyNotKeyedRecord(5, 1)))) + ); + } + + [TestMethod] + public async Task When_ClassNoComparerAndUpdate_Then_TrackItemsUsingKeyEquality() + { + var original = new MyNotKeyedClass[] { new(1, 1), new(2, 1), new(3, 1), new(4, 1) }; + var updated = new MyNotKeyedClass[] { new(1, 1), new(2, 2), new(4, 1), new(5, 1) }; + + async IAsyncEnumerable> GetSource([EnumeratorCancellation] CancellationToken ct = default) + { + yield return original.ToImmutableList(); + yield return updated.ToImmutableList(); + } + + var source = Feed.AsyncEnumerable(GetSource); + var sut = new FeedToListFeedAdapter(source); + var result = sut.Record(); + + await result.Should().BeAsync(r => r + .Message(m => m + .Current(Items.Some(original), Error.No, Progress.Final) + .Changed(Items.Reset(original))) + .Message(m => m + .Current(Items.Some(updated), Error.No, Progress.Final) + .Changed(Items.Remove(0, original) + & Items.Add(0, updated))) + ); + } + + public partial record MyKeyedRecord(int Id, int Version); + + [ImplicitKeyEquality(IsEnabled = false)] + public partial record MyNotKeyedRecord(int Id, int Version); + + public partial class MyNotKeyedClass + { + public MyNotKeyedClass(int id, int version) + { + Id = id; + Version = version; + } + + public int Id { get; init; } + public int Version { get; init; } + } } From 8f534ed219fdad9819c47d7420d64a8c0e8faeaa Mon Sep 17 00:00:00 2001 From: David Date: Fri, 23 Sep 2022 12:03:27 -0400 Subject: [PATCH 3/3] feat(feeds): Enable KeyTracking in fallback BindableListFeed collection tracking --- .../Presentation/Bindings/BindableListFeed.cs | 10 +-- .../Collections/BindableCollection.cs | 69 ++++--------------- .../Collections/Data/DataStructure.cs | 6 +- 3 files changed, 24 insertions(+), 61 deletions(-) diff --git a/src/Uno.Extensions.Reactive.UI/Presentation/Bindings/BindableListFeed.cs b/src/Uno.Extensions.Reactive.UI/Presentation/Bindings/BindableListFeed.cs index f0ff24b404..a74e859a41 100644 --- a/src/Uno.Extensions.Reactive.UI/Presentation/Bindings/BindableListFeed.cs +++ b/src/Uno.Extensions.Reactive.UI/Presentation/Bindings/BindableListFeed.cs @@ -32,7 +32,7 @@ public BindableListFeed(string propertyName, IListFeed source, SourceContext PropertyName = propertyName; _state = ctx.GetOrCreateListState(source); - _items = CreateBindableCollection(ctx); + _items = CreateBindableCollection(_state, ctx); } /// @@ -78,7 +78,7 @@ ValueTask IState>.UpdateMessage(Action _state.UpdateMessage(updater, ct); - private BindableCollection CreateBindableCollection(SourceContext ctx) + private static BindableCollection CreateBindableCollection(IListState state, SourceContext ctx) { var currentCount = 0; var pageTokens = new TokenSetAwaiter(); @@ -86,7 +86,9 @@ private BindableCollection CreateBindableCollection(SourceContext ctx) var requests = new RequestSource(); var pagination = new PaginationService(LoadMore); var services = new SingletonServiceProvider(pagination); - var collection = BindableCollection.Create(services: services); + var collection = BindableCollection.Create( + services: services, + itemComparer: ListFeed.DefaultComparer); if (ctx.Token.CanBeCanceled) { @@ -98,7 +100,7 @@ private BindableCollection CreateBindableCollection(SourceContext ctx) // Note: we have to listen for collection changes on bindable _items to update the state. // https://github.com/unoplatform/uno.extensions/issues/370 - _state.GetSource(ctx.CreateChild(requests), ctx.Token).ForEachAsync( + state.GetSource(ctx.CreateChild(requests), ctx.Token).ForEachAsync( msg => { if (ctx.Token.IsCancellationRequested) diff --git a/src/Uno.Extensions.Reactive.UI/Presentation/Bindings/Collections/BindableCollection.cs b/src/Uno.Extensions.Reactive.UI/Presentation/Bindings/Collections/BindableCollection.cs index 90c55d777c..322f541363 100644 --- a/src/Uno.Extensions.Reactive.UI/Presentation/Bindings/Collections/BindableCollection.cs +++ b/src/Uno.Extensions.Reactive.UI/Presentation/Bindings/Collections/BindableCollection.cs @@ -12,6 +12,7 @@ using Uno.Extensions.Reactive.Bindings.Collections._BindableCollection.Data; using Uno.Extensions.Reactive.Bindings.Collections._BindableCollection.Facets; using Uno.Extensions.Reactive.Bindings.Collections.Services; +using Uno.Extensions.Reactive.Collections; using Uno.Extensions.Reactive.Dispatching; using Uno.Extensions.Reactive.Utils; using ISchedulersProvider = Uno.Extensions.Reactive.Dispatching.DispatcherHelper.FindDispatcher; @@ -32,21 +33,7 @@ internal sealed partial class BindableCollection : ICollectionView, INotifyColle /// Creates a new instance of a . /// /// The initial items in the collection. - /// - /// Comparer used to detect multiple versions of the **same entity (T)**, or null to use default. - /// Usually this should only compare the ID of the entities in order to properly track the changes made on an entity. - /// For better performance, prefer provide null instead of (cf. MapObservableCollection). - /// - /// - /// Comparer used to detect multiple instance of the **same version** of the **same entity (T)**, or null to rely only on the (not recommanded). - /// - /// This comparer will determine if two instances of the same entity (which was considered as equals by the ), - /// are effectively equals or not (i.e. same version or not). - ///
- /// * If **Equals**: it's 2 **instances** of the **same version** of the **same entity** (all properties are equals), so we don't have to raise a .
- /// * If **NOT Equals**: it's 2 **distinct versions** of the **same entity** (not all properties are equals) and we have to raise a 'Replace' to re-evaluate those properties. - ///
- /// + /// Comparer used to track items. /// Schedulers provider to use to handle concurrency. /// A set of services that the collection can use (cf. Remarks) /// Threshold on which the a single reset is raised instead of multiple collection changes. @@ -55,16 +42,12 @@ internal sealed partial class BindableCollection : ICollectionView, INotifyColle /// internal static BindableCollection Create( IObservableCollection? initial = null, - IEqualityComparer? itemComparer = null, - IEqualityComparer? itemVersionComparer = null, + ItemComparer itemComparer = default, ISchedulersProvider? schedulersProvider = null, IServiceProvider? services = null, int resetThreshold = DataStructure.DefaultResetThreshold) { - var dataStructure = new DataStructure - ( - (itemComparer?.ToEqualityComparer(), itemVersionComparer?.ToEqualityComparer()) - ) + var dataStructure = new DataStructure((ItemComparer)itemComparer) { ResetThreshold = resetThreshold }; @@ -77,16 +60,12 @@ internal static BindableCollection Create( /// internal static BindableCollection CreateUntyped( IObservableCollection? initial = null, - IEqualityComparer? itemComparer = null, - IEqualityComparer? itemVersionComparer = null, + ItemComparer itemComparer = default, ISchedulersProvider? schedulersProvider = null, int resetThreshold = DataStructure.DefaultResetThreshold ) { - var dataStructure = new DataStructure - ( - (itemComparer, itemVersionComparer) - ) + var dataStructure = new DataStructure(itemComparer) { ResetThreshold = resetThreshold }; @@ -96,19 +75,13 @@ internal static BindableCollection CreateUntyped( internal static BindableCollection CreateGrouped( IObservableCollection initial, - IEqualityComparer? keyComparer, - IEqualityComparer? keyVersionComparer, - IEqualityComparer? itemComparer = null, - IEqualityComparer? itemVersionComparer = null, + ItemComparer keyComparer, + ItemComparer itemComparer = default, ISchedulersProvider? schedulersProvider = null, int resetThreshold = DataStructure.DefaultResetThreshold) where TKey : IObservableGroup { - var dataStructure = new DataStructure - ( - (keyComparer?.ToEqualityComparer(), keyVersionComparer?.ToEqualityComparer()), - (itemComparer?.ToEqualityComparer(), itemVersionComparer?.ToEqualityComparer()) - ) + var dataStructure = new DataStructure((ItemComparer)keyComparer, (ItemComparer)itemComparer) { ResetThreshold = resetThreshold }; @@ -118,19 +91,13 @@ internal static BindableCollection CreateGrouped( internal static BindableCollection CreateGrouped( IObservableCollection initial, - IEqualityComparer? groupComparer, - IEqualityComparer? groupVersionComparer, - IEqualityComparer? itemComparer, - IEqualityComparer? itemVersionComparer, + ItemComparer groupComparer, + ItemComparer itemComparer, ISchedulersProvider? schedulersProvider = null, int resetThreshold = DataStructure.DefaultResetThreshold) where TGroup : IObservableGroup { - var dataStructure = new DataStructure - ( - (groupComparer?.ToEqualityComparer(), groupVersionComparer?.ToEqualityComparer()), - (itemComparer, itemVersionComparer) - ) + var dataStructure = new DataStructure((ItemComparer)groupComparer, itemComparer) { ResetThreshold = resetThreshold }; @@ -140,18 +107,12 @@ internal static BindableCollection CreateGrouped( internal static BindableCollection CreateUntypedGrouped( IObservableCollection initial, - IEqualityComparer? groupComparer, - IEqualityComparer? groupVersionComparer, - IEqualityComparer? itemComparer, - IEqualityComparer? itemVersionComparer, + ItemComparer groupComparer, + ItemComparer itemComparer, ISchedulersProvider? schedulersProvider = null, int resetThreshold = DataStructure.DefaultResetThreshold) { - var dataStructure = new DataStructure - ( - (groupComparer, groupVersionComparer), - (itemComparer, itemVersionComparer) - ) + var dataStructure = new DataStructure(groupComparer, itemComparer) { ResetThreshold = resetThreshold }; diff --git a/src/Uno.Extensions.Reactive.UI/Presentation/Bindings/Collections/Data/DataStructure.cs b/src/Uno.Extensions.Reactive.UI/Presentation/Bindings/Collections/Data/DataStructure.cs index b96b1457ef..1a48e08588 100644 --- a/src/Uno.Extensions.Reactive.UI/Presentation/Bindings/Collections/Data/DataStructure.cs +++ b/src/Uno.Extensions.Reactive.UI/Presentation/Bindings/Collections/Data/DataStructure.cs @@ -11,9 +11,9 @@ internal class DataStructure : IBindableCollectionDataStructure { public const int DefaultResetThreshold = 5; - private readonly (IEqualityComparer? itemComparer, IEqualityComparer? itemVersionComparer)[] _comparersStructure; + private readonly ItemComparer[] _comparersStructure; - public DataStructure(params (IEqualityComparer? itemComparer, IEqualityComparer? itemVersionComparer)[] comparersStructure) + public DataStructure(params ItemComparer[] comparersStructure) { _comparersStructure = comparersStructure; } @@ -33,7 +33,7 @@ public IBindableCollectionDataLayerStrategy GetLayer(uint level) } var comparers = _comparersStructure[level]; - var tracker = new CollectionAnalyzer(new ItemComparer(comparers.itemComparer, comparers.itemVersionComparer)); + var tracker = new CollectionAnalyzer(comparers); if (level + 1 == _comparersStructure.Length) {