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 24b37e2..f18ed8b 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 @@ namespace ObservableCollections.Internal 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 @@ namespace ObservableCollections.Internal 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 @@ namespace ObservableCollections.Internal { lock (SyncRoot) { - return dict.Count; + return list.Count; } } } @@ -56,7 +56,7 @@ namespace ObservableCollections.Internal lock (SyncRoot) { this.filter = filter; - foreach (var (_, (value, view)) in dict) + foreach (var (_, (value, view)) in list) { if (invokeAddEventForCurrentElements) { @@ -77,7 +77,7 @@ namespace ObservableCollections.Internal this.filter = SynchronizedViewFilter.Null; if (resetAction != null) { - foreach (var (_, (value, view)) in dict) + foreach (var (_, (value, view)) in list) { resetAction(value, view); } @@ -97,7 +97,7 @@ namespace ObservableCollections.Internal { lock (SyncRoot) { - foreach (var item in dict) + foreach (var item in list) { if (filter.IsMatch(item.Value.Value, item.Value.View)) { @@ -121,83 +121,101 @@ namespace ObservableCollections.Internal 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((value, id), (value, view)); + var index = list.IndexOfKey((value, 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((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 value = e.NewItem; - var view = transform(value); - var id = identitySelector(value); - dict.Add((value, id), (value, view)); - - filter.InvokeOnRemove(oldView, e); - filter.InvokeOnAdd(value, view, e); + 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); + list.Add((value, id), (value, view)); + var newIndex = list.IndexOfKey((value, id)); + + 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; @@ -229,4 +247,4 @@ namespace ObservableCollections.Internal } } } -} +} \ 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 be1699b..ca98de2 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 @@ using System.Linq; 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 @@ namespace ObservableCollections.Internal 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 @@ namespace ObservableCollections.Internal 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 @@ namespace ObservableCollections.Internal { lock (SyncRoot) { - return dict.Count; + return list.Count; } } } @@ -60,7 +60,7 @@ namespace ObservableCollections.Internal lock (SyncRoot) { this.filter = filter; - foreach (var (_, (value, view)) in dict) + foreach (var (_, (value, view)) in list) { if (invokeAddEventForCurrentElements) { @@ -81,7 +81,7 @@ namespace ObservableCollections.Internal this.filter = SynchronizedViewFilter.Null; if (resetAction != null) { - foreach (var (_, (value, view)) in dict) + foreach (var (_, (value, view)) in list) { resetAction(value, view); } @@ -102,7 +102,7 @@ namespace ObservableCollections.Internal lock (SyncRoot) { - foreach (var item in dict) + foreach (var item in list) { if (filter.IsMatch(item.Value.Value, item.Value.View)) { @@ -126,96 +126,114 @@ namespace ObservableCollections.Internal 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 key = (view, id); + if (list.TryGetValue(key, out var v)) + { + var index = list.IndexOfKey(key); + list.RemoveAt(index); + filter.InvokeOnRemove(v, NotifyCollectionChangedEventArgs.Remove(v.Value, index)); + } + } + } + else + { + foreach (var value in e.OldItems) { - var value = e.OldItem; var id = identitySelector(value); if (viewMap.Remove(id, out var view)) { - dict.Remove((view, id), out var v); - filter.InvokeOnRemove(v, e); - } - } - else - { - foreach (var value in e.OldItems) - { - 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); - - filter.InvokeOnAdd(value, view, e); } + + 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)); 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/Internal/SortedView.cs b/src/ObservableCollections/Internal/SortedView.cs index 62a8a00..435e483 100644 --- a/src/ObservableCollections/Internal/SortedView.cs +++ b/src/ObservableCollections/Internal/SortedView.cs @@ -12,7 +12,7 @@ namespace ObservableCollections.Internal 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 @@ namespace ObservableCollections.Internal 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 @@ namespace ObservableCollections.Internal { lock (SyncRoot) { - return dict.Count; + return list.Count; } } } @@ -56,7 +56,7 @@ namespace ObservableCollections.Internal lock (SyncRoot) { this.filter = filter; - foreach (var (_, (value, view)) in dict) + foreach (var (_, (value, view)) in list) { if (invokeAddEventForCurrentElements) { @@ -77,7 +77,7 @@ namespace ObservableCollections.Internal this.filter = SynchronizedViewFilter.Null; if (resetAction != null) { - foreach (var (_, (value, view)) in dict) + foreach (var (_, (value, view)) in list) { resetAction(value, view); } @@ -97,7 +97,7 @@ namespace ObservableCollections.Internal { lock (SyncRoot) { - foreach (var item in dict) + foreach (var item in list) { if (filter.IsMatch(item.Value.Value, item.Value.View)) { @@ -121,83 +121,101 @@ namespace ObservableCollections.Internal 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((value, id), (value, view)); + var index = list.IndexOfKey((value, 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((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 value = e.NewItem; - var view = transform(value); - var id = identitySelector(value); - dict.Add((value, id), (value, view)); - - filter.InvokeOnRemove(oldView, e); - filter.InvokeOnAdd(value, view, e); + 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); + list.Add((value, id), (value, view)); + var newIndex = list.IndexOfKey((value, id)); + + 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; @@ -229,4 +247,4 @@ namespace ObservableCollections.Internal } } } -} +} \ No newline at end of file diff --git a/src/ObservableCollections/Internal/SortedViewViewComparer.cs b/src/ObservableCollections/Internal/SortedViewViewComparer.cs index 3731888..5f91980 100644 --- a/src/ObservableCollections/Internal/SortedViewViewComparer.cs +++ b/src/ObservableCollections/Internal/SortedViewViewComparer.cs @@ -7,13 +7,13 @@ using System.Linq; 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 @@ namespace ObservableCollections.Internal 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 @@ namespace ObservableCollections.Internal 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 @@ namespace ObservableCollections.Internal { lock (SyncRoot) { - return dict.Count; + return list.Count; } } } @@ -60,7 +60,7 @@ namespace ObservableCollections.Internal lock (SyncRoot) { this.filter = filter; - foreach (var (_, (value, view)) in dict) + foreach (var (_, (value, view)) in list) { if (invokeAddEventForCurrentElements) { @@ -81,7 +81,7 @@ namespace ObservableCollections.Internal this.filter = SynchronizedViewFilter.Null; if (resetAction != null) { - foreach (var (_, (value, view)) in dict) + foreach (var (_, (value, view)) in list) { resetAction(value, view); } @@ -102,7 +102,7 @@ namespace ObservableCollections.Internal lock (SyncRoot) { - foreach (var item in dict) + foreach (var item in list) { if (filter.IsMatch(item.Value.Value, item.Value.View)) { @@ -126,96 +126,114 @@ namespace ObservableCollections.Internal 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 key = (view, id); + if (list.TryGetValue(key, out var v)) + { + var index = list.IndexOfKey(key); + list.RemoveAt(index); + filter.InvokeOnRemove(v, NotifyCollectionChangedEventArgs.Remove(v.Value, index)); + } + } + } + else + { + foreach (var value in e.OldItems) { - var value = e.OldItem; var id = identitySelector(value); if (viewMap.Remove(id, out var view)) { - dict.Remove((view, id), out var v); - filter.InvokeOnRemove(v, e); - } - } - else - { - foreach (var value in e.OldItems) - { - 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); - - filter.InvokeOnAdd(value, view, e); } + + 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)); 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 @@ namespace ObservableCollections.Tests 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 @@ namespace ObservableCollections.Tests 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)