diff --git a/sandbox/WpfApp/MainWindow.xaml b/sandbox/WpfApp/MainWindow.xaml
index 67a7270..e5b8ead 100644
--- a/sandbox/WpfApp/MainWindow.xaml
+++ b/sandbox/WpfApp/MainWindow.xaml
@@ -20,6 +20,12 @@
+
+
+
+
+
+
diff --git a/sandbox/WpfApp/MainWindow.xaml.cs b/sandbox/WpfApp/MainWindow.xaml.cs
index 18b8f3a..8ba517b 100644
--- a/sandbox/WpfApp/MainWindow.xaml.cs
+++ b/sandbox/WpfApp/MainWindow.xaml.cs
@@ -76,19 +76,26 @@ namespace WpfApp
private ObservableList observableList { get; } = new ObservableList();
public INotifyCollectionChangedSynchronizedView ItemsView { get; }
public ReactiveCommand AddCommand { get; } = new ReactiveCommand();
+ public ReactiveCommand InsertAtRandomCommand { get; } = new ReactiveCommand();
+ public ReactiveCommand RemoveAtRandomCommand { get; } = new ReactiveCommand();
public ReactiveCommand ClearCommand { get; } = new ReactiveCommand();
+ public ReactiveCommand ReverseCommand { get; } = new ReactiveCommand();
+ public ReactiveCommand SortCommand { get; } = new ReactiveCommand();
+ public ReactiveCommand AttachFilterCommand { get; } = new ReactiveCommand();
+ public ReactiveCommand ResetFilterCommand { get; } = new ReactiveCommand();
public ViewModel()
{
observableList.Add(1);
observableList.Add(2);
- ItemsView = observableList.CreateView(x => x).ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current);
+ var view = observableList.CreateView(x => x);
+ ItemsView = view.ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current);
- // ItemsView = observableList.CreateView(x => x).ToNotifyCollectionChanged();
+ // check for optimize list
+ // ItemsView = observableList.ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current);
- // BindingOperations.EnableCollectionSynchronization(ItemsView, new object());
AddCommand.Subscribe(_ =>
{
@@ -98,12 +105,43 @@ namespace WpfApp
});
});
- // var iii = 10;
+ InsertAtRandomCommand.Subscribe(_ =>
+ {
+ var from = Random.Shared.Next(0, view.Count);
+ observableList.Insert(from, Random.Shared.Next());
+ });
+
+ RemoveAtRandomCommand.Subscribe(_ =>
+ {
+ var from = Random.Shared.Next(0, view.Count);
+ observableList.RemoveAt(from);
+ });
+
ClearCommand.Subscribe(_ =>
{
- // observableList.Add(iii++);
observableList.Clear();
});
+
+
+ ReverseCommand.Subscribe(_ =>
+ {
+ observableList.Reverse();
+ });
+
+ SortCommand.Subscribe(_ =>
+ {
+ observableList.Sort();
+ });
+
+ AttachFilterCommand.Subscribe(_ =>
+ {
+ view.AttachFilter(x => x % 2 == 0);
+ });
+
+ ResetFilterCommand.Subscribe(_ =>
+ {
+ view.ResetFilter();
+ });
}
}
diff --git a/src/ObservableCollections.R3/ObservableCollectionR3Extensions.cs b/src/ObservableCollections.R3/ObservableCollectionR3Extensions.cs
index 2b72713..2d3f56b 100644
--- a/src/ObservableCollections.R3/ObservableCollectionR3Extensions.cs
+++ b/src/ObservableCollections.R3/ObservableCollectionR3Extensions.cs
@@ -50,7 +50,7 @@ public static class ObservableCollectionR3Extensions
return new ObservableCollectionClear(source, cancellationToken);
}
- public static Observable ObserveReverse(this IObservableCollection source, CancellationToken cancellationToken = default)
+ public static Observable<(int Index, int Count))> ObserveReverse(this IObservableCollection source, CancellationToken cancellationToken = default)
{
return new ObservableCollectionReverse(source, cancellationToken);
}
@@ -251,24 +251,24 @@ sealed class ObservableCollectionClear(IObservableCollection collection, C
}
}
-sealed class ObservableCollectionReverse(IObservableCollection collection, CancellationToken cancellationToken) : Observable
+sealed class ObservableCollectionReverse(IObservableCollection collection, CancellationToken cancellationToken) : Observable<(int Index, int Count)>
{
- protected override IDisposable SubscribeCore(Observer observer)
+ protected override IDisposable SubscribeCore(Observer<(int Index, int Count)> observer)
{
return new _ObservableCollectionReverse(collection, observer, cancellationToken);
}
sealed class _ObservableCollectionReverse(
IObservableCollection collection,
- Observer observer,
+ Observer<(int Index, int Count)> observer,
CancellationToken cancellationToken)
- : ObservableCollectionObserverBase(collection, observer, cancellationToken)
+ : ObservableCollectionObserverBase(collection, observer, cancellationToken)
{
protected override void Handler(in NotifyCollectionChangedEventArgs eventArgs)
{
if (eventArgs.Action == NotifyCollectionChangedAction.Reset && eventArgs.SortOperation.IsReverse)
{
- observer.OnNext(Unit.Default);
+ observer.OnNext((eventArgs.SortOperation.Index, eventArgs.SortOperation.Count));
}
}
}
diff --git a/src/ObservableCollections/AlternateIndexList.cs b/src/ObservableCollections/AlternateIndexList.cs
index 964905b..19d3110 100644
--- a/src/ObservableCollections/AlternateIndexList.cs
+++ b/src/ObservableCollections/AlternateIndexList.cs
@@ -40,7 +40,7 @@ public class AlternateIndexList : IEnumerable
public int Count => list.Count;
- public void Insert(int alternateIndex, T value)
+ public int Insert(int alternateIndex, T value)
{
var index = list.BinarySearch(alternateIndex);
if (index < 0)
@@ -49,9 +49,10 @@ public class AlternateIndexList : IEnumerable
}
list.Insert(index, new(alternateIndex, value));
UpdateAlternateIndex(index + 1, 1);
+ return index;
}
- public void InsertRange(int startingAlternateIndex, IEnumerable values)
+ public int InsertRange(int startingAlternateIndex, IEnumerable values)
{
var index = list.BinarySearch(startingAlternateIndex);
if (index < 0)
@@ -62,9 +63,10 @@ public class AlternateIndexList : IEnumerable
using var iter = new InsertIterator(startingAlternateIndex, values);
list.InsertRange(index, iter);
UpdateAlternateIndex(index + iter.ConsumedCount, iter.ConsumedCount);
+ return index;
}
- public void Remove(T value)
+ public int Remove(T value)
{
var index = list.FindIndex(x => EqualityComparer.Default.Equals(x.Value, value));
if (index != -1)
@@ -72,9 +74,10 @@ public class AlternateIndexList : IEnumerable
list.RemoveAt(index);
UpdateAlternateIndex(index, -1);
}
+ return index;
}
- public void RemoveAt(int alternateIndex)
+ public int RemoveAt(int alternateIndex)
{
var index = list.BinarySearch(alternateIndex);
if (index != -1)
@@ -82,9 +85,10 @@ public class AlternateIndexList : IEnumerable
list.RemoveAt(index);
UpdateAlternateIndex(index, -1);
}
+ return index;
}
- public void RemoveRange(int alternateIndex, int count)
+ public int RemoveRange(int alternateIndex, int count)
{
var index = list.BinarySearch(alternateIndex);
if (index < 0)
@@ -94,6 +98,7 @@ public class AlternateIndexList : IEnumerable
list.RemoveRange(index, count);
UpdateAlternateIndex(index, -count);
+ return index;
}
public bool TryGetAtAlternateIndex(int alternateIndex, [MaybeNullWhen(true)] out T value)
@@ -108,23 +113,23 @@ public class AlternateIndexList : IEnumerable
return true;
}
- public bool TrySetAtAlternateIndex(int alternateIndex, T value)
+ public bool TrySetAtAlternateIndex(int alternateIndex, T value, out int setIndex)
{
- var index = list.BinarySearch(alternateIndex);
- if (index < 0)
+ setIndex = list.BinarySearch(alternateIndex);
+ if (setIndex < 0)
{
return false;
}
- CollectionsMarshal.AsSpan(list)[index].Value = value;
+ CollectionsMarshal.AsSpan(list)[setIndex].Value = value;
return true;
}
- public bool TryReplaceByValue(T searchValue, T replaceValue)
+ public bool TryReplaceByValue(T searchValue, T replaceValue, out int replacedIndex)
{
- var index = list.FindIndex(x => EqualityComparer.Default.Equals(x.Value, searchValue));
- if (index != -1)
+ replacedIndex = list.FindIndex(x => EqualityComparer.Default.Equals(x.Value, searchValue));
+ if (replacedIndex != -1)
{
- CollectionsMarshal.AsSpan(list)[index].Value = replaceValue;
+ CollectionsMarshal.AsSpan(list)[replacedIndex].Value = replaceValue;
return true;
}
return false;
diff --git a/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs b/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs
index 4ad53a0..45b5fdb 100644
--- a/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs
+++ b/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs
@@ -16,8 +16,6 @@ namespace ObservableCollections
public bool IsReverse => Comparer == ReverseSentinel.Instance;
public bool IsNull => Comparer == null;
-
- [MemberNotNullWhen(true, nameof(Comparer))]
public bool IsSort => !IsNull && !IsReverse;
public SortOperation(int index, int count, IComparer? comparer)
@@ -53,7 +51,7 @@ namespace ObservableCollections
public int Compare(T? x, T? y)
{
- throw new NotImplementedException();
+ return Comparer.Default.Compare(x!, y!);
}
}
}
diff --git a/src/ObservableCollections/ObservableDictionary.Views.cs b/src/ObservableCollections/ObservableDictionary.Views.cs
index 87e61d7..8ce2992 100644
--- a/src/ObservableCollections/ObservableDictionary.Views.cs
+++ b/src/ObservableCollections/ObservableDictionary.Views.cs
@@ -110,7 +110,7 @@ namespace ObservableCollections
public ISynchronizedViewList ToViewList()
{
- return new SynchronizedViewList, TView>(this);
+ return new FiltableSynchronizedViewList, TView>(this);
}
public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged()
diff --git a/src/ObservableCollections/ObservableHashSet.Views.cs b/src/ObservableCollections/ObservableHashSet.Views.cs
index af63254..f24fa55 100644
--- a/src/ObservableCollections/ObservableHashSet.Views.cs
+++ b/src/ObservableCollections/ObservableHashSet.Views.cs
@@ -105,7 +105,7 @@ namespace ObservableCollections
public ISynchronizedViewList ToViewList()
{
- return new SynchronizedViewList(this);
+ return new FiltableSynchronizedViewList(this);
}
public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged()
diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs
index 3ea9b4f..d366b54 100644
--- a/src/ObservableCollections/ObservableList.Views.cs
+++ b/src/ObservableCollections/ObservableList.Views.cs
@@ -16,17 +16,30 @@ namespace ObservableCollections
public ISynchronizedViewList ToViewList()
{
- return CreateView(static x => x).ToViewList();
+ // NOTE: for more optimize, no need to create View.
+ return ToViewList(static x => x);
+ }
+
+ public ISynchronizedViewList ToViewList(Func transform)
+ {
+ // Optimized for non filtered
+ return new NonFilteredSynchronizedViewList(CreateView(transform));
}
public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged()
{
- return CreateView(static x => x).ToNotifyCollectionChanged();
+ return ToNotifyCollectionChanged(null);
}
public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
{
- return CreateView(static x => x).ToNotifyCollectionChanged(collectionEventDispatcher);
+ return ToNotifyCollectionChanged(static x => x, collectionEventDispatcher);
+ }
+
+ public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(Func transform, ICollectionEventDispatcher? collectionEventDispatcher)
+ {
+ // Optimized for non filtered
+ return new NonFilteredNotifyCollectionChangedSynchronizedView(CreateView(transform), collectionEventDispatcher);
}
internal sealed class View : ISynchronizedView
@@ -123,7 +136,7 @@ namespace ObservableCollections
public ISynchronizedViewList ToViewList()
{
- return new SynchronizedViewList(this);
+ return new FiltableSynchronizedViewList(this);
}
public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged()
@@ -315,7 +328,7 @@ namespace ObservableCollections
}
}
- internal sealed class IgnoreViewComparer : IComparer<(T, TView)>
+ sealed class IgnoreViewComparer : IComparer<(T, TView)>
{
readonly IComparer comparer;
diff --git a/src/ObservableCollections/ObservableList.cs b/src/ObservableCollections/ObservableList.cs
index a7d325c..47a0d17 100644
--- a/src/ObservableCollections/ObservableList.cs
+++ b/src/ObservableCollections/ObservableList.cs
@@ -291,7 +291,7 @@ namespace ObservableCollections
{
lock (SyncRoot)
{
- list.Sort();
+ list.Sort(comparer);
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Sort(0, list.Count, comparer));
}
}
@@ -300,7 +300,7 @@ namespace ObservableCollections
{
lock (SyncRoot)
{
- list.Sort();
+ list.Sort(index, count, comparer);
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Sort(index, count, comparer));
}
}
@@ -309,7 +309,7 @@ namespace ObservableCollections
{
lock (SyncRoot)
{
- list.Sort();
+ list.Reverse();
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Reverse(0, list.Count));
}
}
@@ -318,7 +318,7 @@ namespace ObservableCollections
{
lock (SyncRoot)
{
- list.Sort();
+ list.Reverse(index, count);
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Reverse(index, count));
}
}
diff --git a/src/ObservableCollections/ObservableQueue.Views.cs b/src/ObservableCollections/ObservableQueue.Views.cs
index fc2ef42..852c3fd 100644
--- a/src/ObservableCollections/ObservableQueue.Views.cs
+++ b/src/ObservableCollections/ObservableQueue.Views.cs
@@ -105,7 +105,7 @@ namespace ObservableCollections
public ISynchronizedViewList ToViewList()
{
- return new SynchronizedViewList(this);
+ return new FiltableSynchronizedViewList(this);
}
public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged()
diff --git a/src/ObservableCollections/ObservableRingBuffer.Views.cs b/src/ObservableCollections/ObservableRingBuffer.Views.cs
index 848cdd9..4281b0b 100644
--- a/src/ObservableCollections/ObservableRingBuffer.Views.cs
+++ b/src/ObservableCollections/ObservableRingBuffer.Views.cs
@@ -107,7 +107,7 @@ namespace ObservableCollections
public ISynchronizedViewList ToViewList()
{
- return new SynchronizedViewList(this);
+ return new FiltableSynchronizedViewList(this);
}
public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged()
diff --git a/src/ObservableCollections/ObservableStack.Views.cs b/src/ObservableCollections/ObservableStack.Views.cs
index f77b67c..000082d 100644
--- a/src/ObservableCollections/ObservableStack.Views.cs
+++ b/src/ObservableCollections/ObservableStack.Views.cs
@@ -104,7 +104,7 @@ namespace ObservableCollections
public ISynchronizedViewList ToViewList()
{
- return new SynchronizedViewList(this);
+ return new FiltableSynchronizedViewList(this);
}
public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged()
diff --git a/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs b/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs
index 0a47d7a..c87c9cb 100644
--- a/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs
+++ b/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Specialized;
using System.Data;
+using System.Runtime.CompilerServices;
namespace ObservableCollections
{
@@ -28,6 +29,55 @@ namespace ObservableCollections
public readonly int NewStartingIndex = newStartingIndex;
public readonly int OldStartingIndex = oldStartingIndex;
public readonly SortOperation SortOperation = sortOperation;
+
+ public SynchronizedViewChangedEventArgs WithNewStartingIndex(int newStartingIndex)
+ {
+ // MEMO: struct copy and replace only newStartingIndex memory maybe fast.
+ return new SynchronizedViewChangedEventArgs(
+ action,
+ IsSingleItem,
+ newItem: NewItem,
+ oldItem: OldItem,
+ newValues: NewValues,
+ newViews: NewViews,
+ oldValues: OldValues,
+ oldViews: OldViews,
+ newStartingIndex: newStartingIndex, // replace
+ oldStartingIndex: OldStartingIndex,
+ sortOperation: SortOperation);
+ }
+
+ public SynchronizedViewChangedEventArgs WithOldStartingIndex(int oldStartingIndex)
+ {
+ return new SynchronizedViewChangedEventArgs(
+ action,
+ IsSingleItem,
+ newItem: NewItem,
+ oldItem: OldItem,
+ newValues: NewValues,
+ newViews: NewViews,
+ oldValues: OldValues,
+ oldViews: OldViews,
+ newStartingIndex: NewStartingIndex,
+ oldStartingIndex: oldStartingIndex, // replace
+ sortOperation: SortOperation);
+ }
+
+ public SynchronizedViewChangedEventArgs WithNewAndOldStartingIndex(int newStartingIndex, int oldStartingIndex)
+ {
+ return new SynchronizedViewChangedEventArgs(
+ action,
+ IsSingleItem,
+ newItem: NewItem,
+ oldItem: OldItem,
+ newValues: NewValues,
+ newViews: NewViews,
+ oldValues: OldValues,
+ oldViews: OldViews,
+ newStartingIndex: newStartingIndex, // replace
+ oldStartingIndex: oldStartingIndex, // replace
+ sortOperation: SortOperation);
+ }
}
public static class SynchronizedViewExtensions
diff --git a/src/ObservableCollections/SynchronizedViewList.cs b/src/ObservableCollections/SynchronizedViewList.cs
index fe78868..4957208 100644
--- a/src/ObservableCollections/SynchronizedViewList.cs
+++ b/src/ObservableCollections/SynchronizedViewList.cs
@@ -5,17 +5,18 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace ObservableCollections;
-internal class SynchronizedViewList : ISynchronizedViewList
+internal class FiltableSynchronizedViewList : ISynchronizedViewList
{
readonly ISynchronizedView parent;
protected readonly AlternateIndexList listView;
protected readonly object gate = new object();
- public SynchronizedViewList(ISynchronizedView parent)
+ public FiltableSynchronizedViewList(ISynchronizedView parent)
{
this.parent = parent;
lock (parent.SyncRoot)
@@ -60,61 +61,85 @@ internal class SynchronizedViewList : ISynchronizedViewList
case NotifyCollectionChangedAction.Add: // Add or Insert
if (e.IsSingleItem)
{
- listView.Insert(e.NewStartingIndex, e.NewItem.View);
+ var index = listView.Insert(e.NewStartingIndex, e.NewItem.View);
+ OnCollectionChanged(e.WithNewStartingIndex(index));
+ return;
}
else
{
using var array = new CloneCollection(e.NewViews);
- listView.InsertRange(e.NewStartingIndex, array.AsEnumerable());
+ var index = listView.InsertRange(e.NewStartingIndex, array.AsEnumerable());
+ OnCollectionChanged(e.WithNewStartingIndex(index));
+ return;
}
- break;
case NotifyCollectionChangedAction.Remove: // Remove
- if (e.IsSingleItem)
{
- if (e.OldStartingIndex == -1) // can't gurantee correct remove if index is not provided
+ int index = e.OldStartingIndex;
+ if (e.IsSingleItem)
{
- listView.Remove(e.OldItem.View);
- }
- else
- {
- listView.RemoveAt(e.OldStartingIndex);
- }
- }
- else
- {
- if (e.OldStartingIndex == -1)
- {
- foreach (var view in e.OldViews) // index is unknown, can't do batching
+ if (e.OldStartingIndex == -1) // can't gurantee correct remove if index is not provided
{
- listView.Remove(view);
+ index = listView.Remove(e.OldItem.View);
+ }
+ else
+ {
+ index = listView.RemoveAt(e.OldStartingIndex);
}
}
else
{
- listView.RemoveRange(e.OldStartingIndex, e.OldViews.Length);
+ if (e.OldStartingIndex == -1)
+ {
+ foreach (var view in e.OldViews) // index is unknown, can't do batching
+ {
+ listView.Remove(view);
+ OnCollectionChanged(e.WithOldStartingIndex(index));
+ }
+ return;
+ }
+ else
+ {
+ index = listView.RemoveRange(e.OldStartingIndex, e.OldViews.Length);
+ }
}
+ OnCollectionChanged(e.WithOldStartingIndex(index));
+ return;
}
- break;
case NotifyCollectionChangedAction.Replace: // Indexer
if (e.NewStartingIndex == -1)
{
- listView.TryReplaceByValue(e.OldItem.View, e.NewItem.View);
+ if (listView.TryReplaceByValue(e.OldItem.View, e.NewItem.View, out var replacedIndex))
+ {
+ OnCollectionChanged(e.WithNewAndOldStartingIndex(replacedIndex, replacedIndex));
+ return;
+ }
+ else
+ {
+ return;
+ }
}
else
{
- listView.TrySetAtAlternateIndex(e.NewStartingIndex, e.NewItem.View);
+ if (listView.TrySetAtAlternateIndex(e.NewStartingIndex, e.NewItem.View, out var setIndex))
+ {
+ OnCollectionChanged(e.WithNewAndOldStartingIndex(setIndex, setIndex));
+ return;
+ }
+ else
+ {
+ return;
+ }
}
-
- break;
case NotifyCollectionChangedAction.Move: //Remove and Insert
if (e.NewStartingIndex == -1)
{
- // do nothing
+ return; // do nothing
}
else
{
- listView.RemoveAt(e.OldStartingIndex);
- listView.Insert(e.NewStartingIndex, e.NewItem.View);
+ var oldIndex = listView.RemoveAt(e.OldStartingIndex);
+ var newIndex = listView.Insert(e.NewStartingIndex, e.NewItem.View);
+ OnCollectionChanged(e.WithNewAndOldStartingIndex(newStartingIndex: newIndex, oldStartingIndex: oldIndex));
}
break;
case NotifyCollectionChangedAction.Reset: // Clear or drastic changes
@@ -176,8 +201,225 @@ internal class SynchronizedViewList : ISynchronizedViewList
}
}
+internal class NonFilteredSynchronizedViewList : ISynchronizedViewList
+{
+ readonly ISynchronizedView parent;
+ protected readonly List listView; // no filter can be faster
+ protected readonly object gate = new object();
+
+ public NonFilteredSynchronizedViewList(ISynchronizedView parent)
+ {
+ this.parent = parent;
+ lock (parent.SyncRoot)
+ {
+ listView = parent.ToList(); // iterate filtered
+ parent.ViewChanged += Parent_ViewChanged;
+ }
+ }
+
+ private void Parent_ViewChanged(in SynchronizedViewChangedEventArgs e)
+ {
+ // event is called inside parent lock
+ lock (gate)
+ {
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add: // Add or Insert
+ if (e.IsSingleItem)
+ {
+ listView.Insert(e.NewStartingIndex, e.NewItem.View);
+ }
+ else
+ {
+#if NET8_0_OR_GREATER
+ listView.InsertRange(e.NewStartingIndex, e.NewViews);
+#else
+ using var array = new CloneCollection(e.NewViews);
+ listView.InsertRange(e.NewStartingIndex, array.AsEnumerable());
+#endif
+ }
+ break;
+ case NotifyCollectionChangedAction.Remove: // Remove
+ {
+ if (e.IsSingleItem)
+ {
+ if (e.OldStartingIndex == -1) // can't gurantee correct remove if index is not provided
+ {
+ var index = listView.IndexOf(e.OldItem.View);
+ listView.RemoveAt(index);
+ OnCollectionChanged(e.WithOldStartingIndex(index));
+ return;
+ }
+ else
+ {
+ listView.RemoveAt(e.OldStartingIndex);
+ }
+ }
+ else
+ {
+ if (e.OldStartingIndex == -1)
+ {
+ foreach (var view in e.OldViews) // index is unknown, can't do batching
+ {
+ var index = listView.IndexOf(view);
+ listView.RemoveAt(index);
+ OnCollectionChanged(e.WithOldStartingIndex(index));
+ }
+ return;
+ }
+ else
+ {
+ listView.RemoveRange(e.OldStartingIndex, e.OldViews.Length);
+ }
+ }
+ break;
+ }
+ case NotifyCollectionChangedAction.Replace: // Indexer
+ if (e.NewStartingIndex == -1)
+ {
+ var index = listView.IndexOf(e.OldItem.View);
+ if (index != -1)
+ {
+ listView[index] = e.NewItem.View;
+ OnCollectionChanged(e.WithNewAndOldStartingIndex(index, index));
+ return;
+ }
+ else
+ {
+ return;
+ }
+ }
+ else
+ {
+ listView[e.NewStartingIndex] = e.NewItem.View;
+ }
+ break;
+ case NotifyCollectionChangedAction.Move: //Remove and Insert
+ if (e.NewStartingIndex == -1)
+ {
+ return; // do nothing
+ }
+ else
+ {
+ listView.RemoveAt(e.OldStartingIndex);
+ listView.Insert(e.NewStartingIndex, e.NewItem.View);
+ }
+ break;
+ case NotifyCollectionChangedAction.Reset: // Clear or drastic changes
+ if (e.SortOperation.IsNull)
+ {
+ listView.Clear();
+ foreach (var item in parent.Unfiltered) // refresh
+ {
+ listView.Add(item.View);
+ }
+ }
+ else if (e.SortOperation.IsReverse)
+ {
+ listView.Reverse(e.SortOperation.Index, e.SortOperation.Count);
+ }
+ else
+ {
+
+
+
+#if NET6_0_OR_GREATER
+#pragma warning disable CS0436
+ if (parent is ObservableList.View observableListView && typeof(T) == typeof(TView))
+ {
+ var comparer = new ViewComparer(e.SortOperation.Comparer ?? Comparer.Default);
+ var viewSpan = CollectionsMarshal.AsSpan(listView).Slice(e.SortOperation.Index, e.SortOperation.Count);
+ viewSpan.Sort(comparer);
+ }
+ else
+#pragma warning restore CS0436
+#endif
+ {
+ // can not get source Span, do Clear and Refresh
+ listView.Clear();
+ foreach (var item in parent.Unfiltered)
+ {
+ listView.Add(item.View);
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ OnCollectionChanged(e);
+ }
+ }
+
+ sealed class ViewComparer : IComparer
+ {
+ readonly IComparer comparer;
+
+ public ViewComparer(IComparer comparer)
+ {
+ this.comparer = comparer;
+ }
+
+ public int Compare(TView? x, TView? y)
+ {
+ var t1 = Unsafe.As(ref x!);
+ var t2 = Unsafe.As(ref y!);
+ return comparer.Compare(t1, t2);
+ }
+ }
+
+ protected virtual void OnCollectionChanged(in SynchronizedViewChangedEventArgs args)
+ {
+ }
+
+ public TView this[int index]
+ {
+ get
+ {
+ lock (gate)
+ {
+ return listView[index];
+ }
+ }
+ }
+
+ public int Count
+ {
+ get
+ {
+ lock (gate)
+ {
+ return listView.Count;
+ }
+ }
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ lock (gate)
+ {
+ foreach (var item in listView)
+ {
+ yield return item;
+ }
+ }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return listView.GetEnumerator();
+ }
+
+ public void Dispose()
+ {
+ parent.ViewChanged -= Parent_ViewChanged;
+ parent.Dispose(); // Dispose parent
+ }
+}
+
internal class NotifyCollectionChangedSynchronizedView :
- SynchronizedViewList,
+ FiltableSynchronizedViewList,
INotifyCollectionChangedSynchronizedView,
IList, IList
{
@@ -415,6 +657,251 @@ internal class NotifyCollectionChangedSynchronizedView :
throw new NotImplementedException();
}
+ public void RemoveAt(int index)
+ {
+ throw new NotSupportedException();
+ }
+}
+
+internal class NonFilteredNotifyCollectionChangedSynchronizedView :
+ NonFilteredSynchronizedViewList,
+ INotifyCollectionChangedSynchronizedView,
+ IList, IList
+{
+ static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count");
+ static readonly Action raiseChangedEventInvoke = RaiseChangedEvent;
+
+ readonly ICollectionEventDispatcher eventDispatcher;
+
+ public event NotifyCollectionChangedEventHandler? CollectionChanged;
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ public NonFilteredNotifyCollectionChangedSynchronizedView(ISynchronizedView parent, ICollectionEventDispatcher? eventDispatcher)
+ : base(parent)
+ {
+ this.eventDispatcher = eventDispatcher ?? InlineCollectionEventDispatcher.Instance;
+ }
+
+ protected override void OnCollectionChanged(in SynchronizedViewChangedEventArgs args)
+ {
+ if (CollectionChanged == null && PropertyChanged == null) return;
+
+ switch (args.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ if (args.IsSingleItem)
+ {
+ 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:
+ if (args.IsSingleItem)
+ {
+ 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)
+ {
+ Collection = this,
+ Invoker = raiseChangedEventInvoke,
+ IsInvokeCollectionChanged = true,
+ IsInvokePropertyChanged = true
+ });
+ break;
+ case NotifyCollectionChangedAction.Replace:
+ eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Replace, args.NewItem.View, args.OldItem.View, args.NewStartingIndex)
+ {
+ Collection = this,
+ Invoker = raiseChangedEventInvoke,
+ IsInvokeCollectionChanged = true,
+ IsInvokePropertyChanged = false
+ });
+ break;
+ case NotifyCollectionChangedAction.Move:
+ eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Move, args.NewItem.View, args.NewStartingIndex, args.OldStartingIndex)
+ {
+ Collection = this,
+ Invoker = raiseChangedEventInvoke,
+ IsInvokeCollectionChanged = true,
+ IsInvokePropertyChanged = false
+ });
+ break;
+ }
+ }
+
+ static void RaiseChangedEvent(NotifyCollectionChangedEventArgs e)
+ {
+ var e2 = (CollectionEventDispatcherEventArgs)e;
+ var self = (NonFilteredNotifyCollectionChangedSynchronizedView)e2.Collection;
+
+ if (e2.IsInvokeCollectionChanged)
+ {
+ self.CollectionChanged?.Invoke(self, e);
+ }
+ if (e2.IsInvokePropertyChanged)
+ {
+ self.PropertyChanged?.Invoke(self, CountPropertyChangedEventArgs);
+ }
+ }
+
+ // IList, IList implementation
+
+ TView IList.this[int index]
+ {
+ get => ((IReadOnlyList)this)[index];
+ set => throw new NotSupportedException();
+ }
+
+ object? IList.this[int index]
+ {
+ get
+ {
+ return this[index];
+ }
+ set => throw new NotSupportedException();
+ }
+
+ static bool IsCompatibleObject(object? value)
+ {
+ return value is TView || value == null && default(TView) == null;
+ }
+
+ public bool IsReadOnly => true;
+
+ public bool IsFixedSize => false;
+
+ public bool IsSynchronized => true;
+
+ public object SyncRoot => gate;
+
+ public void Add(TView item)
+ {
+ throw new NotSupportedException();
+ }
+
+ public int Add(object? value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Clear()
+ {
+ throw new NotSupportedException();
+ }
+
+ public bool Contains(TView item)
+ {
+ lock (gate)
+ {
+ foreach (var listItem in listView)
+ {
+ if (EqualityComparer.Default.Equals(listItem, item))
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public bool Contains(object? value)
+ {
+ if (IsCompatibleObject(value))
+ {
+ return Contains((TView)value!);
+ }
+ return false;
+ }
+
+ public void CopyTo(TView[] array, int arrayIndex)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void CopyTo(Array array, int index)
+ {
+ throw new NotImplementedException();
+ }
+
+ public int IndexOf(TView item)
+ {
+ lock (gate)
+ {
+ var index = 0;
+ foreach (var listItem in listView)
+ {
+ if (EqualityComparer.Default.Equals(listItem, item))
+ {
+ return index;
+ }
+ index++;
+ }
+ }
+ return -1;
+ }
+
+ public int IndexOf(object? item)
+ {
+ if (IsCompatibleObject(item))
+ {
+ return IndexOf((TView)item!);
+ }
+ return -1;
+ }
+
+ public void Insert(int index, TView item)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void Insert(int index, object? value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool Remove(TView item)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void Remove(object? value)
+ {
+ throw new NotImplementedException();
+ }
+
public void RemoveAt(int index)
{
throw new NotSupportedException();