optimize for ObservableList

This commit is contained in:
neuecc 2024-09-02 21:19:15 +09:00
parent a981b3121f
commit d84965e20e
14 changed files with 668 additions and 71 deletions

View File

@ -20,6 +20,12 @@
<StackPanel> <StackPanel>
<ListBox ItemsSource="{Binding ItemsView}" /> <ListBox ItemsSource="{Binding ItemsView}" />
<Button Content="Add" Command="{Binding AddCommand}" /> <Button Content="Add" Command="{Binding AddCommand}" />
<Button Content="Insert" Command="{Binding InsertAtRandomCommand}" />
<Button Content="Remove" Command="{Binding RemoveAtRandomCommand}" />
<Button Content="Clear" Command="{Binding ClearCommand}" /> <Button Content="Clear" Command="{Binding ClearCommand}" />
<Button Content="Reverse" Command="{Binding ReverseCommand}" />
<Button Content="Sort" Command="{Binding SortCommand}" />
<Button Content="AttachFilter" Command="{Binding AttachFilterCommand}" />
<Button Content="ResetFilter" Command="{Binding ResetFilterCommand}" />
</StackPanel> </StackPanel>
</Window> </Window>

View File

@ -76,19 +76,26 @@ namespace WpfApp
private ObservableList<int> observableList { get; } = new ObservableList<int>(); private ObservableList<int> observableList { get; } = new ObservableList<int>();
public INotifyCollectionChangedSynchronizedView<int> ItemsView { get; } public INotifyCollectionChangedSynchronizedView<int> ItemsView { get; }
public ReactiveCommand<Unit> AddCommand { get; } = new ReactiveCommand<Unit>(); public ReactiveCommand<Unit> AddCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> InsertAtRandomCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> RemoveAtRandomCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> ClearCommand { get; } = new ReactiveCommand<Unit>(); public ReactiveCommand<Unit> ClearCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> ReverseCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> SortCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> AttachFilterCommand { get; } = new ReactiveCommand<Unit>();
public ReactiveCommand<Unit> ResetFilterCommand { get; } = new ReactiveCommand<Unit>();
public ViewModel() public ViewModel()
{ {
observableList.Add(1); observableList.Add(1);
observableList.Add(2); 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(_ => 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(_ => ClearCommand.Subscribe(_ =>
{ {
// observableList.Add(iii++);
observableList.Clear(); observableList.Clear();
}); });
ReverseCommand.Subscribe(_ =>
{
observableList.Reverse();
});
SortCommand.Subscribe(_ =>
{
observableList.Sort();
});
AttachFilterCommand.Subscribe(_ =>
{
view.AttachFilter(x => x % 2 == 0);
});
ResetFilterCommand.Subscribe(_ =>
{
view.ResetFilter();
});
} }
} }

View File

@ -50,7 +50,7 @@ public static class ObservableCollectionR3Extensions
return new ObservableCollectionClear<T>(source, cancellationToken); return new ObservableCollectionClear<T>(source, cancellationToken);
} }
public static Observable<Unit> ObserveReverse<T>(this IObservableCollection<T> source, CancellationToken cancellationToken = default) public static Observable<(int Index, int Count))> ObserveReverse<T>(this IObservableCollection<T> source, CancellationToken cancellationToken = default)
{ {
return new ObservableCollectionReverse<T>(source, cancellationToken); return new ObservableCollectionReverse<T>(source, cancellationToken);
} }
@ -251,24 +251,24 @@ sealed class ObservableCollectionClear<T>(IObservableCollection<T> collection, C
} }
} }
sealed class ObservableCollectionReverse<T>(IObservableCollection<T> collection, CancellationToken cancellationToken) : Observable<Unit> sealed class ObservableCollectionReverse<T>(IObservableCollection<T> collection, CancellationToken cancellationToken) : Observable<(int Index, int Count)>
{ {
protected override IDisposable SubscribeCore(Observer<Unit> observer) protected override IDisposable SubscribeCore(Observer<(int Index, int Count)> observer)
{ {
return new _ObservableCollectionReverse(collection, observer, cancellationToken); return new _ObservableCollectionReverse(collection, observer, cancellationToken);
} }
sealed class _ObservableCollectionReverse( sealed class _ObservableCollectionReverse(
IObservableCollection<T> collection, IObservableCollection<T> collection,
Observer<Unit> observer, Observer<(int Index, int Count)> observer,
CancellationToken cancellationToken) CancellationToken cancellationToken)
: ObservableCollectionObserverBase<T, Unit>(collection, observer, cancellationToken) : ObservableCollectionObserverBase<T, (int Index, int Count)>(collection, observer, cancellationToken)
{ {
protected override void Handler(in NotifyCollectionChangedEventArgs<T> eventArgs) protected override void Handler(in NotifyCollectionChangedEventArgs<T> eventArgs)
{ {
if (eventArgs.Action == NotifyCollectionChangedAction.Reset && eventArgs.SortOperation.IsReverse) if (eventArgs.Action == NotifyCollectionChangedAction.Reset && eventArgs.SortOperation.IsReverse)
{ {
observer.OnNext(Unit.Default); observer.OnNext((eventArgs.SortOperation.Index, eventArgs.SortOperation.Count));
} }
} }
} }

View File

@ -40,7 +40,7 @@ public class AlternateIndexList<T> : IEnumerable<T>
public int Count => list.Count; public int Count => list.Count;
public void Insert(int alternateIndex, T value) public int Insert(int alternateIndex, T value)
{ {
var index = list.BinarySearch(alternateIndex); var index = list.BinarySearch(alternateIndex);
if (index < 0) if (index < 0)
@ -49,9 +49,10 @@ public class AlternateIndexList<T> : IEnumerable<T>
} }
list.Insert(index, new(alternateIndex, value)); list.Insert(index, new(alternateIndex, value));
UpdateAlternateIndex(index + 1, 1); UpdateAlternateIndex(index + 1, 1);
return index;
} }
public void InsertRange(int startingAlternateIndex, IEnumerable<T> values) public int InsertRange(int startingAlternateIndex, IEnumerable<T> values)
{ {
var index = list.BinarySearch(startingAlternateIndex); var index = list.BinarySearch(startingAlternateIndex);
if (index < 0) if (index < 0)
@ -62,9 +63,10 @@ public class AlternateIndexList<T> : IEnumerable<T>
using var iter = new InsertIterator(startingAlternateIndex, values); using var iter = new InsertIterator(startingAlternateIndex, values);
list.InsertRange(index, iter); list.InsertRange(index, iter);
UpdateAlternateIndex(index + iter.ConsumedCount, iter.ConsumedCount); UpdateAlternateIndex(index + iter.ConsumedCount, iter.ConsumedCount);
return index;
} }
public void Remove(T value) public int Remove(T value)
{ {
var index = list.FindIndex(x => EqualityComparer<T>.Default.Equals(x.Value, value)); var index = list.FindIndex(x => EqualityComparer<T>.Default.Equals(x.Value, value));
if (index != -1) if (index != -1)
@ -72,9 +74,10 @@ public class AlternateIndexList<T> : IEnumerable<T>
list.RemoveAt(index); list.RemoveAt(index);
UpdateAlternateIndex(index, -1); UpdateAlternateIndex(index, -1);
} }
return index;
} }
public void RemoveAt(int alternateIndex) public int RemoveAt(int alternateIndex)
{ {
var index = list.BinarySearch(alternateIndex); var index = list.BinarySearch(alternateIndex);
if (index != -1) if (index != -1)
@ -82,9 +85,10 @@ public class AlternateIndexList<T> : IEnumerable<T>
list.RemoveAt(index); list.RemoveAt(index);
UpdateAlternateIndex(index, -1); UpdateAlternateIndex(index, -1);
} }
return index;
} }
public void RemoveRange(int alternateIndex, int count) public int RemoveRange(int alternateIndex, int count)
{ {
var index = list.BinarySearch(alternateIndex); var index = list.BinarySearch(alternateIndex);
if (index < 0) if (index < 0)
@ -94,6 +98,7 @@ public class AlternateIndexList<T> : IEnumerable<T>
list.RemoveRange(index, count); list.RemoveRange(index, count);
UpdateAlternateIndex(index, -count); UpdateAlternateIndex(index, -count);
return index;
} }
public bool TryGetAtAlternateIndex(int alternateIndex, [MaybeNullWhen(true)] out T value) public bool TryGetAtAlternateIndex(int alternateIndex, [MaybeNullWhen(true)] out T value)
@ -108,23 +113,23 @@ public class AlternateIndexList<T> : IEnumerable<T>
return true; return true;
} }
public bool TrySetAtAlternateIndex(int alternateIndex, T value) public bool TrySetAtAlternateIndex(int alternateIndex, T value, out int setIndex)
{ {
var index = list.BinarySearch(alternateIndex); setIndex = list.BinarySearch(alternateIndex);
if (index < 0) if (setIndex < 0)
{ {
return false; return false;
} }
CollectionsMarshal.AsSpan(list)[index].Value = value; CollectionsMarshal.AsSpan(list)[setIndex].Value = value;
return true; 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<T>.Default.Equals(x.Value, searchValue)); replacedIndex = list.FindIndex(x => EqualityComparer<T>.Default.Equals(x.Value, searchValue));
if (index != -1) if (replacedIndex != -1)
{ {
CollectionsMarshal.AsSpan(list)[index].Value = replaceValue; CollectionsMarshal.AsSpan(list)[replacedIndex].Value = replaceValue;
return true; return true;
} }
return false; return false;

View File

@ -16,8 +16,6 @@ namespace ObservableCollections
public bool IsReverse => Comparer == ReverseSentinel.Instance; public bool IsReverse => Comparer == ReverseSentinel.Instance;
public bool IsNull => Comparer == null; public bool IsNull => Comparer == null;
[MemberNotNullWhen(true, nameof(Comparer))]
public bool IsSort => !IsNull && !IsReverse; public bool IsSort => !IsNull && !IsReverse;
public SortOperation(int index, int count, IComparer<T>? comparer) public SortOperation(int index, int count, IComparer<T>? comparer)
@ -53,7 +51,7 @@ namespace ObservableCollections
public int Compare(T? x, T? y) public int Compare(T? x, T? y)
{ {
throw new NotImplementedException(); return Comparer<T>.Default.Compare(x!, y!);
} }
} }
} }

View File

@ -110,7 +110,7 @@ namespace ObservableCollections
public ISynchronizedViewList<TView> ToViewList() public ISynchronizedViewList<TView> ToViewList()
{ {
return new SynchronizedViewList<KeyValuePair<TKey, TValue>, TView>(this); return new FiltableSynchronizedViewList<KeyValuePair<TKey, TValue>, TView>(this);
} }
public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged() public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged()

View File

@ -105,7 +105,7 @@ namespace ObservableCollections
public ISynchronizedViewList<TView> ToViewList() public ISynchronizedViewList<TView> ToViewList()
{ {
return new SynchronizedViewList<T, TView>(this); return new FiltableSynchronizedViewList<T, TView>(this);
} }
public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged() public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged()

View File

@ -16,17 +16,30 @@ namespace ObservableCollections
public ISynchronizedViewList<T> ToViewList() public ISynchronizedViewList<T> ToViewList()
{ {
return CreateView(static x => x).ToViewList(); // NOTE: for more optimize, no need to create View.
return ToViewList(static x => x);
}
public ISynchronizedViewList<TView> ToViewList<TView>(Func<T, TView> transform)
{
// Optimized for non filtered
return new NonFilteredSynchronizedViewList<T, TView>(CreateView(transform));
} }
public INotifyCollectionChangedSynchronizedView<T> ToNotifyCollectionChanged() public INotifyCollectionChangedSynchronizedView<T> ToNotifyCollectionChanged()
{ {
return CreateView(static x => x).ToNotifyCollectionChanged(); return ToNotifyCollectionChanged(null);
} }
public INotifyCollectionChangedSynchronizedView<T> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) public INotifyCollectionChangedSynchronizedView<T> ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher)
{ {
return CreateView(static x => x).ToNotifyCollectionChanged(collectionEventDispatcher); return ToNotifyCollectionChanged(static x => x, collectionEventDispatcher);
}
public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged<TView>(Func<T, TView> transform, ICollectionEventDispatcher? collectionEventDispatcher)
{
// Optimized for non filtered
return new NonFilteredNotifyCollectionChangedSynchronizedView<T, TView>(CreateView(transform), collectionEventDispatcher);
} }
internal sealed class View<TView> : ISynchronizedView<T, TView> internal sealed class View<TView> : ISynchronizedView<T, TView>
@ -123,7 +136,7 @@ namespace ObservableCollections
public ISynchronizedViewList<TView> ToViewList() public ISynchronizedViewList<TView> ToViewList()
{ {
return new SynchronizedViewList<T, TView>(this); return new FiltableSynchronizedViewList<T, TView>(this);
} }
public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged() public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged()
@ -315,7 +328,7 @@ namespace ObservableCollections
} }
} }
internal sealed class IgnoreViewComparer : IComparer<(T, TView)> sealed class IgnoreViewComparer : IComparer<(T, TView)>
{ {
readonly IComparer<T> comparer; readonly IComparer<T> comparer;

View File

@ -291,7 +291,7 @@ namespace ObservableCollections
{ {
lock (SyncRoot) lock (SyncRoot)
{ {
list.Sort(); list.Sort(comparer);
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Sort(0, list.Count, comparer)); CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Sort(0, list.Count, comparer));
} }
} }
@ -300,7 +300,7 @@ namespace ObservableCollections
{ {
lock (SyncRoot) lock (SyncRoot)
{ {
list.Sort(); list.Sort(index, count, comparer);
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Sort(index, count, comparer)); CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Sort(index, count, comparer));
} }
} }
@ -309,7 +309,7 @@ namespace ObservableCollections
{ {
lock (SyncRoot) lock (SyncRoot)
{ {
list.Sort(); list.Reverse();
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Reverse(0, list.Count)); CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Reverse(0, list.Count));
} }
} }
@ -318,7 +318,7 @@ namespace ObservableCollections
{ {
lock (SyncRoot) lock (SyncRoot)
{ {
list.Sort(); list.Reverse(index, count);
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Reverse(index, count)); CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Reverse(index, count));
} }
} }

View File

@ -105,7 +105,7 @@ namespace ObservableCollections
public ISynchronizedViewList<TView> ToViewList() public ISynchronizedViewList<TView> ToViewList()
{ {
return new SynchronizedViewList<T, TView>(this); return new FiltableSynchronizedViewList<T, TView>(this);
} }
public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged() public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged()

View File

@ -107,7 +107,7 @@ namespace ObservableCollections
public ISynchronizedViewList<TView> ToViewList() public ISynchronizedViewList<TView> ToViewList()
{ {
return new SynchronizedViewList<T, TView>(this); return new FiltableSynchronizedViewList<T, TView>(this);
} }
public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged() public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged()

View File

@ -104,7 +104,7 @@ namespace ObservableCollections
public ISynchronizedViewList<TView> ToViewList() public ISynchronizedViewList<TView> ToViewList()
{ {
return new SynchronizedViewList<T, TView>(this); return new FiltableSynchronizedViewList<T, TView>(this);
} }
public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged() public INotifyCollectionChangedSynchronizedView<TView> ToNotifyCollectionChanged()

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Data; using System.Data;
using System.Runtime.CompilerServices;
namespace ObservableCollections namespace ObservableCollections
{ {
@ -28,6 +29,55 @@ namespace ObservableCollections
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;
public SynchronizedViewChangedEventArgs<T, TView> WithNewStartingIndex(int newStartingIndex)
{
// MEMO: struct copy and replace only newStartingIndex memory maybe fast.
return new SynchronizedViewChangedEventArgs<T, TView>(
action,
IsSingleItem,
newItem: NewItem,
oldItem: OldItem,
newValues: NewValues,
newViews: NewViews,
oldValues: OldValues,
oldViews: OldViews,
newStartingIndex: newStartingIndex, // replace
oldStartingIndex: OldStartingIndex,
sortOperation: SortOperation);
}
public SynchronizedViewChangedEventArgs<T, TView> WithOldStartingIndex(int oldStartingIndex)
{
return new SynchronizedViewChangedEventArgs<T, TView>(
action,
IsSingleItem,
newItem: NewItem,
oldItem: OldItem,
newValues: NewValues,
newViews: NewViews,
oldValues: OldValues,
oldViews: OldViews,
newStartingIndex: NewStartingIndex,
oldStartingIndex: oldStartingIndex, // replace
sortOperation: SortOperation);
}
public SynchronizedViewChangedEventArgs<T, TView> WithNewAndOldStartingIndex(int newStartingIndex, int oldStartingIndex)
{
return new SynchronizedViewChangedEventArgs<T, TView>(
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 public static class SynchronizedViewExtensions

View File

@ -5,17 +5,18 @@ using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace ObservableCollections; namespace ObservableCollections;
internal class SynchronizedViewList<T, TView> : ISynchronizedViewList<TView> internal class FiltableSynchronizedViewList<T, TView> : ISynchronizedViewList<TView>
{ {
readonly ISynchronizedView<T, TView> parent; readonly ISynchronizedView<T, TView> parent;
protected readonly AlternateIndexList<TView> listView; protected readonly AlternateIndexList<TView> listView;
protected readonly object gate = new object(); protected readonly object gate = new object();
public SynchronizedViewList(ISynchronizedView<T, TView> parent) public FiltableSynchronizedViewList(ISynchronizedView<T, TView> parent)
{ {
this.parent = parent; this.parent = parent;
lock (parent.SyncRoot) lock (parent.SyncRoot)
@ -60,61 +61,85 @@ internal class SynchronizedViewList<T, TView> : ISynchronizedViewList<TView>
case NotifyCollectionChangedAction.Add: // Add or Insert case NotifyCollectionChangedAction.Add: // Add or Insert
if (e.IsSingleItem) 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 else
{ {
using var array = new CloneCollection<TView>(e.NewViews); using var array = new CloneCollection<TView>(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 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); if (e.OldStartingIndex == -1) // can't gurantee correct remove if index is not provided
}
else
{
listView.RemoveAt(e.OldStartingIndex);
}
}
else
{
if (e.OldStartingIndex == -1)
{
foreach (var view in e.OldViews) // index is unknown, can't do batching
{ {
listView.Remove(view); index = listView.Remove(e.OldItem.View);
}
else
{
index = listView.RemoveAt(e.OldStartingIndex);
} }
} }
else 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 case NotifyCollectionChangedAction.Replace: // Indexer
if (e.NewStartingIndex == -1) 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 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 case NotifyCollectionChangedAction.Move: //Remove and Insert
if (e.NewStartingIndex == -1) if (e.NewStartingIndex == -1)
{ {
// do nothing return; // do nothing
} }
else else
{ {
listView.RemoveAt(e.OldStartingIndex); var oldIndex = listView.RemoveAt(e.OldStartingIndex);
listView.Insert(e.NewStartingIndex, e.NewItem.View); var newIndex = listView.Insert(e.NewStartingIndex, e.NewItem.View);
OnCollectionChanged(e.WithNewAndOldStartingIndex(newStartingIndex: newIndex, oldStartingIndex: oldIndex));
} }
break; break;
case NotifyCollectionChangedAction.Reset: // Clear or drastic changes case NotifyCollectionChangedAction.Reset: // Clear or drastic changes
@ -176,8 +201,225 @@ internal class SynchronizedViewList<T, TView> : ISynchronizedViewList<TView>
} }
} }
internal class NonFilteredSynchronizedViewList<T, TView> : ISynchronizedViewList<TView>
{
readonly ISynchronizedView<T, TView> parent;
protected readonly List<TView> listView; // no filter can be faster
protected readonly object gate = new object();
public NonFilteredSynchronizedViewList(ISynchronizedView<T, TView> parent)
{
this.parent = parent;
lock (parent.SyncRoot)
{
listView = parent.ToList(); // iterate filtered
parent.ViewChanged += Parent_ViewChanged;
}
}
private void Parent_ViewChanged(in SynchronizedViewChangedEventArgs<T, TView> 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<TView>(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<T>.View<TView> observableListView && typeof(T) == typeof(TView))
{
var comparer = new ViewComparer(e.SortOperation.Comparer ?? Comparer<T>.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<TView>
{
readonly IComparer<T> comparer;
public ViewComparer(IComparer<T> comparer)
{
this.comparer = comparer;
}
public int Compare(TView? x, TView? y)
{
var t1 = Unsafe.As<TView, T>(ref x!);
var t2 = Unsafe.As<TView, T>(ref y!);
return comparer.Compare(t1, t2);
}
}
protected virtual void OnCollectionChanged(in SynchronizedViewChangedEventArgs<T, TView> args)
{
}
public TView this[int index]
{
get
{
lock (gate)
{
return listView[index];
}
}
}
public int Count
{
get
{
lock (gate)
{
return listView.Count;
}
}
}
public IEnumerator<TView> 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<T, TView> : internal class NotifyCollectionChangedSynchronizedView<T, TView> :
SynchronizedViewList<T, TView>, FiltableSynchronizedViewList<T, TView>,
INotifyCollectionChangedSynchronizedView<TView>, INotifyCollectionChangedSynchronizedView<TView>,
IList<TView>, IList IList<TView>, IList
{ {
@ -415,6 +657,251 @@ internal class NotifyCollectionChangedSynchronizedView<T, TView> :
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void RemoveAt(int index)
{
throw new NotSupportedException();
}
}
internal class NonFilteredNotifyCollectionChangedSynchronizedView<T, TView> :
NonFilteredSynchronizedViewList<T, TView>,
INotifyCollectionChangedSynchronizedView<TView>,
IList<TView>, IList
{
static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count");
static readonly Action<NotifyCollectionChangedEventArgs> raiseChangedEventInvoke = RaiseChangedEvent;
readonly ICollectionEventDispatcher eventDispatcher;
public event NotifyCollectionChangedEventHandler? CollectionChanged;
public event PropertyChangedEventHandler? PropertyChanged;
public NonFilteredNotifyCollectionChangedSynchronizedView(ISynchronizedView<T, TView> parent, ICollectionEventDispatcher? eventDispatcher)
: base(parent)
{
this.eventDispatcher = eventDispatcher ?? InlineCollectionEventDispatcher.Instance;
}
protected override void OnCollectionChanged(in SynchronizedViewChangedEventArgs<T, TView> 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<T, TView>)e2.Collection;
if (e2.IsInvokeCollectionChanged)
{
self.CollectionChanged?.Invoke(self, e);
}
if (e2.IsInvokePropertyChanged)
{
self.PropertyChanged?.Invoke(self, CountPropertyChangedEventArgs);
}
}
// IList<T>, IList implementation
TView IList<TView>.this[int index]
{
get => ((IReadOnlyList<TView>)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<TView>.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<TView>.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) public void RemoveAt(int index)
{ {
throw new NotSupportedException(); throw new NotSupportedException();