This commit is contained in:
neuecc 2024-08-28 17:53:17 +09:00
parent 7e37cfc878
commit 6ee7fb7301
14 changed files with 361 additions and 178 deletions

View File

@ -60,16 +60,16 @@ class HogeFilter : ISynchronizedViewFilter<int>
switch (eventArgs.Action) switch (eventArgs.Action)
{ {
case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Add:
eventArgs.NewView.Value += " Add"; eventArgs.NewItem.View.Value += " Add";
break; break;
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:
eventArgs.OldView.Value += " Remove"; eventArgs.OldItem.View.Value += " Remove";
break; break;
case NotifyCollectionChangedAction.Move: case NotifyCollectionChangedAction.Move:
eventArgs.NewView.Value += $" Move {eventArgs.OldViewIndex} {eventArgs.NewViewIndex}"; eventArgs.NewItem.View.Value += $" Move {eventArgs.OldStartingIndex} {eventArgs.NewStartingIndex}";
break; break;
case NotifyCollectionChangedAction.Replace: case NotifyCollectionChangedAction.Replace:
eventArgs.NewView.Value += $" Replace {eventArgs.NewViewIndex}"; eventArgs.NewItem.View.Value += $" Replace {eventArgs.NewStartingIndex}";
break; break;
case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Reset:
break; break;

View File

@ -0,0 +1,55 @@
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
namespace ObservableCollections.Internal
{
internal ref struct FixedArray<T>
{
public readonly Span<T> Span;
T[]? array;
public FixedArray(int size)
{
array = ArrayPool<T>.Shared.Rent(size);
Span = array.AsSpan(0, size);
}
public void Dispose()
{
if (array != null)
{
ArrayPool<T>.Shared.Return(array, RuntimeHelpersEx.IsReferenceOrContainsReferences<T>());
}
}
}
internal ref struct FixedBoolArray
{
public const int StackallocSize = 128;
public readonly Span<bool> Span;
bool[]? array;
public FixedBoolArray(Span<bool> scratchBuffer, int capacity)
{
if (scratchBuffer.Length == 0)
{
array = ArrayPool<bool>.Shared.Rent(capacity);
Span = array.AsSpan(0, capacity);
}
else
{
Span = scratchBuffer;
}
}
public void Dispose()
{
if (array != null)
{
ArrayPool<bool>.Shared.Return(array);
}
}
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
namespace ObservableCollections.Internal
{
internal class RemoveAllMatcher<T>
{
readonly HashSet<T> hashSet;
public RemoveAllMatcher(ReadOnlySpan<T> source)
{
#if !NETSTANDARD2_0
var set = new HashSet<T>(capacity: source.Length);
#else
var set = new HashSet<T>();
#endif
foreach (var item in source)
{
set.Add(item);
}
this.hashSet = set;
}
public bool Predicate(T value)
{
return hashSet.Contains(value);
}
}
}

View File

@ -15,6 +15,10 @@
<ItemGroup Condition="$(TargetFramework) == 'netstandard2.0'"> <ItemGroup Condition="$(TargetFramework) == 'netstandard2.0'">
<PackageReference Include="System.Memory" Version="4.5.4" /> <PackageReference Include="System.Memory" Version="4.5.4" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework) == 'netstandard2.1'">
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -94,7 +94,7 @@ namespace ObservableCollections
} }
} }
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<KeyValuePair<TKey, TValue>, TView>(NotifyCollectionChangedAction.Reset)); ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<KeyValuePair<TKey, TValue>, TView>(NotifyCollectionChangedAction.Reset, true));
} }
} }
@ -104,7 +104,7 @@ namespace ObservableCollections
{ {
this.filter = SynchronizedViewFilter<KeyValuePair<TKey, TValue>>.Null; this.filter = SynchronizedViewFilter<KeyValuePair<TKey, TValue>>.Null;
this.filteredCount = dict.Count; this.filteredCount = dict.Count;
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<KeyValuePair<TKey, TValue>, TView>(NotifyCollectionChangedAction.Reset)); ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<KeyValuePair<TKey, TValue>, TView>(NotifyCollectionChangedAction.Reset, true));
} }
} }

View File

@ -89,7 +89,7 @@ namespace ObservableCollections
filteredCount++; filteredCount++;
} }
} }
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset)); ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
} }
} }
@ -99,7 +99,7 @@ namespace ObservableCollections
{ {
this.filter = SynchronizedViewFilter<T>.Null; this.filter = SynchronizedViewFilter<T>.Null;
this.filteredCount = dict.Count; this.filteredCount = dict.Count;
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset)); ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
} }
} }

View File

@ -195,32 +195,7 @@ namespace ObservableCollections
switch (e.Action) switch (e.Action)
{ {
case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Add:
// Add // Add or Insert
if (e.NewStartingIndex == list.Count)
{
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++);
}
}
// Insert
else
{
if (e.IsSingleItem) if (e.IsSingleItem)
{ {
var v = (e.NewItem, selector(e.NewItem)); var v = (e.NewItem, selector(e.NewItem));
@ -229,14 +204,32 @@ namespace ObservableCollections
} }
else else
{ {
var span = e.NewItems; var items = e.NewItems;
for (var i = 0; i < span.Length; i++) var length = items.Length;
using var valueViews = new FixedArray<(T, TView)>(length);
using var views = new FixedArray<TView>(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 = (span[i], selector(span[i])); var item = items[i];
list.Insert(e.NewStartingIndex + i, v); // should we use InsertRange? var view = selector(item);
this.InvokeOnAdd(ref filteredCount, ViewChanged, v, e.NewStartingIndex + i); views.Span[i] = view;
valueViews.Span[i] = (item, view);
var isMatch = matches.Span[i] = Filter.IsMatch(item);
if (isMatch)
{
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; break;
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:
@ -248,17 +241,32 @@ namespace ObservableCollections
} }
else else
{ {
list.RemoveRange(e.OldStartingIndex, e.OldItems.Length); // TODO: no var length = e.OldItems.Length;
using var values = new FixedArray<T>(length);
using var views = new FixedArray<TView>(length);
// TODO: Range operation before remove...! using var matches = new FixedBoolArray(length < FixedBoolArray.StackallocSize ? stackalloc bool[length] : default, length);
var isMatchAll = true;
var len = e.OldStartingIndex + e.OldItems.Length; var to = e.OldStartingIndex + length;
for (var i = e.OldStartingIndex; i < len; i++) var j = 0;
for (int i = e.OldStartingIndex; i < to; i++)
{ {
var v = list[i]; var item = list[i];
this.InvokeOnRemove(ref filteredCount, ViewChanged, v, e.OldStartingIndex + 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; break;
case NotifyCollectionChangedAction.Replace: case NotifyCollectionChangedAction.Replace:
@ -290,14 +298,13 @@ namespace ObservableCollections
{ {
// Reverse // Reverse
list.Reverse(e.SortOperation.Index, e.SortOperation.Count); list.Reverse(e.SortOperation.Index, e.SortOperation.Count);
// TODO:Invoke this.InvokeOnReverseOrSort(ViewChanged, e.SortOperation);
} }
else else
{ {
// Sort // Sort
list.Sort(e.SortOperation.Index, e.SortOperation.Count, new IgnoreViewComparer(e.SortOperation.Comparer ?? Comparer<T>.Default)); list.Sort(e.SortOperation.Index, e.SortOperation.Count, new IgnoreViewComparer(e.SortOperation.Comparer ?? Comparer<T>.Default));
// Span<T> d; this.InvokeOnReverseOrSort(ViewChanged, e.SortOperation);
} }
break; break;
default: default:

View File

@ -89,7 +89,7 @@ namespace ObservableCollections
filteredCount++; filteredCount++;
} }
} }
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset)); ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
} }
} }
@ -99,7 +99,7 @@ namespace ObservableCollections
{ {
this.filter = SynchronizedViewFilter<T>.Null; this.filter = SynchronizedViewFilter<T>.Null;
this.filteredCount = queue.Count; this.filteredCount = queue.Count;
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset)); ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
} }
} }

View File

@ -91,7 +91,7 @@ namespace ObservableCollections
filteredCount++; filteredCount++;
} }
} }
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset)); ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
} }
} }
@ -101,7 +101,7 @@ namespace ObservableCollections
{ {
this.filter = SynchronizedViewFilter<T>.Null; this.filter = SynchronizedViewFilter<T>.Null;
this.filteredCount = ringBuffer.Count; this.filteredCount = ringBuffer.Count;
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset)); ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
} }
} }

View File

@ -88,7 +88,7 @@ namespace ObservableCollections
filteredCount++; filteredCount++;
} }
} }
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset)); ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
} }
} }
@ -98,7 +98,7 @@ namespace ObservableCollections
{ {
this.filter = SynchronizedViewFilter<T>.Null; this.filter = SynchronizedViewFilter<T>.Null;
this.filteredCount = stack.Count; this.filteredCount = stack.Count;
ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset)); ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
} }
} }

View File

@ -11,6 +11,8 @@ namespace System.Collections.Generic
{ {
internal static class CollectionExtensions internal static class CollectionExtensions
{ {
const int ArrayMaxLength = 0X7FFFFFC7;
public static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> kvp, out TKey key, out TValue value) public static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> kvp, out TKey key, out TValue value)
{ {
key = kvp.Key; key = kvp.Key;
@ -108,7 +110,7 @@ namespace System.Collections.Generic
{ {
int newCapacity = list._items.Length == 0 ? 4 : 2 * list._items.Length; 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; if (newCapacity < capacity) newCapacity = capacity;

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Data;
namespace ObservableCollections namespace ObservableCollections
{ {
@ -10,7 +11,8 @@ namespace ObservableCollections
(T Value, TView View) oldItem = default!, (T Value, TView View) oldItem = default!,
ReadOnlySpan<T> newValues = default!, ReadOnlySpan<T> newValues = default!,
ReadOnlySpan<TView> newViews = default!, ReadOnlySpan<TView> newViews = default!,
ReadOnlySpan<(T Value, TView View)> oldItems = default!, ReadOnlySpan<T> oldValues = default!,
ReadOnlySpan<TView> oldViews = default!,
int newStartingIndex = -1, int newStartingIndex = -1,
int oldStartingIndex = -1, int oldStartingIndex = -1,
SortOperation<T> sortOperation = default) SortOperation<T> sortOperation = default)
@ -21,7 +23,8 @@ namespace ObservableCollections
public readonly (T Value, TView View) OldItem = oldItem; public readonly (T Value, TView View) OldItem = oldItem;
public readonly ReadOnlySpan<T> NewValues = newValues; public readonly ReadOnlySpan<T> NewValues = newValues;
public readonly ReadOnlySpan<TView> NewViews = newViews; public readonly ReadOnlySpan<TView> NewViews = newViews;
public readonly ReadOnlySpan<(T Value, TView View)> OldItems = oldItems; public readonly ReadOnlySpan<T> OldValues = oldValues;
public readonly ReadOnlySpan<TView> OldViews = oldViews;
public readonly int NewStartingIndex = newStartingIndex; public readonly int NewStartingIndex = newStartingIndex;
public readonly int OldStartingIndex = oldStartingIndex; public readonly int OldStartingIndex = oldStartingIndex;
public readonly SortOperation<T> SortOperation = sortOperation; public readonly SortOperation<T> SortOperation = sortOperation;
@ -57,15 +60,25 @@ namespace ObservableCollections
} }
} }
internal static void InvokeOnAddRange<T, TView>(this ISynchronizedView<T, TView> collection, ref int filteredCount, NotifyViewChangedEventHandler<T, TView>? ev, ReadOnlySpan<T> values, ReadOnlySpan<TView> views, int index) internal static void InvokeOnAddRange<T, TView>(this ISynchronizedView<T, TView> collection, NotifyViewChangedEventHandler<T, TView>? ev, ReadOnlySpan<T> values, ReadOnlySpan<TView> views, bool isMatchAll, ReadOnlySpan<bool> matches, int index)
{ {
var isMatch = collection.Filter.IsMatch(value);
if (isMatch)
{
filteredCount++;
if (ev != null) if (ev != null)
{ {
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Add, false, newValues: values, newViews: views, newStartingIndex: index)); if (isMatchAll)
{
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(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<T, TView>(NotifyCollectionChangedAction.Add, isSingleItem: true, newItem: item, newStartingIndex: startingIndex++));
}
}
} }
} }
} }
@ -83,7 +96,35 @@ namespace ObservableCollections
filteredCount--; filteredCount--;
if (ev != null) if (ev != null)
{ {
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Remove, true, oldItem: (value, view), oldStartingIndex: oldIndex));
}
}
}
// only use for ObservableList
internal static void InvokeOnRemoveRange<T, TView>(this ISynchronizedView<T, TView> collection, NotifyViewChangedEventHandler<T, TView>? ev, ReadOnlySpan<T> values, ReadOnlySpan<TView> views, bool isMatchAll, ReadOnlySpan<bool> matches, int index)
{
if (ev != null)
{
if (isMatchAll)
{
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(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<T, TView>(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); var isMatch = collection.Filter.IsMatch(value);
if (isMatch) if (isMatch)
{ {
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Move, newValue: value, newView: view, newViewIndex: index, oldViewIndex: oldIndex)); ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Move, true, newItem: (value, view), newStartingIndex: index, oldStartingIndex: oldIndex));
} }
} }
} }
@ -121,7 +162,7 @@ namespace ObservableCollections
{ {
if (ev != null) if (ev != null)
{ {
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Replace, newValue: value, newView: view, oldValue: oldValue, oldView: oldView, newViewIndex: index, oldViewIndex: oldIndex >= 0 ? oldIndex : index)); ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Replace, true, newItem: (value, view), oldItem: (oldValue, oldView), newStartingIndex: index, oldStartingIndex: oldIndex >= 0 ? oldIndex : index));
} }
} }
else if (oldMatched) else if (oldMatched)
@ -130,7 +171,7 @@ namespace ObservableCollections
filteredCount--; filteredCount--;
if (ev != null) if (ev != null)
{ {
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Remove, true, oldItem: (value, view), oldStartingIndex: oldIndex));
} }
} }
@ -140,7 +181,7 @@ namespace ObservableCollections
filteredCount++; filteredCount++;
if (ev != null) if (ev != null)
{ {
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Add, newValue: value, newView: view, newViewIndex: index)); ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Add, true, newItem: (value, view), newStartingIndex: index));
} }
} }
} }
@ -150,7 +191,15 @@ namespace ObservableCollections
filteredCount = 0; filteredCount = 0;
if (ev != null) if (ev != null)
{ {
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset)); ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true));
}
}
internal static void InvokeOnReverseOrSort<T, TView>(this ISynchronizedView<T, TView> collection, NotifyViewChangedEventHandler<T, TView>? ev, SortOperation<T> sortOperation)
{
if (ev != null)
{
ev.Invoke(new SynchronizedViewChangedEventArgs<T, TView>(NotifyCollectionChangedAction.Reset, true, sortOperation: sortOperation));
} }
} }
} }

View File

@ -1,4 +1,5 @@
using System; using ObservableCollections.Internal;
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
@ -8,10 +9,19 @@ using System.Runtime.InteropServices;
namespace ObservableCollections namespace ObservableCollections
{ {
internal class SynchronizedViewList<T, TView> : ISynchronizedViewList<TView> internal class SynchronizedViewList<T, TView> : ISynchronizedViewList<TView>
{ {
readonly ISynchronizedView<T, TView> parent; readonly ISynchronizedView<T, TView> parent;
protected readonly List<TView> listView; protected readonly List<TView> listView;
//protected readonly SortedList<int, TView> listView; // key is original index
protected readonly object gate = new object(); protected readonly object gate = new object();
public SynchronizedViewList(ISynchronizedView<T, TView> parent) public SynchronizedViewList(ISynchronizedView<T, TView> parent)
@ -70,40 +80,38 @@ namespace ObservableCollections
{ {
if (e.OldStartingIndex == -1) if (e.OldStartingIndex == -1)
{ {
// TODO:... var matcher = new RemoveAllMatcher<TView>(e.OldViews);
//listView.RemoveAll( listView.RemoveAll(matcher.Predicate);
// e.OldItems
} }
else else
{ {
listView.RemoveRange(e.OldStartingIndex, e.OldItems.Length); listView.RemoveRange(e.OldStartingIndex, e.OldViews.Length);
} }
} }
break; break;
case NotifyCollectionChangedAction.Replace: // Indexer case NotifyCollectionChangedAction.Replace: // Indexer
if (e.NewViewIndex == -1) if (e.NewStartingIndex == -1)
{ {
var index = listView.IndexOf(e.OldView); var index = listView.IndexOf(e.OldItem.View);
listView[index] = e.NewView; listView[index] = e.NewItem.View;
} }
else else
{ {
listView[e.NewViewIndex] = e.NewView; listView[e.NewStartingIndex] = e.NewItem.View;
} }
break; break;
case NotifyCollectionChangedAction.Move: //Remove and Insert case NotifyCollectionChangedAction.Move: //Remove and Insert
if (e.NewViewIndex == -1) if (e.NewStartingIndex == -1)
{ {
// do nothing // do nothing
} }
else else
{ {
listView.RemoveAt(e.OldViewIndex); listView.RemoveAt(e.OldStartingIndex);
listView.Insert(e.NewViewIndex, e.NewView); listView.Insert(e.NewStartingIndex, e.NewItem.View);
} }
break; break;
case NotifyCollectionChangedAction.Reset: // Clear or drastic changes case NotifyCollectionChangedAction.Reset: // Clear or drastic changes
@ -121,16 +129,18 @@ namespace ObservableCollections
} }
else else
{ {
#if NET6_0_OR_GREATER
#pragma warning disable CS0436
if (parent is ObservableList<T>.View<TView> observableListView) if (parent is ObservableList<T>.View<TView> observableListView)
{ {
#pragma warning disable CS0436
var comparer = new ObservableList<T>.View<TView>.IgnoreViewComparer(e.SortOperation.Comparer ?? Comparer<T>.Default); var comparer = new ObservableList<T>.View<TView>.IgnoreViewComparer(e.SortOperation.Comparer ?? Comparer<T>.Default);
var viewSpan = CollectionsMarshal.AsSpan(listView).Slice(e.SortOperation.Index, e.SortOperation.Count); 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); var sourceSpan = CollectionsMarshal.AsSpan(observableListView.list).Slice(e.SortOperation.Index, e.SortOperation.Count);
sourceSpan.Sort(viewSpan, comparer); sourceSpan.Sort(viewSpan, comparer); // span.Sort is NET6 or greater
#pragma warning restore CS0436
} }
else else
#pragma warning restore CS0436
#endif
{ {
// can not get source Span, do Clear and Refresh // can not get source Span, do Clear and Refresh
listView.Clear(); listView.Clear();
@ -223,22 +233,48 @@ namespace ObservableCollections
switch (args.Action) switch (args.Action)
{ {
case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Add:
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewView, args.NewViewIndex) if (args.IsSingleItem)
{
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewItem.View, args.NewStartingIndex)
{ {
Collection = this, Collection = this,
Invoker = raiseChangedEventInvoke, Invoker = raiseChangedEventInvoke,
IsInvokeCollectionChanged = true, IsInvokeCollectionChanged = true,
IsInvokePropertyChanged = true IsInvokePropertyChanged = true
}); });
}
else
{
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewViews.ToArray(), args.NewStartingIndex)
{
Collection = this,
Invoker = raiseChangedEventInvoke,
IsInvokeCollectionChanged = true,
IsInvokePropertyChanged = true
});
}
break; break;
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldView, args.OldViewIndex) if (args.IsSingleItem)
{
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldItem.View, args.OldStartingIndex)
{ {
Collection = this, Collection = this,
Invoker = raiseChangedEventInvoke, Invoker = raiseChangedEventInvoke,
IsInvokeCollectionChanged = true, IsInvokeCollectionChanged = true,
IsInvokePropertyChanged = true IsInvokePropertyChanged = true
}); });
}
else
{
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldViews.ToArray(), args.OldStartingIndex)
{
Collection = this,
Invoker = raiseChangedEventInvoke,
IsInvokeCollectionChanged = true,
IsInvokePropertyChanged = true
});
}
break; break;
case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Reset:
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Reset) eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Reset)
@ -250,7 +286,7 @@ namespace ObservableCollections
}); });
break; break;
case NotifyCollectionChangedAction.Replace: 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, Collection = this,
Invoker = raiseChangedEventInvoke, Invoker = raiseChangedEventInvoke,
@ -259,7 +295,7 @@ namespace ObservableCollections
}); });
break; break;
case NotifyCollectionChangedAction.Move: 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, Collection = this,
Invoker = raiseChangedEventInvoke, Invoker = raiseChangedEventInvoke,