From 7eef45cadbcc11bebaa55065fda82be039d65e0a Mon Sep 17 00:00:00 2001 From: neuecc Date: Wed, 28 Aug 2024 03:09:29 +0900 Subject: [PATCH] more changed wip --- .../ISynchronizedViewFilter.cs | 133 +--------------- .../NotifyCollectionChangedEventArgs.cs | 61 +++++++- .../ObservableList.Views.cs | 47 +++++- src/ObservableCollections/ObservableList.cs | 53 ++++++- .../Shims/Collections.cs | 86 +++++++++++ .../Shims/CollectionsMarshalEx.cs | 34 +++++ .../SynchronizedViewChangedEventArgs.cs | 144 ++++++++++++++++++ .../SynchronizedViewList.cs | 80 ++++++++-- 8 files changed, 486 insertions(+), 152 deletions(-) create mode 100644 src/ObservableCollections/Shims/CollectionsMarshalEx.cs create mode 100644 src/ObservableCollections/SynchronizedViewChangedEventArgs.cs diff --git a/src/ObservableCollections/ISynchronizedViewFilter.cs b/src/ObservableCollections/ISynchronizedViewFilter.cs index 84108a0..de267e6 100644 --- a/src/ObservableCollections/ISynchronizedViewFilter.cs +++ b/src/ObservableCollections/ISynchronizedViewFilter.cs @@ -3,24 +3,6 @@ using System.Collections.Specialized; namespace ObservableCollections { - public readonly ref struct SynchronizedViewChangedEventArgs( - NotifyCollectionChangedAction action, - T newValue = default!, - T oldValue = default!, - TView newView = default!, - TView oldView = default!, - int newViewIndex = -1, - int oldViewIndex = -1) - { - public readonly NotifyCollectionChangedAction Action = action; - public readonly T NewValue = newValue; - public readonly T OldValue = oldValue; - public readonly TView NewView = newView; - public readonly TView OldView = oldView; - public readonly int NewViewIndex = newViewIndex; - public readonly int OldViewIndex = oldViewIndex; - } - public interface ISynchronizedViewFilter { bool IsMatch(T value); @@ -38,118 +20,5 @@ namespace ObservableCollections } } - public static class SynchronizedViewFilterExtensions - { - public static void AttachFilter(this ISynchronizedView source, Func filter) - { - source.AttachFilter(new SynchronizedViewFilter(filter)); - } - - public static bool IsNullFilter(this ISynchronizedViewFilter filter) - { - return filter == SynchronizedViewFilter.Null; - } - - internal static void InvokeOnAdd(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, (T value, TView view) value, int index) - { - InvokeOnAdd(collection, ref filteredCount, ev, value.value, value.view, index); - } - - internal static void InvokeOnAdd(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, T value, TView view, int index) - { - var isMatch = collection.Filter.IsMatch(value); - if (isMatch) - { - filteredCount++; - if (ev != null) - { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, newValue: value, newView: view, newViewIndex: index)); - } - } - } - - internal static void InvokeOnRemove(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, (T value, TView view) value, int oldIndex) - { - InvokeOnRemove(collection, ref filteredCount, ev, value.value, value.view, oldIndex); - } - - internal static void InvokeOnRemove(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, T value, TView view, int oldIndex) - { - var isMatch = collection.Filter.IsMatch(value); - if (isMatch) - { - filteredCount--; - if (ev != null) - { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); - } - } - } - - internal static void InvokeOnMove(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, (T value, TView view) value, int index, int oldIndex) - { - InvokeOnMove(collection, ref filteredCount, ev, value.value, value.view, index, oldIndex); - } - - internal static void InvokeOnMove(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, T value, TView view, int index, int oldIndex) - { - if (ev != null) - { - // move does not changes filtered-count - var isMatch = collection.Filter.IsMatch(value); - if (isMatch) - { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Move, newValue: value, newView: view, newViewIndex: index, oldViewIndex: oldIndex)); - } - } - } - - internal static void InvokeOnReplace(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, (T value, TView view) value, (T value, TView view) oldValue, int index, int oldIndex = -1) - { - InvokeOnReplace(collection, ref filteredCount, ev, value.value, value.view, oldValue.value, oldValue.view, index, oldIndex); - } - - internal static void InvokeOnReplace(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, T value, TView view, T oldValue, TView oldView, int index, int oldIndex = -1) - { - var oldMatched = collection.Filter.IsMatch(oldValue); - var newMatched = collection.Filter.IsMatch(value); - var bothMatched = oldMatched && newMatched; - - if (bothMatched) - { - if (ev != null) - { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Replace, newValue: value, newView: view, oldValue: oldValue, oldView: oldView, newViewIndex: index, oldViewIndex: oldIndex >= 0 ? oldIndex : index)); - } - } - else if (oldMatched) - { - // only-old is remove - filteredCount--; - if (ev != null) - { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); - } - - } - else if (newMatched) - { - // only-new is add - filteredCount++; - if (ev != null) - { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, newValue: value, newView: view, newViewIndex: index)); - } - } - } - - internal static void InvokeOnReset(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev) - { - filteredCount = 0; - if (ev != null) - { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - } - } + } diff --git a/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs b/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs index f5064bc..05b13fd 100644 --- a/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs +++ b/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs @@ -1,9 +1,54 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Collections.Specialized; using System.Runtime.InteropServices; namespace ObservableCollections { + // default(SortOperation) == IsNull + public readonly struct SortOperation + { + public readonly int Index; + public readonly int Count; + public readonly IComparer? Comparer; + + public bool IsReverse => Comparer == ReverseSentinel.Instance; + public bool IsNull => Comparer == null; + + public SortOperation(int index, int count, IComparer? comparer) + { + Index = index; + Count = count; + Comparer = comparer ?? NullComparerSentinel.Instance; + } + + public static SortOperation CreateReverse(int index, int count) + { + return new SortOperation(index, count, ReverseSentinel.Instance); + } + + sealed class ReverseSentinel : IComparer + { + public static IComparer Instance = new ReverseSentinel(); + + public int Compare(T? x, T? y) + { + throw new NotImplementedException(); + } + } + + sealed class NullComparerSentinel : IComparer + { + public static IComparer Instance = new NullComparerSentinel(); + + public int Compare(T? x, T? y) + { + throw new NotImplementedException(); + } + } + } + /// /// Contract: /// IsSingleItem ? (NewItem, OldItem) : (NewItems, OldItems) @@ -16,7 +61,7 @@ namespace ObservableCollections /// Action.Move /// NewStartingIndex, OldStartingIndex /// Action.Reset - /// - + /// SortOperation(IsNull, IsReverse, Comparer) /// [StructLayout(LayoutKind.Auto)] public readonly ref struct NotifyCollectionChangedEventArgs @@ -29,8 +74,9 @@ namespace ObservableCollections public readonly ReadOnlySpan OldItems; public readonly int NewStartingIndex; public readonly int OldStartingIndex; + public readonly SortOperation SortOperation; - public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, bool isSingleItem, T newItem = default!, T oldItem = default!, ReadOnlySpan newItems = default, ReadOnlySpan oldItems = default, int newStartingIndex = -1, int oldStartingIndex = -1) + public NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction action, bool isSingleItem, T newItem = default!, T oldItem = default!, ReadOnlySpan newItems = default, ReadOnlySpan oldItems = default, int newStartingIndex = -1, int oldStartingIndex = -1, SortOperation sortOperation = default) { Action = action; IsSingleItem = isSingleItem; @@ -40,6 +86,7 @@ namespace ObservableCollections OldItems = oldItems; NewStartingIndex = newStartingIndex; OldStartingIndex = oldStartingIndex; + SortOperation = sortOperation; } public static NotifyCollectionChangedEventArgs Add(T newItem, int newStartingIndex) @@ -81,5 +128,15 @@ namespace ObservableCollections { return new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, true); } + + public static NotifyCollectionChangedEventArgs Reverse(int index, int count) + { + return new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, true, sortOperation: SortOperation.CreateReverse(index, count)); + } + + public static NotifyCollectionChangedEventArgs Sort(int index, int count, IComparer? comparer) + { + return new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, true, sortOperation: new SortOperation(index, count, comparer)); + } } } \ No newline at end of file diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index f1a8669..4635d2d 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -41,7 +41,7 @@ namespace ObservableCollections readonly ObservableList source; readonly Func selector; - readonly List<(T, TView)> list; + internal readonly List<(T, TView)> list; // unsafe, be careful to use int filteredCount; ISynchronizedViewFilter filter; @@ -207,6 +207,10 @@ namespace ObservableCollections else { var i = e.NewStartingIndex; + + //new CloneCollection<(T, TView)>(); + using var array = new ResizableArray<(T, TView)>(e.NewItems.Length); + foreach (var item in e.NewItems) { var v = (item, selector(item)); @@ -245,7 +249,10 @@ namespace ObservableCollections } else { - list.RemoveRange(e.OldStartingIndex, e.OldItems.Length); // remove from list first + 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++) @@ -274,8 +281,25 @@ namespace ObservableCollections } break; case NotifyCollectionChangedAction.Reset: - list.Clear(); - this.InvokeOnReset(ref filteredCount, ViewChanged); + if (e.SortOperation.IsNull) + { + // None(Clear) + list.Clear(); + this.InvokeOnReset(ref filteredCount, ViewChanged); + } + else if (e.SortOperation.IsReverse) + { + // Reverse + list.Reverse(e.SortOperation.Index, e.SortOperation.Count); + // TODO:Invoke + } + else + { + // Sort + list.Sort(e.SortOperation.Index, e.SortOperation.Count, new IgnoreViewComparer(e.SortOperation.Comparer ?? Comparer.Default)); + // Span d; + + } break; default: break; @@ -284,6 +308,21 @@ namespace ObservableCollections CollectionStateChanged?.Invoke(e.Action); } } + + internal sealed class IgnoreViewComparer : IComparer<(T, TView)> + { + readonly IComparer comparer; + + public IgnoreViewComparer(IComparer comparer) + { + this.comparer = comparer; + } + + public int Compare((T, TView) x, (T, TView) y) + { + return comparer.Compare(x.Item1, y.Item1); + } + } } } } diff --git a/src/ObservableCollections/ObservableList.cs b/src/ObservableCollections/ObservableList.cs index ff86ec0..bf9e950 100644 --- a/src/ObservableCollections/ObservableList.cs +++ b/src/ObservableCollections/ObservableList.cs @@ -245,12 +245,9 @@ namespace ObservableCollections { lock (SyncRoot) { -#if NET5_0_OR_GREATER +#pragma warning disable CS0436 var range = CollectionsMarshal.AsSpan(list).Slice(index, count); -#else - var range = list.GetRange(index, count); -#endif - +#pragma warning restore CS0436 // require copy before remove using (var xs = new CloneCollection(range)) { @@ -270,5 +267,51 @@ namespace ObservableCollections CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Move(removedItem, newIndex, oldIndex)); } } + + public void Sort() + { + lock (SyncRoot) + { + list.Sort(); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Sort(0, list.Count, null)); + } + } + + public void Sort(IComparer comparer) + { + lock (SyncRoot) + { + list.Sort(); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Sort(0, list.Count, comparer)); + } + } + + public void Sort(int index, int count, IComparer comparer) + { + lock (SyncRoot) + { + list.Sort(); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Sort(index, count, comparer)); + } + } + + public void Reverse() + { + lock (SyncRoot) + { + list.Sort(); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Reverse(0, list.Count)); + } + } + + + public void Reverse(int index, int count) + { + lock (SyncRoot) + { + list.Sort(); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Reverse(index, count)); + } + } } } diff --git a/src/ObservableCollections/Shims/Collections.cs b/src/ObservableCollections/Shims/Collections.cs index d20c328..96a393d 100644 --- a/src/ObservableCollections/Shims/Collections.cs +++ b/src/ObservableCollections/Shims/Collections.cs @@ -2,7 +2,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Text; +using System.Runtime.InteropServices; namespace System.Collections.Generic { @@ -32,6 +35,89 @@ namespace System.Collections.Generic return false; } +#if !NET8_0_OR_GREATER +#pragma warning disable CS0436 + + // CollectionExtensions.AddRange + public static void AddRange(this List list, ReadOnlySpan source) + { + if (!source.IsEmpty) + { + ref var view = ref Unsafe.As, CollectionsMarshal.ListView>(ref list!); + + if (view._items.Length - view._size < source.Length) + { + Grow(ref view, checked(view._size + source.Length)); + } + + source.CopyTo(view._items.AsSpan(view._size)); + view._size += source.Length; + view._version++; + } + } + + // CollectionExtensions.InsertRange + public static void InsertRange(this List list, int index, ReadOnlySpan source) + { + if (!source.IsEmpty) + { + ref var view = ref Unsafe.As, CollectionsMarshal.ListView>(ref list!); + + if (view._items.Length - view._size < source.Length) + { + Grow(ref view, checked(view._size + source.Length)); + } + + if (index < view._size) + { + Array.Copy(view._items, index, view._items, index + source.Length, view._size - index); + } + + source.CopyTo(view._items.AsSpan(index)); + view._size += source.Length; + view._version++; + } + } + + static void Grow(ref CollectionsMarshal.ListView list, int capacity) + { + SetCapacity(ref list, GetNewCapacity(ref list, capacity)); + } + + static void SetCapacity(ref CollectionsMarshal.ListView list, int value) + { + if (value != list._items.Length) + { + if (value > 0) + { + T[] newItems = new T[value]; + if (list._size > 0) + { + Array.Copy(list._items, newItems, list._size); + } + list._items = newItems; + } + else + { + list._items = Array.Empty(); + } + } + } + + static int GetNewCapacity(ref CollectionsMarshal.ListView list, int capacity) + { + int newCapacity = list._items.Length == 0 ? 4 : 2 * list._items.Length; + + if ((uint)newCapacity > Array.MaxLength) newCapacity = Array.MaxLength; + + if (newCapacity < capacity) newCapacity = capacity; + + return newCapacity; + } + +#pragma warning restore CS0436 +#endif + #if !NET6_0_OR_GREATER public static bool TryGetNonEnumeratedCount(this IEnumerable source, out int count) diff --git a/src/ObservableCollections/Shims/CollectionsMarshalEx.cs b/src/ObservableCollections/Shims/CollectionsMarshalEx.cs new file mode 100644 index 0000000..6541623 --- /dev/null +++ b/src/ObservableCollections/Shims/CollectionsMarshalEx.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +#if !NET7_0_OR_GREATER + +#pragma warning disable CS0649 +#pragma warning disable CS8618 +#pragma warning disable CS8619 + +namespace System.Runtime.InteropServices; + +internal static class CollectionsMarshal +{ + /// + /// similar as AsSpan but modify size to create fixed-size span. + /// + public static Span AsSpan(List? list) + { + if (list is null) return default; + + ref var view = ref Unsafe.As, ListView>(ref list!); + return view._items.AsSpan(0, view._size); + } + + internal sealed class ListView + { + public T[] _items; + public int _size; + public int _version; + } +} + +#endif \ No newline at end of file diff --git a/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs b/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs new file mode 100644 index 0000000..31cdc1d --- /dev/null +++ b/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Specialized; + +namespace ObservableCollections +{ + public readonly ref struct SynchronizedViewChangedEventArgs( + NotifyCollectionChangedAction action, + bool isSingleItem, + (T Value, TView View) newItem = default!, + (T Value, TView View) oldItem = default!, + ReadOnlySpan newValues = default!, + ReadOnlySpan newViews = default!, + ReadOnlySpan<(T Value, TView View)> oldItems = default!, + int newStartingIndex = -1, + int oldStartingIndex = -1, + SortOperation sortOperation = default) + { + public readonly NotifyCollectionChangedAction Action = action; + public readonly bool IsSingleItem = isSingleItem; + public readonly (T Value, TView View) NewItem = newItem; + 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 int NewStartingIndex = newStartingIndex; + public readonly int OldStartingIndex = oldStartingIndex; + public readonly SortOperation SortOperation = sortOperation; + } + + public static class SynchronizedViewExtensions + { + public static void AttachFilter(this ISynchronizedView source, Func filter) + { + source.AttachFilter(new SynchronizedViewFilter(filter)); + } + + public static bool IsNullFilter(this ISynchronizedViewFilter filter) + { + return filter == SynchronizedViewFilter.Null; + } + + internal static void InvokeOnAdd(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, (T value, TView view) value, int index) + { + InvokeOnAdd(collection, ref filteredCount, ev, value.value, value.view, index); + } + + internal static void InvokeOnAdd(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, T value, TView view, int index) + { + var isMatch = collection.Filter.IsMatch(value); + if (isMatch) + { + filteredCount++; + if (ev != null) + { + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, newValue: value, newView: view, newViewIndex: index)); + } + } + } + + internal static void InvokeOnRemove(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, (T value, TView view) value, int oldIndex) + { + InvokeOnRemove(collection, ref filteredCount, ev, value.value, value.view, oldIndex); + } + + internal static void InvokeOnRemove(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, T value, TView view, int oldIndex) + { + var isMatch = collection.Filter.IsMatch(value); + if (isMatch) + { + filteredCount--; + if (ev != null) + { + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); + } + } + } + + internal static void InvokeOnMove(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, (T value, TView view) value, int index, int oldIndex) + { + InvokeOnMove(collection, ref filteredCount, ev, value.value, value.view, index, oldIndex); + } + + internal static void InvokeOnMove(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, T value, TView view, int index, int oldIndex) + { + if (ev != null) + { + // move does not changes filtered-count + var isMatch = collection.Filter.IsMatch(value); + if (isMatch) + { + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Move, newValue: value, newView: view, newViewIndex: index, oldViewIndex: oldIndex)); + } + } + } + + internal static void InvokeOnReplace(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, (T value, TView view) value, (T value, TView view) oldValue, int index, int oldIndex = -1) + { + InvokeOnReplace(collection, ref filteredCount, ev, value.value, value.view, oldValue.value, oldValue.view, index, oldIndex); + } + + internal static void InvokeOnReplace(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, T value, TView view, T oldValue, TView oldView, int index, int oldIndex = -1) + { + var oldMatched = collection.Filter.IsMatch(oldValue); + var newMatched = collection.Filter.IsMatch(value); + var bothMatched = oldMatched && newMatched; + + if (bothMatched) + { + if (ev != null) + { + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Replace, newValue: value, newView: view, oldValue: oldValue, oldView: oldView, newViewIndex: index, oldViewIndex: oldIndex >= 0 ? oldIndex : index)); + } + } + else if (oldMatched) + { + // only-old is remove + filteredCount--; + if (ev != null) + { + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); + } + + } + else if (newMatched) + { + // only-new is add + filteredCount++; + if (ev != null) + { + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, newValue: value, newView: view, newViewIndex: index)); + } + } + } + + internal static void InvokeOnReset(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev) + { + filteredCount = 0; + if (ev != null) + { + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + } + } +} diff --git a/src/ObservableCollections/SynchronizedViewList.cs b/src/ObservableCollections/SynchronizedViewList.cs index d722adb..56e7c37 100644 --- a/src/ObservableCollections/SynchronizedViewList.cs +++ b/src/ObservableCollections/SynchronizedViewList.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; +using System.Runtime.InteropServices; namespace ObservableCollections { @@ -30,24 +31,57 @@ namespace ObservableCollections switch (e.Action) { case NotifyCollectionChangedAction.Add: // Add or Insert - if (e.NewViewIndex == -1) + if (e.IsSingleItem) { - listView.Add(e.NewView); + if (e.NewStartingIndex == -1) + { + listView.Add(e.NewItem.View); + } + else + { + listView.Insert(e.NewStartingIndex, e.NewItem.View); + } } else { - listView.Insert(e.NewViewIndex, e.NewView); + if (e.NewStartingIndex == -1) + { + listView.AddRange(e.NewViews); + } + else + { + listView.InsertRange(e.NewStartingIndex, e.NewViews); + } } break; case NotifyCollectionChangedAction.Remove: // Remove - if (e.OldViewIndex == -1) // can't gurantee correct remove if index is not provided + if (e.IsSingleItem) { - listView.Remove(e.OldView); + if (e.OldStartingIndex == -1) // can't gurantee correct remove if index is not provided + { + listView.Remove(e.OldItem.View); + } + else + { + listView.RemoveAt(e.OldStartingIndex); + } } else { - listView.RemoveAt(e.OldViewIndex); + if (e.OldStartingIndex == -1) + { + // TODO:... + //listView.RemoveAll( + + // e.OldItems + } + else + { + listView.RemoveRange(e.OldStartingIndex, e.OldItems.Length); + } } + + break; case NotifyCollectionChangedAction.Replace: // Indexer if (e.NewViewIndex == -1) @@ -73,10 +107,38 @@ namespace ObservableCollections } break; case NotifyCollectionChangedAction.Reset: // Clear or drastic changes - listView.Clear(); - foreach (var item in parent) // refresh + if (e.SortOperation.IsNull) { - listView.Add(item); + listView.Clear(); + foreach (var item in parent) // refresh + { + listView.Add(item); + } + } + else if (e.SortOperation.IsReverse) + { + listView.Reverse(); + } + else + { + 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 + } + else + { + // can not get source Span, do Clear and Refresh + listView.Clear(); + foreach (var item in parent) + { + listView.Add(item); + } + } } break; default: