diff --git a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/CloneCollection.cs b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/CloneCollection.cs index 0c830f6..1e3193b 100644 --- a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/CloneCollection.cs +++ b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/CloneCollection.cs @@ -49,7 +49,7 @@ public CloneCollection(IEnumerable source) } else { - var array = ArrayPool.Shared.Rent(count); + var array = ArrayPool.Shared.Rent(16); var i = 0; foreach (var item in source) @@ -75,8 +75,8 @@ static void TryEnsureCapacity(ref T[] array, int index) if (array.Length == index) { ArrayPool.Shared.Return(array, RuntimeHelpersEx.IsReferenceOrContainsReferences()); + array = ArrayPool.Shared.Rent(index * 2); } - array = ArrayPool.Shared.Rent(index * 2); } public void Dispose() diff --git a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/FreezedView.cs b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/FreezedView.cs index 710428c..73eeb82 100644 --- a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/FreezedView.cs +++ b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/FreezedView.cs @@ -114,7 +114,7 @@ internal sealed class FreezedSortableView : ISortableSynchronizedView< public event Action CollectionStateChanged; public event NotifyCollectionChangedEventHandler RoutingCollectionChanged; - public object SyncRoot { get; } = new object(); + public object SyncRoot { get; } = new(); public FreezedSortableView(IEnumerable source, Func selector) { diff --git a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/SortedView.cs b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/SortedView.cs index 43b8a98..b8fc49c 100644 --- a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/SortedView.cs +++ b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/SortedView.cs @@ -12,7 +12,7 @@ internal class SortedView : ISynchronizedView readonly IObservableCollection source; readonly Func transform; readonly Func identitySelector; - readonly SortedDictionary<(T Value, TKey Key), (T Value, TView View)> dict; + readonly SortedList<(T Value, TKey Key), (T Value, TView View)> list; ISynchronizedViewFilter filter; @@ -29,13 +29,13 @@ public SortedView(IObservableCollection source, Func identitySelecto this.filter = SynchronizedViewFilter.Null; lock (source.SyncRoot) { - var dict = new SortedDictionary<(T, TKey), (T, TView)>(new Comparer(comparer)); + var dict = new Dictionary<(T, TKey), (T, TView)>(source.Count); foreach (var v in source) { dict.Add((v, identitySelector(v)), (v, transform(v))); } - this.dict = dict; + this.list = new SortedList<(T Value, TKey Key), (T Value, TView View)>(dict, new Comparer(comparer)); this.source.CollectionChanged += SourceCollectionChanged; } } @@ -46,7 +46,7 @@ public int Count { lock (SyncRoot) { - return dict.Count; + return list.Count; } } } @@ -56,7 +56,7 @@ public void AttachFilter(ISynchronizedViewFilter filter) lock (SyncRoot) { this.filter = filter; - foreach (var (_, (value, view)) in dict) + foreach (var (_, (value, view)) in list) { filter.InvokeOnAttach(value, view); } @@ -70,7 +70,7 @@ public void ResetFilter(Action resetAction) this.filter = SynchronizedViewFilter.Null; if (resetAction != null) { - foreach (var (_, (value, view)) in dict) + foreach (var (_, (value, view)) in list) { resetAction(value, view); } @@ -90,7 +90,7 @@ public INotifyCollectionChangedSynchronizedView WithINotifyCollectionC { lock (SyncRoot) { - foreach (var item in dict) + foreach (var item in list) { if (filter.IsMatch(item.Value.Value, item.Value.View)) { @@ -114,83 +114,101 @@ private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs e) switch (e.Action) { case NotifyCollectionChangedAction.Add: + { + // Add, Insert + if (e.IsSingleItem) + { + var value = e.NewItem; + var view = transform(value); + var id = identitySelector(value); + list.Add((value, id), (value, view)); + var index = list.IndexOfKey((value, id)); + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, index)); + } + else { - // Add, Insert - if (e.IsSingleItem) + foreach (var value in e.NewItems) { - var value = e.NewItem; var view = transform(value); var id = identitySelector(value); - dict.Add((value, id), (value, view)); - filter.InvokeOnAdd(value, view, e); - } - else - { - foreach (var value in e.NewItems) - { - var view = transform(value); - var id = identitySelector(value); - dict.Add((value, id), (value, view)); - filter.InvokeOnAdd(value, view, e); - } + list.Add((value, id), (value, view)); + var index = list.IndexOfKey((value, id)); + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, index)); } } + } break; case NotifyCollectionChangedAction.Remove: + { + if (e.IsSingleItem) { - if (e.IsSingleItem) + var value = e.OldItem; + var id = identitySelector(value); + var key = (value, id); + if (list.TryGetValue(key, out var v)) { - var value = e.OldItem; - var id = identitySelector(value); - dict.Remove((value, id), out var v); - filter.InvokeOnRemove(v.Value, v.View, e); + var index = list.IndexOfKey(key); + list.RemoveAt(index); + filter.InvokeOnRemove(v.Value, v.View, NotifyCollectionChangedEventArgs.Remove(v.Value, index)); } - else + } + else + { + foreach (var value in e.OldItems) { - foreach (var value in e.OldItems) + var id = identitySelector(value); + var key = (value, id); + if (list.TryGetValue(key, out var v)) { - var id = identitySelector(value); - dict.Remove((value, id), out var v); - filter.InvokeOnRemove(v.Value, v.View, e); + var index = list.IndexOfKey((value, id)); + list.RemoveAt(index); + filter.InvokeOnRemove(v.Value, v.View, NotifyCollectionChangedEventArgs.Remove(v.Value, index)); } } } + } break; case NotifyCollectionChangedAction.Replace: // ReplaceRange is not supported in all ObservableCollections collections // Replace is remove old item and insert new item. + { + var oldValue = e.OldItem; + var oldKey = (oldValue, identitySelector(oldValue)); + if (list.TryGetValue(oldKey, out var o)) { - var oldValue = e.OldItem; - dict.Remove((oldValue, identitySelector(oldValue)), out var oldView); + var oldIndex = list.IndexOfKey(oldKey); + list.RemoveAt(oldIndex); + filter.InvokeOnRemove(o, NotifyCollectionChangedEventArgs.Remove(oldValue, oldIndex)); + } - var value = e.NewItem; - var view = transform(value); - var id = identitySelector(value); - dict.Add((value, id), (value, view)); + var value = e.NewItem; + var view = transform(value); + var id = identitySelector(value); + list.Add((value, id), (value, view)); + var newIndex = list.IndexOfKey((value, id)); - filter.InvokeOnRemove(oldView, e); - filter.InvokeOnAdd(value, view, e); - } + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, newIndex)); + } break; case NotifyCollectionChangedAction.Move: + { + // Move(index change) does not affect sorted list. + var oldValue = e.OldItem; + if (list.TryGetValue((oldValue, identitySelector(oldValue)), out var view)) { - // Move(index change) does not affect sorted list. - var oldValue = e.OldItem; - if (dict.TryGetValue((oldValue, identitySelector(oldValue)), out var view)) - { - filter.InvokeOnMove(view, e); - } + filter.InvokeOnMove(view, e); } + } break; case NotifyCollectionChangedAction.Reset: if (!filter.IsNullFilter()) { - foreach (var item in dict) + foreach (var item in list) { filter.InvokeOnRemove(item.Value, e); } } - dict.Clear(); + list.Clear(); break; default: break; @@ -222,4 +240,4 @@ public int Compare((T value, TKey id) x, (T value, TKey id) y) } } } -} +} \ No newline at end of file diff --git a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/SortedViewViewComparer.cs b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/SortedViewViewComparer.cs index 35b75cd..db41992 100644 --- a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/SortedViewViewComparer.cs +++ b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Internal/SortedViewViewComparer.cs @@ -7,13 +7,13 @@ namespace ObservableCollections.Internal { internal class SortedViewViewComparer : ISynchronizedView - + { readonly IObservableCollection source; readonly Func transform; readonly Func identitySelector; readonly Dictionary viewMap; // view-map needs to use in remove. - readonly SortedDictionary<(TView View, TKey Key), (T Value, TView View)> dict; + readonly SortedList<(TView View, TKey Key), (T Value, TView View)> list; ISynchronizedViewFilter filter; @@ -30,7 +30,7 @@ public SortedViewViewComparer(IObservableCollection source, Func ide this.filter = SynchronizedViewFilter.Null; lock (source.SyncRoot) { - var dict = new SortedDictionary<(TView, TKey), (T, TView)>(new Comparer(comparer)); + var dict = new Dictionary<(TView, TKey), (T, TView)>(source.Count); this.viewMap = new Dictionary(); foreach (var value in source) { @@ -39,7 +39,7 @@ public SortedViewViewComparer(IObservableCollection source, Func ide dict.Add((view, id), (value, view)); viewMap.Add(id, view); } - this.dict = dict; + this.list = new SortedList<(TView View, TKey Key), (T Value, TView View)>(dict, new Comparer(comparer)); this.source.CollectionChanged += SourceCollectionChanged; } } @@ -50,7 +50,7 @@ public int Count { lock (SyncRoot) { - return dict.Count; + return list.Count; } } } @@ -60,7 +60,7 @@ public void AttachFilter(ISynchronizedViewFilter filter) lock (SyncRoot) { this.filter = filter; - foreach (var (_, (value, view)) in dict) + foreach (var (_, (value, view)) in list) { filter.InvokeOnAttach(value, view); } @@ -74,7 +74,7 @@ public void ResetFilter(Action resetAction) this.filter = SynchronizedViewFilter.Null; if (resetAction != null) { - foreach (var (_, (value, view)) in dict) + foreach (var (_, (value, view)) in list) { resetAction(value, view); } @@ -95,7 +95,7 @@ public INotifyCollectionChangedSynchronizedView WithINotifyCollectionC lock (SyncRoot) { - foreach (var item in dict) + foreach (var item in list) { if (filter.IsMatch(item.Value.Value, item.Value.View)) { @@ -119,96 +119,114 @@ private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs e) switch (e.Action) { case NotifyCollectionChangedAction.Add: + { + // Add, Insert + if (e.IsSingleItem) { - // Add, Insert - if (e.IsSingleItem) + var value = e.NewItem; + var view = transform(value); + var id = identitySelector(value); + list.Add((view, id), (value, view)); + viewMap.Add(id, view); + var index = list.IndexOfKey((view, id)); + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, index)); + } + else + { + foreach (var value in e.NewItems) { - var value = e.NewItem; var view = transform(value); var id = identitySelector(value); - dict.Add((view, id), (value, view)); + list.Add((view, id), (value, view)); viewMap.Add(id, view); - filter.InvokeOnAdd(value, view, e); - } - else - { - foreach (var value in e.NewItems) - { - var view = transform(value); - var id = identitySelector(value); - dict.Add((view, id), (value, view)); - viewMap.Add(id, view); - filter.InvokeOnAdd(value, view, e); - } + var index = list.IndexOfKey((view, id)); + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, index)); } } break; + } case NotifyCollectionChangedAction.Remove: + { + if (e.IsSingleItem) { - if (e.IsSingleItem) + var value = e.OldItem; + var id = identitySelector(value); + if (viewMap.Remove(id, out var view)) { - var value = e.OldItem; - var id = identitySelector(value); - if (viewMap.Remove(id, out var view)) + var key = (view, id); + if (list.TryGetValue(key, out var v)) { - dict.Remove((view, id), out var v); - filter.InvokeOnRemove(v, e); + var index = list.IndexOfKey(key); + list.RemoveAt(index); + filter.InvokeOnRemove(v, NotifyCollectionChangedEventArgs.Remove(v.Value, index)); } } - else + } + else + { + foreach (var value in e.OldItems) { - foreach (var value in e.OldItems) + var id = identitySelector(value); + if (viewMap.Remove(id, out var view)) { - var id = identitySelector(value); - if (viewMap.Remove(id, out var view)) + var key = (view, id); + if (list.TryGetValue(key, out var v)) { - dict.Remove((view, id), out var v); - filter.InvokeOnRemove(v, e); + var index = list.IndexOfKey((view, id)); + list.RemoveAt(index); + filter.InvokeOnRemove(v, NotifyCollectionChangedEventArgs.Remove(v.Value, index)); } } } } break; + } case NotifyCollectionChangedAction.Replace: // Replace is remove old item and insert new item. + { + var oldValue = e.OldItem; + var oldId = identitySelector(oldValue); + if (viewMap.Remove(oldId, out var oldView)) { - var oldValue = e.OldItem; - var oldKey = identitySelector(oldValue); - if (viewMap.Remove(oldKey, out var oldView)) + var oldKey = (oldView, oldId); + if (list.TryGetValue(oldKey, out var v)) { - dict.Remove((oldView, oldKey)); - filter.InvokeOnRemove(oldValue, oldView, e); + var oldIndex = list.IndexOfKey(oldKey); + list.RemoveAt(oldIndex); + filter.InvokeOnRemove(oldValue, oldView, NotifyCollectionChangedEventArgs.Remove(v.Value, oldIndex)); } + } - var value = e.NewItem; - var view = transform(value); - var id = identitySelector(value); - dict.Add((view, id), (value, view)); - viewMap.Add(id, view); + var value = e.NewItem; + var view = transform(value); + var id = identitySelector(value); + list.Add((view, id), (value, view)); + viewMap.Add(id, view); - filter.InvokeOnAdd(value, view, e); - } + var index = list.IndexOfKey((view, id)); + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, index)); break; + } case NotifyCollectionChangedAction.Move: // Move(index change) does not affect soreted dict. + { + var value = e.OldItem; + var key = identitySelector(value); + if (viewMap.TryGetValue(key, out var view)) { - var value = e.OldItem; - var key = identitySelector(value); - if (viewMap.TryGetValue(key, out var view)) - { - filter.InvokeOnMove(value, view, e); - } + filter.InvokeOnMove(value, view, e); } break; + } case NotifyCollectionChangedAction.Reset: if (!filter.IsNullFilter()) { - foreach (var item in dict) + foreach (var item in list) { filter.InvokeOnRemove(item.Value, e); } } - dict.Clear(); + list.Clear(); viewMap.Clear(); break; default: diff --git a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Shims/Nullables.cs b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Shims/Nullables.cs index eba1209..9b745a3 100644 --- a/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Shims/Nullables.cs +++ b/src/ObservableCollections.Unity/Assets/Plugins/ObservableCollections/Runtime/Shims/Nullables.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -#if (NETSTANDARD2_0 || NET_STANDARD_2_0 || NET_4_6) && !UNITY_2021_1_OR_NEWER +#if NETSTANDARD2_0 || NET_STANDARD_2_0 || NET_4_6 namespace System.Diagnostics.CodeAnalysis { diff --git a/src/ObservableCollections/Internal/FreezedView.cs b/src/ObservableCollections/Internal/FreezedView.cs index 95f7748..371ee87 100644 --- a/src/ObservableCollections/Internal/FreezedView.cs +++ b/src/ObservableCollections/Internal/FreezedView.cs @@ -114,7 +114,7 @@ internal sealed class FreezedSortableView : ISortableSynchronizedView< public event Action? CollectionStateChanged; public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; - public object SyncRoot { get; } = new object(); + public object SyncRoot { get; } = new(); public FreezedSortableView(IEnumerable source, Func selector) { diff --git a/src/ObservableCollections/Internal/SortedView.cs b/src/ObservableCollections/Internal/SortedView.cs index 0411985..a43f128 100644 --- a/src/ObservableCollections/Internal/SortedView.cs +++ b/src/ObservableCollections/Internal/SortedView.cs @@ -12,7 +12,7 @@ internal class SortedView : ISynchronizedView readonly IObservableCollection source; readonly Func transform; readonly Func identitySelector; - readonly SortedDictionary<(T Value, TKey Key), (T Value, TView View)> dict; + readonly SortedList<(T Value, TKey Key), (T Value, TView View)> list; ISynchronizedViewFilter filter; @@ -29,13 +29,13 @@ public SortedView(IObservableCollection source, Func identitySelecto this.filter = SynchronizedViewFilter.Null; lock (source.SyncRoot) { - var dict = new SortedDictionary<(T, TKey), (T, TView)>(new Comparer(comparer)); + var dict = new Dictionary<(T, TKey), (T, TView)>(source.Count); foreach (var v in source) { dict.Add((v, identitySelector(v)), (v, transform(v))); } - this.dict = dict; + this.list = new SortedList<(T Value, TKey Key), (T Value, TView View)>(dict, new Comparer(comparer)); this.source.CollectionChanged += SourceCollectionChanged; } } @@ -46,7 +46,7 @@ public int Count { lock (SyncRoot) { - return dict.Count; + return list.Count; } } } @@ -56,7 +56,7 @@ public void AttachFilter(ISynchronizedViewFilter filter) lock (SyncRoot) { this.filter = filter; - foreach (var (_, (value, view)) in dict) + foreach (var (_, (value, view)) in list) { filter.InvokeOnAttach(value, view); } @@ -70,7 +70,7 @@ public void ResetFilter(Action? resetAction) this.filter = SynchronizedViewFilter.Null; if (resetAction != null) { - foreach (var (_, (value, view)) in dict) + foreach (var (_, (value, view)) in list) { resetAction(value, view); } @@ -90,7 +90,7 @@ public INotifyCollectionChangedSynchronizedView WithINotifyCollectionC { lock (SyncRoot) { - foreach (var item in dict) + foreach (var item in list) { if (filter.IsMatch(item.Value.Value, item.Value.View)) { @@ -114,83 +114,101 @@ private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs e) switch (e.Action) { case NotifyCollectionChangedAction.Add: + { + // Add, Insert + if (e.IsSingleItem) + { + var value = e.NewItem; + var view = transform(value); + var id = identitySelector(value); + list.Add((value, id), (value, view)); + var index = list.IndexOfKey((value, id)); + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, index)); + } + else { - // Add, Insert - if (e.IsSingleItem) + foreach (var value in e.NewItems) { - var value = e.NewItem; var view = transform(value); var id = identitySelector(value); - dict.Add((value, id), (value, view)); - filter.InvokeOnAdd(value, view, e); - } - else - { - foreach (var value in e.NewItems) - { - var view = transform(value); - var id = identitySelector(value); - dict.Add((value, id), (value, view)); - filter.InvokeOnAdd(value, view, e); - } + list.Add((value, id), (value, view)); + var index = list.IndexOfKey((value, id)); + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, index)); } } + } break; case NotifyCollectionChangedAction.Remove: + { + if (e.IsSingleItem) { - if (e.IsSingleItem) + var value = e.OldItem; + var id = identitySelector(value); + var key = (value, id); + if (list.TryGetValue(key, out var v)) { - var value = e.OldItem; - var id = identitySelector(value); - dict.Remove((value, id), out var v); - filter.InvokeOnRemove(v.Value, v.View, e); + var index = list.IndexOfKey(key); + list.RemoveAt(index); + filter.InvokeOnRemove(v.Value, v.View, NotifyCollectionChangedEventArgs.Remove(v.Value, index)); } - else + } + else + { + foreach (var value in e.OldItems) { - foreach (var value in e.OldItems) + var id = identitySelector(value); + var key = (value, id); + if (list.TryGetValue(key, out var v)) { - var id = identitySelector(value); - dict.Remove((value, id), out var v); - filter.InvokeOnRemove(v.Value, v.View, e); + var index = list.IndexOfKey((value, id)); + list.RemoveAt(index); + filter.InvokeOnRemove(v.Value, v.View, NotifyCollectionChangedEventArgs.Remove(v.Value, index)); } } } + } break; case NotifyCollectionChangedAction.Replace: // ReplaceRange is not supported in all ObservableCollections collections // Replace is remove old item and insert new item. + { + var oldValue = e.OldItem; + var oldKey = (oldValue, identitySelector(oldValue)); + if (list.TryGetValue(oldKey, out var o)) { - var oldValue = e.OldItem; - dict.Remove((oldValue, identitySelector(oldValue)), out var oldView); + var oldIndex = list.IndexOfKey(oldKey); + list.RemoveAt(oldIndex); + filter.InvokeOnRemove(o, NotifyCollectionChangedEventArgs.Remove(oldValue, oldIndex)); + } - var value = e.NewItem; - var view = transform(value); - var id = identitySelector(value); - dict.Add((value, id), (value, view)); + var value = e.NewItem; + var view = transform(value); + var id = identitySelector(value); + list.Add((value, id), (value, view)); + var newIndex = list.IndexOfKey((value, id)); - filter.InvokeOnRemove(oldView, e); - filter.InvokeOnAdd(value, view, e); - } + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, newIndex)); + } break; case NotifyCollectionChangedAction.Move: + { + // Move(index change) does not affect sorted list. + var oldValue = e.OldItem; + if (list.TryGetValue((oldValue, identitySelector(oldValue)), out var view)) { - // Move(index change) does not affect sorted list. - var oldValue = e.OldItem; - if (dict.TryGetValue((oldValue, identitySelector(oldValue)), out var view)) - { - filter.InvokeOnMove(view, e); - } + filter.InvokeOnMove(view, e); } + } break; case NotifyCollectionChangedAction.Reset: if (!filter.IsNullFilter()) { - foreach (var item in dict) + foreach (var item in list) { filter.InvokeOnRemove(item.Value, e); } } - dict.Clear(); + list.Clear(); break; default: break; @@ -222,4 +240,4 @@ public int Compare((T value, TKey id) x, (T value, TKey id) y) } } } -} +} \ No newline at end of file diff --git a/src/ObservableCollections/Internal/SortedViewViewComparer.cs b/src/ObservableCollections/Internal/SortedViewViewComparer.cs index 383c2eb..e8dc8a1 100644 --- a/src/ObservableCollections/Internal/SortedViewViewComparer.cs +++ b/src/ObservableCollections/Internal/SortedViewViewComparer.cs @@ -7,13 +7,13 @@ namespace ObservableCollections.Internal { internal class SortedViewViewComparer : ISynchronizedView - where TKey : notnull + where TKey : notnull { readonly IObservableCollection source; readonly Func transform; readonly Func identitySelector; readonly Dictionary viewMap; // view-map needs to use in remove. - readonly SortedDictionary<(TView View, TKey Key), (T Value, TView View)> dict; + readonly SortedList<(TView View, TKey Key), (T Value, TView View)> list; ISynchronizedViewFilter filter; @@ -30,7 +30,7 @@ public SortedViewViewComparer(IObservableCollection source, Func ide this.filter = SynchronizedViewFilter.Null; lock (source.SyncRoot) { - var dict = new SortedDictionary<(TView, TKey), (T, TView)>(new Comparer(comparer)); + var dict = new Dictionary<(TView, TKey), (T, TView)>(source.Count); this.viewMap = new Dictionary(); foreach (var value in source) { @@ -39,7 +39,7 @@ public SortedViewViewComparer(IObservableCollection source, Func ide dict.Add((view, id), (value, view)); viewMap.Add(id, view); } - this.dict = dict; + this.list = new SortedList<(TView View, TKey Key), (T Value, TView View)>(dict, new Comparer(comparer)); this.source.CollectionChanged += SourceCollectionChanged; } } @@ -50,7 +50,7 @@ public int Count { lock (SyncRoot) { - return dict.Count; + return list.Count; } } } @@ -60,7 +60,7 @@ public void AttachFilter(ISynchronizedViewFilter filter) lock (SyncRoot) { this.filter = filter; - foreach (var (_, (value, view)) in dict) + foreach (var (_, (value, view)) in list) { filter.InvokeOnAttach(value, view); } @@ -74,7 +74,7 @@ public void ResetFilter(Action? resetAction) this.filter = SynchronizedViewFilter.Null; if (resetAction != null) { - foreach (var (_, (value, view)) in dict) + foreach (var (_, (value, view)) in list) { resetAction(value, view); } @@ -95,7 +95,7 @@ public INotifyCollectionChangedSynchronizedView WithINotifyCollectionC lock (SyncRoot) { - foreach (var item in dict) + foreach (var item in list) { if (filter.IsMatch(item.Value.Value, item.Value.View)) { @@ -119,96 +119,114 @@ private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs e) switch (e.Action) { case NotifyCollectionChangedAction.Add: + { + // Add, Insert + if (e.IsSingleItem) { - // Add, Insert - if (e.IsSingleItem) + var value = e.NewItem; + var view = transform(value); + var id = identitySelector(value); + list.Add((view, id), (value, view)); + viewMap.Add(id, view); + var index = list.IndexOfKey((view, id)); + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, index)); + } + else + { + foreach (var value in e.NewItems) { - var value = e.NewItem; var view = transform(value); var id = identitySelector(value); - dict.Add((view, id), (value, view)); + list.Add((view, id), (value, view)); viewMap.Add(id, view); - filter.InvokeOnAdd(value, view, e); - } - else - { - foreach (var value in e.NewItems) - { - var view = transform(value); - var id = identitySelector(value); - dict.Add((view, id), (value, view)); - viewMap.Add(id, view); - filter.InvokeOnAdd(value, view, e); - } + var index = list.IndexOfKey((view, id)); + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, index)); } } break; + } case NotifyCollectionChangedAction.Remove: + { + if (e.IsSingleItem) { - if (e.IsSingleItem) + var value = e.OldItem; + var id = identitySelector(value); + if (viewMap.Remove(id, out var view)) { - var value = e.OldItem; - var id = identitySelector(value); - if (viewMap.Remove(id, out var view)) + var key = (view, id); + if (list.TryGetValue(key, out var v)) { - dict.Remove((view, id), out var v); - filter.InvokeOnRemove(v, e); + var index = list.IndexOfKey(key); + list.RemoveAt(index); + filter.InvokeOnRemove(v, NotifyCollectionChangedEventArgs.Remove(v.Value, index)); } } - else + } + else + { + foreach (var value in e.OldItems) { - foreach (var value in e.OldItems) + var id = identitySelector(value); + if (viewMap.Remove(id, out var view)) { - var id = identitySelector(value); - if (viewMap.Remove(id, out var view)) + var key = (view, id); + if (list.TryGetValue(key, out var v)) { - dict.Remove((view, id), out var v); - filter.InvokeOnRemove(v, e); + var index = list.IndexOfKey((view, id)); + list.RemoveAt(index); + filter.InvokeOnRemove(v, NotifyCollectionChangedEventArgs.Remove(v.Value, index)); } } } } break; + } case NotifyCollectionChangedAction.Replace: // Replace is remove old item and insert new item. + { + var oldValue = e.OldItem; + var oldId = identitySelector(oldValue); + if (viewMap.Remove(oldId, out var oldView)) { - var oldValue = e.OldItem; - var oldKey = identitySelector(oldValue); - if (viewMap.Remove(oldKey, out var oldView)) + var oldKey = (oldView, oldId); + if (list.TryGetValue(oldKey, out var v)) { - dict.Remove((oldView, oldKey)); - filter.InvokeOnRemove(oldValue, oldView, e); + var oldIndex = list.IndexOfKey(oldKey); + list.RemoveAt(oldIndex); + filter.InvokeOnRemove(oldValue, oldView, NotifyCollectionChangedEventArgs.Remove(v.Value, oldIndex)); } + } - var value = e.NewItem; - var view = transform(value); - var id = identitySelector(value); - dict.Add((view, id), (value, view)); - viewMap.Add(id, view); + var value = e.NewItem; + var view = transform(value); + var id = identitySelector(value); + list.Add((view, id), (value, view)); + viewMap.Add(id, view); - filter.InvokeOnAdd(value, view, e); - } + var index = list.IndexOfKey((view, id)); + filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, index)); break; + } case NotifyCollectionChangedAction.Move: // Move(index change) does not affect soreted dict. + { + var value = e.OldItem; + var key = identitySelector(value); + if (viewMap.TryGetValue(key, out var view)) { - var value = e.OldItem; - var key = identitySelector(value); - if (viewMap.TryGetValue(key, out var view)) - { - filter.InvokeOnMove(value, view, e); - } + filter.InvokeOnMove(value, view, e); } break; + } case NotifyCollectionChangedAction.Reset: if (!filter.IsNullFilter()) { - foreach (var item in dict) + foreach (var item in list) { filter.InvokeOnRemove(item.Value, e); } } - dict.Clear(); + list.Clear(); viewMap.Clear(); break; default: diff --git a/tests/ObservableCollections.Tests/SortedViewTest.cs b/tests/ObservableCollections.Tests/SortedViewTest.cs new file mode 100644 index 0000000..4b22bec --- /dev/null +++ b/tests/ObservableCollections.Tests/SortedViewTest.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; + +namespace ObservableCollections.Tests; + +public class SortedViewTest +{ + [Fact] + public void Sort() + { + var list = new ObservableList(); + var sortedView = list.CreateSortedView( + x => x, + x => new ViewContainer(x), + Comparer.Default); + + list.Add(10); + list.Add(50); + list.Add(30); + list.Add(20); + list.Add(40); + + using var e = sortedView.GetEnumerator(); + e.MoveNext().Should().BeTrue(); + e.Current.Value.Should().Be(10); + e.MoveNext().Should().BeTrue(); + e.Current.Value.Should().Be(20); + e.MoveNext().Should().BeTrue(); + e.Current.Value.Should().Be(30); + e.MoveNext().Should().BeTrue(); + e.Current.Value.Should().Be(40); + e.MoveNext().Should().BeTrue(); + e.Current.Value.Should().Be(50); + e.MoveNext().Should().BeFalse(); + } + + [Fact] + public void ObserveIndex() + { + var list = new ObservableList(); + var sortedView = list.CreateSortedView( + x => x, + x => new ViewContainer(x), + Comparer.Default); + + var filter = new TestFilter((value, view) => value % 2 == 0); + list.Add(50); + list.Add(10); + + sortedView.AttachFilter(filter); + + list.Add(20); + filter.CalledOnCollectionChanged[0].changedKind.Should().Be(ChangedKind.Add); + filter.CalledOnCollectionChanged[0].value.Should().Be(20); + filter.CalledOnCollectionChanged[0].index.Should().Be(1); + + list.Remove(20); + filter.CalledOnCollectionChanged[1].changedKind.Should().Be(ChangedKind.Remove); + filter.CalledOnCollectionChanged[1].value.Should().Be(20); + filter.CalledOnCollectionChanged[1].oldIndex.Should().Be(1); + + list[1] = 999; // from 10(at 0 in original) to 999 + filter.CalledOnCollectionChanged[2].changedKind.Should().Be(ChangedKind.Remove); + filter.CalledOnCollectionChanged[2].value.Should().Be(10); + filter.CalledOnCollectionChanged[2].oldIndex.Should().Be(0); + filter.CalledOnCollectionChanged[3].changedKind.Should().Be(ChangedKind.Add); + filter.CalledOnCollectionChanged[3].value.Should().Be(999); + filter.CalledOnCollectionChanged[3].index.Should().Be(1); + } +} \ No newline at end of file diff --git a/tests/ObservableCollections.Tests/SortedViewViewComparerTest.cs b/tests/ObservableCollections.Tests/SortedViewViewComparerTest.cs new file mode 100644 index 0000000..ee6dcf3 --- /dev/null +++ b/tests/ObservableCollections.Tests/SortedViewViewComparerTest.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; + +namespace ObservableCollections.Tests; + +public class SortedViewViewComparerTest +{ + [Fact] + public void Sort() + { + var list = new ObservableList(); + var sortedView = list.CreateSortedView( + x => x, + x => new ViewContainer(x), + Comparer>.Default); + + list.Add(10); + list.Add(50); + list.Add(30); + list.Add(20); + list.Add(40); + + using var e = sortedView.GetEnumerator(); + e.MoveNext().Should().BeTrue(); + e.Current.Value.Should().Be(10); + e.MoveNext().Should().BeTrue(); + e.Current.Value.Should().Be(20); + e.MoveNext().Should().BeTrue(); + e.Current.Value.Should().Be(30); + e.MoveNext().Should().BeTrue(); + e.Current.Value.Should().Be(40); + e.MoveNext().Should().BeTrue(); + e.Current.Value.Should().Be(50); + e.MoveNext().Should().BeFalse(); + } + + [Fact] + public void ObserveIndex() + { + var list = new ObservableList(); + var sortedView = list.CreateSortedView( + x => x, + x => new ViewContainer(x), + Comparer>.Default); + + var filter = new TestFilter((value, view) => value % 2 == 0); + list.Add(50); + list.Add(10); + + sortedView.AttachFilter(filter); + + list.Add(20); + filter.CalledOnCollectionChanged[0].changedKind.Should().Be(ChangedKind.Add); + filter.CalledOnCollectionChanged[0].value.Should().Be(20); + filter.CalledOnCollectionChanged[0].index.Should().Be(1); + + list.Remove(20); + filter.CalledOnCollectionChanged[1].changedKind.Should().Be(ChangedKind.Remove); + filter.CalledOnCollectionChanged[1].value.Should().Be(20); + filter.CalledOnCollectionChanged[1].oldIndex.Should().Be(1); + + list[1] = 999; // from 10(at 0 in original) to 999 + filter.CalledOnCollectionChanged[2].changedKind.Should().Be(ChangedKind.Remove); + filter.CalledOnCollectionChanged[2].value.Should().Be(10); + filter.CalledOnCollectionChanged[2].oldIndex.Should().Be(0); + filter.CalledOnCollectionChanged[3].changedKind.Should().Be(ChangedKind.Add); + filter.CalledOnCollectionChanged[3].value.Should().Be(999); + filter.CalledOnCollectionChanged[3].index.Should().Be(1); + } +} \ No newline at end of file diff --git a/tests/ObservableCollections.Tests/ViewContainer.cs b/tests/ObservableCollections.Tests/ViewContainer.cs index 947d689..256941b 100644 --- a/tests/ObservableCollections.Tests/ViewContainer.cs +++ b/tests/ObservableCollections.Tests/ViewContainer.cs @@ -35,7 +35,7 @@ public class TestFilter : ISynchronizedViewFilter> readonly Func, bool> filter; public List<(T, ViewContainer)> CalledWhenTrue = new(); public List<(T, ViewContainer)> CalledWhenFalse = new(); - public List<(ChangedKind changedKind, T value, ViewContainer view)> CalledOnCollectionChanged = new(); + public List<(ChangedKind changedKind, T value, ViewContainer view, int index, int oldIndex)> CalledOnCollectionChanged = new(); public TestFilter(Func, bool> filter) { @@ -56,7 +56,7 @@ public bool IsMatch(T value, ViewContainer view) public void OnCollectionChanged(ChangedKind changedKind, T value, ViewContainer view, in NotifyCollectionChangedEventArgs eventArgs) { - CalledOnCollectionChanged.Add((changedKind, value, view)); + CalledOnCollectionChanged.Add((changedKind, value, view, eventArgs.NewStartingIndex, eventArgs.OldStartingIndex)); } public void WhenTrue(T value, ViewContainer view)