From 6ee7fb730193a957d5a092b541327a99cfe4e675 Mon Sep 17 00:00:00 2001 From: neuecc Date: Wed, 28 Aug 2024 17:53:17 +0900 Subject: [PATCH] list --- sandbox/ConsoleApp/Program.cs | 8 +- .../Internal/FixedArray.cs | 55 +++++++++ .../Internal/RemoveAllMatcher.cs | 30 +++++ .../Internal/ResizableArray.cs | 114 +++++++++--------- .../ObservableCollections.csproj | 36 +++--- .../ObservableDictionary.Views.cs | 4 +- .../ObservableHashSet.Views.cs | 4 +- .../ObservableList.Views.cs | 99 ++++++++------- .../ObservableQueue.Views.cs | 4 +- .../ObservableRingBuffer.Views.cs | 4 +- .../ObservableStack.Views.cs | 4 +- .../Shims/Collections.cs | 4 +- .../SynchronizedViewChangedEventArgs.cs | 77 +++++++++--- .../SynchronizedViewList.cs | 96 ++++++++++----- 14 files changed, 361 insertions(+), 178 deletions(-) create mode 100644 src/ObservableCollections/Internal/FixedArray.cs create mode 100644 src/ObservableCollections/Internal/RemoveAllMatcher.cs diff --git a/sandbox/ConsoleApp/Program.cs b/sandbox/ConsoleApp/Program.cs index 63eb431..c13a736 100644 --- a/sandbox/ConsoleApp/Program.cs +++ b/sandbox/ConsoleApp/Program.cs @@ -60,16 +60,16 @@ class HogeFilter : ISynchronizedViewFilter switch (eventArgs.Action) { case NotifyCollectionChangedAction.Add: - eventArgs.NewView.Value += " Add"; + eventArgs.NewItem.View.Value += " Add"; break; case NotifyCollectionChangedAction.Remove: - eventArgs.OldView.Value += " Remove"; + eventArgs.OldItem.View.Value += " Remove"; break; case NotifyCollectionChangedAction.Move: - eventArgs.NewView.Value += $" Move {eventArgs.OldViewIndex} {eventArgs.NewViewIndex}"; + eventArgs.NewItem.View.Value += $" Move {eventArgs.OldStartingIndex} {eventArgs.NewStartingIndex}"; break; case NotifyCollectionChangedAction.Replace: - eventArgs.NewView.Value += $" Replace {eventArgs.NewViewIndex}"; + eventArgs.NewItem.View.Value += $" Replace {eventArgs.NewStartingIndex}"; break; case NotifyCollectionChangedAction.Reset: break; diff --git a/src/ObservableCollections/Internal/FixedArray.cs b/src/ObservableCollections/Internal/FixedArray.cs new file mode 100644 index 0000000..721c070 --- /dev/null +++ b/src/ObservableCollections/Internal/FixedArray.cs @@ -0,0 +1,55 @@ +using System; +using System.Buffers; +using System.Runtime.CompilerServices; + +namespace ObservableCollections.Internal +{ + internal ref struct FixedArray + { + public readonly Span Span; + T[]? array; + + public FixedArray(int size) + { + array = ArrayPool.Shared.Rent(size); + Span = array.AsSpan(0, size); + } + + public void Dispose() + { + if (array != null) + { + ArrayPool.Shared.Return(array, RuntimeHelpersEx.IsReferenceOrContainsReferences()); + } + } + } + + internal ref struct FixedBoolArray + { + public const int StackallocSize = 128; + + public readonly Span Span; + bool[]? array; + + public FixedBoolArray(Span scratchBuffer, int capacity) + { + if (scratchBuffer.Length == 0) + { + array = ArrayPool.Shared.Rent(capacity); + Span = array.AsSpan(0, capacity); + } + else + { + Span = scratchBuffer; + } + } + + public void Dispose() + { + if (array != null) + { + ArrayPool.Shared.Return(array); + } + } + } +} diff --git a/src/ObservableCollections/Internal/RemoveAllMatcher.cs b/src/ObservableCollections/Internal/RemoveAllMatcher.cs new file mode 100644 index 0000000..ecd81a7 --- /dev/null +++ b/src/ObservableCollections/Internal/RemoveAllMatcher.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; + +namespace ObservableCollections.Internal +{ + internal class RemoveAllMatcher + { + readonly HashSet hashSet; + + public RemoveAllMatcher(ReadOnlySpan source) + { +#if !NETSTANDARD2_0 + var set = new HashSet(capacity: source.Length); +#else + var set = new HashSet(); +#endif + foreach (var item in source) + { + set.Add(item); + } + + this.hashSet = set; + } + + public bool Predicate(T value) + { + return hashSet.Contains(value); + } + } +} \ No newline at end of file diff --git a/src/ObservableCollections/Internal/ResizableArray.cs b/src/ObservableCollections/Internal/ResizableArray.cs index ba032d0..7474ddc 100644 --- a/src/ObservableCollections/Internal/ResizableArray.cs +++ b/src/ObservableCollections/Internal/ResizableArray.cs @@ -1,58 +1,58 @@ -using System; -using System.Buffers; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -namespace ObservableCollections.Internal -{ - // internal ref struct ResizableArray - internal struct ResizableArray : IDisposable - { - T[]? array; - int count; - - public ReadOnlySpan Span => array.AsSpan(0, count); - - public ResizableArray(int initialCapacity) - { - array = ArrayPool.Shared.Rent(initialCapacity); - count = 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(T item) - { - if (array == null) Throw(); - if (array.Length == count) - { - EnsureCapacity(); - } - array[count++] = item; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - void EnsureCapacity() - { - var oldArray = array!; +using System; +using System.Buffers; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace ObservableCollections.Internal +{ + // internal ref struct ResizableArray + internal struct ResizableArray : IDisposable + { + T[]? array; + int count; + + public ReadOnlySpan Span => array.AsSpan(0, count); + + public ResizableArray(int initialCapacity) + { + array = ArrayPool.Shared.Rent(initialCapacity); + count = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (array == null) Throw(); + if (array.Length == count) + { + EnsureCapacity(); + } + array[count++] = item; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + void EnsureCapacity() + { + var oldArray = array!; var newArray = ArrayPool.Shared.Rent(oldArray.Length * 2); - Array.Copy(oldArray, newArray, oldArray.Length); - ArrayPool.Shared.Return(oldArray, RuntimeHelpersEx.IsReferenceOrContainsReferences()); - array = newArray; - } - - public void Dispose() - { - if (array != null) - { - ArrayPool.Shared.Return(array, RuntimeHelpersEx.IsReferenceOrContainsReferences()); - array = null; - } - } - - [DoesNotReturn] - void Throw() - { - throw new ObjectDisposedException("ResizableArray"); - } - } -} + Array.Copy(oldArray, newArray, oldArray.Length); + ArrayPool.Shared.Return(oldArray, RuntimeHelpersEx.IsReferenceOrContainsReferences()); + array = newArray; + } + + public void Dispose() + { + if (array != null) + { + ArrayPool.Shared.Return(array, RuntimeHelpersEx.IsReferenceOrContainsReferences()); + array = null; + } + } + + [DoesNotReturn] + void Throw() + { + throw new ObjectDisposedException("ResizableArray"); + } + } +} diff --git a/src/ObservableCollections/ObservableCollections.csproj b/src/ObservableCollections/ObservableCollections.csproj index 9e531f8..07827b9 100644 --- a/src/ObservableCollections/ObservableCollections.csproj +++ b/src/ObservableCollections/ObservableCollections.csproj @@ -1,24 +1,28 @@  - - netstandard2.0;netstandard2.1;net6.0;net8.0 - enable - 12.0 - disable + + netstandard2.0;netstandard2.1;net6.0;net8.0 + enable + 12.0 + disable - - collection - High performance observable collections and synchronized views, for WPF, Blazor, Unity. - true - true - + + collection + High performance observable collections and synchronized views, for WPF, Blazor, Unity. + true + true + - - - + + + + + + + - + - + diff --git a/src/ObservableCollections/ObservableDictionary.Views.cs b/src/ObservableCollections/ObservableDictionary.Views.cs index 4703d84..87e61d7 100644 --- a/src/ObservableCollections/ObservableDictionary.Views.cs +++ b/src/ObservableCollections/ObservableDictionary.Views.cs @@ -94,7 +94,7 @@ namespace ObservableCollections } } - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs, TView>(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs, TView>(NotifyCollectionChangedAction.Reset, true)); } } @@ -104,7 +104,7 @@ namespace ObservableCollections { this.filter = SynchronizedViewFilter>.Null; this.filteredCount = dict.Count; - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs, TView>(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs, TView>(NotifyCollectionChangedAction.Reset, true)); } } diff --git a/src/ObservableCollections/ObservableHashSet.Views.cs b/src/ObservableCollections/ObservableHashSet.Views.cs index 95d604c..af63254 100644 --- a/src/ObservableCollections/ObservableHashSet.Views.cs +++ b/src/ObservableCollections/ObservableHashSet.Views.cs @@ -89,7 +89,7 @@ namespace ObservableCollections filteredCount++; } } - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); } } @@ -99,7 +99,7 @@ namespace ObservableCollections { this.filter = SynchronizedViewFilter.Null; this.filteredCount = dict.Count; - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); } } diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index c39a274..3ea9b4f 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -195,48 +195,41 @@ namespace ObservableCollections switch (e.Action) { case NotifyCollectionChangedAction.Add: - // Add - if (e.NewStartingIndex == list.Count) + // Add or Insert + if (e.IsSingleItem) { - if (e.IsSingleItem) - { - var v = (e.NewItem, selector(e.NewItem)); - list.Add(v); - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, e.NewStartingIndex); - } - else - { - var i = e.NewStartingIndex; - - using var array = new ResizableArray<(T, TView)>(e.NewItems.Length); - foreach (var item in e.NewItems) - { - array.Add((item, selector(item))); - } - - list.AddRange(array.Span); - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, i++); - } + var v = (e.NewItem, selector(e.NewItem)); + list.Insert(e.NewStartingIndex, v); + this.InvokeOnAdd(ref filteredCount, ViewChanged, v, e.NewStartingIndex); } - // Insert else { - if (e.IsSingleItem) + var items = e.NewItems; + var length = items.Length; + + using var valueViews = new FixedArray<(T, TView)>(length); + using var views = new FixedArray(length); + using var matches = new FixedBoolArray(length < FixedBoolArray.StackallocSize ? stackalloc bool[length] : default, length); + var isMatchAll = true; + for (int i = 0; i < items.Length; i++) { - var v = (e.NewItem, selector(e.NewItem)); - list.Insert(e.NewStartingIndex, v); - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, e.NewStartingIndex); - } - else - { - var span = e.NewItems; - for (var i = 0; i < span.Length; i++) + var item = items[i]; + var view = selector(item); + views.Span[i] = view; + valueViews.Span[i] = (item, view); + var isMatch = matches.Span[i] = Filter.IsMatch(item); + if (isMatch) { - var v = (span[i], selector(span[i])); - list.Insert(e.NewStartingIndex + i, v); // should we use InsertRange? - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, e.NewStartingIndex + i); + filteredCount++; // increment in this process + } + else + { + isMatchAll = false; } } + + list.InsertRange(e.NewStartingIndex, valueViews.Span); + this.InvokeOnAddRange(ViewChanged, e.NewItems, views.Span, isMatchAll, matches.Span, e.NewStartingIndex); } break; case NotifyCollectionChangedAction.Remove: @@ -248,17 +241,32 @@ namespace ObservableCollections } else { - list.RemoveRange(e.OldStartingIndex, e.OldItems.Length); // TODO: no - - - // TODO: Range operation before remove...! - - var len = e.OldStartingIndex + e.OldItems.Length; - for (var i = e.OldStartingIndex; i < len; i++) + var length = e.OldItems.Length; + using var values = new FixedArray(length); + using var views = new FixedArray(length); + using var matches = new FixedBoolArray(length < FixedBoolArray.StackallocSize ? stackalloc bool[length] : default, length); + var isMatchAll = true; + var to = e.OldStartingIndex + length; + var j = 0; + for (int i = e.OldStartingIndex; i < to; i++) { - var v = list[i]; - this.InvokeOnRemove(ref filteredCount, ViewChanged, v, e.OldStartingIndex + i); + var item = list[i]; + values.Span[j] = item.Item1; + views.Span[j] = item.Item2; + var isMatch = matches.Span[j] = Filter.IsMatch(item.Item1); + if (isMatch) + { + filteredCount--; // decrement in this process + } + else + { + isMatchAll = false; + } + j++; } + + list.RemoveRange(e.OldStartingIndex, e.OldItems.Length); + this.InvokeOnRemoveRange(ViewChanged, values.Span, views.Span, isMatchAll, matches.Span, e.OldStartingIndex); } break; case NotifyCollectionChangedAction.Replace: @@ -290,14 +298,13 @@ namespace ObservableCollections { // Reverse list.Reverse(e.SortOperation.Index, e.SortOperation.Count); - // TODO:Invoke + this.InvokeOnReverseOrSort(ViewChanged, e.SortOperation); } else { // Sort list.Sort(e.SortOperation.Index, e.SortOperation.Count, new IgnoreViewComparer(e.SortOperation.Comparer ?? Comparer.Default)); - // Span d; - + this.InvokeOnReverseOrSort(ViewChanged, e.SortOperation); } break; default: diff --git a/src/ObservableCollections/ObservableQueue.Views.cs b/src/ObservableCollections/ObservableQueue.Views.cs index d09e89c..fc2ef42 100644 --- a/src/ObservableCollections/ObservableQueue.Views.cs +++ b/src/ObservableCollections/ObservableQueue.Views.cs @@ -89,7 +89,7 @@ namespace ObservableCollections filteredCount++; } } - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); } } @@ -99,7 +99,7 @@ namespace ObservableCollections { this.filter = SynchronizedViewFilter.Null; this.filteredCount = queue.Count; - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); } } diff --git a/src/ObservableCollections/ObservableRingBuffer.Views.cs b/src/ObservableCollections/ObservableRingBuffer.Views.cs index f4df53b..848cdd9 100644 --- a/src/ObservableCollections/ObservableRingBuffer.Views.cs +++ b/src/ObservableCollections/ObservableRingBuffer.Views.cs @@ -91,7 +91,7 @@ namespace ObservableCollections filteredCount++; } } - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); } } @@ -101,7 +101,7 @@ namespace ObservableCollections { this.filter = SynchronizedViewFilter.Null; this.filteredCount = ringBuffer.Count; - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); } } diff --git a/src/ObservableCollections/ObservableStack.Views.cs b/src/ObservableCollections/ObservableStack.Views.cs index ee8ee2c..f77b67c 100644 --- a/src/ObservableCollections/ObservableStack.Views.cs +++ b/src/ObservableCollections/ObservableStack.Views.cs @@ -88,7 +88,7 @@ namespace ObservableCollections filteredCount++; } } - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); } } @@ -98,7 +98,7 @@ namespace ObservableCollections { this.filter = SynchronizedViewFilter.Null; this.filteredCount = stack.Count; - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); } } diff --git a/src/ObservableCollections/Shims/Collections.cs b/src/ObservableCollections/Shims/Collections.cs index 96a393d..c4ae823 100644 --- a/src/ObservableCollections/Shims/Collections.cs +++ b/src/ObservableCollections/Shims/Collections.cs @@ -11,6 +11,8 @@ namespace System.Collections.Generic { internal static class CollectionExtensions { + const int ArrayMaxLength = 0X7FFFFFC7; + public static void Deconstruct(this KeyValuePair kvp, out TKey key, out TValue value) { key = kvp.Key; @@ -108,7 +110,7 @@ namespace System.Collections.Generic { int newCapacity = list._items.Length == 0 ? 4 : 2 * list._items.Length; - if ((uint)newCapacity > Array.MaxLength) newCapacity = Array.MaxLength; + if ((uint)newCapacity > ArrayMaxLength) newCapacity = ArrayMaxLength; if (newCapacity < capacity) newCapacity = capacity; diff --git a/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs b/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs index f5a26df..0a47d7a 100644 --- a/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs +++ b/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Specialized; +using System.Data; namespace ObservableCollections { @@ -10,7 +11,8 @@ namespace ObservableCollections (T Value, TView View) oldItem = default!, ReadOnlySpan newValues = default!, ReadOnlySpan newViews = default!, - ReadOnlySpan<(T Value, TView View)> oldItems = default!, + ReadOnlySpan oldValues = default!, + ReadOnlySpan oldViews = default!, int newStartingIndex = -1, int oldStartingIndex = -1, SortOperation sortOperation = default) @@ -21,7 +23,8 @@ namespace ObservableCollections public readonly (T Value, TView View) OldItem = oldItem; public readonly ReadOnlySpan NewValues = newValues; public readonly ReadOnlySpan NewViews = newViews; - public readonly ReadOnlySpan<(T Value, TView View)> OldItems = oldItems; + public readonly ReadOnlySpan OldValues = oldValues; + public readonly ReadOnlySpan OldViews = oldViews; public readonly int NewStartingIndex = newStartingIndex; public readonly int OldStartingIndex = oldStartingIndex; public readonly SortOperation SortOperation = sortOperation; @@ -57,15 +60,25 @@ namespace ObservableCollections } } - internal static void InvokeOnAddRange(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, ReadOnlySpan values, ReadOnlySpan views, int index) + internal static void InvokeOnAddRange(this ISynchronizedView collection, NotifyViewChangedEventHandler? ev, ReadOnlySpan values, ReadOnlySpan views, bool isMatchAll, ReadOnlySpan matches, int index) { - var isMatch = collection.Filter.IsMatch(value); - if (isMatch) + if (ev != null) { - filteredCount++; - if (ev != null) + if (isMatchAll) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, false, newValues: values, newViews: views, newStartingIndex: index)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, isSingleItem: false, newValues: values, newViews: views, newStartingIndex: index)); + } + else + { + var startingIndex = index; + for (var i = 0; i < matches.Length; i++) + { + if (matches[i]) + { + var item = (values[i], views[i]); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, isSingleItem: true, newItem: item, newStartingIndex: startingIndex++)); + } + } } } } @@ -83,7 +96,35 @@ namespace ObservableCollections filteredCount--; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, true, oldItem: (value, view), oldStartingIndex: oldIndex)); + } + } + } + + // only use for ObservableList + internal static void InvokeOnRemoveRange(this ISynchronizedView collection, NotifyViewChangedEventHandler? ev, ReadOnlySpan values, ReadOnlySpan views, bool isMatchAll, ReadOnlySpan matches, int index) + { + if (ev != null) + { + if (isMatchAll) + { + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, isSingleItem: false, oldValues: values, oldViews: views, oldStartingIndex: index)); + } + else + { + var startingIndex = index; + for (var i = 0; i < matches.Length; i++) + { + if (matches[i]) + { + var item = (values[i], views[i]); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, isSingleItem: true, oldItem: item, oldStartingIndex: index)); //remove for list, always same index + } + else + { + index++; // not matched, skip index + } + } } } } @@ -101,7 +142,7 @@ namespace ObservableCollections var isMatch = collection.Filter.IsMatch(value); if (isMatch) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Move, newValue: value, newView: view, newViewIndex: index, oldViewIndex: oldIndex)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Move, true, newItem: (value, view), newStartingIndex: index, oldStartingIndex: oldIndex)); } } } @@ -121,7 +162,7 @@ namespace ObservableCollections { if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Replace, newValue: value, newView: view, oldValue: oldValue, oldView: oldView, newViewIndex: index, oldViewIndex: oldIndex >= 0 ? oldIndex : index)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Replace, true, newItem: (value, view), oldItem: (oldValue, oldView), newStartingIndex: index, oldStartingIndex: oldIndex >= 0 ? oldIndex : index)); } } else if (oldMatched) @@ -130,7 +171,7 @@ namespace ObservableCollections filteredCount--; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, true, oldItem: (value, view), oldStartingIndex: oldIndex)); } } @@ -140,7 +181,7 @@ namespace ObservableCollections filteredCount++; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, newValue: value, newView: view, newViewIndex: index)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, true, newItem: (value, view), newStartingIndex: index)); } } } @@ -150,7 +191,15 @@ namespace ObservableCollections filteredCount = 0; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); + } + } + + internal static void InvokeOnReverseOrSort(this ISynchronizedView collection, NotifyViewChangedEventHandler? ev, SortOperation sortOperation) + { + if (ev != null) + { + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true, sortOperation: sortOperation)); } } } diff --git a/src/ObservableCollections/SynchronizedViewList.cs b/src/ObservableCollections/SynchronizedViewList.cs index 56e7c37..bf0e686 100644 --- a/src/ObservableCollections/SynchronizedViewList.cs +++ b/src/ObservableCollections/SynchronizedViewList.cs @@ -1,4 +1,5 @@ -using System; +using ObservableCollections.Internal; +using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; @@ -8,10 +9,19 @@ using System.Runtime.InteropServices; namespace ObservableCollections { + internal class SynchronizedViewList : ISynchronizedViewList { readonly ISynchronizedView parent; protected readonly List listView; + + + + + //protected readonly SortedList listView; // key is original index + + + protected readonly object gate = new object(); public SynchronizedViewList(ISynchronizedView parent) @@ -70,40 +80,38 @@ namespace ObservableCollections { if (e.OldStartingIndex == -1) { - // TODO:... - //listView.RemoveAll( - - // e.OldItems + var matcher = new RemoveAllMatcher(e.OldViews); + listView.RemoveAll(matcher.Predicate); } else { - listView.RemoveRange(e.OldStartingIndex, e.OldItems.Length); + listView.RemoveRange(e.OldStartingIndex, e.OldViews.Length); } } break; case NotifyCollectionChangedAction.Replace: // Indexer - if (e.NewViewIndex == -1) + if (e.NewStartingIndex == -1) { - var index = listView.IndexOf(e.OldView); - listView[index] = e.NewView; + var index = listView.IndexOf(e.OldItem.View); + listView[index] = e.NewItem.View; } else { - listView[e.NewViewIndex] = e.NewView; + listView[e.NewStartingIndex] = e.NewItem.View; } break; case NotifyCollectionChangedAction.Move: //Remove and Insert - if (e.NewViewIndex == -1) + if (e.NewStartingIndex == -1) { // do nothing } else { - listView.RemoveAt(e.OldViewIndex); - listView.Insert(e.NewViewIndex, e.NewView); + listView.RemoveAt(e.OldStartingIndex); + listView.Insert(e.NewStartingIndex, e.NewItem.View); } break; case NotifyCollectionChangedAction.Reset: // Clear or drastic changes @@ -121,16 +129,18 @@ namespace ObservableCollections } else { +#if NET6_0_OR_GREATER +#pragma warning disable CS0436 if (parent is ObservableList.View observableListView) { -#pragma warning disable CS0436 var comparer = new ObservableList.View.IgnoreViewComparer(e.SortOperation.Comparer ?? Comparer.Default); var viewSpan = CollectionsMarshal.AsSpan(listView).Slice(e.SortOperation.Index, e.SortOperation.Count); var sourceSpan = CollectionsMarshal.AsSpan(observableListView.list).Slice(e.SortOperation.Index, e.SortOperation.Count); - sourceSpan.Sort(viewSpan, comparer); -#pragma warning restore CS0436 + sourceSpan.Sort(viewSpan, comparer); // span.Sort is NET6 or greater } else +#pragma warning restore CS0436 +#endif { // can not get source Span, do Clear and Refresh listView.Clear(); @@ -223,22 +233,48 @@ namespace ObservableCollections switch (args.Action) { case NotifyCollectionChangedAction.Add: - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewView, args.NewViewIndex) + if (args.IsSingleItem) { - Collection = this, - Invoker = raiseChangedEventInvoke, - IsInvokeCollectionChanged = true, - IsInvokePropertyChanged = true - }); + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewItem.View, args.NewStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + } + else + { + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewViews.ToArray(), args.NewStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + } break; case NotifyCollectionChangedAction.Remove: - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldView, args.OldViewIndex) + if (args.IsSingleItem) { - Collection = this, - Invoker = raiseChangedEventInvoke, - IsInvokeCollectionChanged = true, - IsInvokePropertyChanged = true - }); + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldItem.View, args.OldStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + } + else + { + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldViews.ToArray(), args.OldStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + } break; case NotifyCollectionChangedAction.Reset: eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Reset) @@ -250,7 +286,7 @@ namespace ObservableCollections }); break; case NotifyCollectionChangedAction.Replace: - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Replace, args.NewView, args.OldView, args.NewViewIndex) + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Replace, args.NewItem.View, args.OldItem.View, args.NewStartingIndex) { Collection = this, Invoker = raiseChangedEventInvoke, @@ -259,7 +295,7 @@ namespace ObservableCollections }); break; case NotifyCollectionChangedAction.Move: - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Move, args.NewView, args.NewViewIndex, args.OldViewIndex) + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Move, args.NewItem.View, args.NewStartingIndex, args.OldStartingIndex) { Collection = this, Invoker = raiseChangedEventInvoke,