From d5f52ee6b72456e3e2168aaad3a5501df1c67fa6 Mon Sep 17 00:00:00 2001 From: neuecc Date: Tue, 20 Aug 2024 17:00:17 +0900 Subject: [PATCH 01/22] todo --- .../IObservableCollection.cs | 4 + .../ISynchronizedViewFilter.cs | 9 +- ...NotifyCollectionChangedSynchronizedView.cs | 106 ++++++++++++++++++ .../ObservableList.Views.cs | 14 ++- 4 files changed, 129 insertions(+), 4 deletions(-) diff --git a/src/ObservableCollections/IObservableCollection.cs b/src/ObservableCollections/IObservableCollection.cs index 474bb8e..efcfae7 100644 --- a/src/ObservableCollections/IObservableCollection.cs +++ b/src/ObservableCollections/IObservableCollection.cs @@ -57,6 +57,10 @@ namespace ObservableCollections //{ //} + public interface ISynchronizedViewList : IReadOnlyList, IDisposable + { + } + public interface INotifyCollectionChangedSynchronizedView : IReadOnlyCollection, INotifyCollectionChanged, INotifyPropertyChanged, IDisposable { } diff --git a/src/ObservableCollections/ISynchronizedViewFilter.cs b/src/ObservableCollections/ISynchronizedViewFilter.cs index 968b286..a2daec2 100644 --- a/src/ObservableCollections/ISynchronizedViewFilter.cs +++ b/src/ObservableCollections/ISynchronizedViewFilter.cs @@ -73,8 +73,8 @@ namespace ObservableCollections { return filter == SynchronizedViewFilter.Null; } - - + + internal static void InvokeOnAdd(this ISynchronizedViewFilter filter, (T value, TView view) value, int index) { filter.InvokeOnAdd(value.value, value.view, index); @@ -139,5 +139,10 @@ namespace ObservableCollections filter.WhenFalse(value, view); } } + + internal static bool IsMatch(this ISynchronizedViewFilter filter, (T, TView) value) + { + return filter.IsMatch(value); + } } } diff --git a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs index d28aea7..ee25a44 100644 --- a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs +++ b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs @@ -1,11 +1,117 @@ using System; +using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; +using System.Linq; namespace ObservableCollections.Internal { + internal class SynchronizedViewList : ISynchronizedViewList + { + readonly ISynchronizedView parent; + readonly List listView; + readonly Func selector; + + public SynchronizedViewList(ISynchronizedView parent, Func selector) + { + this.parent = parent; + this.selector = selector; + parent.RoutingCollectionChanged += Parent_RoutingCollectionChanged; // TODO: -= + } + + private void Parent_RoutingCollectionChanged(in NotifyCollectionChangedEventArgs e) + { + // call in parent.lock. + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + if (e.IsSingleItem) + { + // parent.CurrentFilter.IsMatch( + + var item = selector(e.NewItem); + + + + + if (e.NewStartingIndex != -1) + { + listView.Insert(e.NewStartingIndex, selector(e.NewItem)); + } + else // dict is -1 + { + listView.Add(selector(e.NewItem)); + } + } + else + { + if (e.NewStartingIndex != -1) + { + var array = ArrayPool.Shared.Rent(e.NewItems.Length); + try + { + var i = 0; + foreach (var item in e.NewItems) + { + array[i++] = selector(item); + } + + listView.InsertRange(e.NewStartingIndex, array.Take(e.NewItems.Length)); + } + finally + { + ArrayPool.Shared.Return(array, true); + } + } + else + { + foreach (var item in e.NewItems) + { + listView.Add(selector(item)); + } + } + } + break; + case NotifyCollectionChangedAction.Remove: + break; + case NotifyCollectionChangedAction.Replace: + break; + case NotifyCollectionChangedAction.Move: + break; + case NotifyCollectionChangedAction.Reset: + break; + default: + break; + } + + // throw new NotImplementedException(); + } + + public TView this[int index] => throw new NotImplementedException(); + + public int Count => throw new NotImplementedException(); + + public void Dispose() + { + throw new NotImplementedException(); + } + + public IEnumerator GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + } + + + + internal class NotifyCollectionChangedSynchronizedView : INotifyCollectionChangedSynchronizedView, ISynchronizedViewFilter diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index 03c56dd..549bbcb 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -27,7 +27,8 @@ namespace ObservableCollections readonly ObservableList source; readonly Func selector; readonly bool reverse; - internal readonly List<(T, TView)> list; // be careful to use + readonly List<(T, TView)> list; + int filteredCount; ISynchronizedViewFilter filter; @@ -46,6 +47,7 @@ namespace ObservableCollections lock (source.SyncRoot) { this.list = source.list.Select(x => (x, selector(x))).ToList(); + this.filteredCount = list.Count; this.source.CollectionChanged += SourceCollectionChanged; } } @@ -56,7 +58,7 @@ namespace ObservableCollections { lock (SyncRoot) { - return list.Count; + return filteredCount; } } } @@ -160,6 +162,10 @@ namespace ObservableCollections { var v = (e.NewItem, selector(e.NewItem)); list.Add(v); + if (filter.IsMatch(v)) + { + filteredCount++; + } filter.InvokeOnAdd(v, e.NewStartingIndex); } else @@ -169,6 +175,10 @@ namespace ObservableCollections { var v = (item, selector(item)); list.Add(v); + if (filter.IsMatch(v)) + { + filteredCount++; + } filter.InvokeOnAdd(v, i++); } } From ffa8a97e35049c355e06fe764e087db9dcab484f Mon Sep 17 00:00:00 2001 From: neuecc Date: Tue, 20 Aug 2024 19:06:57 +0900 Subject: [PATCH 02/22] reworking --- .../IObservableCollection.cs | 3 + ...NotifyCollectionChangedSynchronizedView.cs | 92 ++++++++----------- 2 files changed, 43 insertions(+), 52 deletions(-) diff --git a/src/ObservableCollections/IObservableCollection.cs b/src/ObservableCollections/IObservableCollection.cs index efcfae7..b17a30c 100644 --- a/src/ObservableCollections/IObservableCollection.cs +++ b/src/ObservableCollections/IObservableCollection.cs @@ -37,6 +37,9 @@ namespace ObservableCollections object SyncRoot { get; } ISynchronizedViewFilter CurrentFilter { get; } + // TODO: add + event Action>? ViewChanged; + // TODO: remove event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; event Action? CollectionStateChanged; diff --git a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs index ee25a44..e6c4c3c 100644 --- a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs +++ b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs @@ -12,75 +12,62 @@ namespace ObservableCollections.Internal { readonly ISynchronizedView parent; readonly List listView; - readonly Func selector; - public SynchronizedViewList(ISynchronizedView parent, Func selector) + public SynchronizedViewList(ISynchronizedView parent) { this.parent = parent; - this.selector = selector; - parent.RoutingCollectionChanged += Parent_RoutingCollectionChanged; // TODO: -= + // TODO:add + parent.ViewChanged += Parent_ViewChanged; } - private void Parent_RoutingCollectionChanged(in NotifyCollectionChangedEventArgs e) + private void Parent_ViewChanged(SynchronizedViewChangedEventArgs e) { - // call in parent.lock. + // event is called inside lock(parent.SyncRoot) + // TODO: invoke in ICollectionEventDispatcher? + switch (e.Action) { - case NotifyCollectionChangedAction.Add: - if (e.IsSingleItem) + case NotifyCollectionChangedAction.Add: // Add or Insert + if (e.NewViewIndex == -1) { - // parent.CurrentFilter.IsMatch( - - var item = selector(e.NewItem); - - - - - if (e.NewStartingIndex != -1) - { - listView.Insert(e.NewStartingIndex, selector(e.NewItem)); - } - else // dict is -1 - { - listView.Add(selector(e.NewItem)); - } + listView.Add(e.NewView); } else { - if (e.NewStartingIndex != -1) - { - var array = ArrayPool.Shared.Rent(e.NewItems.Length); - try - { - var i = 0; - foreach (var item in e.NewItems) - { - array[i++] = selector(item); - } - - listView.InsertRange(e.NewStartingIndex, array.Take(e.NewItems.Length)); - } - finally - { - ArrayPool.Shared.Return(array, true); - } - } - else - { - foreach (var item in e.NewItems) - { - listView.Add(selector(item)); - } - } + listView.Insert(e.NewViewIndex, e.NewView); } break; - case NotifyCollectionChangedAction.Remove: + case NotifyCollectionChangedAction.Remove: // Remove + if (e.OldViewIndex == -1) // can't gurantee correct remove if index is not provided + { + listView.Remove(e.OldView); + } + else + { + listView.RemoveAt(e.OldViewIndex); + } break; - case NotifyCollectionChangedAction.Replace: + case NotifyCollectionChangedAction.Replace: // Indexer + if (e.NewViewIndex == -1) + { + } + else + { + listView[e.NewViewIndex] = e.NewView; + } + break; - case NotifyCollectionChangedAction.Move: + case NotifyCollectionChangedAction.Move: //Remove and Insert + if (e.NewViewIndex == -1) + { + } + else + { + listView.RemoveAt(e.OldViewIndex); + listView.Insert(e.NewViewIndex, e.NewView); + } break; - case NotifyCollectionChangedAction.Reset: + case NotifyCollectionChangedAction.Reset: // Clear break; default: break; @@ -89,6 +76,7 @@ namespace ObservableCollections.Internal // throw new NotImplementedException(); } + public TView this[int index] => throw new NotImplementedException(); public int Count => throw new NotImplementedException(); From 4aacf11beeb77f9d393a70a7e335fdbf1d8aec65 Mon Sep 17 00:00:00 2001 From: neuecc Date: Wed, 21 Aug 2024 17:00:28 +0900 Subject: [PATCH 03/22] WIP --- .../IObservableCollection.cs | 12 +- .../ISynchronizedViewFilter.cs | 152 ++++++++++-------- ...NotifyCollectionChangedSynchronizedView.cs | 27 ++-- .../ObservableList.Views.cs | 9 +- 4 files changed, 106 insertions(+), 94 deletions(-) diff --git a/src/ObservableCollections/IObservableCollection.cs b/src/ObservableCollections/IObservableCollection.cs index b17a30c..108fab8 100644 --- a/src/ObservableCollections/IObservableCollection.cs +++ b/src/ObservableCollections/IObservableCollection.cs @@ -21,7 +21,7 @@ namespace ObservableCollections { } - public interface IReadOnlyObservableDictionary : + public interface IReadOnlyObservableDictionary : IReadOnlyDictionary, IObservableCollection> { } @@ -35,16 +35,16 @@ namespace ObservableCollections public interface ISynchronizedView : IReadOnlyCollection<(T Value, TView View)>, IDisposable { object SyncRoot { get; } - ISynchronizedViewFilter CurrentFilter { get; } + ISynchronizedViewFilter CurrentFilter { get; } // TODO: add - event Action>? ViewChanged; + event Action>? ViewChanged; // TODO: remove - event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; + // event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; event Action? CollectionStateChanged; - void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForInitialElements = false); - void ResetFilter(Action? resetAction); + void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForInitialElements = false); + void ResetFilter(Action? resetAction); INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(); INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher); } diff --git a/src/ObservableCollections/ISynchronizedViewFilter.cs b/src/ObservableCollections/ISynchronizedViewFilter.cs index a2daec2..9f4b32b 100644 --- a/src/ObservableCollections/ISynchronizedViewFilter.cs +++ b/src/ObservableCollections/ISynchronizedViewFilter.cs @@ -21,111 +21,130 @@ namespace ObservableCollections public readonly int OldViewIndex = oldViewIndex; } - public interface ISynchronizedViewFilter + public interface ISynchronizedViewFilter { - bool IsMatch(T value, TView view); - void WhenTrue(T value, TView view); - void WhenFalse(T value, TView view); - void OnCollectionChanged(in SynchronizedViewChangedEventArgs eventArgs); + bool IsMatch(T value); } - public class SynchronizedViewFilter( - Func isMatch, - Action? whenTrue, - Action? whenFalse, - Action>? onCollectionChanged) - : ISynchronizedViewFilter + public class SynchronizedViewFilter(Func isMatch) : ISynchronizedViewFilter { - public static readonly ISynchronizedViewFilter Null = new NullViewFilter(); + public static readonly ISynchronizedViewFilter Null = new NullViewFilter(); - public bool IsMatch(T value, TView view) => isMatch(value, view); - public void WhenFalse(T value, TView view) => whenFalse?.Invoke(value, view); - public void WhenTrue(T value, TView view) => whenTrue?.Invoke(value, view); - public void OnCollectionChanged(in SynchronizedViewChangedEventArgs eventArgs) => onCollectionChanged?.Invoke(eventArgs); + public bool IsMatch(T value) => isMatch(value); - class NullViewFilter : ISynchronizedViewFilter + class NullViewFilter : ISynchronizedViewFilter { - public bool IsMatch(T value, TView view) => true; - public void WhenFalse(T value, TView view) { } - public void WhenTrue(T value, TView view) { } - public void OnCollectionChanged(in SynchronizedViewChangedEventArgs eventArgs) { } + public bool IsMatch(T value) => true; } } public static class SynchronizedViewFilterExtensions { - public static void AttachFilter(this ISynchronizedView source, Func filter) + public static void AttachFilter(this ISynchronizedView source, Func filter) { - source.AttachFilter(new SynchronizedViewFilter(filter, null, null, null)); + source.AttachFilter(new SynchronizedViewFilter(filter)); } - public static void AttachFilter(this ISynchronizedView source, Func isMatch, Action? whenTrue, Action? whenFalse) + public static bool IsNullFilter(this ISynchronizedViewFilter filter) { - source.AttachFilter(new SynchronizedViewFilter(isMatch, whenTrue, whenFalse, null)); + return filter == SynchronizedViewFilter.Null; } - public static void AttachFilter(this ISynchronizedView source, Func isMatch, Action? whenTrue, Action? whenFalse, Action>? onCollectionChanged) + internal static void InvokeOnAdd(this ISynchronizedView collection, ref int filteredCount, Action>? ev, (T value, TView view) value, int index) { - source.AttachFilter(new SynchronizedViewFilter(isMatch, whenTrue, whenFalse, onCollectionChanged)); + InvokeOnAdd(collection, ref filteredCount, ev, value.value, value.view, index); } - public static bool IsNullFilter(this ISynchronizedViewFilter filter) + internal static void InvokeOnAdd(this ISynchronizedView collection, ref int filteredCount, Action>? ev, T value, TView view, int index) { - return filter == SynchronizedViewFilter.Null; - } - - - internal static void InvokeOnAdd(this ISynchronizedViewFilter filter, (T value, TView view) value, int index) - { - filter.InvokeOnAdd(value.value, value.view, index); - } - - internal static void InvokeOnAdd(this ISynchronizedViewFilter filter, T value, TView view, int index) - { - if (filter.IsMatch(value, view)) + var isMatch = collection.CurrentFilter.IsMatch(value); + if (isMatch) { - filter.WhenTrue(value, view); + filteredCount++; + if (ev != null) + { + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, newValue: value, newView: view, newViewIndex: index)); + } } - else + } + + internal static void InvokeOnRemove(this ISynchronizedView collection, ref int filteredCount, Action>? 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, Action>? ev, T value, TView view, int oldIndex) + { + var isMatch = collection.CurrentFilter.IsMatch(value); + if (isMatch) { - filter.WhenFalse(value, view); + filteredCount--; + if (ev != null) + { + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, isMatch, oldValue: value, oldView: view, oldViewIndex: oldIndex)); + } } - filter.OnCollectionChanged(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, newValue: value, newView: view, newViewIndex: index)); } - internal static void InvokeOnRemove(this ISynchronizedViewFilter filter, (T value, TView view) value, int oldIndex) + internal static void InvokeOnMove(this ISynchronizedView collection, ref int filteredCount, Action>? ev, (T value, TView view) value, int index, int oldIndex) { - filter.InvokeOnRemove(value.value, value.view, oldIndex); + InvokeOnMove(collection, ref filteredCount, ev, value.value, value.view, index, oldIndex); } - internal static void InvokeOnRemove(this ISynchronizedViewFilter filter, T value, TView view, int oldIndex) + internal static void InvokeOnMove(this ISynchronizedView collection, ref int filteredCount, Action>? ev, T value, TView view, int index, int oldIndex) { - filter.OnCollectionChanged(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); + if (ev != null) + { + // move does not changes filtered-count + var isMatch = collection.CurrentFilter.IsMatch(value); + if (isMatch) + { + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Move, newValue: value, newView: view, newViewIndex: index, oldViewIndex: oldIndex)); + } + } } - internal static void InvokeOnMove(this ISynchronizedViewFilter filter, (T value, TView view) value, int index, int oldIndex) + internal static void InvokeOnReplace(this ISynchronizedView collection, ref int filteredCount, Action>? ev, (T value, TView view) value, (T value, TView view) oldValue, int index, int oldIndex = -1) { - InvokeOnMove(filter, value.value, value.view, index, oldIndex); + InvokeOnReplace(collection, ref filteredCount, ev, value.value, value.view, oldValue.value, oldValue.view, index, oldIndex); } - internal static void InvokeOnMove(this ISynchronizedViewFilter filter, T value, TView view, int index, int oldIndex) + internal static void InvokeOnReplace(this ISynchronizedView collection, ref int filteredCount, Action>? ev, T value, TView view, T oldValue, TView oldView, int index, int oldIndex = -1) { - filter.OnCollectionChanged(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Move, newValue: value, newView: view, newViewIndex: index, oldViewIndex: oldIndex)); + var oldMatched = collection.CurrentFilter.IsMatch(oldValue); + var newMatched = collection.CurrentFilter.IsMatch(value); + var bothMatched = oldMatched && newMatched; + + // TODO:...! + if (bothMatched) + { + } + else if (oldMatched) + { + // only-old is remove + } + else if (newMatched) + { + // only-new is add + } + + + + + if (ev != null) + { + var isMatch = collection.CurrentFilter.IsMatch(value); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Replace, isMatch, newValue: value, newView: view, oldValue: oldValue, oldView: oldView, newViewIndex: index, oldViewIndex: oldIndex >= 0 ? oldIndex : index)); + } } - internal static void InvokeOnReplace(this ISynchronizedViewFilter filter, (T value, TView view) value, (T value, TView view) oldValue, int index, int oldIndex = -1) + internal static void InvokeOnReset(this ISynchronizedView collection, ref int filteredCount, Action>? ev) { - filter.InvokeOnReplace(value.value, value.view, oldValue.value, oldValue.view, index, oldIndex); - } - - internal static void InvokeOnReplace(this ISynchronizedViewFilter filter, T value, TView view, T oldValue, TView oldView, int index, int oldIndex = -1) - { - filter.OnCollectionChanged(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Replace, newValue: value, newView: view, oldValue: oldValue, oldView: oldView, newViewIndex: index, oldViewIndex: oldIndex >= 0 ? oldIndex : index)); - } - - internal static void InvokeOnReset(this ISynchronizedViewFilter filter) - { - filter.OnCollectionChanged(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + filteredCount = 0; + if (ev != null) + { + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); + } } internal static void InvokeOnAttach(this ISynchronizedViewFilter filter, T value, TView view) @@ -139,10 +158,5 @@ namespace ObservableCollections filter.WhenFalse(value, view); } } - - internal static bool IsMatch(this ISynchronizedViewFilter filter, (T, TView) value) - { - return filter.IsMatch(value); - } } } diff --git a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs index e6c4c3c..97a5251 100644 --- a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs +++ b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs @@ -1,5 +1,4 @@ using System; -using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; @@ -16,6 +15,7 @@ namespace ObservableCollections.Internal public SynchronizedViewList(ISynchronizedView parent) { this.parent = parent; + this.listView = parent.Select(x => x.View).ToList(); // need lock // TODO:add parent.ViewChanged += Parent_ViewChanged; } @@ -24,7 +24,6 @@ namespace ObservableCollections.Internal { // event is called inside lock(parent.SyncRoot) // TODO: invoke in ICollectionEventDispatcher? - switch (e.Action) { case NotifyCollectionChangedAction.Add: // Add or Insert @@ -50,6 +49,8 @@ namespace ObservableCollections.Internal case NotifyCollectionChangedAction.Replace: // Indexer if (e.NewViewIndex == -1) { + var index = listView.IndexOf(e.OldView); + listView[index] = e.NewView; } else { @@ -60,6 +61,7 @@ namespace ObservableCollections.Internal case NotifyCollectionChangedAction.Move: //Remove and Insert if (e.NewViewIndex == -1) { + // do nothing } else { @@ -68,32 +70,31 @@ namespace ObservableCollections.Internal } break; case NotifyCollectionChangedAction.Reset: // Clear + listView.Clear(); break; default: break; } - - // throw new NotImplementedException(); } - public TView this[int index] => throw new NotImplementedException(); + public TView this[int index] => listView[index]; - public int Count => throw new NotImplementedException(); - - public void Dispose() - { - throw new NotImplementedException(); - } + public int Count => listView.Count; public IEnumerator GetEnumerator() { - throw new NotImplementedException(); + return listView.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { - throw new NotImplementedException(); + return listView.GetEnumerator(); + } + + public void Dispose() + { + parent.ViewChanged -= Parent_ViewChanged; } } diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index 549bbcb..eac67a2 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -32,7 +32,8 @@ namespace ObservableCollections ISynchronizedViewFilter filter; - public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; + public event Action>? ViewChanged; + // public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; public event Action? CollectionStateChanged; public object SyncRoot { get; } @@ -162,11 +163,7 @@ namespace ObservableCollections { var v = (e.NewItem, selector(e.NewItem)); list.Add(v); - if (filter.IsMatch(v)) - { - filteredCount++; - } - filter.InvokeOnAdd(v, e.NewStartingIndex); + this.InvokeOnAdd(ref filteredCount, ViewChanged, v, e.NewStartingIndex); } else { From abed307158a7b9d570a3bdfc7db6fb7aaa93ee38 Mon Sep 17 00:00:00 2001 From: neuecc Date: Thu, 22 Aug 2024 17:19:28 +0900 Subject: [PATCH 04/22] more --- .../ISynchronizedViewFilter.cs | 41 ++++++++----------- .../ObservableList.Views.cs | 13 +++--- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/src/ObservableCollections/ISynchronizedViewFilter.cs b/src/ObservableCollections/ISynchronizedViewFilter.cs index 9f4b32b..0a080a9 100644 --- a/src/ObservableCollections/ISynchronizedViewFilter.cs +++ b/src/ObservableCollections/ISynchronizedViewFilter.cs @@ -81,7 +81,7 @@ namespace ObservableCollections filteredCount--; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, isMatch, oldValue: value, oldView: view, oldViewIndex: oldIndex)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); } } } @@ -115,26 +115,31 @@ namespace ObservableCollections var newMatched = collection.CurrentFilter.IsMatch(value); var bothMatched = oldMatched && newMatched; - // TODO:...! 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 - } - - - - - if (ev != null) - { - var isMatch = collection.CurrentFilter.IsMatch(value); - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Replace, isMatch, newValue: value, newView: view, oldValue: oldValue, oldView: oldView, newViewIndex: index, oldViewIndex: oldIndex >= 0 ? oldIndex : index)); + filteredCount++; + if (ev != null) + { + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, newValue: value, newView: view, newViewIndex: index)); + } } } @@ -143,19 +148,7 @@ namespace ObservableCollections filteredCount = 0; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); - } - } - - internal static void InvokeOnAttach(this ISynchronizedViewFilter filter, T value, TView view) - { - if (filter.IsMatch(value, view)) - { - filter.WhenTrue(value, view); - } - else - { - filter.WhenFalse(value, view); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } } diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index eac67a2..8ec2beb 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -16,7 +16,7 @@ namespace ObservableCollections internal sealed class View : ISynchronizedView { - public ISynchronizedViewFilter CurrentFilter + public ISynchronizedViewFilter CurrentFilter { get { @@ -30,10 +30,9 @@ namespace ObservableCollections readonly List<(T, TView)> list; int filteredCount; - ISynchronizedViewFilter filter; + ISynchronizedViewFilter filter; public event Action>? ViewChanged; - // public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; public event Action? CollectionStateChanged; public object SyncRoot { get; } @@ -43,7 +42,7 @@ namespace ObservableCollections this.source = source; this.selector = selector; this.reverse = reverse; - this.filter = SynchronizedViewFilter.Null; + this.filter = SynchronizedViewFilter.Null; this.SyncRoot = new object(); lock (source.SyncRoot) { @@ -64,7 +63,7 @@ namespace ObservableCollections } } - public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) + public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) { lock (SyncRoot) { @@ -84,11 +83,11 @@ namespace ObservableCollections } } - public void ResetFilter(Action? resetAction) + public void ResetFilter(Action? resetAction) { lock (SyncRoot) { - this.filter = SynchronizedViewFilter.Null; + this.filter = SynchronizedViewFilter.Null; if (resetAction != null) { foreach (var (item, view) in list) From 68618fda10faef131724223cf85309b15d921a31 Mon Sep 17 00:00:00 2001 From: neuecc Date: Fri, 23 Aug 2024 17:09:32 +0900 Subject: [PATCH 05/22] more interface change --- .../IObservableCollection.cs | 12 +++--- .../ISynchronizedViewFilter.cs | 38 ++++++++++++------- ...NotifyCollectionChangedSynchronizedView.cs | 19 +++++++--- .../ObservableList.Views.cs | 34 ++++++++--------- 4 files changed, 59 insertions(+), 44 deletions(-) diff --git a/src/ObservableCollections/IObservableCollection.cs b/src/ObservableCollections/IObservableCollection.cs index 108fab8..7ce908e 100644 --- a/src/ObservableCollections/IObservableCollection.cs +++ b/src/ObservableCollections/IObservableCollection.cs @@ -32,19 +32,17 @@ namespace ObservableCollections ISortableSynchronizedView CreateSortableView(Func transform); } - public interface ISynchronizedView : IReadOnlyCollection<(T Value, TView View)>, IDisposable + public interface ISynchronizedView : IReadOnlyCollection, IDisposable { object SyncRoot { get; } - ISynchronizedViewFilter CurrentFilter { get; } + ISynchronizedViewFilter Filter { get; } + IEnumerable<(T Value, TView View)> Unfiltered { get; } - // TODO: add event Action>? ViewChanged; - // TODO: remove - // event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; event Action? CollectionStateChanged; - void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForInitialElements = false); - void ResetFilter(Action? resetAction); + void AttachFilter(ISynchronizedViewFilter filter); + void ResetFilter(); INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(); INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher); } diff --git a/src/ObservableCollections/ISynchronizedViewFilter.cs b/src/ObservableCollections/ISynchronizedViewFilter.cs index 0a080a9..85c0b11 100644 --- a/src/ObservableCollections/ISynchronizedViewFilter.cs +++ b/src/ObservableCollections/ISynchronizedViewFilter.cs @@ -4,7 +4,7 @@ using System.Collections.Specialized; namespace ObservableCollections { public readonly struct SynchronizedViewChangedEventArgs( - NotifyCollectionChangedAction action, + NotifyViewChangedAction action, T newValue = default!, T oldValue = default!, TView newView = default!, @@ -12,7 +12,7 @@ namespace ObservableCollections int newViewIndex = -1, int oldViewIndex = -1) { - public readonly NotifyCollectionChangedAction Action = action; + public readonly NotifyViewChangedAction Action = action; public readonly T NewValue = newValue; public readonly T OldValue = oldValue; public readonly TView NewView = newView; @@ -21,6 +21,16 @@ namespace ObservableCollections public readonly int OldViewIndex = oldViewIndex; } + public enum NotifyViewChangedAction + { + Add = 0, + Remove = 1, + Replace = 2, + Move = 3, + Reset = 4, + FilterReset = 5, + } + public interface ISynchronizedViewFilter { bool IsMatch(T value); @@ -57,13 +67,13 @@ namespace ObservableCollections internal static void InvokeOnAdd(this ISynchronizedView collection, ref int filteredCount, Action>? ev, T value, TView view, int index) { - var isMatch = collection.CurrentFilter.IsMatch(value); + var isMatch = collection.Filter.IsMatch(value); if (isMatch) { filteredCount++; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, newValue: value, newView: view, newViewIndex: index)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.Add, newValue: value, newView: view, newViewIndex: index)); } } } @@ -75,13 +85,13 @@ namespace ObservableCollections internal static void InvokeOnRemove(this ISynchronizedView collection, ref int filteredCount, Action>? ev, T value, TView view, int oldIndex) { - var isMatch = collection.CurrentFilter.IsMatch(value); + var isMatch = collection.Filter.IsMatch(value); if (isMatch) { filteredCount--; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); } } } @@ -96,10 +106,10 @@ namespace ObservableCollections if (ev != null) { // move does not changes filtered-count - var isMatch = collection.CurrentFilter.IsMatch(value); + var isMatch = collection.Filter.IsMatch(value); if (isMatch) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Move, newValue: value, newView: view, newViewIndex: index, oldViewIndex: oldIndex)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.Move, newValue: value, newView: view, newViewIndex: index, oldViewIndex: oldIndex)); } } } @@ -111,15 +121,15 @@ namespace ObservableCollections internal static void InvokeOnReplace(this ISynchronizedView collection, ref int filteredCount, Action>? ev, T value, TView view, T oldValue, TView oldView, int index, int oldIndex = -1) { - var oldMatched = collection.CurrentFilter.IsMatch(oldValue); - var newMatched = collection.CurrentFilter.IsMatch(value); + 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)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.Replace, newValue: value, newView: view, oldValue: oldValue, oldView: oldView, newViewIndex: index, oldViewIndex: oldIndex >= 0 ? oldIndex : index)); } } else if (oldMatched) @@ -128,7 +138,7 @@ namespace ObservableCollections filteredCount--; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); } } @@ -138,7 +148,7 @@ namespace ObservableCollections filteredCount++; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, newValue: value, newView: view, newViewIndex: index)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.Add, newValue: value, newView: view, newViewIndex: index)); } } } @@ -148,7 +158,7 @@ namespace ObservableCollections filteredCount = 0; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.Reset)); } } } diff --git a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs index 97a5251..0a3ed01 100644 --- a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs +++ b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs @@ -26,7 +26,7 @@ namespace ObservableCollections.Internal // TODO: invoke in ICollectionEventDispatcher? switch (e.Action) { - case NotifyCollectionChangedAction.Add: // Add or Insert + case NotifyViewChangedAction.Add: // Add or Insert if (e.NewViewIndex == -1) { listView.Add(e.NewView); @@ -36,7 +36,7 @@ namespace ObservableCollections.Internal listView.Insert(e.NewViewIndex, e.NewView); } break; - case NotifyCollectionChangedAction.Remove: // Remove + case NotifyViewChangedAction.Remove: // Remove if (e.OldViewIndex == -1) // can't gurantee correct remove if index is not provided { listView.Remove(e.OldView); @@ -46,7 +46,7 @@ namespace ObservableCollections.Internal listView.RemoveAt(e.OldViewIndex); } break; - case NotifyCollectionChangedAction.Replace: // Indexer + case NotifyViewChangedAction.Replace: // Indexer if (e.NewViewIndex == -1) { var index = listView.IndexOf(e.OldView); @@ -58,7 +58,7 @@ namespace ObservableCollections.Internal } break; - case NotifyCollectionChangedAction.Move: //Remove and Insert + case NotifyViewChangedAction.Move: //Remove and Insert if (e.NewViewIndex == -1) { // do nothing @@ -69,9 +69,16 @@ namespace ObservableCollections.Internal listView.Insert(e.NewViewIndex, e.NewView); } break; - case NotifyCollectionChangedAction.Reset: // Clear + case NotifyViewChangedAction.Reset: // Clear listView.Clear(); break; + case NotifyViewChangedAction.FilterReset: + listView.Clear(); + foreach (var item in parent) + { + listView.Add(item.View); + } + break; default: break; } @@ -116,7 +123,7 @@ namespace ObservableCollections.Internal { this.parent = parent; this.eventDispatcher = eventDispatcher ?? DirectCollectionEventDispatcher.Instance; - currentFilter = parent.CurrentFilter; + currentFilter = parent.Filter; parent.AttachFilter(this); } diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index 8ec2beb..03a4ccd 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -16,7 +16,7 @@ namespace ObservableCollections internal sealed class View : ISynchronizedView { - public ISynchronizedViewFilter CurrentFilter + public ISynchronizedViewFilter Filter { get { @@ -63,38 +63,38 @@ namespace ObservableCollections } } - public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) + public void AttachFilter(ISynchronizedViewFilter filter) { + if (filter.IsNullFilter()) + { + ResetFilter(); + return; + } + lock (SyncRoot) { this.filter = filter; + + this.filteredCount = 0; for (var i = 0; i < list.Count; i++) { - var (value, view) = list[i]; - if (invokeAddEventForCurrentElements) + if (filter.IsMatch(list[i].Item1)) { - filter.InvokeOnAdd(value, view, i); - } - else - { - filter.InvokeOnAttach(value, view); + filteredCount++; } } + + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.FilterReset)); } } - public void ResetFilter(Action? resetAction) + public void ResetFilter() { lock (SyncRoot) { this.filter = SynchronizedViewFilter.Null; - if (resetAction != null) - { - foreach (var (item, view) in list) - { - resetAction(item, view); - } - } + this.filteredCount = list.Count; + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.FilterReset)); } } From 9ffd0417baf5f2059e7934c20de25ddd57b16e9a Mon Sep 17 00:00:00 2001 From: neuecc Date: Mon, 26 Aug 2024 16:33:36 +0900 Subject: [PATCH 06/22] lll --- .../IObservableCollection.cs | 4 +- ...NotifyCollectionChangedSynchronizedView.cs | 8 +- .../ObservableList.Views.cs | 94 +++++++++++-------- 3 files changed, 64 insertions(+), 42 deletions(-) diff --git a/src/ObservableCollections/IObservableCollection.cs b/src/ObservableCollections/IObservableCollection.cs index 7ce908e..ac650df 100644 --- a/src/ObservableCollections/IObservableCollection.cs +++ b/src/ObservableCollections/IObservableCollection.cs @@ -13,7 +13,7 @@ namespace ObservableCollections { event NotifyCollectionChangedEventHandler? CollectionChanged; object SyncRoot { get; } - ISynchronizedView CreateView(Func transform, bool reverse = false); + ISynchronizedView CreateView(Func transform); } public interface IReadOnlyObservableList : @@ -36,7 +36,9 @@ namespace ObservableCollections { object SyncRoot { get; } ISynchronizedViewFilter Filter { get; } + IEnumerable<(T Value, TView View)> Filtered { get; } IEnumerable<(T Value, TView View)> Unfiltered { get; } + int UnfilteredCount { get; } event Action>? ViewChanged; event Action? CollectionStateChanged; diff --git a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs index 0a3ed01..3114d96 100644 --- a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs +++ b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs @@ -15,9 +15,11 @@ namespace ObservableCollections.Internal public SynchronizedViewList(ISynchronizedView parent) { this.parent = parent; - this.listView = parent.Select(x => x.View).ToList(); // need lock - // TODO:add - parent.ViewChanged += Parent_ViewChanged; + lock (parent.SyncRoot) + { + this.listView = parent.ToList(); + parent.ViewChanged += Parent_ViewChanged; + } } private void Parent_ViewChanged(SynchronizedViewChangedEventArgs e) diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index 03a4ccd..df02e15 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -9,9 +9,9 @@ namespace ObservableCollections { public sealed partial class ObservableList : IList, IReadOnlyObservableList { - public ISynchronizedView CreateView(Func transform, bool reverse = false) + public ISynchronizedView CreateView(Func transform) { - return new View(this, transform, reverse); + return new View(this, transform); } internal sealed class View : ISynchronizedView @@ -26,7 +26,6 @@ namespace ObservableCollections readonly ObservableList source; readonly Func selector; - readonly bool reverse; readonly List<(T, TView)> list; int filteredCount; @@ -37,11 +36,10 @@ namespace ObservableCollections public object SyncRoot { get; } - public View(ObservableList source, Func selector, bool reverse) + public View(ObservableList source, Func selector) { this.source = source; this.selector = selector; - this.reverse = reverse; this.filter = SynchronizedViewFilter.Null; this.SyncRoot = new object(); lock (source.SyncRoot) @@ -63,6 +61,17 @@ namespace ObservableCollections } } + public int UnfilteredCount + { + get + { + lock (SyncRoot) + { + return list.Count; + } + } + } + public void AttachFilter(ISynchronizedViewFilter filter) { if (filter.IsNullFilter()) @@ -114,25 +123,31 @@ namespace ObservableCollections } } - public IEnumerator<(T, TView)> GetEnumerator() + public IEnumerator GetEnumerator() { lock (SyncRoot) { - if (!reverse) + foreach (var item in list) + { + if (filter.IsMatch(item.Item1)) + { + yield return item.Item2; + } + } + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public IEnumerable<(T Value, TView View)> Filtered + { + get + { + lock (SyncRoot) { foreach (var item in list) { - if (filter.IsMatch(item.Item1, item.Item2)) - { - yield return item; - } - } - } - else - { - foreach (var item in list.AsEnumerable().Reverse()) - { - if (filter.IsMatch(item.Item1, item.Item2)) + if (filter.IsMatch(item.Item1)) { yield return item; } @@ -141,7 +156,19 @@ namespace ObservableCollections } } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerable<(T Value, TView View)> Unfiltered + { + get + { + lock (SyncRoot) + { + foreach (var item in list) + { + yield return item; + } + } + } + } public void Dispose() { @@ -171,11 +198,7 @@ namespace ObservableCollections { var v = (item, selector(item)); list.Add(v); - if (filter.IsMatch(v)) - { - filteredCount++; - } - filter.InvokeOnAdd(v, i++); + this.InvokeOnAdd(ref filteredCount, ViewChanged, v, i++); } } } @@ -186,20 +209,17 @@ namespace ObservableCollections { var v = (e.NewItem, selector(e.NewItem)); list.Insert(e.NewStartingIndex, v); - filter.InvokeOnAdd(v, e.NewStartingIndex); + this.InvokeOnAdd(ref filteredCount, ViewChanged, v, e.NewStartingIndex); } else { - // inefficient copy, need refactoring - var newArray = new (T, TView)[e.NewItems.Length]; var span = e.NewItems; for (var i = 0; i < span.Length; i++) { var v = (span[i], selector(span[i])); - newArray[i] = v; - filter.InvokeOnAdd(v, e.NewStartingIndex + i); + list.Insert(e.NewStartingIndex + i, v); // should we use InsertRange? + this.InvokeOnAdd(ref filteredCount, ViewChanged, v, e.NewStartingIndex + i); } - list.InsertRange(e.NewStartingIndex, newArray); } } break; @@ -208,7 +228,7 @@ namespace ObservableCollections { var v = list[e.OldStartingIndex]; list.RemoveAt(e.OldStartingIndex); - filter.InvokeOnRemove(v, e.OldStartingIndex); + this.InvokeOnRemove(ref filteredCount, ViewChanged, v, e.OldStartingIndex); } else { @@ -216,10 +236,9 @@ namespace ObservableCollections for (var i = e.OldStartingIndex; i < len; i++) { var v = list[i]; - filter.InvokeOnRemove(v, e.OldStartingIndex + i); + list.RemoveAt(e.OldStartingIndex + i); // should we use RemoveRange? + this.InvokeOnRemove(ref filteredCount, ViewChanged, v, e.OldStartingIndex + i); } - - list.RemoveRange(e.OldStartingIndex, e.OldItems.Length); } break; case NotifyCollectionChangedAction.Replace: @@ -228,7 +247,7 @@ namespace ObservableCollections var v = (e.NewItem, selector(e.NewItem)); var ov = (e.OldItem, list[e.OldStartingIndex].Item2); list[e.NewStartingIndex] = v; - filter.InvokeOnReplace(v, ov, e.NewStartingIndex); + this.InvokeOnReplace(ref filteredCount, ViewChanged, v, ov, e.NewStartingIndex); break; } case NotifyCollectionChangedAction.Move: @@ -237,18 +256,17 @@ namespace ObservableCollections list.RemoveAt(e.OldStartingIndex); list.Insert(e.NewStartingIndex, removeItem); - filter.InvokeOnMove(removeItem, e.NewStartingIndex, e.OldStartingIndex); + this.InvokeOnMove(ref filteredCount, ViewChanged, removeItem, e.NewStartingIndex, e.OldStartingIndex); } break; case NotifyCollectionChangedAction.Reset: list.Clear(); - filter.InvokeOnReset(); + this.InvokeOnReset(ref filteredCount, ViewChanged); break; default: break; } - RoutingCollectionChanged?.Invoke(e); CollectionStateChanged?.Invoke(e.Action); } } From ce624265f3754e5717c527612e231e3796f38457 Mon Sep 17 00:00:00 2001 From: neuecc Date: Tue, 27 Aug 2024 15:50:19 +0900 Subject: [PATCH 07/22] ObservableList --- .../ICollectionEventDispatcher.cs | 6 +- .../IObservableCollection.cs | 2 +- ...NotifyCollectionChangedSynchronizedView.cs | 254 ++++++++---------- .../ObservableList.Views.cs | 30 ++- 4 files changed, 139 insertions(+), 153 deletions(-) diff --git a/src/ObservableCollections/ICollectionEventDispatcher.cs b/src/ObservableCollections/ICollectionEventDispatcher.cs index 59040bd..c2ccfa0 100644 --- a/src/ObservableCollections/ICollectionEventDispatcher.cs +++ b/src/ObservableCollections/ICollectionEventDispatcher.cs @@ -45,11 +45,11 @@ namespace ObservableCollections } } - internal class DirectCollectionEventDispatcher : ICollectionEventDispatcher + internal class InlineCollectionEventDispatcher : ICollectionEventDispatcher { - public static readonly ICollectionEventDispatcher Instance = new DirectCollectionEventDispatcher(); + public static readonly ICollectionEventDispatcher Instance = new InlineCollectionEventDispatcher(); - DirectCollectionEventDispatcher() + InlineCollectionEventDispatcher() { } diff --git a/src/ObservableCollections/IObservableCollection.cs b/src/ObservableCollections/IObservableCollection.cs index ac650df..78b0d78 100644 --- a/src/ObservableCollections/IObservableCollection.cs +++ b/src/ObservableCollections/IObservableCollection.cs @@ -1,6 +1,5 @@ using ObservableCollections.Internal; using System; -using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; @@ -45,6 +44,7 @@ namespace ObservableCollections void AttachFilter(ISynchronizedViewFilter filter); void ResetFilter(); + ISynchronizedViewList ToViewList(); INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(); INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher); } diff --git a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs index 3114d96..f5e45c5 100644 --- a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs +++ b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs @@ -10,7 +10,8 @@ namespace ObservableCollections.Internal internal class SynchronizedViewList : ISynchronizedViewList { readonly ISynchronizedView parent; - readonly List listView; + protected readonly List listView; + protected readonly object gate = new object(); public SynchronizedViewList(ISynchronizedView parent) { @@ -24,76 +25,106 @@ namespace ObservableCollections.Internal private void Parent_ViewChanged(SynchronizedViewChangedEventArgs e) { - // event is called inside lock(parent.SyncRoot) - // TODO: invoke in ICollectionEventDispatcher? - switch (e.Action) + lock (gate) { - case NotifyViewChangedAction.Add: // Add or Insert - if (e.NewViewIndex == -1) - { - listView.Add(e.NewView); - } - else - { - listView.Insert(e.NewViewIndex, e.NewView); - } - break; - case NotifyViewChangedAction.Remove: // Remove - if (e.OldViewIndex == -1) // can't gurantee correct remove if index is not provided - { - listView.Remove(e.OldView); - } - else - { - listView.RemoveAt(e.OldViewIndex); - } - break; - case NotifyViewChangedAction.Replace: // Indexer - if (e.NewViewIndex == -1) - { - var index = listView.IndexOf(e.OldView); - listView[index] = e.NewView; - } - else - { - listView[e.NewViewIndex] = e.NewView; - } + switch (e.Action) + { + case NotifyViewChangedAction.Add: // Add or Insert + if (e.NewViewIndex == -1) + { + listView.Add(e.NewView); + } + else + { + listView.Insert(e.NewViewIndex, e.NewView); + } + break; + case NotifyViewChangedAction.Remove: // Remove + if (e.OldViewIndex == -1) // can't gurantee correct remove if index is not provided + { + listView.Remove(e.OldView); + } + else + { + listView.RemoveAt(e.OldViewIndex); + } + break; + case NotifyViewChangedAction.Replace: // Indexer + if (e.NewViewIndex == -1) + { + var index = listView.IndexOf(e.OldView); + listView[index] = e.NewView; + } + else + { + listView[e.NewViewIndex] = e.NewView; + } - break; - case NotifyViewChangedAction.Move: //Remove and Insert - if (e.NewViewIndex == -1) - { - // do nothing - } - else - { - listView.RemoveAt(e.OldViewIndex); - listView.Insert(e.NewViewIndex, e.NewView); - } - break; - case NotifyViewChangedAction.Reset: // Clear - listView.Clear(); - break; - case NotifyViewChangedAction.FilterReset: - listView.Clear(); - foreach (var item in parent) - { - listView.Add(item.View); - } - break; - default: - break; + break; + case NotifyViewChangedAction.Move: //Remove and Insert + if (e.NewViewIndex == -1) + { + // do nothing + } + else + { + listView.RemoveAt(e.OldViewIndex); + listView.Insert(e.NewViewIndex, e.NewView); + } + break; + case NotifyViewChangedAction.Reset: // Clear + listView.Clear(); + break; + case NotifyViewChangedAction.FilterReset: + listView.Clear(); + foreach (var item in parent) + { + listView.Add(item); + } + break; + default: + break; + } + + OnCollectionChanged(e); } } + protected virtual void OnCollectionChanged(in SynchronizedViewChangedEventArgs args) + { + } - public TView this[int index] => listView[index]; + public TView this[int index] + { + get + { + lock (gate) + { + return listView[index]; + } + } + } - public int Count => listView.Count; + public int Count + { + get + { + lock (gate) + { + return listView.Count; + } + } + } public IEnumerator GetEnumerator() { - return listView.GetEnumerator(); + lock (gate) + { + foreach (var item in listView) + { + yield return item; + } + } } IEnumerator IEnumerable.GetEnumerator() @@ -107,73 +138,32 @@ namespace ObservableCollections.Internal } } - - - internal class NotifyCollectionChangedSynchronizedView : + SynchronizedViewList, INotifyCollectionChangedSynchronizedView, - ISynchronizedViewFilter + IList, IList { static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count"); static readonly Action raiseChangedEventInvoke = RaiseChangedEvent; - readonly ISynchronizedView parent; - readonly ISynchronizedViewFilter currentFilter; readonly ICollectionEventDispatcher eventDispatcher; - public NotifyCollectionChangedSynchronizedView(ISynchronizedView parent, ICollectionEventDispatcher? eventDispatcher) - { - this.parent = parent; - this.eventDispatcher = eventDispatcher ?? DirectCollectionEventDispatcher.Instance; - currentFilter = parent.Filter; - parent.AttachFilter(this); - } - - public int Count => parent.Count; - public event NotifyCollectionChangedEventHandler? CollectionChanged; public event PropertyChangedEventHandler? PropertyChanged; - public event Action? CollectionStateChanged + public NotifyCollectionChangedSynchronizedView(ISynchronizedView parent, ICollectionEventDispatcher? eventDispatcher) + : base(parent) { - add { parent.CollectionStateChanged += value; } - remove { parent.CollectionStateChanged -= value; } + this.eventDispatcher = eventDispatcher ?? InlineCollectionEventDispatcher.Instance; } - public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged + protected override void OnCollectionChanged(in SynchronizedViewChangedEventArgs args) { - add { parent.RoutingCollectionChanged += value; } - remove { parent.RoutingCollectionChanged -= value; } - } - - public void Dispose() - { - parent.Dispose(); - } - - public IEnumerator GetEnumerator() - { - foreach (var (value, view) in parent) - { - yield return view; - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public bool IsMatch(T value, TView view) => currentFilter.IsMatch(value, view); - public void WhenTrue(T value, TView view) => currentFilter.WhenTrue(value, view); - public void WhenFalse(T value, TView view) => currentFilter.WhenFalse(value, view); - - public void OnCollectionChanged(in SynchronizedViewChangedEventArgs args) - { - currentFilter.OnCollectionChanged(args); - if (CollectionChanged == null && PropertyChanged == null) return; switch (args.Action) { - case NotifyCollectionChangedAction.Add: + case NotifyViewChangedAction.Add: eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewView, args.NewViewIndex) { Collection = this, @@ -182,7 +172,7 @@ namespace ObservableCollections.Internal IsInvokePropertyChanged = true }); break; - case NotifyCollectionChangedAction.Remove: + case NotifyViewChangedAction.Remove: eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldView, args.OldViewIndex) { Collection = this, @@ -191,7 +181,7 @@ namespace ObservableCollections.Internal IsInvokePropertyChanged = true }); break; - case NotifyCollectionChangedAction.Reset: + case NotifyViewChangedAction.Reset: eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Reset) { Collection = this, @@ -200,7 +190,7 @@ namespace ObservableCollections.Internal IsInvokePropertyChanged = true }); break; - case NotifyCollectionChangedAction.Replace: + case NotifyViewChangedAction.Replace: eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Replace, args.NewView, args.OldView, args.NewViewIndex) { Collection = this, @@ -209,7 +199,7 @@ namespace ObservableCollections.Internal IsInvokePropertyChanged = false }); break; - case NotifyCollectionChangedAction.Move: + case NotifyViewChangedAction.Move: eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Move, args.NewView, args.NewViewIndex, args.OldViewIndex) { Collection = this, @@ -235,30 +225,12 @@ namespace ObservableCollections.Internal self.PropertyChanged?.Invoke(self, CountPropertyChangedEventArgs); } } - } - internal class ListNotifyCollectionChangedSynchronizedView - : NotifyCollectionChangedSynchronizedView - , IList, IReadOnlyList - , IList - { - readonly ObservableList.View view; + // IList, IList implementation - public ListNotifyCollectionChangedSynchronizedView(ObservableList.View parent, ICollectionEventDispatcher? eventDispatcher) - : base(parent, eventDispatcher) + TView IList.this[int index] { - this.view = parent; - } - - public TView this[int index] - { - get - { - lock (view.SyncRoot) - { - return view.list[index].Item2; - } - } + get => ((IReadOnlyList)this)[index]; set => throw new NotSupportedException(); } @@ -282,7 +254,7 @@ namespace ObservableCollections.Internal public bool IsSynchronized => true; - public object SyncRoot => view.SyncRoot; + public object SyncRoot => gate; public void Add(TView item) { @@ -301,11 +273,11 @@ namespace ObservableCollections.Internal public bool Contains(TView item) { - lock (view.SyncRoot) + lock (gate) { - foreach (var listItem in view.list) + foreach (var listItem in listView) { - if (EqualityComparer.Default.Equals(listItem.Item2, item)) + if (EqualityComparer.Default.Equals(listItem, item)) { return true; } @@ -335,12 +307,12 @@ namespace ObservableCollections.Internal public int IndexOf(TView item) { - lock (view.SyncRoot) + lock (gate) { var index = 0; - foreach (var listItem in view.list) + foreach (var listItem in listView) { - if (EqualityComparer.Default.Equals(listItem.Item2, item)) + if (EqualityComparer.Default.Equals(listItem, item)) { return index; } diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index df02e15..b2e0bc0 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -14,6 +14,21 @@ namespace ObservableCollections return new View(this, transform); } + public ISynchronizedViewList ToViewList() + { + return CreateView(static x => x).ToViewList(); + } + + public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() + { + return CreateView(static x => x).ToNotifyCollectionChanged(); + } + + public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) + { + return CreateView(static x => x).ToNotifyCollectionChanged(collectionEventDispatcher); + } + internal sealed class View : ISynchronizedView { public ISynchronizedViewFilter Filter @@ -107,20 +122,19 @@ namespace ObservableCollections } } + public ISynchronizedViewList ToViewList() + { + return new SynchronizedViewList(this); + } + public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() { - lock (SyncRoot) - { - return new ListNotifyCollectionChangedSynchronizedView(this, null); - } + return new NotifyCollectionChangedSynchronizedView(this, null); } public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) { - lock (SyncRoot) - { - return new ListNotifyCollectionChangedSynchronizedView(this, collectionEventDispatcher); - } + return new NotifyCollectionChangedSynchronizedView(this, collectionEventDispatcher); } public IEnumerator GetEnumerator() From 295cef5ae54441671bfcad836b70362e44eb62de Mon Sep 17 00:00:00 2001 From: neuecc Date: Tue, 27 Aug 2024 18:36:01 +0900 Subject: [PATCH 08/22] Dict, HashSet, Queue, RingBuffer, Stack --- .../ObservableDictionary.Views.cs | 152 +++++++++++------- .../ObservableDictionary.cs | 3 +- .../ObservableHashSet.Views.cs | 117 +++++++++----- .../ObservableList.Views.cs | 1 - .../ObservableQueue.Views.cs | 139 +++++++++------- .../ObservableRingBuffer.Views.cs | 132 +++++++++------ .../ObservableStack.Views.cs | 122 ++++++++------ 7 files changed, 418 insertions(+), 248 deletions(-) diff --git a/src/ObservableCollections/ObservableDictionary.Views.cs b/src/ObservableCollections/ObservableDictionary.Views.cs index c80914b..4bf347f 100644 --- a/src/ObservableCollections/ObservableDictionary.Views.cs +++ b/src/ObservableCollections/ObservableDictionary.Views.cs @@ -9,7 +9,7 @@ namespace ObservableCollections { public sealed partial class ObservableDictionary { - public ISynchronizedView, TView> CreateView(Func, TView> transform, bool _ = false) + public ISynchronizedView, TView> CreateView(Func, TView> transform) { // reverse is no used. return new View(this, transform); @@ -19,32 +19,45 @@ namespace ObservableCollections { readonly ObservableDictionary source; readonly Func, TView> selector; - ISynchronizedViewFilter, TView> filter; + ISynchronizedViewFilter> filter; readonly Dictionary dict; + int filteredCount; public View(ObservableDictionary source, Func, TView> selector) { this.source = source; this.selector = selector; - this.filter = SynchronizedViewFilter, TView>.Null; + this.filter = SynchronizedViewFilter>.Null; this.SyncRoot = new object(); lock (source.SyncRoot) { this.dict = source.dictionary.ToDictionary(x => x.Key, x => (x.Value, selector(x))); + this.filteredCount = dict.Count; this.source.CollectionChanged += SourceCollectionChanged; } } public object SyncRoot { get; } - public event NotifyCollectionChangedEventHandler>? RoutingCollectionChanged; + public event Action, TView>>? ViewChanged; public event Action? CollectionStateChanged; - public ISynchronizedViewFilter, TView> CurrentFilter + public ISynchronizedViewFilter> Filter { get { lock (SyncRoot) return filter; } } public int Count + { + get + { + lock (SyncRoot) + { + return filteredCount; + } + } + } + + public int UnfilteredCount { get { @@ -60,76 +73,104 @@ namespace ObservableCollections this.source.CollectionChanged -= SourceCollectionChanged; } - public void AttachFilter(ISynchronizedViewFilter, TView> filter, bool invokeAddEventForCurrentElements = false) + public void AttachFilter(ISynchronizedViewFilter> filter) { + if (filter.IsNullFilter()) + { + ResetFilter(); + return; + } + lock (SyncRoot) { this.filter = filter; + this.filteredCount = 0; foreach (var v in dict) { var value = new KeyValuePair(v.Key, v.Value.Item1); - var view = v.Value.Item2; - if (invokeAddEventForCurrentElements) + if (filter.IsMatch(value)) { - filter.InvokeOnAdd(value, view, -1); - } - else - { - filter.InvokeOnAttach(value, view); + filteredCount++; } } + + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs, TView>(NotifyViewChangedAction.FilterReset)); } } - public void ResetFilter(Action, TView>? resetAction) + public void ResetFilter() { lock (SyncRoot) { - this.filter = SynchronizedViewFilter, TView>.Null; - if (resetAction != null) - { - foreach (var v in dict) - { - resetAction(new KeyValuePair(v.Key, v.Value.Item1), v.Value.Item2); - } - } + this.filter = SynchronizedViewFilter>.Null; + this.filteredCount = dict.Count; + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs, TView>(NotifyViewChangedAction.FilterReset)); } } + public ISynchronizedViewList ToViewList() + { + return new SynchronizedViewList, TView>(this); + } + public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() { - lock (SyncRoot) - { - return new NotifyCollectionChangedSynchronizedView, TView>(this, null); - } + return new NotifyCollectionChangedSynchronizedView, TView>(this, null); } public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) { - lock (SyncRoot) - { - return new NotifyCollectionChangedSynchronizedView, TView>(this, collectionEventDispatcher); - } + return new NotifyCollectionChangedSynchronizedView, TView>(this, collectionEventDispatcher); } - public IEnumerator<(KeyValuePair, TView)> GetEnumerator() + public IEnumerator GetEnumerator() { lock (SyncRoot) { foreach (var item in dict) { var v = (new KeyValuePair(item.Key, item.Value.Item1), item.Value.Item2); - if (filter.IsMatch(v.Item1, v.Item2)) + if (filter.IsMatch(v.Item1)) { - yield return v; + yield return v.Item2; } } } } - IEnumerator IEnumerable.GetEnumerator() + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public IEnumerable<(KeyValuePair Value, TView View)> Filtered { - return GetEnumerator(); + get + { + lock (SyncRoot) + { + foreach (var item in dict) + { + var v = (new KeyValuePair(item.Key, item.Value.Item1), item.Value.Item2); + if (filter.IsMatch(v.Item1)) + { + yield return v; + } + } + } + } + } + + public IEnumerable<(KeyValuePair Value, TView View)> Unfiltered + { + get + { + lock (SyncRoot) + { + foreach (var item in dict) + { + var v = (new KeyValuePair(item.Key, item.Value.Item1), item.Value.Item2); + yield return v; + } + } + } } private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs> e) @@ -140,41 +181,40 @@ namespace ObservableCollections switch (e.Action) { case NotifyCollectionChangedAction.Add: - { - var v = selector(e.NewItem); - dict.Add(e.NewItem.Key, (e.NewItem.Value, v)); - filter.InvokeOnAdd(e.NewItem, v, -1); - } + { + var v = selector(e.NewItem); + dict.Add(e.NewItem.Key, (e.NewItem.Value, v)); + this.InvokeOnAdd(ref filteredCount, ViewChanged, e.NewItem, v, -1); + } break; case NotifyCollectionChangedAction.Remove: - { - if (dict.Remove(e.OldItem.Key, out var v)) { - filter.InvokeOnRemove(e.OldItem, v.Item2, -1); + if (dict.Remove(e.OldItem.Key, out var v)) + { + this.InvokeOnRemove(ref filteredCount, ViewChanged, e.OldItem, v.Item2, -1); + } } - } break; case NotifyCollectionChangedAction.Replace: - { - var v = selector(e.NewItem); - dict.Remove(e.OldItem.Key, out var ov); - dict[e.NewItem.Key] = (e.NewItem.Value, v); - - filter.InvokeOnReplace(e.NewItem, v, e.OldItem, ov.Item2, -1); - } + { + var v = selector(e.NewItem); + dict.Remove(e.OldItem.Key, out var ov); + dict[e.NewItem.Key] = (e.NewItem.Value, v); + + this.InvokeOnReplace(ref filteredCount, ViewChanged, e.NewItem, v, e.OldItem, ov.Item2, -1); + } break; case NotifyCollectionChangedAction.Reset: - { - dict.Clear(); - filter.InvokeOnReset(); - } + { + dict.Clear(); + this.InvokeOnReset(ref filteredCount, ViewChanged); + } break; case NotifyCollectionChangedAction.Move: // ObservableDictionary have no Move operation. default: break; } - RoutingCollectionChanged?.Invoke(e); CollectionStateChanged?.Invoke(e.Action); } } diff --git a/src/ObservableCollections/ObservableDictionary.cs b/src/ObservableCollections/ObservableDictionary.cs index 118fc87..bb9e2f8 100644 --- a/src/ObservableCollections/ObservableDictionary.cs +++ b/src/ObservableCollections/ObservableDictionary.cs @@ -7,8 +7,7 @@ using System.Linq; namespace ObservableCollections { - public sealed partial class ObservableDictionary : IDictionary, - IReadOnlyObservableDictionary + public sealed partial class ObservableDictionary : IDictionary, IReadOnlyObservableDictionary where TKey : notnull { readonly Dictionary dictionary; diff --git a/src/ObservableCollections/ObservableHashSet.Views.cs b/src/ObservableCollections/ObservableHashSet.Views.cs index 5bf9123..ea846f2 100644 --- a/src/ObservableCollections/ObservableHashSet.Views.cs +++ b/src/ObservableCollections/ObservableHashSet.Views.cs @@ -4,19 +4,20 @@ using System.Collections.Generic; using System.Collections.Specialized; using System; using System.Linq; +using System.Threading.Tasks; namespace ObservableCollections { public sealed partial class ObservableHashSet : IReadOnlyCollection, IObservableCollection { - public ISynchronizedView CreateView(Func transform, bool _ = false) + public ISynchronizedView CreateView(Func transform) { return new View(this, transform); } sealed class View : ISynchronizedView { - public ISynchronizedViewFilter CurrentFilter + public ISynchronizedViewFilter Filter { get { lock (SyncRoot) return filter; } } @@ -24,10 +25,11 @@ namespace ObservableCollections readonly ObservableHashSet source; readonly Func selector; readonly Dictionary dict; + int filteredCount; - ISynchronizedViewFilter filter; + ISynchronizedViewFilter filter; - public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; + public event Action>? ViewChanged; public event Action? CollectionStateChanged; public object SyncRoot { get; } @@ -36,16 +38,28 @@ namespace ObservableCollections { this.source = source; this.selector = selector; - this.filter = SynchronizedViewFilter.Null; + this.filter = SynchronizedViewFilter.Null; this.SyncRoot = new object(); lock (source.SyncRoot) { this.dict = source.set.ToDictionary(x => x, x => (x, selector(x))); + this.filteredCount = dict.Count; this.source.CollectionChanged += SourceCollectionChanged; } } public int Count + { + get + { + lock (SyncRoot) + { + return filteredCount; + } + } + } + + public int UnfilteredCount { get { @@ -56,65 +70,62 @@ namespace ObservableCollections } } - public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) + public void AttachFilter(ISynchronizedViewFilter filter) { + if (filter.IsNullFilter()) + { + ResetFilter(); + return; + } + lock (SyncRoot) { this.filter = filter; + this.filteredCount = 0; foreach (var (_, (value, view)) in dict) { - if (invokeAddEventForCurrentElements) + if (filter.IsMatch(value)) { - filter.InvokeOnAdd((value, view), -1); - } - else - { - filter.InvokeOnAttach(value, view); + filteredCount++; } } } } - public void ResetFilter(Action? resetAction) + public void ResetFilter() { lock (SyncRoot) { - this.filter = SynchronizedViewFilter.Null; - if (resetAction != null) - { - foreach (var (_, (value, view)) in dict) - { - resetAction(value, view); - } - } + this.filter = SynchronizedViewFilter.Null; + this.filteredCount = dict.Count; + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.FilterReset)); } } + public ISynchronizedViewList ToViewList() + { + return new SynchronizedViewList(this); + } + public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() { - lock (SyncRoot) - { - return new NotifyCollectionChangedSynchronizedView(this, null); - } + return new NotifyCollectionChangedSynchronizedView(this, null); } public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) { - lock (SyncRoot) - { - return new NotifyCollectionChangedSynchronizedView(this, collectionEventDispatcher); - } + return new NotifyCollectionChangedSynchronizedView(this, collectionEventDispatcher); } - public IEnumerator<(T, TView)> GetEnumerator() + public IEnumerator GetEnumerator() { lock (SyncRoot) { foreach (var item in dict) { - if (filter.IsMatch(item.Value.Item1, item.Value.Item2)) + if (filter.IsMatch(item.Value.Item1)) { - yield return item.Value; + yield return item.Value.Item2; } } } @@ -122,6 +133,37 @@ namespace ObservableCollections IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerable<(T Value, TView View)> Filtered + { + get + { + lock (SyncRoot) + { + foreach (var item in dict) + { + if (filter.IsMatch(item.Value.Item1)) + { + yield return item.Value; + } + } + } + } + } + + public IEnumerable<(T Value, TView View)> Unfiltered + { + get + { + lock (SyncRoot) + { + foreach (var item in dict) + { + yield return item.Value; + } + } + } + } + public void Dispose() { this.source.CollectionChanged -= SourceCollectionChanged; @@ -138,7 +180,7 @@ namespace ObservableCollections { var v = (e.NewItem, selector(e.NewItem)); dict.Add(e.NewItem, v); - filter.InvokeOnAdd(v, -1); + this.InvokeOnAdd(ref filteredCount, ViewChanged, v, -1); } else { @@ -147,7 +189,7 @@ namespace ObservableCollections { var v = (item, selector(item)); dict.Add(item, v); - filter.InvokeOnAdd(v, i++); + this.InvokeOnAdd(ref filteredCount, ViewChanged, v, i++); } } break; @@ -156,7 +198,7 @@ namespace ObservableCollections { if (dict.Remove(e.OldItem, out var value)) { - filter.InvokeOnRemove(value, -1); + this.InvokeOnRemove(ref filteredCount, ViewChanged, value, -1); } } else @@ -165,14 +207,14 @@ namespace ObservableCollections { if (dict.Remove(item, out var value)) { - filter.InvokeOnRemove(value, -1); + this.InvokeOnRemove(ref filteredCount, ViewChanged, value, -1); } } } break; case NotifyCollectionChangedAction.Reset: dict.Clear(); - filter.InvokeOnReset(); + this.InvokeOnReset(ref filteredCount, ViewChanged); break; case NotifyCollectionChangedAction.Replace: case NotifyCollectionChangedAction.Move: @@ -180,7 +222,6 @@ namespace ObservableCollections break; } - RoutingCollectionChanged?.Invoke(e); CollectionStateChanged?.Invoke(e.Action); } } diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index b2e0bc0..6048a27 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -98,7 +98,6 @@ namespace ObservableCollections lock (SyncRoot) { this.filter = filter; - this.filteredCount = 0; for (var i = 0; i < list.Count; i++) { diff --git a/src/ObservableCollections/ObservableQueue.Views.cs b/src/ObservableCollections/ObservableQueue.Views.cs index 37bdfbc..b6d9601 100644 --- a/src/ObservableCollections/ObservableQueue.Views.cs +++ b/src/ObservableCollections/ObservableQueue.Views.cs @@ -4,14 +4,15 @@ using System.Collections.Specialized; using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; namespace ObservableCollections { public sealed partial class ObservableQueue : IReadOnlyCollection, IObservableCollection { - public ISynchronizedView CreateView(Func transform, bool reverse = false) + public ISynchronizedView CreateView(Func transform) { - return new View(this, transform, reverse); + return new View(this, transform); } class View : ISynchronizedView @@ -20,34 +21,46 @@ namespace ObservableCollections readonly Func selector; readonly bool reverse; protected readonly Queue<(T, TView)> queue; + int filteredCount; - ISynchronizedViewFilter filter; + ISynchronizedViewFilter filter; - public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; + public event Action>? ViewChanged; public event Action? CollectionStateChanged; public object SyncRoot { get; } - public ISynchronizedViewFilter CurrentFilter + public ISynchronizedViewFilter Filter { get { lock (SyncRoot) return filter; } } - public View(ObservableQueue source, Func selector, bool reverse) + public View(ObservableQueue source, Func selector) { this.source = source; this.selector = selector; - this.reverse = reverse; - this.filter = SynchronizedViewFilter.Null; + this.filter = SynchronizedViewFilter.Null; this.SyncRoot = new object(); lock (source.SyncRoot) { this.queue = new Queue<(T, TView)>(source.queue.Select(x => (x, selector(x)))); + this.filteredCount = queue.Count; this.source.CollectionChanged += SourceCollectionChanged; } } public int Count + { + get + { + lock (SyncRoot) + { + return filteredCount; + } + } + } + + public int UnfilteredCount { get { @@ -58,79 +71,63 @@ namespace ObservableCollections } } - public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) + public void AttachFilter(ISynchronizedViewFilter filter) { + if (filter.IsNullFilter()) + { + ResetFilter(); + return; + } + lock (SyncRoot) { this.filter = filter; - var i = 0; + this.filteredCount = 0; foreach (var (value, view) in queue) { - if (invokeAddEventForCurrentElements) + if (filter.IsMatch(value)) { - filter.InvokeOnAdd(value, view, i++); - } - else - { - filter.InvokeOnAttach(value, view); + filteredCount++; } } + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.FilterReset)); } } - public void ResetFilter(Action? resetAction) + public void ResetFilter() { lock (SyncRoot) { - this.filter = SynchronizedViewFilter.Null; - if (resetAction != null) - { - foreach (var (item, view) in queue) - { - resetAction(item, view); - } - } + this.filter = SynchronizedViewFilter.Null; + this.filteredCount = queue.Count; + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.FilterReset)); } } + public ISynchronizedViewList ToViewList() + { + return new SynchronizedViewList(this); + } + public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() { - lock (SyncRoot) - { - return new NotifyCollectionChangedSynchronizedView(this, null); - } + return new NotifyCollectionChangedSynchronizedView(this, null); } public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) { - lock (SyncRoot) - { - return new NotifyCollectionChangedSynchronizedView(this, collectionEventDispatcher); - } + return new NotifyCollectionChangedSynchronizedView(this, collectionEventDispatcher); } - public IEnumerator<(T, TView)> GetEnumerator() + public IEnumerator GetEnumerator() { lock (SyncRoot) { - if (!reverse) + foreach (var item in queue) { - foreach (var item in queue) + if (filter.IsMatch(item.Item1)) { - if (filter.IsMatch(item.Item1, item.Item2)) - { - yield return item; - } - } - } - else - { - foreach (var item in queue.AsEnumerable().Reverse()) - { - if (filter.IsMatch(item.Item1, item.Item2)) - { - yield return item; - } + yield return item.Item2; } } } @@ -138,6 +135,37 @@ namespace ObservableCollections IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerable<(T Value, TView View)> Filtered + { + get + { + lock (SyncRoot) + { + foreach (var item in queue) + { + if (filter.IsMatch(item.Item1)) + { + yield return item; + } + } + } + } + } + + public IEnumerable<(T Value, TView View)> Unfiltered + { + get + { + lock (SyncRoot) + { + foreach (var item in queue) + { + yield return item; + } + } + } + } + public void Dispose() { this.source.CollectionChanged -= SourceCollectionChanged; @@ -155,7 +183,7 @@ namespace ObservableCollections { var v = (e.NewItem, selector(e.NewItem)); queue.Enqueue(v); - filter.InvokeOnAdd(v, e.NewStartingIndex); + this.InvokeOnAdd(ref filteredCount, ViewChanged, v, e.NewStartingIndex); } else { @@ -164,7 +192,7 @@ namespace ObservableCollections { var v = (item, selector(item)); queue.Enqueue(v); - filter.InvokeOnAdd(v, i++); + this.InvokeOnAdd(ref filteredCount, ViewChanged, v, i++); } } break; @@ -173,7 +201,7 @@ namespace ObservableCollections if (e.IsSingleItem) { var v = queue.Dequeue(); - filter.InvokeOnRemove(v.Item1, v.Item2, 0); + this.InvokeOnRemove(ref filteredCount, ViewChanged, v.Item1, v.Item2, 0); } else { @@ -181,13 +209,13 @@ namespace ObservableCollections for (int i = 0; i < len; i++) { var v = queue.Dequeue(); - filter.InvokeOnRemove(v.Item1, v.Item2, 0); + this.InvokeOnRemove(ref filteredCount, ViewChanged, v.Item1, v.Item2, 0); } } break; case NotifyCollectionChangedAction.Reset: queue.Clear(); - filter.InvokeOnReset(); + this.InvokeOnReset(ref filteredCount, ViewChanged); break; case NotifyCollectionChangedAction.Replace: case NotifyCollectionChangedAction.Move: @@ -195,7 +223,6 @@ namespace ObservableCollections break; } - RoutingCollectionChanged?.Invoke(e); CollectionStateChanged?.Invoke(e.Action); } } diff --git a/src/ObservableCollections/ObservableRingBuffer.Views.cs b/src/ObservableCollections/ObservableRingBuffer.Views.cs index b19c409..95a1b78 100644 --- a/src/ObservableCollections/ObservableRingBuffer.Views.cs +++ b/src/ObservableCollections/ObservableRingBuffer.Views.cs @@ -4,51 +4,63 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; +using System.Threading.Tasks; namespace ObservableCollections { public sealed partial class ObservableRingBuffer { - public ISynchronizedView CreateView(Func transform, bool reverse = false) + public ISynchronizedView CreateView(Func transform) { - return new View(this, transform, reverse); + return new View(this, transform); } // used with ObservableFixedSizeRingBuffer internal sealed class View : ISynchronizedView { - public ISynchronizedViewFilter CurrentFilter + public ISynchronizedViewFilter Filter { get { lock (SyncRoot) return filter; } } readonly IObservableCollection source; readonly Func selector; - readonly bool reverse; readonly RingBuffer<(T, TView)> ringBuffer; + int filteredCount; - ISynchronizedViewFilter filter; + ISynchronizedViewFilter filter; - public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; + public event Action>? ViewChanged; public event Action? CollectionStateChanged; public object SyncRoot { get; } - public View(IObservableCollection source, Func selector, bool reverse) + public View(IObservableCollection source, Func selector) { this.source = source; this.selector = selector; - this.reverse = reverse; - this.filter = SynchronizedViewFilter.Null; + this.filter = SynchronizedViewFilter.Null; this.SyncRoot = new object(); lock (source.SyncRoot) { this.ringBuffer = new RingBuffer<(T, TView)>(source.Select(x => (x, selector(x)))); + this.filteredCount = ringBuffer.Count; this.source.CollectionChanged += SourceCollectionChanged; } } public int Count + { + get + { + lock (SyncRoot) + { + return filteredCount; + } + } + } + + public int UnfilteredCount { get { @@ -59,41 +71,44 @@ namespace ObservableCollections } } - public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) + public void AttachFilter(ISynchronizedViewFilter filter) { + if (filter.IsNullFilter()) + { + ResetFilter(); + return; + } + lock (SyncRoot) { this.filter = filter; + this.filteredCount = 0; for (var i = 0; i < ringBuffer.Count; i++) { var (value, view) = ringBuffer[i]; - if (invokeAddEventForCurrentElements) + if (filter.IsMatch(value)) { - filter.InvokeOnAdd(value, view, i); - } - else - { - filter.InvokeOnAttach(value, view); + filteredCount++; } } } } - public void ResetFilter(Action? resetAction) + public void ResetFilter() { lock (SyncRoot) { - this.filter = SynchronizedViewFilter.Null; - if (resetAction != null) - { - foreach (var (item, view) in ringBuffer) - { - resetAction(item, view); - } - } + this.filter = SynchronizedViewFilter.Null; + this.filteredCount = ringBuffer.Count; + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.FilterReset)); } } + public ISynchronizedViewList ToViewList() + { + return new SynchronizedViewList(this); + } + public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() { lock (SyncRoot) @@ -110,25 +125,31 @@ namespace ObservableCollections } } - public IEnumerator<(T, TView)> GetEnumerator() + public IEnumerator GetEnumerator() { lock (SyncRoot) { - if (!reverse) + foreach (var item in ringBuffer) + { + if (filter.IsMatch(item.Item1)) + { + yield return item.Item2; + } + } + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public IEnumerable<(T Value, TView View)> Filtered + { + get + { + lock (SyncRoot) { foreach (var item in ringBuffer) { - if (filter.IsMatch(item.Item1, item.Item2)) - { - yield return item; - } - } - } - else - { - foreach (var item in ringBuffer.AsEnumerable().Reverse()) - { - if (filter.IsMatch(item.Item1, item.Item2)) + if (filter.IsMatch(item.Item1)) { yield return item; } @@ -137,7 +158,19 @@ namespace ObservableCollections } } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerable<(T Value, TView View)> Unfiltered + { + get + { + lock (SyncRoot) + { + foreach (var item in ringBuffer) + { + yield return item; + } + } + } + } public void Dispose() { @@ -162,7 +195,7 @@ namespace ObservableCollections { var v = (e.NewItem, selector(e.NewItem)); ringBuffer.AddFirst(v); - filter.InvokeOnAdd(v, 0); + this.InvokeOnAdd(ref filteredCount, ViewChanged, v, 0); } else { @@ -170,7 +203,7 @@ namespace ObservableCollections { var v = (item, selector(item)); ringBuffer.AddFirst(v); - filter.InvokeOnAdd(v, 0); + this.InvokeOnAdd(ref filteredCount, ViewChanged, v, 0); } } } @@ -181,7 +214,7 @@ namespace ObservableCollections { var v = (e.NewItem, selector(e.NewItem)); ringBuffer.AddLast(v); - filter.InvokeOnAdd(v, ringBuffer.Count - 1); + this.InvokeOnAdd(ref filteredCount, ViewChanged, v, ringBuffer.Count - 1); } else { @@ -189,7 +222,7 @@ namespace ObservableCollections { var v = (item, selector(item)); ringBuffer.AddLast(v); - filter.InvokeOnAdd(v, ringBuffer.Count - 1); + this.InvokeOnAdd(ref filteredCount, ViewChanged, v, ringBuffer.Count - 1); } } } @@ -202,14 +235,14 @@ namespace ObservableCollections if (e.IsSingleItem) { var v = ringBuffer.RemoveFirst(); - filter.InvokeOnRemove(v, 0); + this.InvokeOnRemove(ref filteredCount, ViewChanged, v, 0); } else { for (int i = 0; i < e.OldItems.Length; i++) { var v = ringBuffer.RemoveFirst(); - filter.InvokeOnRemove(v, 0); + this.InvokeOnRemove(ref filteredCount, ViewChanged, v, 0); } } } @@ -220,7 +253,7 @@ namespace ObservableCollections { var index = ringBuffer.Count - 1; var v = ringBuffer.RemoveLast(); - filter.InvokeOnRemove(v, index); + this.InvokeOnRemove(ref filteredCount, ViewChanged, v, index); } else { @@ -228,14 +261,14 @@ namespace ObservableCollections { var index = ringBuffer.Count - 1; var v = ringBuffer.RemoveLast(); - filter.InvokeOnRemove(v, index); + this.InvokeOnRemove(ref filteredCount, ViewChanged, v, index); } } } break; case NotifyCollectionChangedAction.Reset: ringBuffer.Clear(); - filter.InvokeOnReset(); + this.InvokeOnReset(ref filteredCount, ViewChanged); break; case NotifyCollectionChangedAction.Replace: // range is not supported @@ -243,7 +276,7 @@ namespace ObservableCollections var ov = ringBuffer[e.OldStartingIndex]; var v = (e.NewItem, selector(e.NewItem)); ringBuffer[e.NewStartingIndex] = v; - filter.InvokeOnReplace(v, ov, e.NewStartingIndex); + this.InvokeOnReplace(ref filteredCount, ViewChanged, v, ov, e.NewStartingIndex); break; } case NotifyCollectionChangedAction.Move: @@ -251,7 +284,6 @@ namespace ObservableCollections break; } - RoutingCollectionChanged?.Invoke(e); CollectionStateChanged?.Invoke(e.Action); } } diff --git a/src/ObservableCollections/ObservableStack.Views.cs b/src/ObservableCollections/ObservableStack.Views.cs index d0ced1a..2bfa68a 100644 --- a/src/ObservableCollections/ObservableStack.Views.cs +++ b/src/ObservableCollections/ObservableStack.Views.cs @@ -9,45 +9,56 @@ namespace ObservableCollections { public sealed partial class ObservableStack : IReadOnlyCollection, IObservableCollection { - public ISynchronizedView CreateView(Func transform, bool reverse = false) + public ISynchronizedView CreateView(Func transform) { - return new View(this, transform, reverse); + return new View(this, transform); } class View : ISynchronizedView { readonly ObservableStack source; readonly Func selector; - readonly bool reverse; protected readonly Stack<(T, TView)> stack; + int filteredCount; - ISynchronizedViewFilter filter; + ISynchronizedViewFilter filter; - public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; + public event Action>? ViewChanged; public event Action? CollectionStateChanged; public object SyncRoot { get; } - public ISynchronizedViewFilter CurrentFilter + public ISynchronizedViewFilter Filter { get { lock (SyncRoot) return filter; } } - public View(ObservableStack source, Func selector, bool reverse) + public View(ObservableStack source, Func selector) { this.source = source; this.selector = selector; - this.reverse = reverse; - this.filter = SynchronizedViewFilter.Null; + this.filter = SynchronizedViewFilter.Null; this.SyncRoot = new object(); lock (source.SyncRoot) { this.stack = new Stack<(T, TView)>(source.stack.Select(x => (x, selector(x)))); + this.filteredCount = stack.Count; this.source.CollectionChanged += SourceCollectionChanged; } } public int Count + { + get + { + lock (SyncRoot) + { + return filteredCount; + } + } + } + + public int UnfilteredCount { get { @@ -58,40 +69,44 @@ namespace ObservableCollections } } - public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) + public void AttachFilter(ISynchronizedViewFilter filter) { + if (filter.IsNullFilter()) + { + ResetFilter(); + return; + } + lock (SyncRoot) { this.filter = filter; + this.filteredCount = 0; foreach (var (value, view) in stack) { - if (invokeAddEventForCurrentElements) + if (filter.IsMatch(value)) { - filter.InvokeOnAdd(value, view, 0); - } - else - { - filter.InvokeOnAttach(value, view); + filteredCount++; } } + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.FilterReset)); } } - public void ResetFilter(Action? resetAction) + public void ResetFilter() { lock (SyncRoot) { - this.filter = SynchronizedViewFilter.Null; - if (resetAction != null) - { - foreach (var (item, view) in stack) - { - resetAction(item, view); - } - } + this.filter = SynchronizedViewFilter.Null; + this.filteredCount = stack.Count; + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.FilterReset)); } } + public ISynchronizedViewList ToViewList() + { + return new SynchronizedViewList(this); + } + public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() { lock (SyncRoot) @@ -108,25 +123,31 @@ namespace ObservableCollections } } - public IEnumerator<(T, TView)> GetEnumerator() + public IEnumerator GetEnumerator() { lock (SyncRoot) { - if (!reverse) + foreach (var item in stack) + { + if (filter.IsMatch(item.Item1)) + { + yield return item.Item2; + } + } + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public IEnumerable<(T Value, TView View)> Filtered + { + get + { + lock (SyncRoot) { foreach (var item in stack) { - if (filter.IsMatch(item.Item1, item.Item2)) - { - yield return item; - } - } - } - else - { - foreach (var item in stack.AsEnumerable().Reverse()) - { - if (filter.IsMatch(item.Item1, item.Item2)) + if (filter.IsMatch(item.Item1)) { yield return item; } @@ -135,7 +156,19 @@ namespace ObservableCollections } } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerable<(T Value, TView View)> Unfiltered + { + get + { + lock (SyncRoot) + { + foreach (var item in stack) + { + yield return item; + } + } + } + } public void Dispose() { @@ -154,7 +187,7 @@ namespace ObservableCollections { var v = (e.NewItem, selector(e.NewItem)); stack.Push(v); - filter.InvokeOnAdd(v, 0); + this.InvokeOnAdd(ref filteredCount, ViewChanged, v, 0); } else { @@ -162,7 +195,7 @@ namespace ObservableCollections { var v = (item, selector(item)); stack.Push(v); - filter.InvokeOnAdd(v, 0); + this.InvokeOnAdd(ref filteredCount, ViewChanged, v, 0); } } break; @@ -171,7 +204,7 @@ namespace ObservableCollections if (e.IsSingleItem) { var v = stack.Pop(); - filter.InvokeOnRemove(v.Item1, v.Item2, 0); + this.InvokeOnRemove(ref filteredCount, ViewChanged, v.Item1, v.Item2, 0); } else { @@ -179,13 +212,13 @@ namespace ObservableCollections for (int i = 0; i < len; i++) { var v = stack.Pop(); - filter.InvokeOnRemove(v.Item1, v.Item2, 0); + this.InvokeOnRemove(ref filteredCount, ViewChanged, v.Item1, v.Item2, 0); } } break; case NotifyCollectionChangedAction.Reset: stack.Clear(); - filter.InvokeOnReset(); + this.InvokeOnReset(ref filteredCount, ViewChanged); break; case NotifyCollectionChangedAction.Replace: case NotifyCollectionChangedAction.Move: @@ -193,7 +226,6 @@ namespace ObservableCollections break; } - RoutingCollectionChanged?.Invoke(e); CollectionStateChanged?.Invoke(e.Action); } } From 5b3eb8015814377611a869a47714050d19f18e47 Mon Sep 17 00:00:00 2001 From: neuecc Date: Tue, 27 Aug 2024 23:28:30 +0900 Subject: [PATCH 09/22] c --- sandbox/ConsoleApp/Program.cs | 20 +- .../FreezedDictionary.cs | 59 ---- src/ObservableCollections/FreezedList.cs | 61 ---- .../IObservableCollection.cs | 89 +----- .../ISynchronizedViewFilter.cs | 48 ++- .../Internal/FreezedView.cs | 264 ----------------- .../Internal/SortedView.cs | 259 ---------------- .../Internal/SortedViewViewComparer.cs | 278 ------------------ .../ObservableDictionary.Views.cs | 6 +- .../ObservableFixedSizeRingBuffer.cs | 4 +- .../ObservableHashSet.Views.cs | 5 +- .../ObservableList.Views.cs | 9 +- .../ObservableQueue.Views.cs | 7 +- .../ObservableRingBuffer.Views.cs | 5 +- .../ObservableStack.Views.cs | 6 +- ...ronizedView.cs => SynchronizedViewList.cs} | 33 +-- .../ObservableDictionaryTest.cs | 141 --------- .../ObservableHashSetTest.cs | 62 ---- .../ObservableListTest.cs | 239 ++++++--------- .../ObservableQueueTest.cs | 111 ++++--- .../ObservableRingBufferTest.cs | 2 - .../ObservableStackTest.cs | 67 ----- .../SortedViewTest.cs | 70 ----- .../SortedViewViewComparerTest.cs | 73 ----- .../ToNotifyCollectionChangedTest.cs | 4 +- .../ViewContainer.cs | 130 ++++---- 26 files changed, 269 insertions(+), 1783 deletions(-) delete mode 100644 src/ObservableCollections/FreezedDictionary.cs delete mode 100644 src/ObservableCollections/FreezedList.cs delete mode 100644 src/ObservableCollections/Internal/FreezedView.cs delete mode 100644 src/ObservableCollections/Internal/SortedView.cs delete mode 100644 src/ObservableCollections/Internal/SortedViewViewComparer.cs rename src/ObservableCollections/{Internal/NotifyCollectionChangedSynchronizedView.cs => SynchronizedViewList.cs} (90%) delete mode 100644 tests/ObservableCollections.Tests/SortedViewTest.cs delete mode 100644 tests/ObservableCollections.Tests/SortedViewViewComparerTest.cs diff --git a/sandbox/ConsoleApp/Program.cs b/sandbox/ConsoleApp/Program.cs index 758e3d5..63eb431 100644 --- a/sandbox/ConsoleApp/Program.cs +++ b/sandbox/ConsoleApp/Program.cs @@ -33,13 +33,13 @@ var viewModels = models.CreateView(x => new ViewModel Value = "@" + x }); -viewModels.AttachFilter(new HogeFilter(), true); +viewModels.AttachFilter(new HogeFilter()); models.Add(100); -foreach (var (x, xs) in viewModels) +foreach (var x in viewModels) { - System.Console.WriteLine(xs.Value); + System.Console.WriteLine(x); } class ViewModel @@ -48,23 +48,13 @@ class ViewModel public string Value { get; set; } = default!; } -class HogeFilter : ISynchronizedViewFilter +class HogeFilter : ISynchronizedViewFilter { - public bool IsMatch(int value, ViewModel view) + public bool IsMatch(int value) { return value % 2 == 0; } - public void WhenTrue(int value, ViewModel view) - { - view.Value = $"@{value} (even)"; - } - - public void WhenFalse(int value, ViewModel view) - { - view.Value = $"@{value} (odd)"; - } - public void OnCollectionChanged(in SynchronizedViewChangedEventArgs eventArgs) { switch (eventArgs.Action) diff --git a/src/ObservableCollections/FreezedDictionary.cs b/src/ObservableCollections/FreezedDictionary.cs deleted file mode 100644 index c973850..0000000 --- a/src/ObservableCollections/FreezedDictionary.cs +++ /dev/null @@ -1,59 +0,0 @@ -#nullable disable - -using ObservableCollections.Internal; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace ObservableCollections -{ - public sealed class FreezedDictionary : IReadOnlyDictionary, IFreezedCollection> - where TKey : notnull - { - readonly IReadOnlyDictionary dictionary; - - public FreezedDictionary(IReadOnlyDictionary dictionary) - { - this.dictionary = dictionary; - } - - public TValue this[TKey key] => dictionary[key]; - - public IEnumerable Keys => dictionary.Keys; - - public IEnumerable Values => dictionary.Values; - - public int Count => dictionary.Count; - - public bool ContainsKey(TKey key) - { - return dictionary.ContainsKey(key); - } - - public IEnumerator> GetEnumerator() - { - return dictionary.GetEnumerator(); - } - - public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) - { - return dictionary.TryGetValue(key, out value); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)dictionary).GetEnumerator(); - } - - public ISynchronizedView, TView> CreateView(Func, TView> transform, bool reverse = false) - { - return new FreezedView, TView>(dictionary, transform, reverse); - } - - public ISortableSynchronizedView, TView> CreateSortableView(Func, TView> transform) - { - return new FreezedSortableView, TView>(dictionary, transform); - } - } -} \ No newline at end of file diff --git a/src/ObservableCollections/FreezedList.cs b/src/ObservableCollections/FreezedList.cs deleted file mode 100644 index f7666e5..0000000 --- a/src/ObservableCollections/FreezedList.cs +++ /dev/null @@ -1,61 +0,0 @@ -using ObservableCollections.Internal; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace ObservableCollections -{ - public sealed class FreezedList : IReadOnlyList, IFreezedCollection - { - readonly IReadOnlyList list; - - public T this[int index] - { - get - { - return list[index]; - } - } - - public int Count - { - get - { - return list.Count; - } - } - - public bool IsReadOnly => true; - - public FreezedList(IReadOnlyList list) - { - this.list = list; - } - - public ISynchronizedView CreateView(Func transform, bool reverse = false) - { - return new FreezedView(list, transform, reverse); - } - - public ISortableSynchronizedView CreateSortableView(Func transform) - { - return new FreezedSortableView(list, transform); - } - - public bool Contains(T item) - { - return list.Contains(item); - } - - public IEnumerator GetEnumerator() - { - return list.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} \ No newline at end of file diff --git a/src/ObservableCollections/IObservableCollection.cs b/src/ObservableCollections/IObservableCollection.cs index 78b0d78..0568de1 100644 --- a/src/ObservableCollections/IObservableCollection.cs +++ b/src/ObservableCollections/IObservableCollection.cs @@ -1,4 +1,3 @@ -using ObservableCollections.Internal; using System; using System.Collections.Generic; using System.Collections.Specialized; @@ -7,6 +6,7 @@ using System.ComponentModel; namespace ObservableCollections { public delegate void NotifyCollectionChangedEventHandler(in NotifyCollectionChangedEventArgs e); + public delegate void NotifyViewChangedEventHandler(in SynchronizedViewChangedEventArgs e); public interface IObservableCollection : IReadOnlyCollection { @@ -25,12 +25,6 @@ namespace ObservableCollections { } - public interface IFreezedCollection - { - ISynchronizedView CreateView(Func transform, bool reverse = false); - ISortableSynchronizedView CreateSortableView(Func transform); - } - public interface ISynchronizedView : IReadOnlyCollection, IDisposable { object SyncRoot { get; } @@ -39,7 +33,7 @@ namespace ObservableCollections IEnumerable<(T Value, TView View)> Unfiltered { get; } int UnfilteredCount { get; } - event Action>? ViewChanged; + event NotifyViewChangedEventHandler? ViewChanged; event Action? CollectionStateChanged; void AttachFilter(ISynchronizedViewFilter filter); @@ -49,17 +43,6 @@ namespace ObservableCollections INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher); } - public interface ISortableSynchronizedView : ISynchronizedView - { - void Sort(IComparer comparer); - void Sort(IComparer viewComparer); - } - - // will be implemented in the future? - //public interface IGroupedSynchoronizedView : ILookup, ISynchronizedView - //{ - //} - public interface ISynchronizedViewList : IReadOnlyList, IDisposable { } @@ -67,72 +50,4 @@ namespace ObservableCollections public interface INotifyCollectionChangedSynchronizedView : IReadOnlyCollection, INotifyCollectionChanged, INotifyPropertyChanged, IDisposable { } - - public static class ObservableCollectionsExtensions - { - public static ISynchronizedView CreateSortedView(this IObservableCollection source, Func identitySelector, Func transform, IComparer comparer) - where TKey : notnull - { - return new SortedView(source, identitySelector, transform, comparer); - } - - public static ISynchronizedView CreateSortedView(this IObservableCollection source, Func identitySelector, Func transform, IComparer viewComparer) - where TKey : notnull - { - return new SortedViewViewComparer(source, identitySelector, transform, viewComparer); - } - - public static ISynchronizedView CreateSortedView(this IObservableCollection source, Func identitySelector, Func transform, Func compareSelector, bool ascending = true) - where TKey : notnull - { - return source.CreateSortedView(identitySelector, transform, new AnonymousComparer(compareSelector, ascending)); - } - - public static ISortableSynchronizedView CreateSortableView(this IFreezedCollection source, Func transform, IComparer initialSort) - { - var view = source.CreateSortableView(transform); - view.Sort(initialSort); - return view; - } - - public static ISortableSynchronizedView CreateSortableView(this IFreezedCollection source, Func transform, IComparer initialViewSort) - { - var view = source.CreateSortableView(transform); - view.Sort(initialViewSort); - return view; - } - - public static ISortableSynchronizedView CreateSortableView(this IFreezedCollection source, Func transform, Func initialCompareSelector, bool ascending = true) - { - var view = source.CreateSortableView(transform); - view.Sort(initialCompareSelector, ascending); - return view; - } - - public static void Sort(this ISortableSynchronizedView source, Func compareSelector, bool ascending = true) - { - source.Sort(new AnonymousComparer(compareSelector, ascending)); - } - - class AnonymousComparer : IComparer - { - readonly Func selector; - readonly int f; - - public AnonymousComparer(Func selector, bool ascending) - { - this.selector = selector; - this.f = ascending ? 1 : -1; - } - - public int Compare(T? x, T? y) - { - if (x == null && y == null) return 0; - if (x == null) return 1 * f; - if (y == null) return -1 * f; - - return Comparer.Default.Compare(selector(x), selector(y)) * f; - } - } - } } \ No newline at end of file diff --git a/src/ObservableCollections/ISynchronizedViewFilter.cs b/src/ObservableCollections/ISynchronizedViewFilter.cs index 85c0b11..84108a0 100644 --- a/src/ObservableCollections/ISynchronizedViewFilter.cs +++ b/src/ObservableCollections/ISynchronizedViewFilter.cs @@ -3,8 +3,8 @@ using System.Collections.Specialized; namespace ObservableCollections { - public readonly struct SynchronizedViewChangedEventArgs( - NotifyViewChangedAction action, + public readonly ref struct SynchronizedViewChangedEventArgs( + NotifyCollectionChangedAction action, T newValue = default!, T oldValue = default!, TView newView = default!, @@ -12,7 +12,7 @@ namespace ObservableCollections int newViewIndex = -1, int oldViewIndex = -1) { - public readonly NotifyViewChangedAction Action = action; + public readonly NotifyCollectionChangedAction Action = action; public readonly T NewValue = newValue; public readonly T OldValue = oldValue; public readonly TView NewView = newView; @@ -21,16 +21,6 @@ namespace ObservableCollections public readonly int OldViewIndex = oldViewIndex; } - public enum NotifyViewChangedAction - { - Add = 0, - Remove = 1, - Replace = 2, - Move = 3, - Reset = 4, - FilterReset = 5, - } - public interface ISynchronizedViewFilter { bool IsMatch(T value); @@ -60,12 +50,12 @@ namespace ObservableCollections return filter == SynchronizedViewFilter.Null; } - internal static void InvokeOnAdd(this ISynchronizedView collection, ref int filteredCount, Action>? ev, (T value, TView view) value, int index) + 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, Action>? ev, T value, TView view, int 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) @@ -73,17 +63,17 @@ namespace ObservableCollections filteredCount++; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.Add, newValue: value, newView: view, newViewIndex: index)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, newValue: value, newView: view, newViewIndex: index)); } } } - internal static void InvokeOnRemove(this ISynchronizedView collection, ref int filteredCount, Action>? ev, (T value, TView view) value, int oldIndex) + 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, Action>? ev, T value, TView view, int 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) @@ -91,17 +81,17 @@ namespace ObservableCollections filteredCount--; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); } } } - internal static void InvokeOnMove(this ISynchronizedView collection, ref int filteredCount, Action>? ev, (T value, TView view) value, int index, int 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, Action>? ev, T value, TView view, int index, int oldIndex) + internal static void InvokeOnMove(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, T value, TView view, int index, int oldIndex) { if (ev != null) { @@ -109,17 +99,17 @@ namespace ObservableCollections var isMatch = collection.Filter.IsMatch(value); if (isMatch) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.Move, newValue: value, newView: view, newViewIndex: index, oldViewIndex: oldIndex)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Move, newValue: value, newView: view, newViewIndex: index, oldViewIndex: oldIndex)); } } } - internal static void InvokeOnReplace(this ISynchronizedView collection, ref int filteredCount, Action>? ev, (T value, TView view) value, (T value, TView view) oldValue, int index, int oldIndex = -1) + 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, Action>? ev, T value, TView view, T oldValue, TView oldView, int index, int oldIndex = -1) + 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); @@ -129,7 +119,7 @@ namespace ObservableCollections { if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.Replace, newValue: value, newView: view, oldValue: oldValue, oldView: oldView, newViewIndex: index, oldViewIndex: oldIndex >= 0 ? oldIndex : index)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Replace, newValue: value, newView: view, oldValue: oldValue, oldView: oldView, newViewIndex: index, oldViewIndex: oldIndex >= 0 ? oldIndex : index)); } } else if (oldMatched) @@ -138,7 +128,7 @@ namespace ObservableCollections filteredCount--; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); } } @@ -148,17 +138,17 @@ namespace ObservableCollections filteredCount++; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.Add, newValue: value, newView: view, newViewIndex: index)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, newValue: value, newView: view, newViewIndex: index)); } } } - internal static void InvokeOnReset(this ISynchronizedView collection, ref int filteredCount, Action>? ev) + internal static void InvokeOnReset(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev) { filteredCount = 0; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.Reset)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } } diff --git a/src/ObservableCollections/Internal/FreezedView.cs b/src/ObservableCollections/Internal/FreezedView.cs deleted file mode 100644 index f256fb7..0000000 --- a/src/ObservableCollections/Internal/FreezedView.cs +++ /dev/null @@ -1,264 +0,0 @@ -#pragma warning disable CS0067 - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; - -namespace ObservableCollections.Internal -{ - internal sealed class FreezedView : ISynchronizedView - { - readonly bool reverse; - readonly List<(T, TView)> list; - - ISynchronizedViewFilter filter; - - public ISynchronizedViewFilter CurrentFilter - { - get { lock (SyncRoot) return filter; } - } - - public event Action? CollectionStateChanged; - public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; - - public object SyncRoot { get; } = new object(); - - public FreezedView(IEnumerable source, Func selector, bool reverse) - { - this.reverse = reverse; - this.filter = SynchronizedViewFilter.Null; - this.list = source.Select(x => (x, selector(x))).ToList(); - } - - public int Count - { - get - { - lock (SyncRoot) - { - return list.Count; - } - } - } - - public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) - { - lock (SyncRoot) - { - this.filter = filter; - for (var i = 0; i < list.Count; i++) - { - var (value, view) = list[i]; - if (invokeAddEventForCurrentElements) - { - filter.InvokeOnAdd(value, view, i); - } - else - { - filter.InvokeOnAttach(value, view); - } - } - } - } - - public void ResetFilter(Action? resetAction) - { - lock (SyncRoot) - { - this.filter = SynchronizedViewFilter.Null; - if (resetAction != null) - { - foreach (var (item, view) in list) - { - resetAction(item, view); - } - } - } - } - - public IEnumerator<(T, TView)> GetEnumerator() - { - lock (SyncRoot) - { - if (!reverse) - { - foreach (var item in list) - { - if (filter.IsMatch(item.Item1, item.Item2)) - { - yield return item; - } - } - } - else - { - foreach (var item in list.AsEnumerable().Reverse()) - { - if (filter.IsMatch(item.Item1, item.Item2)) - { - yield return item; - } - } - } - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public void Dispose() - { - - } - - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() - { - return new NotifyCollectionChangedSynchronizedView(this, null); - } - - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) - { - return new NotifyCollectionChangedSynchronizedView(this, collectionEventDispatcher); - } - } - - internal sealed class FreezedSortableView : ISortableSynchronizedView - { - readonly (T, TView)[] array; - - ISynchronizedViewFilter filter; - - public ISynchronizedViewFilter CurrentFilter - { - get { lock (SyncRoot) return filter; } - } - - public event Action? CollectionStateChanged; - public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; - - public object SyncRoot { get; } = new object(); - - public FreezedSortableView(IEnumerable source, Func selector) - { - this.filter = SynchronizedViewFilter.Null; - this.array = source.Select(x => (x, selector(x))).ToArray(); - } - - public int Count - { - get - { - lock (SyncRoot) - { - return array.Length; - } - } - } - - public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) - { - lock (SyncRoot) - { - this.filter = filter; - for (var i = 0; i < array.Length; i++) - { - var (value, view) = array[i]; - if (invokeAddEventForCurrentElements) - { - filter.InvokeOnAdd(value, view, i); - } - else - { - filter.InvokeOnAttach(value, view); - } - } - } - } - - public void ResetFilter(Action? resetAction) - { - lock (SyncRoot) - { - this.filter = SynchronizedViewFilter.Null; - if (resetAction != null) - { - foreach (var (item, view) in array) - { - resetAction(item, view); - } - } - } - } - - public IEnumerator<(T, TView)> GetEnumerator() - { - lock (SyncRoot) - { - foreach (var item in array) - { - if (filter.IsMatch(item.Item1, item.Item2)) - { - yield return item; - } - } - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public void Dispose() - { - } - - public void Sort(IComparer comparer) - { - Array.Sort(array, new TComparer(comparer)); - } - - public void Sort(IComparer viewComparer) - { - Array.Sort(array, new TViewComparer(viewComparer)); - } - - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() - { - return new NotifyCollectionChangedSynchronizedView(this, null); - } - - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) - { - return new NotifyCollectionChangedSynchronizedView(this, collectionEventDispatcher); - } - - class TComparer : IComparer<(T, TView)> - { - readonly IComparer comparer; - - public TComparer(IComparer comparer) - { - this.comparer = comparer; - } - - public int Compare((T, TView) x, (T, TView) y) - { - return comparer.Compare(x.Item1, y.Item1); - } - } - - class TViewComparer : IComparer<(T, TView)> - { - readonly IComparer comparer; - - public TViewComparer(IComparer comparer) - { - this.comparer = comparer; - } - - public int Compare((T, TView) x, (T, TView) y) - { - return comparer.Compare(x.Item2, y.Item2); - } - } - } -} \ No newline at end of file diff --git a/src/ObservableCollections/Internal/SortedView.cs b/src/ObservableCollections/Internal/SortedView.cs deleted file mode 100644 index a0a6559..0000000 --- a/src/ObservableCollections/Internal/SortedView.cs +++ /dev/null @@ -1,259 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; - -namespace ObservableCollections.Internal -{ - internal class SortedView : ISynchronizedView - where TKey : notnull - { - public ISynchronizedViewFilter CurrentFilter - { - get { lock (SyncRoot) return filter; } - } - - readonly IObservableCollection source; - readonly Func transform; - readonly Func identitySelector; - readonly SortedList<(T Value, TKey Key), (T Value, TView View)> list; - - ISynchronizedViewFilter filter; - - public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; - public event Action? CollectionStateChanged; - - public object SyncRoot { get; } = new object(); - - public SortedView(IObservableCollection source, Func identitySelector, Func transform, IComparer comparer) - { - this.source = source; - this.identitySelector = identitySelector; - this.transform = transform; - this.filter = SynchronizedViewFilter.Null; - lock (source.SyncRoot) - { - var dict = new Dictionary<(T, TKey), (T, TView)>(source.Count); - foreach (var v in source) - { - dict.Add((v, identitySelector(v)), (v, transform(v))); - } - - this.list = new SortedList<(T Value, TKey Key), (T Value, TView View)>(dict, new Comparer(comparer)); - this.source.CollectionChanged += SourceCollectionChanged; - } - } - - public int Count - { - get - { - lock (SyncRoot) - { - return list.Count; - } - } - } - - public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) - { - lock (SyncRoot) - { - this.filter = filter; - var i = 0; - foreach (var (_, (value, view)) in list) - { - if (invokeAddEventForCurrentElements) - { - filter.InvokeOnAdd(value, view, i++); - } - else - { - filter.InvokeOnAttach(value, view); - } - } - } - } - - public void ResetFilter(Action? resetAction) - { - lock (SyncRoot) - { - this.filter = SynchronizedViewFilter.Null; - if (resetAction != null) - { - foreach (var (_, (value, view)) in list) - { - resetAction(value, view); - } - } - } - } - - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() - { - lock (SyncRoot) - { - return new NotifyCollectionChangedSynchronizedView(this, null); - } - } - - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) - { - lock (SyncRoot) - { - return new NotifyCollectionChangedSynchronizedView(this, collectionEventDispatcher); - } - } - - public IEnumerator<(T, TView)> GetEnumerator() - { - lock (SyncRoot) - { - foreach (var item in list) - { - if (filter.IsMatch(item.Value.Value, item.Value.View)) - { - yield return item.Value; - } - } - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public void Dispose() - { - this.source.CollectionChanged -= SourceCollectionChanged; - } - - private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs e) - { - lock (SyncRoot) - { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - { - // Add, Insert - if (e.IsSingleItem) - { - var value = e.NewItem; - var view = transform(value); - var id = identitySelector(value); - list.Add((value, id), (value, view)); - var index = list.IndexOfKey((value, id)); - filter.InvokeOnAdd(value, view, index); - } - else - { - foreach (var value in e.NewItems) - { - var view = transform(value); - var id = identitySelector(value); - list.Add((value, id), (value, view)); - var index = list.IndexOfKey((value, id)); - filter.InvokeOnAdd(value, view, index); - } - } - } - break; - case NotifyCollectionChangedAction.Remove: - { - if (e.IsSingleItem) - { - var value = e.OldItem; - var id = identitySelector(value); - var key = (value, id); - if (list.TryGetValue(key, out var v)) - { - var index = list.IndexOfKey(key); - list.RemoveAt(index); - filter.InvokeOnRemove(v.Value, v.View, index); - } - } - else - { - foreach (var value in e.OldItems) - { - var id = identitySelector(value); - var key = (value, id); - if (list.TryGetValue(key, out var v)) - { - var index = list.IndexOfKey((value, id)); - list.RemoveAt(index); - filter.InvokeOnRemove(v.Value, v.View, index); - } - } - } - } - break; - case NotifyCollectionChangedAction.Replace: - // ReplaceRange is not supported in all ObservableCollections collections - // Replace is remove old item and insert new item. - { - var oldValue = e.OldItem; - var oldKey = (oldValue, identitySelector(oldValue)); - var oldIndex = -1; - if (list.TryGetValue(oldKey, out var o)) - { - oldIndex = list.IndexOfKey(oldKey); - list.RemoveAt(oldIndex); - } - - var value = e.NewItem; - var view = transform(value); - var id = identitySelector(value); - list.Add((value, id), (value, view)); - var newIndex = list.IndexOfKey((value, id)); - - filter.InvokeOnReplace((value, view), o, newIndex, oldIndex: oldIndex); - } - break; - case NotifyCollectionChangedAction.Move: - { - // Move(index change) does not affect sorted list. - var oldValue = e.OldItem; - var oldKey = (oldValue, identitySelector(oldValue)); - if (list.TryGetValue(oldKey, out var v)) - { - var index = list.IndexOfKey(oldKey); - filter.InvokeOnMove(v, index, index); - } - } - break; - case NotifyCollectionChangedAction.Reset: - list.Clear(); - filter.InvokeOnReset(); - break; - default: - break; - } - - RoutingCollectionChanged?.Invoke(e); - CollectionStateChanged?.Invoke(e.Action); - } - } - - sealed class Comparer : IComparer<(T value, TKey id)> - { - readonly IComparer comparer; - - public Comparer(IComparer comparer) - { - this.comparer = comparer; - } - - public int Compare((T value, TKey id) x, (T value, TKey id) y) - { - var compare = comparer.Compare(x.value, y.value); - if (compare == 0) - { - compare = Comparer.Default.Compare(x.id, y.id); - } - - return compare; - } - } - } -} \ No newline at end of file diff --git a/src/ObservableCollections/Internal/SortedViewViewComparer.cs b/src/ObservableCollections/Internal/SortedViewViewComparer.cs deleted file mode 100644 index b247118..0000000 --- a/src/ObservableCollections/Internal/SortedViewViewComparer.cs +++ /dev/null @@ -1,278 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; - -namespace ObservableCollections.Internal -{ - internal class SortedViewViewComparer : ISynchronizedView - where TKey : notnull - { - readonly IObservableCollection source; - readonly Func transform; - readonly Func identitySelector; - readonly Dictionary viewMap; // view-map needs to use in remove. - readonly SortedList<(TView View, TKey Key), (T Value, TView View)> list; - - ISynchronizedViewFilter filter; - - public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; - public event Action? CollectionStateChanged; - - public object SyncRoot { get; } = new object(); - - public ISynchronizedViewFilter CurrentFilter - { - get { lock (SyncRoot) return filter; } - } - - public SortedViewViewComparer(IObservableCollection source, Func identitySelector, Func transform, IComparer comparer) - { - this.source = source; - this.identitySelector = identitySelector; - this.transform = transform; - this.filter = SynchronizedViewFilter.Null; - lock (source.SyncRoot) - { - var dict = new Dictionary<(TView, TKey), (T, TView)>(source.Count); - this.viewMap = new Dictionary(); - foreach (var value in source) - { - var view = transform(value); - var id = identitySelector(value); - dict.Add((view, id), (value, view)); - viewMap.Add(id, view); - } - this.list = new SortedList<(TView View, TKey Key), (T Value, TView View)>(dict, new Comparer(comparer)); - this.source.CollectionChanged += SourceCollectionChanged; - } - } - - public int Count - { - get - { - lock (SyncRoot) - { - return list.Count; - } - } - } - - public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) - { - lock (SyncRoot) - { - this.filter = filter; - var i = 0; - foreach (var (_, (value, view)) in list) - { - if (invokeAddEventForCurrentElements) - { - filter.InvokeOnAdd(value, view, i++); - } - else - { - filter.InvokeOnAttach(value, view); - } - } - } - } - - public void ResetFilter(Action? resetAction) - { - lock (SyncRoot) - { - this.filter = SynchronizedViewFilter.Null; - if (resetAction != null) - { - foreach (var (_, (value, view)) in list) - { - resetAction(value, view); - } - } - } - } - - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() - { - lock (SyncRoot) - { - return new NotifyCollectionChangedSynchronizedView(this, null); - } - } - - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) - { - lock (SyncRoot) - { - return new NotifyCollectionChangedSynchronizedView(this, collectionEventDispatcher); - } - } - - public IEnumerator<(T, TView)> GetEnumerator() - { - - lock (SyncRoot) - { - foreach (var item in list) - { - if (filter.IsMatch(item.Value.Value, item.Value.View)) - { - yield return item.Value; - } - } - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public void Dispose() - { - this.source.CollectionChanged -= SourceCollectionChanged; - } - - private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs e) - { - lock (SyncRoot) - { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - { - // Add, Insert - if (e.IsSingleItem) - { - var value = e.NewItem; - var view = transform(value); - var id = identitySelector(value); - list.Add((view, id), (value, view)); - viewMap.Add(id, view); - var index = list.IndexOfKey((view, id)); - filter.InvokeOnAdd(value, view, index); - } - else - { - foreach (var value in e.NewItems) - { - var view = transform(value); - var id = identitySelector(value); - list.Add((view, id), (value, view)); - viewMap.Add(id, view); - var index = list.IndexOfKey((view, id)); - filter.InvokeOnAdd(value, view, index); - } - } - break; - } - case NotifyCollectionChangedAction.Remove: - { - if (e.IsSingleItem) - { - var value = e.OldItem; - var id = identitySelector(value); - if (viewMap.Remove(id, out var view)) - { - var key = (view, id); - if (list.TryGetValue(key, out var v)) - { - var index = list.IndexOfKey(key); - list.RemoveAt(index); - filter.InvokeOnRemove(v, index); - } - } - } - else - { - foreach (var value in e.OldItems) - { - var id = identitySelector(value); - if (viewMap.Remove(id, out var view)) - { - var key = (view, id); - if (list.TryGetValue(key, out var v)) - { - var index = list.IndexOfKey((view, id)); - list.RemoveAt(index); - filter.InvokeOnRemove(v, index); - } - } - } - } - break; - } - case NotifyCollectionChangedAction.Replace: - // Replace is remove old item and insert new item. - { - var oldValue = e.OldItem; - var oldId = identitySelector(oldValue); - var oldIndex = -1; - if (viewMap.Remove(oldId, out var oldView)) - { - var oldKey = (oldView, oldId); - if (list.TryGetValue(oldKey, out var v)) - { - oldIndex = list.IndexOfKey(oldKey); - list.RemoveAt(oldIndex); - } - } - - var value = e.NewItem; - var view = transform(value); - var id = identitySelector(value); - list.Add((view, id), (value, view)); - viewMap.Add(id, view); - - var index = list.IndexOfKey((view, id)); - filter.InvokeOnReplace(value, view, oldValue, oldView!, index, oldIndex); - break; - } - case NotifyCollectionChangedAction.Move: - // Move(index change) does not affect soreted dict. - { - var value = e.OldItem; - var id = identitySelector(value); - if (viewMap.TryGetValue(id, out var view)) - { - var index = list.IndexOfKey((view, id)); - filter.InvokeOnMove(value, view, index, index); - } - break; - } - case NotifyCollectionChangedAction.Reset: - list.Clear(); - viewMap.Clear(); - filter.InvokeOnReset(); - break; - default: - break; - } - - RoutingCollectionChanged?.Invoke(e); - CollectionStateChanged?.Invoke(e.Action); - } - } - - sealed class Comparer : IComparer<(TView view, TKey id)> - { - readonly IComparer comparer; - - public Comparer(IComparer comparer) - { - this.comparer = comparer; - } - - public int Compare((TView view, TKey id) x, (TView view, TKey id) y) - { - var compare = comparer.Compare(x.view, y.view); - if (compare == 0) - { - compare = Comparer.Default.Compare(x.id, y.id); - } - - return compare; - } - } - } -} \ No newline at end of file diff --git a/src/ObservableCollections/ObservableDictionary.Views.cs b/src/ObservableCollections/ObservableDictionary.Views.cs index 4bf347f..4703d84 100644 --- a/src/ObservableCollections/ObservableDictionary.Views.cs +++ b/src/ObservableCollections/ObservableDictionary.Views.cs @@ -38,7 +38,7 @@ namespace ObservableCollections } public object SyncRoot { get; } - public event Action, TView>>? ViewChanged; + public event NotifyViewChangedEventHandler, TView>? ViewChanged; public event Action? CollectionStateChanged; public ISynchronizedViewFilter> Filter @@ -94,7 +94,7 @@ namespace ObservableCollections } } - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs, TView>(NotifyViewChangedAction.FilterReset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs, TView>(NotifyCollectionChangedAction.Reset)); } } @@ -104,7 +104,7 @@ namespace ObservableCollections { this.filter = SynchronizedViewFilter>.Null; this.filteredCount = dict.Count; - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs, TView>(NotifyViewChangedAction.FilterReset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs, TView>(NotifyCollectionChangedAction.Reset)); } } diff --git a/src/ObservableCollections/ObservableFixedSizeRingBuffer.cs b/src/ObservableCollections/ObservableFixedSizeRingBuffer.cs index 358397d..658a69c 100644 --- a/src/ObservableCollections/ObservableFixedSizeRingBuffer.cs +++ b/src/ObservableCollections/ObservableFixedSizeRingBuffer.cs @@ -320,9 +320,9 @@ namespace ObservableCollections return GetEnumerator(); } - public ISynchronizedView CreateView(Func transform, bool reverse = false) + public ISynchronizedView CreateView(Func transform) { - return new ObservableRingBuffer.View(this, transform, reverse); + return new ObservableRingBuffer.View(this, transform); } } } diff --git a/src/ObservableCollections/ObservableHashSet.Views.cs b/src/ObservableCollections/ObservableHashSet.Views.cs index ea846f2..95d604c 100644 --- a/src/ObservableCollections/ObservableHashSet.Views.cs +++ b/src/ObservableCollections/ObservableHashSet.Views.cs @@ -29,7 +29,7 @@ namespace ObservableCollections ISynchronizedViewFilter filter; - public event Action>? ViewChanged; + public event NotifyViewChangedEventHandler? ViewChanged; public event Action? CollectionStateChanged; public object SyncRoot { get; } @@ -89,6 +89,7 @@ namespace ObservableCollections filteredCount++; } } + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } @@ -98,7 +99,7 @@ namespace ObservableCollections { this.filter = SynchronizedViewFilter.Null; this.filteredCount = dict.Count; - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.FilterReset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index 6048a27..f1a8669 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -46,7 +46,7 @@ namespace ObservableCollections ISynchronizedViewFilter filter; - public event Action>? ViewChanged; + public event NotifyViewChangedEventHandler? ViewChanged; public event Action? CollectionStateChanged; public object SyncRoot { get; } @@ -107,7 +107,7 @@ namespace ObservableCollections } } - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.FilterReset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } @@ -117,7 +117,7 @@ namespace ObservableCollections { this.filter = SynchronizedViewFilter.Null; this.filteredCount = list.Count; - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.FilterReset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } @@ -245,11 +245,12 @@ namespace ObservableCollections } else { + list.RemoveRange(e.OldStartingIndex, e.OldItems.Length); // remove from list first + var len = e.OldStartingIndex + e.OldItems.Length; for (var i = e.OldStartingIndex; i < len; i++) { var v = list[i]; - list.RemoveAt(e.OldStartingIndex + i); // should we use RemoveRange? this.InvokeOnRemove(ref filteredCount, ViewChanged, v, e.OldStartingIndex + i); } } diff --git a/src/ObservableCollections/ObservableQueue.Views.cs b/src/ObservableCollections/ObservableQueue.Views.cs index b6d9601..d09e89c 100644 --- a/src/ObservableCollections/ObservableQueue.Views.cs +++ b/src/ObservableCollections/ObservableQueue.Views.cs @@ -19,13 +19,12 @@ namespace ObservableCollections { readonly ObservableQueue source; readonly Func selector; - readonly bool reverse; protected readonly Queue<(T, TView)> queue; int filteredCount; ISynchronizedViewFilter filter; - public event Action>? ViewChanged; + public event NotifyViewChangedEventHandler? ViewChanged; public event Action? CollectionStateChanged; public object SyncRoot { get; } @@ -90,7 +89,7 @@ namespace ObservableCollections filteredCount++; } } - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.FilterReset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } @@ -100,7 +99,7 @@ namespace ObservableCollections { this.filter = SynchronizedViewFilter.Null; this.filteredCount = queue.Count; - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.FilterReset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } diff --git a/src/ObservableCollections/ObservableRingBuffer.Views.cs b/src/ObservableCollections/ObservableRingBuffer.Views.cs index 95a1b78..f4df53b 100644 --- a/src/ObservableCollections/ObservableRingBuffer.Views.cs +++ b/src/ObservableCollections/ObservableRingBuffer.Views.cs @@ -30,7 +30,7 @@ namespace ObservableCollections ISynchronizedViewFilter filter; - public event Action>? ViewChanged; + public event NotifyViewChangedEventHandler? ViewChanged; public event Action? CollectionStateChanged; public object SyncRoot { get; } @@ -91,6 +91,7 @@ namespace ObservableCollections filteredCount++; } } + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } @@ -100,7 +101,7 @@ namespace ObservableCollections { this.filter = SynchronizedViewFilter.Null; this.filteredCount = ringBuffer.Count; - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.FilterReset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } diff --git a/src/ObservableCollections/ObservableStack.Views.cs b/src/ObservableCollections/ObservableStack.Views.cs index 2bfa68a..ee8ee2c 100644 --- a/src/ObservableCollections/ObservableStack.Views.cs +++ b/src/ObservableCollections/ObservableStack.Views.cs @@ -23,7 +23,7 @@ namespace ObservableCollections ISynchronizedViewFilter filter; - public event Action>? ViewChanged; + public event NotifyViewChangedEventHandler? ViewChanged; public event Action? CollectionStateChanged; public object SyncRoot { get; } @@ -88,7 +88,7 @@ namespace ObservableCollections filteredCount++; } } - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.FilterReset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } @@ -98,7 +98,7 @@ namespace ObservableCollections { this.filter = SynchronizedViewFilter.Null; this.filteredCount = stack.Count; - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyViewChangedAction.FilterReset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } diff --git a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs b/src/ObservableCollections/SynchronizedViewList.cs similarity index 90% rename from src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs rename to src/ObservableCollections/SynchronizedViewList.cs index f5e45c5..d722adb 100644 --- a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs +++ b/src/ObservableCollections/SynchronizedViewList.cs @@ -5,7 +5,7 @@ using System.Collections.Specialized; using System.ComponentModel; using System.Linq; -namespace ObservableCollections.Internal +namespace ObservableCollections { internal class SynchronizedViewList : ISynchronizedViewList { @@ -18,18 +18,18 @@ namespace ObservableCollections.Internal this.parent = parent; lock (parent.SyncRoot) { - this.listView = parent.ToList(); + listView = parent.ToList(); parent.ViewChanged += Parent_ViewChanged; } } - private void Parent_ViewChanged(SynchronizedViewChangedEventArgs e) + private void Parent_ViewChanged(in SynchronizedViewChangedEventArgs e) { lock (gate) { switch (e.Action) { - case NotifyViewChangedAction.Add: // Add or Insert + case NotifyCollectionChangedAction.Add: // Add or Insert if (e.NewViewIndex == -1) { listView.Add(e.NewView); @@ -39,7 +39,7 @@ namespace ObservableCollections.Internal listView.Insert(e.NewViewIndex, e.NewView); } break; - case NotifyViewChangedAction.Remove: // Remove + case NotifyCollectionChangedAction.Remove: // Remove if (e.OldViewIndex == -1) // can't gurantee correct remove if index is not provided { listView.Remove(e.OldView); @@ -49,7 +49,7 @@ namespace ObservableCollections.Internal listView.RemoveAt(e.OldViewIndex); } break; - case NotifyViewChangedAction.Replace: // Indexer + case NotifyCollectionChangedAction.Replace: // Indexer if (e.NewViewIndex == -1) { var index = listView.IndexOf(e.OldView); @@ -61,7 +61,7 @@ namespace ObservableCollections.Internal } break; - case NotifyViewChangedAction.Move: //Remove and Insert + case NotifyCollectionChangedAction.Move: //Remove and Insert if (e.NewViewIndex == -1) { // do nothing @@ -72,12 +72,9 @@ namespace ObservableCollections.Internal listView.Insert(e.NewViewIndex, e.NewView); } break; - case NotifyViewChangedAction.Reset: // Clear + case NotifyCollectionChangedAction.Reset: // Clear or drastic changes listView.Clear(); - break; - case NotifyViewChangedAction.FilterReset: - listView.Clear(); - foreach (var item in parent) + foreach (var item in parent) // refresh { listView.Add(item); } @@ -163,7 +160,7 @@ namespace ObservableCollections.Internal switch (args.Action) { - case NotifyViewChangedAction.Add: + case NotifyCollectionChangedAction.Add: eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewView, args.NewViewIndex) { Collection = this, @@ -172,7 +169,7 @@ namespace ObservableCollections.Internal IsInvokePropertyChanged = true }); break; - case NotifyViewChangedAction.Remove: + case NotifyCollectionChangedAction.Remove: eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldView, args.OldViewIndex) { Collection = this, @@ -181,7 +178,7 @@ namespace ObservableCollections.Internal IsInvokePropertyChanged = true }); break; - case NotifyViewChangedAction.Reset: + case NotifyCollectionChangedAction.Reset: eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Reset) { Collection = this, @@ -190,7 +187,7 @@ namespace ObservableCollections.Internal IsInvokePropertyChanged = true }); break; - case NotifyViewChangedAction.Replace: + case NotifyCollectionChangedAction.Replace: eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Replace, args.NewView, args.OldView, args.NewViewIndex) { Collection = this, @@ -199,7 +196,7 @@ namespace ObservableCollections.Internal IsInvokePropertyChanged = false }); break; - case NotifyViewChangedAction.Move: + case NotifyCollectionChangedAction.Move: eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Move, args.NewView, args.NewViewIndex, args.OldViewIndex) { Collection = this, @@ -245,7 +242,7 @@ namespace ObservableCollections.Internal static bool IsCompatibleObject(object? value) { - return (value is T) || (value == null && default(T) == null); + return value is T || value == null && default(T) == null; } public bool IsReadOnly => true; diff --git a/tests/ObservableCollections.Tests/ObservableDictionaryTest.cs b/tests/ObservableCollections.Tests/ObservableDictionaryTest.cs index fa6a633..0bca54c 100644 --- a/tests/ObservableCollections.Tests/ObservableDictionaryTest.cs +++ b/tests/ObservableCollections.Tests/ObservableDictionaryTest.cs @@ -24,7 +24,6 @@ namespace ObservableCollections.Tests void Equal(params int[] expected) { dict.Select(x => x.Value).OrderByDescending(x => x).Should().Equal(expected); - view.Select(x => x.Value.Value).OrderByDescending(x => x).Should().Equal(expected); } Equal(-10, -20, -30, -40, -50); @@ -42,146 +41,6 @@ namespace ObservableCollections.Tests Equal(new int[0]); } - [Fact] - public void ViewSorted() - { - var dict = new ObservableDictionary(); - var view1 = dict.CreateSortedView(x => x.Key, x => new ViewContainer(x.Value), x => x.Value, true); - var view2 = dict.CreateSortedView(x => x.Key, x => new ViewContainer(x.Value), x => x.Value, false); - - dict.Add(10, 10); // 0 - dict.Add(50, 50); // 1 - dict.Add(30, 30); // 2 - dict.Add(20, 20); // 3 - dict.Add(40, 40); // 4 - - void Equal(params int[] expected) - { - dict.Select(x => x.Value).OrderBy(x => x).Should().Equal(expected); - view1.Select(x => x.Value.Value).Should().Equal(expected); - view2.Select(x => x.Value.Value).Should().Equal(expected.OrderByDescending(x => x)); - } - - Equal(10, 20, 30, 40, 50); - - dict[99] = 100; - Equal(10, 20, 30, 40, 50, 100); - - dict[10] = -5; - Equal(-5, 20, 30, 40, 50, 100); - - dict.Remove(20); - Equal(-5, 30, 40, 50, 100); - - dict.Clear(); - Equal(new int[0]); - } - - [Fact] - public void Freezed() - { - var dict = new FreezedDictionary(new Dictionary - { - [10] = 10, - [50] = 50, - [30] = 30, - [20] = 20, - [40] = 40, - [60] = 60 - }); - - var view = dict.CreateSortableView(x => new ViewContainer(x.Value)); - - view.Sort(x => x.Key, true); - view.Select(x => x.Value.Value).Should().Equal(10, 20, 30, 40, 50, 60); - view.Select(x => x.View).Should().Equal(10, 20, 30, 40, 50, 60); - - view.Sort(x => x.Key, false); - view.Select(x => x.Value.Value).Should().Equal(60, 50, 40, 30, 20, 10); - view.Select(x => x.View).Should().Equal(60, 50, 40, 30, 20, 10); - } - - [Fact] - public void FilterTest() - { - var dict = new ObservableDictionary(); - var view1 = dict.CreateView(x => new ViewContainer(x.Value)); - var view2 = dict.CreateSortedView(x => x.Key, x => new ViewContainer(x.Value), x => x.Value, true); - var view3 = dict.CreateSortedView(x => new ViewContainer(x.Value), x => x.Value, viewComparer: Comparer>.Default); - var filter1 = new TestFilter2((x, v) => x.Value % 2 == 0); - var filter2 = new TestFilter2((x, v) => x.Value % 2 == 0); - var filter3 = new TestFilter2((x, v) => x.Value % 2 == 0); - - dict.Add(10, -12); // 0 - dict.Add(50, -53); // 1 - dict.Add(30, -34); // 2 - dict.Add(20, -25); // 3 - dict.Add(40, -40); // 4 - - view1.AttachFilter(filter1); - view2.AttachFilter(filter2); - view3.AttachFilter(filter3); - - filter1.CalledWhenTrue.Select(x => x.Item1.Value).Should().Equal(-12, -34, -40); - filter2.CalledWhenTrue.Select(x => x.Item1.Value).Should().Equal(-40, -34, -12); - filter3.CalledWhenTrue.Select(x => x.Item1.Value).Should().Equal(-40, -34, -12); - - dict.Add(99, -100); - filter1.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue.Value)).Should().Equal((NotifyCollectionChangedAction.Add, -100)); - filter2.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue.Value)).Should().Equal((NotifyCollectionChangedAction.Add, -100)); - filter3.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue.Value)).Should().Equal((NotifyCollectionChangedAction.Add, -100)); - foreach (var item in new[] { filter1, filter2, filter3 }) item.CalledOnCollectionChanged.Clear(); - - dict[10] = -1090; - filter1.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue.Value, x.OldValue.Value)).Should().Equal((NotifyCollectionChangedAction.Replace, -1090, -12)); - filter2.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue.Value, x.OldValue.Value)).Should().Equal((NotifyCollectionChangedAction.Replace, -1090, -12)); - filter3.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue.Value, x.OldValue.Value)).Should().Equal((NotifyCollectionChangedAction.Replace, -1090, -12)); - foreach (var item in new[] { filter1, filter2, filter3 }) item.CalledOnCollectionChanged.Clear(); - - dict.Remove(20); - filter1.CalledOnCollectionChanged.Select(x => (x.Action, x.OldValue.Value)).Should().Equal((NotifyCollectionChangedAction.Remove, -25)); - filter2.CalledOnCollectionChanged.Select(x => (x.Action, x.OldValue.Value)).Should().Equal((NotifyCollectionChangedAction.Remove, -25)); - filter3.CalledOnCollectionChanged.Select(x => (x.Action, x.OldValue.Value)).Should().Equal((NotifyCollectionChangedAction.Remove, -25)); - foreach (var item in new[] { filter1, filter2, filter3 }) item.CalledOnCollectionChanged.Clear(); - - dict.Clear(); - filter1.CalledOnCollectionChanged.Select(x => x.Action) - .Should().Equal(NotifyCollectionChangedAction.Reset); - filter2.CalledOnCollectionChanged.Select(x => x.Action) - .Should().Equal(NotifyCollectionChangedAction.Reset); - filter3.CalledOnCollectionChanged.Select(x => x.Action) - .Should().Equal(NotifyCollectionChangedAction.Reset); - } - [Fact] - public void FilterAndInvokeAddEvent() - { - var dict = new ObservableDictionary(); - var view1 = dict.CreateView(x => new ViewContainer(x.Value)); - var filter1 = new TestFilter2((x, v) => x.Value % 2 == 0); - - dict.Add(10, -12); // 0 - dict.Add(50, -53); // 1 - dict.Add(30, -34); // 2 - dict.Add(20, -25); // 3 - dict.Add(40, -40); // 4 - - view1.AttachFilter(filter1, true); - - filter1.CalledOnCollectionChanged.Count.Should().Be(5); - filter1.CalledOnCollectionChanged[0].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter1.CalledOnCollectionChanged[0].NewValue.Key.Should().Be(10); - filter1.CalledOnCollectionChanged[1].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter1.CalledOnCollectionChanged[1].NewValue.Key.Should().Be(50); - filter1.CalledOnCollectionChanged[2].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter1.CalledOnCollectionChanged[2].NewValue.Key.Should().Be(30); - filter1.CalledOnCollectionChanged[3].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter1.CalledOnCollectionChanged[3].NewValue.Key.Should().Be(20); - filter1.CalledOnCollectionChanged[4].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter1.CalledOnCollectionChanged[4].NewValue.Key.Should().Be(40); - - filter1.CalledWhenTrue.Count.Should().Be(3); - filter1.CalledWhenFalse.Count.Should().Be(2); - } } } diff --git a/tests/ObservableCollections.Tests/ObservableHashSetTest.cs b/tests/ObservableCollections.Tests/ObservableHashSetTest.cs index 3ef24d6..65b6ff4 100644 --- a/tests/ObservableCollections.Tests/ObservableHashSetTest.cs +++ b/tests/ObservableCollections.Tests/ObservableHashSetTest.cs @@ -27,7 +27,6 @@ namespace ObservableCollections.Tests { set.Should().BeEquivalentTo(expected); view.Select(x => x.Value).Should().BeEquivalentTo(expected); - view.Select(x => x.View.Value).Should().BeEquivalentTo(expected); } Equal(10, 50, 30, 20, 40); @@ -46,68 +45,7 @@ namespace ObservableCollections.Tests Equal(); } - [Fact] - public void Filter() - { - var set = new ObservableHashSet(); - var view = set.CreateView(x => new ViewContainer(x)); - var filter = new TestFilter((x, v) => x % 3 == 0); - - set.Add(10); - set.Add(50); - set.Add(30); - set.Add(20); - set.Add(40); - - view.AttachFilter(filter); - filter.CalledWhenTrue.Select(x => x.Item1).Should().Equal(30); - filter.CalledWhenFalse.Select(x => x.Item1).Should().Equal(10, 50, 20, 40); - - view.Select(x => x.Value).Should().Equal(30); - - filter.Clear(); - - set.Add(33); - set.AddRange(new[] { 98 }); - - filter.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue)).Should().Equal((NotifyCollectionChangedAction.Add, 33), (NotifyCollectionChangedAction.Add, 98)); - filter.Clear(); - - set.Remove(10); - set.RemoveRange(new[] { 50, 30 }); - filter.CalledOnCollectionChanged.Select(x => (x.Action, x.OldValue)).Should().Equal((NotifyCollectionChangedAction.Remove, 10), (NotifyCollectionChangedAction.Remove, 50), (NotifyCollectionChangedAction.Remove, 30)); - } - [Fact] - public void FilterAndInvokeAddEvent() - { - var set = new ObservableHashSet(); - var view = set.CreateView(x => new ViewContainer(x)); - var filter = new TestFilter((x, v) => x % 3 == 0); - - set.Add(10); - set.Add(50); - set.Add(30); - set.Add(20); - set.Add(40); - - view.AttachFilter(filter, true); - filter.CalledOnCollectionChanged.Count.Should().Be(5); - filter.CalledOnCollectionChanged[0].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter.CalledOnCollectionChanged[0].NewValue.Should().Be(10); - filter.CalledOnCollectionChanged[1].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter.CalledOnCollectionChanged[1].NewValue.Should().Be(50); - filter.CalledOnCollectionChanged[2].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter.CalledOnCollectionChanged[2].NewValue.Should().Be(30); - filter.CalledOnCollectionChanged[3].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter.CalledOnCollectionChanged[3].NewValue.Should().Be(20); - filter.CalledOnCollectionChanged[4].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter.CalledOnCollectionChanged[4].NewValue.Should().Be(40); - - filter.CalledWhenTrue.Count.Should().Be(1); - filter.CalledWhenFalse.Count.Should().Be(4); - } - [Fact] public void IndexOutOfRange() { diff --git a/tests/ObservableCollections.Tests/ObservableListTest.cs b/tests/ObservableCollections.Tests/ObservableListTest.cs index c2888dd..77e5b1a 100644 --- a/tests/ObservableCollections.Tests/ObservableListTest.cs +++ b/tests/ObservableCollections.Tests/ObservableListTest.cs @@ -27,14 +27,14 @@ namespace ObservableCollections.Tests reference.Should().Equal(expected); list.Should().Equal(expected); view.Select(x => x.Value).Should().Equal(expected); - view.Select(x => x.View).Should().Equal(expected.Select(x => new ViewContainer(x))); + view.Filtered.Select(x => x.View).Should().Equal(expected.Select(x => new ViewContainer(x))); } void Equal2(params int[] expected) { list.Should().Equal(expected); view.Select(x => x.Value).Should().Equal(expected); - view.Select(x => x.View).Should().Equal(expected.Select(x => new ViewContainer(x))); + view.Filtered.Select(x => x.View).Should().Equal(expected.Select(x => new ViewContainer(x))); } Equal(10, 50, 30, 20, 40); @@ -69,178 +69,107 @@ namespace ObservableCollections.Tests Equal2(100, 400, 200, 300); } - [Fact] - public void ViewSorted() - { - var list = new ObservableList(); - var view1 = list.CreateSortedView(x => x, x => new ViewContainer(x), comparer: Comparer.Default); - var view2 = list.CreateSortedView(x => x, x => new ViewContainer(x), viewComparer: Comparer>.Default); - var view3 = list.CreateSortedView(x => x, x => new ViewContainer(x), x => x, ascending: true); - var view4 = list.CreateSortedView(x => x, x => new ViewContainer(x), x => x, ascending: false); + //[Fact] + //public void FilterTest() + //{ + // var list = new ObservableList(); + // var view1 = list.CreateView(x => new ViewContainer(x)); + // list.AddRange(new[] { 10, 21, 30, 44, 45, 66, 90 }); - list.Add(10); // 0 - list.Add(50); // 1 - list.Add(30); // 2 - list.Add(20); // 3 - list.Add(40); // 4 + // var filter1 = new TestFilter((x, v) => x % 2 == 0); + // var filter2 = new TestFilter((x, v) => x % 2 == 0); + // var filter3 = new TestFilter((x, v) => x % 2 == 0); + // view1.AttachFilter(filter1); + // view2.AttachFilter(filter2); + // view3.AttachFilter(filter3); - void Equal(params int[] expected) - { - list.Should().Equal(expected); + // filter1.CalledWhenTrue.Select(x => x.Item1).Should().Equal(10, 30, 44, 66, 90); + // filter2.CalledWhenTrue.Select(x => x.Item1).Should().Equal(10, 30, 44, 66, 90); + // filter3.CalledWhenTrue.Select(x => x.Item1).Should().Equal(10, 30, 44, 66, 90); - var sorted = expected.OrderBy(x => x).ToArray(); - view1.Select(x => x.Value).Should().Equal(sorted); - view2.Select(x => x.View).Should().Equal(sorted.Select(x => new ViewContainer(x))); - view3.Select(x => x.Value).Should().Equal(sorted); - view4.Select(x => x.Value).Should().Equal(expected.OrderByDescending(x => x).ToArray()); - } + // filter1.CalledWhenFalse.Select(x => x.Item1).Should().Equal(21, 45); + // filter2.CalledWhenFalse.Select(x => x.Item1).Should().Equal(21, 45); + // filter3.CalledWhenFalse.Select(x => x.Item1).Should().Equal(21, 45); - Equal(10, 50, 30, 20, 40); + // view1.Select(x => x.Value).Should().Equal(10, 30, 44, 66, 90); + // view2.Select(x => x.Value).Should().Equal(10, 30, 44, 66, 90); + // view3.Select(x => x.Value).Should().Equal(10, 30, 44, 66, 90); - list.Move(3, 1); - Equal(10, 20, 50, 30, 40); + // filter1.Clear(); + // filter2.Clear(); + // filter3.Clear(); - list.Insert(2, 99); - Equal(10, 20, 99, 50, 30, 40); + // list.Add(100); + // list.AddRange(new[] { 101 }); + // filter1.CalledWhenTrue.Select(x => x.Item1).Should().Equal(100); + // filter2.CalledWhenTrue.Select(x => x.Item1).Should().Equal(100); + // filter3.CalledWhenTrue.Select(x => x.Item1).Should().Equal(100); + // filter1.CalledWhenFalse.Select(x => x.Item1).Should().Equal(101); + // filter2.CalledWhenFalse.Select(x => x.Item1).Should().Equal(101); + // filter3.CalledWhenFalse.Select(x => x.Item1).Should().Equal(101); - list.RemoveAt(2); - Equal(10, 20, 50, 30, 40); + // filter1.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex)).Should().Equal((NotifyCollectionChangedAction.Add, 100, 7), (NotifyCollectionChangedAction.Add, 101, 8)); + // filter2.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex)).Should().Equal((NotifyCollectionChangedAction.Add, 100, 7), (NotifyCollectionChangedAction.Add, 101, 8)); + // filter3.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex)).Should().Equal((NotifyCollectionChangedAction.Add, 100, 7), (NotifyCollectionChangedAction.Add, 101, 8)); - list[3] = 88; - Equal(10, 20, 50, 88, 40); + // foreach (var item in new[] { filter1, filter2, filter3 }) item.CalledOnCollectionChanged.Clear(); - list.Clear(); - Equal(new int[0]); + // list.Insert(0, 1000); + // list.InsertRange(0, new[] { 999 }); - list.AddRange(new[] { 100, 200, 300 }); - Equal(100, 200, 300); + // filter1.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex)).Should().Equal((NotifyCollectionChangedAction.Add, 1000, 0), (NotifyCollectionChangedAction.Add, 999, 0)); + // filter2.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex)).Should().Equal((NotifyCollectionChangedAction.Add, 1000, 9), (NotifyCollectionChangedAction.Add, 999, 9)); // sorted index + // filter3.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex)).Should().Equal((NotifyCollectionChangedAction.Add, 1000, 9), (NotifyCollectionChangedAction.Add, 999, 9)); // sorted index + // foreach (var item in new[] { filter1, filter2, filter3 }) item.CalledOnCollectionChanged.Clear(); - list.InsertRange(1, new[] { 400, 500, 600 }); - Equal(100, 400, 500, 600, 200, 300); + // list.RemoveAt(0); + // list.RemoveRange(0, 1); - list.RemoveRange(2, 2); - Equal(100, 400, 200, 300); - } + // filter1.CalledOnCollectionChanged.Select(x => (x.Action, x.OldValue, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Remove, 999, 0), (NotifyCollectionChangedAction.Remove, 1000, 0)); + // filter2.CalledOnCollectionChanged.Select(x => (x.Action, x.OldValue, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Remove, 999, 9), (NotifyCollectionChangedAction.Remove, 1000, 9)); + // filter3.CalledOnCollectionChanged.Select(x => (x.Action, x.OldValue, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Remove, 999, 9), (NotifyCollectionChangedAction.Remove, 1000, 9)); + // foreach (var item in new[] { filter1, filter2, filter3 }) item.CalledOnCollectionChanged.Clear(); - [Fact] - public void Freezed() - { - var list = new FreezedList(new[] { 10, 20, 50, 30, 40, 60 }); + // list[0] = 9999; - var view = list.CreateSortableView(x => new ViewContainer(x)); + // filter1.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Replace, 9999, 0, 0)); + // filter2.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Replace, 9999, 8, 0)); + // filter3.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Replace, 9999, 8, 0)); + // foreach (var item in new[] { filter1, filter2, filter3 }) item.CalledOnCollectionChanged.Clear(); - view.Sort(x => x, true); - view.Select(x => x.Value).Should().Equal(10, 20, 30, 40, 50, 60); - view.Select(x => x.View).Should().Equal(10, 20, 30, 40, 50, 60); + // list.Move(3, 0); + // filter1.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Move, 44, 0, 3)); + // filter2.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Move, 44, 2, 2)); + // filter3.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Move, 44, 2, 2)); + // foreach (var item in new[] { filter1, filter2, filter3 }) item.CalledOnCollectionChanged.Clear(); - view.Sort(x => x, false); - view.Select(x => x.Value).Should().Equal(60, 50, 40, 30, 20, 10); - view.Select(x => x.View).Should().Equal(60, 50, 40, 30, 20, 10); - } + // list.Clear(); + // filter1.CalledOnCollectionChanged.Select(x => x.Action).Should().Equal(NotifyCollectionChangedAction.Reset); + // filter2.CalledOnCollectionChanged.Select(x => x.Action).Should().Equal(NotifyCollectionChangedAction.Reset); + // filter3.CalledOnCollectionChanged.Select(x => x.Action).Should().Equal(NotifyCollectionChangedAction.Reset); + //} - [Fact] - public void FilterTest() - { - var list = new ObservableList(); - var view1 = list.CreateView(x => new ViewContainer(x)); - var view2 = list.CreateSortedView(x => x, x => new ViewContainer(x), comparer: Comparer.Default); - var view3 = list.CreateSortedView(x => x, x => new ViewContainer(x), viewComparer: Comparer>.Default); - list.AddRange(new[] { 10, 21, 30, 44, 45, 66, 90 }); + //[Fact] + //public void FilterAndInvokeAddEvent() + //{ + // var list = new ObservableList(); + // var view1 = list.CreateView(x => new ViewContainer(x)); + // list.AddRange(new[] { 10, 21, 30, 44 }); - var filter1 = new TestFilter((x, v) => x % 2 == 0); - var filter2 = new TestFilter((x, v) => x % 2 == 0); - var filter3 = new TestFilter((x, v) => x % 2 == 0); - view1.AttachFilter(filter1); - view2.AttachFilter(filter2); - view3.AttachFilter(filter3); - - filter1.CalledWhenTrue.Select(x => x.Item1).Should().Equal(10, 30, 44, 66, 90); - filter2.CalledWhenTrue.Select(x => x.Item1).Should().Equal(10, 30, 44, 66, 90); - filter3.CalledWhenTrue.Select(x => x.Item1).Should().Equal(10, 30, 44, 66, 90); - - filter1.CalledWhenFalse.Select(x => x.Item1).Should().Equal(21, 45); - filter2.CalledWhenFalse.Select(x => x.Item1).Should().Equal(21, 45); - filter3.CalledWhenFalse.Select(x => x.Item1).Should().Equal(21, 45); - - view1.Select(x => x.Value).Should().Equal(10, 30, 44, 66, 90); - view2.Select(x => x.Value).Should().Equal(10, 30, 44, 66, 90); - view3.Select(x => x.Value).Should().Equal(10, 30, 44, 66, 90); - - filter1.Clear(); - filter2.Clear(); - filter3.Clear(); - - list.Add(100); - list.AddRange(new[] { 101 }); - filter1.CalledWhenTrue.Select(x => x.Item1).Should().Equal(100); - filter2.CalledWhenTrue.Select(x => x.Item1).Should().Equal(100); - filter3.CalledWhenTrue.Select(x => x.Item1).Should().Equal(100); - filter1.CalledWhenFalse.Select(x => x.Item1).Should().Equal(101); - filter2.CalledWhenFalse.Select(x => x.Item1).Should().Equal(101); - filter3.CalledWhenFalse.Select(x => x.Item1).Should().Equal(101); - - filter1.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex)).Should().Equal((NotifyCollectionChangedAction.Add, 100, 7), (NotifyCollectionChangedAction.Add, 101, 8)); - filter2.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex)).Should().Equal((NotifyCollectionChangedAction.Add, 100, 7), (NotifyCollectionChangedAction.Add, 101, 8)); - filter3.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex)).Should().Equal((NotifyCollectionChangedAction.Add, 100, 7), (NotifyCollectionChangedAction.Add, 101, 8)); - - foreach (var item in new[] { filter1, filter2, filter3 }) item.CalledOnCollectionChanged.Clear(); - - list.Insert(0, 1000); - list.InsertRange(0, new[] { 999 }); - - filter1.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex)).Should().Equal((NotifyCollectionChangedAction.Add, 1000, 0), (NotifyCollectionChangedAction.Add, 999, 0)); - filter2.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex)).Should().Equal((NotifyCollectionChangedAction.Add, 1000, 9), (NotifyCollectionChangedAction.Add, 999, 9)); // sorted index - filter3.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex)).Should().Equal((NotifyCollectionChangedAction.Add, 1000, 9), (NotifyCollectionChangedAction.Add, 999, 9)); // sorted index - foreach (var item in new[] { filter1, filter2, filter3 }) item.CalledOnCollectionChanged.Clear(); - - list.RemoveAt(0); - list.RemoveRange(0, 1); - - filter1.CalledOnCollectionChanged.Select(x => (x.Action, x.OldValue, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Remove, 999, 0), (NotifyCollectionChangedAction.Remove, 1000, 0)); - filter2.CalledOnCollectionChanged.Select(x => (x.Action, x.OldValue, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Remove, 999, 9), (NotifyCollectionChangedAction.Remove, 1000, 9)); - filter3.CalledOnCollectionChanged.Select(x => (x.Action, x.OldValue, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Remove, 999, 9), (NotifyCollectionChangedAction.Remove, 1000, 9)); - foreach (var item in new[] { filter1, filter2, filter3 }) item.CalledOnCollectionChanged.Clear(); - - list[0] = 9999; - - filter1.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Replace, 9999, 0, 0)); - filter2.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Replace, 9999, 8, 0)); - filter3.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Replace, 9999, 8, 0)); - foreach (var item in new[] { filter1, filter2, filter3 }) item.CalledOnCollectionChanged.Clear(); - - list.Move(3, 0); - filter1.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Move, 44, 0, 3)); - filter2.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Move, 44, 2, 2)); - filter3.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Move, 44, 2, 2)); - foreach (var item in new[] { filter1, filter2, filter3 }) item.CalledOnCollectionChanged.Clear(); - - list.Clear(); - filter1.CalledOnCollectionChanged.Select(x => x.Action).Should().Equal(NotifyCollectionChangedAction.Reset); - filter2.CalledOnCollectionChanged.Select(x => x.Action).Should().Equal(NotifyCollectionChangedAction.Reset); - filter3.CalledOnCollectionChanged.Select(x => x.Action).Should().Equal(NotifyCollectionChangedAction.Reset); - } - - [Fact] - public void FilterAndInvokeAddEvent() - { - var list = new ObservableList(); - var view1 = list.CreateView(x => new ViewContainer(x)); - list.AddRange(new[] { 10, 21, 30, 44 }); - - var filter1 = new TestFilter((x, v) => x % 2 == 0); - view1.AttachFilter(filter1, true); + // var filter1 = new TestFilter((x, v) => x % 2 == 0); + // view1.AttachFilter(filter1); - filter1.CalledOnCollectionChanged[0].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter1.CalledOnCollectionChanged[0].NewValue.Should().Be(10); - filter1.CalledOnCollectionChanged[1].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter1.CalledOnCollectionChanged[1].NewValue.Should().Be(21); - filter1.CalledOnCollectionChanged[2].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter1.CalledOnCollectionChanged[2].NewValue.Should().Be(30); - filter1.CalledOnCollectionChanged[3].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter1.CalledOnCollectionChanged[3].NewValue.Should().Be(44); + // filter1.CalledOnCollectionChanged[0].Action.Should().Be(NotifyCollectionChangedAction.Add); + // filter1.CalledOnCollectionChanged[0].NewValue.Should().Be(10); + // filter1.CalledOnCollectionChanged[1].Action.Should().Be(NotifyCollectionChangedAction.Add); + // filter1.CalledOnCollectionChanged[1].NewValue.Should().Be(21); + // filter1.CalledOnCollectionChanged[2].Action.Should().Be(NotifyCollectionChangedAction.Add); + // filter1.CalledOnCollectionChanged[2].NewValue.Should().Be(30); + // filter1.CalledOnCollectionChanged[3].Action.Should().Be(NotifyCollectionChangedAction.Add); + // filter1.CalledOnCollectionChanged[3].NewValue.Should().Be(44); - filter1.CalledWhenTrue.Count.Should().Be(3); - filter1.CalledWhenFalse.Count.Should().Be(1); - } + // filter1.CalledWhenTrue.Count.Should().Be(3); + // filter1.CalledWhenFalse.Count.Should().Be(1); + //} } } \ No newline at end of file diff --git a/tests/ObservableCollections.Tests/ObservableQueueTest.cs b/tests/ObservableCollections.Tests/ObservableQueueTest.cs index 69a6601..6ed0ff3 100644 --- a/tests/ObservableCollections.Tests/ObservableQueueTest.cs +++ b/tests/ObservableCollections.Tests/ObservableQueueTest.cs @@ -27,7 +27,6 @@ namespace ObservableCollections.Tests { queue.Should().Equal(expected); view.Select(x => x.Value).Should().Equal(expected); - view.Select(x => x.View).Should().Equal(expected.Select(x => new ViewContainer(x))); } Equal(10, 50, 30, 20, 40); @@ -50,72 +49,72 @@ namespace ObservableCollections.Tests Equal(); } - [Fact] - public void Filter() - { - var queue = new ObservableQueue(); - var view = queue.CreateView(x => new ViewContainer(x)); - var filter = new TestFilter((x, v) => x % 3 == 0); + //[Fact] + //public void Filter() + //{ + // var queue = new ObservableQueue(); + // var view = queue.CreateView(x => new ViewContainer(x)); + // var filter = new TestFilter((x, v) => x % 3 == 0); - queue.Enqueue(10); - queue.Enqueue(50); - queue.Enqueue(30); - queue.Enqueue(20); - queue.Enqueue(40); + // queue.Enqueue(10); + // queue.Enqueue(50); + // queue.Enqueue(30); + // queue.Enqueue(20); + // queue.Enqueue(40); - view.AttachFilter(filter); - filter.CalledWhenTrue.Select(x => x.Item1).Should().Equal(30); - filter.CalledWhenFalse.Select(x => x.Item1).Should().Equal(10, 50, 20, 40); + // view.AttachFilter(filter); + // filter.CalledWhenTrue.Select(x => x.Item1).Should().Equal(30); + // filter.CalledWhenFalse.Select(x => x.Item1).Should().Equal(10, 50, 20, 40); - view.Select(x => x.Value).Should().Equal(30); + // view.Select(x => x.Value).Should().Equal(30); - filter.Clear(); + // filter.Clear(); - queue.Enqueue(33); - queue.EnqueueRange(new[] { 98 }); + // queue.Enqueue(33); + // queue.EnqueueRange(new[] { 98 }); - filter.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex)).Should().Equal((NotifyCollectionChangedAction.Add, 33, 5), (NotifyCollectionChangedAction.Add, 98, 6)); - filter.Clear(); + // filter.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex)).Should().Equal((NotifyCollectionChangedAction.Add, 33, 5), (NotifyCollectionChangedAction.Add, 98, 6)); + // filter.Clear(); - queue.Dequeue(); - queue.DequeueRange(2); - filter.CalledOnCollectionChanged.Select(x => (x.Action, x.OldValue, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Remove, 10, 0), (NotifyCollectionChangedAction.Remove, 50, 0), (NotifyCollectionChangedAction.Remove, 30, 0)); - } + // queue.Dequeue(); + // queue.DequeueRange(2); + // filter.CalledOnCollectionChanged.Select(x => (x.Action, x.OldValue, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Remove, 10, 0), (NotifyCollectionChangedAction.Remove, 50, 0), (NotifyCollectionChangedAction.Remove, 30, 0)); + //} - [Fact] - public void FilterAndInvokeAddEvent() - { - var queue = new ObservableQueue(); - var view = queue.CreateView(x => new ViewContainer(x)); - var filter = new TestFilter((x, v) => x % 3 == 0); + //[Fact] + //public void FilterAndInvokeAddEvent() + //{ + // var queue = new ObservableQueue(); + // var view = queue.CreateView(x => new ViewContainer(x)); + // var filter = new TestFilter((x, v) => x % 3 == 0); - queue.Enqueue(10); - queue.Enqueue(50); - queue.Enqueue(30); - queue.Enqueue(20); - queue.Enqueue(40); + // queue.Enqueue(10); + // queue.Enqueue(50); + // queue.Enqueue(30); + // queue.Enqueue(20); + // queue.Enqueue(40); - view.AttachFilter(filter, true); + // view.AttachFilter(filter, true); - filter.CalledOnCollectionChanged.Count.Should().Be(5); - filter.CalledOnCollectionChanged[0].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter.CalledOnCollectionChanged[0].NewValue.Should().Be(10); - filter.CalledOnCollectionChanged[0].NewViewIndex.Should().Be(0); - filter.CalledOnCollectionChanged[1].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter.CalledOnCollectionChanged[1].NewValue.Should().Be(50); - filter.CalledOnCollectionChanged[1].NewViewIndex.Should().Be(1); - filter.CalledOnCollectionChanged[2].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter.CalledOnCollectionChanged[2].NewValue.Should().Be(30); - filter.CalledOnCollectionChanged[2].NewViewIndex.Should().Be(2); - filter.CalledOnCollectionChanged[3].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter.CalledOnCollectionChanged[3].NewValue.Should().Be(20); - filter.CalledOnCollectionChanged[3].NewViewIndex.Should().Be(3); - filter.CalledOnCollectionChanged[4].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter.CalledOnCollectionChanged[4].NewValue.Should().Be(40); - filter.CalledOnCollectionChanged[4].NewViewIndex.Should().Be(4); + // filter.CalledOnCollectionChanged.Count.Should().Be(5); + // filter.CalledOnCollectionChanged[0].Action.Should().Be(NotifyCollectionChangedAction.Add); + // filter.CalledOnCollectionChanged[0].NewValue.Should().Be(10); + // filter.CalledOnCollectionChanged[0].NewViewIndex.Should().Be(0); + // filter.CalledOnCollectionChanged[1].Action.Should().Be(NotifyCollectionChangedAction.Add); + // filter.CalledOnCollectionChanged[1].NewValue.Should().Be(50); + // filter.CalledOnCollectionChanged[1].NewViewIndex.Should().Be(1); + // filter.CalledOnCollectionChanged[2].Action.Should().Be(NotifyCollectionChangedAction.Add); + // filter.CalledOnCollectionChanged[2].NewValue.Should().Be(30); + // filter.CalledOnCollectionChanged[2].NewViewIndex.Should().Be(2); + // filter.CalledOnCollectionChanged[3].Action.Should().Be(NotifyCollectionChangedAction.Add); + // filter.CalledOnCollectionChanged[3].NewValue.Should().Be(20); + // filter.CalledOnCollectionChanged[3].NewViewIndex.Should().Be(3); + // filter.CalledOnCollectionChanged[4].Action.Should().Be(NotifyCollectionChangedAction.Add); + // filter.CalledOnCollectionChanged[4].NewValue.Should().Be(40); + // filter.CalledOnCollectionChanged[4].NewViewIndex.Should().Be(4); - filter.CalledWhenTrue.Count.Should().Be(1); - filter.CalledWhenFalse.Count.Should().Be(4); - } + // filter.CalledWhenTrue.Count.Should().Be(1); + // filter.CalledWhenFalse.Count.Should().Be(4); + //} } } diff --git a/tests/ObservableCollections.Tests/ObservableRingBufferTest.cs b/tests/ObservableCollections.Tests/ObservableRingBufferTest.cs index d2fa603..f3dad10 100644 --- a/tests/ObservableCollections.Tests/ObservableRingBufferTest.cs +++ b/tests/ObservableCollections.Tests/ObservableRingBufferTest.cs @@ -24,7 +24,6 @@ namespace ObservableCollections.Tests { buf.Should().Equal(expected); view.Select(x => x.Value).Should().Equal(expected); - view.Select(x => x.View).Should().Equal(expected.Select(x => new ViewContainer(x))); } Equal(10, 50, 30, 20, 40); @@ -65,7 +64,6 @@ namespace ObservableCollections.Tests { buf.Should().Equal(expected); view.Select(x => x.Value).Should().Equal(expected); - view.Select(x => x.View).Should().Equal(expected.Select(x => new ViewContainer(x))); } buf.AddLast(10); diff --git a/tests/ObservableCollections.Tests/ObservableStackTest.cs b/tests/ObservableCollections.Tests/ObservableStackTest.cs index 85e93dd..710f2ff 100644 --- a/tests/ObservableCollections.Tests/ObservableStackTest.cs +++ b/tests/ObservableCollections.Tests/ObservableStackTest.cs @@ -27,7 +27,6 @@ namespace ObservableCollections.Tests { stack.Should().Equal(expected); view.Select(x => x.Value).Should().Equal(expected); - view.Select(x => x.View).Should().Equal(expected.Select(x => new ViewContainer(x))); } Equal(40, 20, 30, 50, 10); @@ -50,72 +49,6 @@ namespace ObservableCollections.Tests Equal(); } - [Fact] - public void Filter() - { - var stack = new ObservableStack(); - var view = stack.CreateView(x => new ViewContainer(x)); - var filter = new TestFilter((x, v) => x % 3 == 0); - - stack.Push(10); - stack.Push(50); - stack.Push(30); - stack.Push(20); - stack.Push(40); - - view.AttachFilter(filter); - filter.CalledWhenTrue.Select(x => x.Item1).Should().Equal(30); - filter.CalledWhenFalse.Select(x => x.Item1).Should().Equal(40, 20, 50, 10); - - view.Select(x => x.Value).Should().Equal(30); - - filter.Clear(); - - stack.Push(33); - stack.PushRange(new[] { 98 }); - - filter.CalledOnCollectionChanged.Select(x => (x.Action, x.NewValue, x.NewViewIndex)).Should().Equal((NotifyCollectionChangedAction.Add, 33, 0), (NotifyCollectionChangedAction.Add, 98, 0)); - filter.Clear(); - - stack.Pop(); - stack.PopRange(2); - filter.CalledOnCollectionChanged.Select(x => (x.Action, x.OldValue, x.OldViewIndex)).Should().Equal((NotifyCollectionChangedAction.Remove, 98, 0), (NotifyCollectionChangedAction.Remove, 33, 0), (NotifyCollectionChangedAction.Remove, 40, 0)); - } - - [Fact] - public void FilterAndInvokeAddEvent() - { - var stack = new ObservableStack(); - var view = stack.CreateView(x => new ViewContainer(x)); - var filter = new TestFilter((x, v) => x % 3 == 0); - - stack.Push(10); - stack.Push(50); - stack.Push(30); - stack.Push(20); - stack.Push(40); - - view.AttachFilter(filter, true); - filter.CalledOnCollectionChanged.Count.Should().Be(5); - filter.CalledOnCollectionChanged[4].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter.CalledOnCollectionChanged[4].NewValue.Should().Be(10); - filter.CalledOnCollectionChanged[4].NewViewIndex.Should().Be(0); - filter.CalledOnCollectionChanged[3].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter.CalledOnCollectionChanged[3].NewValue.Should().Be(50); - filter.CalledOnCollectionChanged[3].NewViewIndex.Should().Be(0); - filter.CalledOnCollectionChanged[2].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter.CalledOnCollectionChanged[2].NewValue.Should().Be(30); - filter.CalledOnCollectionChanged[2].NewViewIndex.Should().Be(0); - filter.CalledOnCollectionChanged[1].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter.CalledOnCollectionChanged[1].NewValue.Should().Be(20); - filter.CalledOnCollectionChanged[1].NewViewIndex.Should().Be(0); - filter.CalledOnCollectionChanged[0].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter.CalledOnCollectionChanged[0].NewValue.Should().Be(40); - filter.CalledOnCollectionChanged[0].NewViewIndex.Should().Be(0); - - filter.CalledWhenTrue.Count.Should().Be(1); - filter.CalledWhenFalse.Count.Should().Be(4); - } } } diff --git a/tests/ObservableCollections.Tests/SortedViewTest.cs b/tests/ObservableCollections.Tests/SortedViewTest.cs deleted file mode 100644 index fe15c73..0000000 --- a/tests/ObservableCollections.Tests/SortedViewTest.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Specialized; - -namespace ObservableCollections.Tests; - -public class SortedViewTest -{ - [Fact] - public void Sort() - { - var list = new ObservableList(); - var sortedView = list.CreateSortedView( - x => x, - x => new ViewContainer(x), - Comparer.Default); - - list.Add(10); - list.Add(50); - list.Add(30); - list.Add(20); - list.Add(40); - - using var e = sortedView.GetEnumerator(); - e.MoveNext().Should().BeTrue(); - e.Current.Value.Should().Be(10); - e.MoveNext().Should().BeTrue(); - e.Current.Value.Should().Be(20); - e.MoveNext().Should().BeTrue(); - e.Current.Value.Should().Be(30); - e.MoveNext().Should().BeTrue(); - e.Current.Value.Should().Be(40); - e.MoveNext().Should().BeTrue(); - e.Current.Value.Should().Be(50); - e.MoveNext().Should().BeFalse(); - } - - [Fact] - public void ObserveIndex() - { - var list = new ObservableList(); - var sortedView = list.CreateSortedView( - x => x, - x => new ViewContainer(x), - Comparer.Default); - - var filter = new TestFilter((value, view) => value % 2 == 0); - list.Add(50); - list.Add(10); - - sortedView.AttachFilter(filter); - - list.Add(20); - filter.CalledOnCollectionChanged[0].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter.CalledOnCollectionChanged[0].NewValue.Should().Be(20); - filter.CalledOnCollectionChanged[0].NewView.Should().Be(new ViewContainer(20)); - filter.CalledOnCollectionChanged[0].NewViewIndex.Should().Be(1); - - list.Remove(20); - filter.CalledOnCollectionChanged[1].Action.Should().Be(NotifyCollectionChangedAction.Remove); - filter.CalledOnCollectionChanged[1].OldValue.Should().Be(20); - filter.CalledOnCollectionChanged[1].OldView.Should().Be(new ViewContainer(20)); - filter.CalledOnCollectionChanged[1].OldViewIndex.Should().Be(1); - - list[1] = 999; // from 10(at 0 in original) to 999 - filter.CalledOnCollectionChanged[2].Action.Should().Be(NotifyCollectionChangedAction.Replace); - filter.CalledOnCollectionChanged[2].NewValue.Should().Be(999); - filter.CalledOnCollectionChanged[2].OldValue.Should().Be(10); - filter.CalledOnCollectionChanged[2].NewViewIndex.Should().Be(1); - } -} \ No newline at end of file diff --git a/tests/ObservableCollections.Tests/SortedViewViewComparerTest.cs b/tests/ObservableCollections.Tests/SortedViewViewComparerTest.cs deleted file mode 100644 index 071f382..0000000 --- a/tests/ObservableCollections.Tests/SortedViewViewComparerTest.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Specialized; - -namespace ObservableCollections.Tests; - -public class SortedViewViewComparerTest -{ - [Fact] - public void Sort() - { - var list = new ObservableList(); - var sortedView = list.CreateSortedView( - x => x, - x => new ViewContainer(x), - Comparer>.Default); - - list.Add(10); - list.Add(50); - list.Add(30); - list.Add(20); - list.Add(40); - - using var e = sortedView.GetEnumerator(); - e.MoveNext().Should().BeTrue(); - e.Current.Value.Should().Be(10); - e.MoveNext().Should().BeTrue(); - e.Current.Value.Should().Be(20); - e.MoveNext().Should().BeTrue(); - e.Current.Value.Should().Be(30); - e.MoveNext().Should().BeTrue(); - e.Current.Value.Should().Be(40); - e.MoveNext().Should().BeTrue(); - e.Current.Value.Should().Be(50); - e.MoveNext().Should().BeFalse(); - } - - [Fact] - public void ObserveIndex() - { - var list = new ObservableList(); - var sortedView = list.CreateSortedView( - x => x, - x => new ViewContainer(x), - Comparer>.Default); - - var filter = new TestFilter((value, view) => value % 2 == 0); - list.Add(50); - list.Add(10); - - sortedView.AttachFilter(filter); - - list.Add(20); - filter.CalledOnCollectionChanged[0].Action.Should().Be(NotifyCollectionChangedAction.Add); - filter.CalledOnCollectionChanged[0].NewValue.Should().Be(20); - filter.CalledOnCollectionChanged[0].NewView.Should().Be(new ViewContainer(20)); - filter.CalledOnCollectionChanged[0].NewViewIndex.Should().Be(1); - - list.Remove(20); - filter.CalledOnCollectionChanged[1].Action.Should().Be(NotifyCollectionChangedAction.Remove); - filter.CalledOnCollectionChanged[1].OldValue.Should().Be(20); - filter.CalledOnCollectionChanged[1].OldView.Should().Be(new ViewContainer(20)); - filter.CalledOnCollectionChanged[1].OldViewIndex.Should().Be(1); - - list[1] = 999; // from 10(at 0 in original) to 999 - filter.CalledOnCollectionChanged[2].Action.Should().Be(NotifyCollectionChangedAction.Replace); - filter.CalledOnCollectionChanged[2].NewValue.Should().Be(999); - filter.CalledOnCollectionChanged[2].OldValue.Should().Be(10); - filter.CalledOnCollectionChanged[2].NewView.Should().Be(new ViewContainer(999)); - filter.CalledOnCollectionChanged[2].OldView.Should().Be(new ViewContainer(10)); - filter.CalledOnCollectionChanged[2].NewViewIndex.Should().Be(1); - filter.CalledOnCollectionChanged[2].OldViewIndex.Should().Be(0); - } -} \ No newline at end of file diff --git a/tests/ObservableCollections.Tests/ToNotifyCollectionChangedTest.cs b/tests/ObservableCollections.Tests/ToNotifyCollectionChangedTest.cs index 106b6ec..e8f4fa0 100644 --- a/tests/ObservableCollections.Tests/ToNotifyCollectionChangedTest.cs +++ b/tests/ObservableCollections.Tests/ToNotifyCollectionChangedTest.cs @@ -1,3 +1,5 @@ +using ObservableCollections; + namespace ObservableCollections.Tests; public class ToNotifyCollectionChangedTest @@ -43,7 +45,7 @@ public class ToNotifyCollectionChangedTest var view = list.CreateView(x => $"${x}"); var notify = view.ToNotifyCollectionChanged(); - view.AttachFilter((value, view) => value % 2 == 0); + view.AttachFilter((value) => value % 2 == 0); list.Add(4); diff --git a/tests/ObservableCollections.Tests/ViewContainer.cs b/tests/ObservableCollections.Tests/ViewContainer.cs index b9dcb34..4a02766 100644 --- a/tests/ObservableCollections.Tests/ViewContainer.cs +++ b/tests/ObservableCollections.Tests/ViewContainer.cs @@ -31,83 +31,81 @@ namespace ObservableCollections.Tests } } - public class TestFilter : ISynchronizedViewFilter> - { - readonly Func, bool> filter; - public List<(T, ViewContainer)> CalledWhenTrue = new(); - public List<(T, ViewContainer)> CalledWhenFalse = new(); - public List>> CalledOnCollectionChanged = new(); + //public class TestFilter : ISynchronizedViewFilter + //{ + // readonly Func filter; + // public List>> CalledOnCollectionChanged = new(); - public TestFilter(Func, bool> filter) - { - this.filter = filter; - } + // public TestFilter(Func filter) + // { + // this.filter = filter; + // } - public void Clear() - { - CalledWhenTrue.Clear(); - CalledWhenFalse.Clear(); - CalledOnCollectionChanged.Clear(); - } + // public void Clear() + // { + // CalledWhenTrue.Clear(); + // CalledWhenFalse.Clear(); + // CalledOnCollectionChanged.Clear(); + // } - public bool IsMatch(T value, ViewContainer view) - { - return this.filter.Invoke(value, view); - } + // public bool IsMatch(T value) + // { + // return this.filter.Invoke(value); + // } - public void OnCollectionChanged(in SynchronizedViewChangedEventArgs> args) - { - CalledOnCollectionChanged.Add(args); - } + // public void OnCollectionChanged(in SynchronizedViewChangedEventArgs> args) + // { + // CalledOnCollectionChanged.Add(args); + // } - public void WhenTrue(T value, ViewContainer view) - { - CalledWhenTrue.Add((value, view)); - } + // public void WhenTrue(T value, ViewContainer view) + // { + // CalledWhenTrue.Add((value, view)); + // } - public void WhenFalse(T value, ViewContainer view) - { - CalledWhenFalse.Add((value, view)); - } - } + // public void WhenFalse(T value, ViewContainer view) + // { + // CalledWhenFalse.Add((value, view)); + // } + //} - public class TestFilter2 : ISynchronizedViewFilter, ViewContainer> - { - readonly Func, ViewContainer, bool> filter; - public List<(KeyValuePair, ViewContainer)> CalledWhenTrue = new(); - public List<(KeyValuePair, ViewContainer)> CalledWhenFalse = new(); - public List, ViewContainer>> CalledOnCollectionChanged = new(); + //public class TestFilter2 : ISynchronizedViewFilter, ViewContainer> + //{ + // readonly Func, ViewContainer, bool> filter; + // public List<(KeyValuePair, ViewContainer)> CalledWhenTrue = new(); + // public List<(KeyValuePair, ViewContainer)> CalledWhenFalse = new(); + // public List, ViewContainer>> CalledOnCollectionChanged = new(); - public TestFilter2(Func, ViewContainer, bool> filter) - { - this.filter = filter; - } + // public TestFilter2(Func, ViewContainer, bool> filter) + // { + // this.filter = filter; + // } - public void Clear() - { - CalledWhenTrue.Clear(); - CalledWhenFalse.Clear(); - CalledOnCollectionChanged.Clear(); - } + // public void Clear() + // { + // CalledWhenTrue.Clear(); + // CalledWhenFalse.Clear(); + // CalledOnCollectionChanged.Clear(); + // } - public bool IsMatch(KeyValuePair value, ViewContainer view) - { - return this.filter.Invoke(value, view); - } + // public bool IsMatch(KeyValuePair value, ViewContainer view) + // { + // return this.filter.Invoke(value, view); + // } - public void OnCollectionChanged(in SynchronizedViewChangedEventArgs, ViewContainer> args) - { - CalledOnCollectionChanged.Add(args); - } + // public void OnCollectionChanged(in SynchronizedViewChangedEventArgs, ViewContainer> args) + // { + // CalledOnCollectionChanged.Add(args); + // } - public void WhenTrue(KeyValuePair value, ViewContainer view) - { - CalledWhenTrue.Add((value, view)); - } + // public void WhenTrue(KeyValuePair value, ViewContainer view) + // { + // CalledWhenTrue.Add((value, view)); + // } - public void WhenFalse(KeyValuePair value, ViewContainer view) - { - CalledWhenFalse.Add((value, view)); - } - } + // public void WhenFalse(KeyValuePair value, ViewContainer view) + // { + // CalledWhenFalse.Add((value, view)); + // } + //} } From 7eef45cadbcc11bebaa55065fda82be039d65e0a Mon Sep 17 00:00:00 2001 From: neuecc Date: Wed, 28 Aug 2024 03:09:29 +0900 Subject: [PATCH 10/22] 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: From 7e37cfc878aefdd0fd574e10bae3a14cbb2f42f8 Mon Sep 17 00:00:00 2001 From: neuecc Date: Wed, 28 Aug 2024 03:52:42 +0900 Subject: [PATCH 11/22] f --- src/ObservableCollections/ObservableList.Views.cs | 13 ++++++------- .../SynchronizedViewChangedEventArgs.cs | 15 ++++++++++++++- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index 4635d2d..c39a274 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -107,7 +107,7 @@ namespace ObservableCollections } } - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); } } @@ -117,7 +117,7 @@ namespace ObservableCollections { this.filter = SynchronizedViewFilter.Null; this.filteredCount = list.Count; - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); } } @@ -208,15 +208,14 @@ namespace ObservableCollections { 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)); - list.Add(v); - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, i++); + array.Add((item, selector(item))); } + + list.AddRange(array.Span); + this.InvokeOnAdd(ref filteredCount, ViewChanged, v, i++); } } // Insert diff --git a/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs b/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs index 31cdc1d..f5a26df 100644 --- a/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs +++ b/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs @@ -52,7 +52,20 @@ namespace ObservableCollections filteredCount++; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, newValue: value, newView: view, newViewIndex: index)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, true, newItem: (value, view), newStartingIndex: index)); + } + } + } + + internal static void InvokeOnAddRange(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, ReadOnlySpan values, ReadOnlySpan views, int index) + { + var isMatch = collection.Filter.IsMatch(value); + if (isMatch) + { + filteredCount++; + if (ev != null) + { + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, false, newValues: values, newViews: views, newStartingIndex: index)); } } } From 6ee7fb730193a957d5a092b541327a99cfe4e675 Mon Sep 17 00:00:00 2001 From: neuecc Date: Wed, 28 Aug 2024 17:53:17 +0900 Subject: [PATCH 12/22] list --- sandbox/ConsoleApp/Program.cs | 8 +- .../Internal/FixedArray.cs | 55 +++++++++ .../Internal/RemoveAllMatcher.cs | 30 +++++ .../Internal/ResizableArray.cs | 114 +++++++++--------- .../ObservableCollections.csproj | 36 +++--- .../ObservableDictionary.Views.cs | 4 +- .../ObservableHashSet.Views.cs | 4 +- .../ObservableList.Views.cs | 99 ++++++++------- .../ObservableQueue.Views.cs | 4 +- .../ObservableRingBuffer.Views.cs | 4 +- .../ObservableStack.Views.cs | 4 +- .../Shims/Collections.cs | 4 +- .../SynchronizedViewChangedEventArgs.cs | 77 +++++++++--- .../SynchronizedViewList.cs | 96 ++++++++++----- 14 files changed, 361 insertions(+), 178 deletions(-) create mode 100644 src/ObservableCollections/Internal/FixedArray.cs create mode 100644 src/ObservableCollections/Internal/RemoveAllMatcher.cs diff --git a/sandbox/ConsoleApp/Program.cs b/sandbox/ConsoleApp/Program.cs index 63eb431..c13a736 100644 --- a/sandbox/ConsoleApp/Program.cs +++ b/sandbox/ConsoleApp/Program.cs @@ -60,16 +60,16 @@ class HogeFilter : ISynchronizedViewFilter switch (eventArgs.Action) { case NotifyCollectionChangedAction.Add: - eventArgs.NewView.Value += " Add"; + eventArgs.NewItem.View.Value += " Add"; break; case NotifyCollectionChangedAction.Remove: - eventArgs.OldView.Value += " Remove"; + eventArgs.OldItem.View.Value += " Remove"; break; case NotifyCollectionChangedAction.Move: - eventArgs.NewView.Value += $" Move {eventArgs.OldViewIndex} {eventArgs.NewViewIndex}"; + eventArgs.NewItem.View.Value += $" Move {eventArgs.OldStartingIndex} {eventArgs.NewStartingIndex}"; break; case NotifyCollectionChangedAction.Replace: - eventArgs.NewView.Value += $" Replace {eventArgs.NewViewIndex}"; + eventArgs.NewItem.View.Value += $" Replace {eventArgs.NewStartingIndex}"; break; case NotifyCollectionChangedAction.Reset: break; diff --git a/src/ObservableCollections/Internal/FixedArray.cs b/src/ObservableCollections/Internal/FixedArray.cs new file mode 100644 index 0000000..721c070 --- /dev/null +++ b/src/ObservableCollections/Internal/FixedArray.cs @@ -0,0 +1,55 @@ +using System; +using System.Buffers; +using System.Runtime.CompilerServices; + +namespace ObservableCollections.Internal +{ + internal ref struct FixedArray + { + public readonly Span Span; + T[]? array; + + public FixedArray(int size) + { + array = ArrayPool.Shared.Rent(size); + Span = array.AsSpan(0, size); + } + + public void Dispose() + { + if (array != null) + { + ArrayPool.Shared.Return(array, RuntimeHelpersEx.IsReferenceOrContainsReferences()); + } + } + } + + internal ref struct FixedBoolArray + { + public const int StackallocSize = 128; + + public readonly Span Span; + bool[]? array; + + public FixedBoolArray(Span scratchBuffer, int capacity) + { + if (scratchBuffer.Length == 0) + { + array = ArrayPool.Shared.Rent(capacity); + Span = array.AsSpan(0, capacity); + } + else + { + Span = scratchBuffer; + } + } + + public void Dispose() + { + if (array != null) + { + ArrayPool.Shared.Return(array); + } + } + } +} diff --git a/src/ObservableCollections/Internal/RemoveAllMatcher.cs b/src/ObservableCollections/Internal/RemoveAllMatcher.cs new file mode 100644 index 0000000..ecd81a7 --- /dev/null +++ b/src/ObservableCollections/Internal/RemoveAllMatcher.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; + +namespace ObservableCollections.Internal +{ + internal class RemoveAllMatcher + { + readonly HashSet hashSet; + + public RemoveAllMatcher(ReadOnlySpan source) + { +#if !NETSTANDARD2_0 + var set = new HashSet(capacity: source.Length); +#else + var set = new HashSet(); +#endif + foreach (var item in source) + { + set.Add(item); + } + + this.hashSet = set; + } + + public bool Predicate(T value) + { + return hashSet.Contains(value); + } + } +} \ No newline at end of file diff --git a/src/ObservableCollections/Internal/ResizableArray.cs b/src/ObservableCollections/Internal/ResizableArray.cs index ba032d0..7474ddc 100644 --- a/src/ObservableCollections/Internal/ResizableArray.cs +++ b/src/ObservableCollections/Internal/ResizableArray.cs @@ -1,58 +1,58 @@ -using System; -using System.Buffers; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -namespace ObservableCollections.Internal -{ - // internal ref struct ResizableArray - internal struct ResizableArray : IDisposable - { - T[]? array; - int count; - - public ReadOnlySpan Span => array.AsSpan(0, count); - - public ResizableArray(int initialCapacity) - { - array = ArrayPool.Shared.Rent(initialCapacity); - count = 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(T item) - { - if (array == null) Throw(); - if (array.Length == count) - { - EnsureCapacity(); - } - array[count++] = item; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - void EnsureCapacity() - { - var oldArray = array!; +using System; +using System.Buffers; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace ObservableCollections.Internal +{ + // internal ref struct ResizableArray + internal struct ResizableArray : IDisposable + { + T[]? array; + int count; + + public ReadOnlySpan Span => array.AsSpan(0, count); + + public ResizableArray(int initialCapacity) + { + array = ArrayPool.Shared.Rent(initialCapacity); + count = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (array == null) Throw(); + if (array.Length == count) + { + EnsureCapacity(); + } + array[count++] = item; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + void EnsureCapacity() + { + var oldArray = array!; var newArray = ArrayPool.Shared.Rent(oldArray.Length * 2); - Array.Copy(oldArray, newArray, oldArray.Length); - ArrayPool.Shared.Return(oldArray, RuntimeHelpersEx.IsReferenceOrContainsReferences()); - array = newArray; - } - - public void Dispose() - { - if (array != null) - { - ArrayPool.Shared.Return(array, RuntimeHelpersEx.IsReferenceOrContainsReferences()); - array = null; - } - } - - [DoesNotReturn] - void Throw() - { - throw new ObjectDisposedException("ResizableArray"); - } - } -} + Array.Copy(oldArray, newArray, oldArray.Length); + ArrayPool.Shared.Return(oldArray, RuntimeHelpersEx.IsReferenceOrContainsReferences()); + array = newArray; + } + + public void Dispose() + { + if (array != null) + { + ArrayPool.Shared.Return(array, RuntimeHelpersEx.IsReferenceOrContainsReferences()); + array = null; + } + } + + [DoesNotReturn] + void Throw() + { + throw new ObjectDisposedException("ResizableArray"); + } + } +} diff --git a/src/ObservableCollections/ObservableCollections.csproj b/src/ObservableCollections/ObservableCollections.csproj index 9e531f8..07827b9 100644 --- a/src/ObservableCollections/ObservableCollections.csproj +++ b/src/ObservableCollections/ObservableCollections.csproj @@ -1,24 +1,28 @@  - - netstandard2.0;netstandard2.1;net6.0;net8.0 - enable - 12.0 - disable + + netstandard2.0;netstandard2.1;net6.0;net8.0 + enable + 12.0 + disable - - collection - High performance observable collections and synchronized views, for WPF, Blazor, Unity. - true - true - + + collection + High performance observable collections and synchronized views, for WPF, Blazor, Unity. + true + true + - - - + + + + + + + - + - + diff --git a/src/ObservableCollections/ObservableDictionary.Views.cs b/src/ObservableCollections/ObservableDictionary.Views.cs index 4703d84..87e61d7 100644 --- a/src/ObservableCollections/ObservableDictionary.Views.cs +++ b/src/ObservableCollections/ObservableDictionary.Views.cs @@ -94,7 +94,7 @@ namespace ObservableCollections } } - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs, TView>(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs, TView>(NotifyCollectionChangedAction.Reset, true)); } } @@ -104,7 +104,7 @@ namespace ObservableCollections { this.filter = SynchronizedViewFilter>.Null; this.filteredCount = dict.Count; - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs, TView>(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs, TView>(NotifyCollectionChangedAction.Reset, true)); } } diff --git a/src/ObservableCollections/ObservableHashSet.Views.cs b/src/ObservableCollections/ObservableHashSet.Views.cs index 95d604c..af63254 100644 --- a/src/ObservableCollections/ObservableHashSet.Views.cs +++ b/src/ObservableCollections/ObservableHashSet.Views.cs @@ -89,7 +89,7 @@ namespace ObservableCollections filteredCount++; } } - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); } } @@ -99,7 +99,7 @@ namespace ObservableCollections { this.filter = SynchronizedViewFilter.Null; this.filteredCount = dict.Count; - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); } } diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index c39a274..3ea9b4f 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -195,48 +195,41 @@ namespace ObservableCollections switch (e.Action) { case NotifyCollectionChangedAction.Add: - // Add - if (e.NewStartingIndex == list.Count) + // Add or Insert + if (e.IsSingleItem) { - if (e.IsSingleItem) - { - var v = (e.NewItem, selector(e.NewItem)); - list.Add(v); - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, e.NewStartingIndex); - } - else - { - var i = e.NewStartingIndex; - - using var array = new ResizableArray<(T, TView)>(e.NewItems.Length); - foreach (var item in e.NewItems) - { - array.Add((item, selector(item))); - } - - list.AddRange(array.Span); - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, i++); - } + var v = (e.NewItem, selector(e.NewItem)); + list.Insert(e.NewStartingIndex, v); + this.InvokeOnAdd(ref filteredCount, ViewChanged, v, e.NewStartingIndex); } - // Insert else { - if (e.IsSingleItem) + var items = e.NewItems; + var length = items.Length; + + using var valueViews = new FixedArray<(T, TView)>(length); + using var views = new FixedArray(length); + using var matches = new FixedBoolArray(length < FixedBoolArray.StackallocSize ? stackalloc bool[length] : default, length); + var isMatchAll = true; + for (int i = 0; i < items.Length; i++) { - var v = (e.NewItem, selector(e.NewItem)); - list.Insert(e.NewStartingIndex, v); - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, e.NewStartingIndex); - } - else - { - var span = e.NewItems; - for (var i = 0; i < span.Length; i++) + var item = items[i]; + var view = selector(item); + views.Span[i] = view; + valueViews.Span[i] = (item, view); + var isMatch = matches.Span[i] = Filter.IsMatch(item); + if (isMatch) { - var v = (span[i], selector(span[i])); - list.Insert(e.NewStartingIndex + i, v); // should we use InsertRange? - this.InvokeOnAdd(ref filteredCount, ViewChanged, v, e.NewStartingIndex + i); + filteredCount++; // increment in this process + } + else + { + isMatchAll = false; } } + + list.InsertRange(e.NewStartingIndex, valueViews.Span); + this.InvokeOnAddRange(ViewChanged, e.NewItems, views.Span, isMatchAll, matches.Span, e.NewStartingIndex); } break; case NotifyCollectionChangedAction.Remove: @@ -248,17 +241,32 @@ namespace ObservableCollections } else { - list.RemoveRange(e.OldStartingIndex, e.OldItems.Length); // TODO: no - - - // TODO: Range operation before remove...! - - var len = e.OldStartingIndex + e.OldItems.Length; - for (var i = e.OldStartingIndex; i < len; i++) + var length = e.OldItems.Length; + using var values = new FixedArray(length); + using var views = new FixedArray(length); + using var matches = new FixedBoolArray(length < FixedBoolArray.StackallocSize ? stackalloc bool[length] : default, length); + var isMatchAll = true; + var to = e.OldStartingIndex + length; + var j = 0; + for (int i = e.OldStartingIndex; i < to; i++) { - var v = list[i]; - this.InvokeOnRemove(ref filteredCount, ViewChanged, v, e.OldStartingIndex + i); + var item = list[i]; + values.Span[j] = item.Item1; + views.Span[j] = item.Item2; + var isMatch = matches.Span[j] = Filter.IsMatch(item.Item1); + if (isMatch) + { + filteredCount--; // decrement in this process + } + else + { + isMatchAll = false; + } + j++; } + + list.RemoveRange(e.OldStartingIndex, e.OldItems.Length); + this.InvokeOnRemoveRange(ViewChanged, values.Span, views.Span, isMatchAll, matches.Span, e.OldStartingIndex); } break; case NotifyCollectionChangedAction.Replace: @@ -290,14 +298,13 @@ namespace ObservableCollections { // Reverse list.Reverse(e.SortOperation.Index, e.SortOperation.Count); - // TODO:Invoke + this.InvokeOnReverseOrSort(ViewChanged, e.SortOperation); } else { // Sort list.Sort(e.SortOperation.Index, e.SortOperation.Count, new IgnoreViewComparer(e.SortOperation.Comparer ?? Comparer.Default)); - // Span d; - + this.InvokeOnReverseOrSort(ViewChanged, e.SortOperation); } break; default: diff --git a/src/ObservableCollections/ObservableQueue.Views.cs b/src/ObservableCollections/ObservableQueue.Views.cs index d09e89c..fc2ef42 100644 --- a/src/ObservableCollections/ObservableQueue.Views.cs +++ b/src/ObservableCollections/ObservableQueue.Views.cs @@ -89,7 +89,7 @@ namespace ObservableCollections filteredCount++; } } - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); } } @@ -99,7 +99,7 @@ namespace ObservableCollections { this.filter = SynchronizedViewFilter.Null; this.filteredCount = queue.Count; - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); } } diff --git a/src/ObservableCollections/ObservableRingBuffer.Views.cs b/src/ObservableCollections/ObservableRingBuffer.Views.cs index f4df53b..848cdd9 100644 --- a/src/ObservableCollections/ObservableRingBuffer.Views.cs +++ b/src/ObservableCollections/ObservableRingBuffer.Views.cs @@ -91,7 +91,7 @@ namespace ObservableCollections filteredCount++; } } - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); } } @@ -101,7 +101,7 @@ namespace ObservableCollections { this.filter = SynchronizedViewFilter.Null; this.filteredCount = ringBuffer.Count; - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); } } diff --git a/src/ObservableCollections/ObservableStack.Views.cs b/src/ObservableCollections/ObservableStack.Views.cs index ee8ee2c..f77b67c 100644 --- a/src/ObservableCollections/ObservableStack.Views.cs +++ b/src/ObservableCollections/ObservableStack.Views.cs @@ -88,7 +88,7 @@ namespace ObservableCollections filteredCount++; } } - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); } } @@ -98,7 +98,7 @@ namespace ObservableCollections { this.filter = SynchronizedViewFilter.Null; this.filteredCount = stack.Count; - ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ViewChanged?.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); } } diff --git a/src/ObservableCollections/Shims/Collections.cs b/src/ObservableCollections/Shims/Collections.cs index 96a393d..c4ae823 100644 --- a/src/ObservableCollections/Shims/Collections.cs +++ b/src/ObservableCollections/Shims/Collections.cs @@ -11,6 +11,8 @@ namespace System.Collections.Generic { internal static class CollectionExtensions { + const int ArrayMaxLength = 0X7FFFFFC7; + public static void Deconstruct(this KeyValuePair kvp, out TKey key, out TValue value) { key = kvp.Key; @@ -108,7 +110,7 @@ namespace System.Collections.Generic { int newCapacity = list._items.Length == 0 ? 4 : 2 * list._items.Length; - if ((uint)newCapacity > Array.MaxLength) newCapacity = Array.MaxLength; + if ((uint)newCapacity > ArrayMaxLength) newCapacity = ArrayMaxLength; if (newCapacity < capacity) newCapacity = capacity; diff --git a/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs b/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs index f5a26df..0a47d7a 100644 --- a/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs +++ b/src/ObservableCollections/SynchronizedViewChangedEventArgs.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Specialized; +using System.Data; namespace ObservableCollections { @@ -10,7 +11,8 @@ namespace ObservableCollections (T Value, TView View) oldItem = default!, ReadOnlySpan newValues = default!, ReadOnlySpan newViews = default!, - ReadOnlySpan<(T Value, TView View)> oldItems = default!, + ReadOnlySpan oldValues = default!, + ReadOnlySpan oldViews = default!, int newStartingIndex = -1, int oldStartingIndex = -1, SortOperation sortOperation = default) @@ -21,7 +23,8 @@ namespace ObservableCollections public readonly (T Value, TView View) OldItem = oldItem; public readonly ReadOnlySpan NewValues = newValues; public readonly ReadOnlySpan NewViews = newViews; - public readonly ReadOnlySpan<(T Value, TView View)> OldItems = oldItems; + public readonly ReadOnlySpan OldValues = oldValues; + public readonly ReadOnlySpan OldViews = oldViews; public readonly int NewStartingIndex = newStartingIndex; public readonly int OldStartingIndex = oldStartingIndex; public readonly SortOperation SortOperation = sortOperation; @@ -57,15 +60,25 @@ namespace ObservableCollections } } - internal static void InvokeOnAddRange(this ISynchronizedView collection, ref int filteredCount, NotifyViewChangedEventHandler? ev, ReadOnlySpan values, ReadOnlySpan views, int index) + internal static void InvokeOnAddRange(this ISynchronizedView collection, NotifyViewChangedEventHandler? ev, ReadOnlySpan values, ReadOnlySpan views, bool isMatchAll, ReadOnlySpan matches, int index) { - var isMatch = collection.Filter.IsMatch(value); - if (isMatch) + if (ev != null) { - filteredCount++; - if (ev != null) + if (isMatchAll) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, false, newValues: values, newViews: views, newStartingIndex: index)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, isSingleItem: false, newValues: values, newViews: views, newStartingIndex: index)); + } + else + { + var startingIndex = index; + for (var i = 0; i < matches.Length; i++) + { + if (matches[i]) + { + var item = (values[i], views[i]); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, isSingleItem: true, newItem: item, newStartingIndex: startingIndex++)); + } + } } } } @@ -83,7 +96,35 @@ namespace ObservableCollections filteredCount--; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, true, oldItem: (value, view), oldStartingIndex: oldIndex)); + } + } + } + + // only use for ObservableList + internal static void InvokeOnRemoveRange(this ISynchronizedView collection, NotifyViewChangedEventHandler? ev, ReadOnlySpan values, ReadOnlySpan views, bool isMatchAll, ReadOnlySpan matches, int index) + { + if (ev != null) + { + if (isMatchAll) + { + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, isSingleItem: false, oldValues: values, oldViews: views, oldStartingIndex: index)); + } + else + { + var startingIndex = index; + for (var i = 0; i < matches.Length; i++) + { + if (matches[i]) + { + var item = (values[i], views[i]); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, isSingleItem: true, oldItem: item, oldStartingIndex: index)); //remove for list, always same index + } + else + { + index++; // not matched, skip index + } + } } } } @@ -101,7 +142,7 @@ namespace ObservableCollections var isMatch = collection.Filter.IsMatch(value); if (isMatch) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Move, newValue: value, newView: view, newViewIndex: index, oldViewIndex: oldIndex)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Move, true, newItem: (value, view), newStartingIndex: index, oldStartingIndex: oldIndex)); } } } @@ -121,7 +162,7 @@ namespace ObservableCollections { if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Replace, newValue: value, newView: view, oldValue: oldValue, oldView: oldView, newViewIndex: index, oldViewIndex: oldIndex >= 0 ? oldIndex : index)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Replace, true, newItem: (value, view), oldItem: (oldValue, oldView), newStartingIndex: index, oldStartingIndex: oldIndex >= 0 ? oldIndex : index)); } } else if (oldMatched) @@ -130,7 +171,7 @@ namespace ObservableCollections filteredCount--; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, oldValue: value, oldView: view, oldViewIndex: oldIndex)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Remove, true, oldItem: (value, view), oldStartingIndex: oldIndex)); } } @@ -140,7 +181,7 @@ namespace ObservableCollections filteredCount++; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, newValue: value, newView: view, newViewIndex: index)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Add, true, newItem: (value, view), newStartingIndex: index)); } } } @@ -150,7 +191,15 @@ namespace ObservableCollections filteredCount = 0; if (ev != null) { - ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset)); + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true)); + } + } + + internal static void InvokeOnReverseOrSort(this ISynchronizedView collection, NotifyViewChangedEventHandler? ev, SortOperation sortOperation) + { + if (ev != null) + { + ev.Invoke(new SynchronizedViewChangedEventArgs(NotifyCollectionChangedAction.Reset, true, sortOperation: sortOperation)); } } } diff --git a/src/ObservableCollections/SynchronizedViewList.cs b/src/ObservableCollections/SynchronizedViewList.cs index 56e7c37..bf0e686 100644 --- a/src/ObservableCollections/SynchronizedViewList.cs +++ b/src/ObservableCollections/SynchronizedViewList.cs @@ -1,4 +1,5 @@ -using System; +using ObservableCollections.Internal; +using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; @@ -8,10 +9,19 @@ using System.Runtime.InteropServices; namespace ObservableCollections { + internal class SynchronizedViewList : ISynchronizedViewList { readonly ISynchronizedView parent; protected readonly List listView; + + + + + //protected readonly SortedList listView; // key is original index + + + protected readonly object gate = new object(); public SynchronizedViewList(ISynchronizedView parent) @@ -70,40 +80,38 @@ namespace ObservableCollections { if (e.OldStartingIndex == -1) { - // TODO:... - //listView.RemoveAll( - - // e.OldItems + var matcher = new RemoveAllMatcher(e.OldViews); + listView.RemoveAll(matcher.Predicate); } else { - listView.RemoveRange(e.OldStartingIndex, e.OldItems.Length); + listView.RemoveRange(e.OldStartingIndex, e.OldViews.Length); } } break; case NotifyCollectionChangedAction.Replace: // Indexer - if (e.NewViewIndex == -1) + if (e.NewStartingIndex == -1) { - var index = listView.IndexOf(e.OldView); - listView[index] = e.NewView; + var index = listView.IndexOf(e.OldItem.View); + listView[index] = e.NewItem.View; } else { - listView[e.NewViewIndex] = e.NewView; + listView[e.NewStartingIndex] = e.NewItem.View; } break; case NotifyCollectionChangedAction.Move: //Remove and Insert - if (e.NewViewIndex == -1) + if (e.NewStartingIndex == -1) { // do nothing } else { - listView.RemoveAt(e.OldViewIndex); - listView.Insert(e.NewViewIndex, e.NewView); + listView.RemoveAt(e.OldStartingIndex); + listView.Insert(e.NewStartingIndex, e.NewItem.View); } break; case NotifyCollectionChangedAction.Reset: // Clear or drastic changes @@ -121,16 +129,18 @@ namespace ObservableCollections } else { +#if NET6_0_OR_GREATER +#pragma warning disable CS0436 if (parent is ObservableList.View observableListView) { -#pragma warning disable CS0436 var comparer = new ObservableList.View.IgnoreViewComparer(e.SortOperation.Comparer ?? Comparer.Default); var viewSpan = CollectionsMarshal.AsSpan(listView).Slice(e.SortOperation.Index, e.SortOperation.Count); var sourceSpan = CollectionsMarshal.AsSpan(observableListView.list).Slice(e.SortOperation.Index, e.SortOperation.Count); - sourceSpan.Sort(viewSpan, comparer); -#pragma warning restore CS0436 + sourceSpan.Sort(viewSpan, comparer); // span.Sort is NET6 or greater } else +#pragma warning restore CS0436 +#endif { // can not get source Span, do Clear and Refresh listView.Clear(); @@ -223,22 +233,48 @@ namespace ObservableCollections switch (args.Action) { case NotifyCollectionChangedAction.Add: - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewView, args.NewViewIndex) + if (args.IsSingleItem) { - Collection = this, - Invoker = raiseChangedEventInvoke, - IsInvokeCollectionChanged = true, - IsInvokePropertyChanged = true - }); + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewItem.View, args.NewStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + } + else + { + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewViews.ToArray(), args.NewStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + } break; case NotifyCollectionChangedAction.Remove: - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldView, args.OldViewIndex) + if (args.IsSingleItem) { - Collection = this, - Invoker = raiseChangedEventInvoke, - IsInvokeCollectionChanged = true, - IsInvokePropertyChanged = true - }); + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldItem.View, args.OldStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + } + else + { + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldViews.ToArray(), args.OldStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + } break; case NotifyCollectionChangedAction.Reset: eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Reset) @@ -250,7 +286,7 @@ namespace ObservableCollections }); break; case NotifyCollectionChangedAction.Replace: - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Replace, args.NewView, args.OldView, args.NewViewIndex) + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Replace, args.NewItem.View, args.OldItem.View, args.NewStartingIndex) { Collection = this, Invoker = raiseChangedEventInvoke, @@ -259,7 +295,7 @@ namespace ObservableCollections }); break; case NotifyCollectionChangedAction.Move: - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Move, args.NewView, args.NewViewIndex, args.OldViewIndex) + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Move, args.NewItem.View, args.NewStartingIndex, args.OldStartingIndex) { Collection = this, Invoker = raiseChangedEventInvoke, From b69f32c45040ce051e4be443e1c880ed2a58affb Mon Sep 17 00:00:00 2001 From: neuecc Date: Wed, 28 Aug 2024 19:03:03 +0900 Subject: [PATCH 13/22] how sync... --- src/ObservableCollections/SynchronizedViewList.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/ObservableCollections/SynchronizedViewList.cs b/src/ObservableCollections/SynchronizedViewList.cs index bf0e686..5e4fd7e 100644 --- a/src/ObservableCollections/SynchronizedViewList.cs +++ b/src/ObservableCollections/SynchronizedViewList.cs @@ -14,14 +14,6 @@ namespace ObservableCollections { readonly ISynchronizedView parent; protected readonly List listView; - - - - - //protected readonly SortedList listView; // key is original index - - - protected readonly object gate = new object(); public SynchronizedViewList(ISynchronizedView parent) @@ -60,6 +52,8 @@ namespace ObservableCollections } else { + // fnew SortedList().sort + listView.InsertRange(e.NewStartingIndex, e.NewViews); } } From 08b328c16fa304051fc33734daa9e7f676782111 Mon Sep 17 00:00:00 2001 From: neuecc Date: Mon, 2 Sep 2024 16:27:23 +0900 Subject: [PATCH 14/22] impl AlternateIndexList --- .../Internal/AlternateIndexList.cs | 211 ++++++++++++++++++ .../ObservableCollections.csproj | 7 + .../AlternateIndexListTest.cs | 100 +++++++++ 3 files changed, 318 insertions(+) create mode 100644 src/ObservableCollections/Internal/AlternateIndexList.cs create mode 100644 tests/ObservableCollections.Tests/AlternateIndexListTest.cs diff --git a/src/ObservableCollections/Internal/AlternateIndexList.cs b/src/ObservableCollections/Internal/AlternateIndexList.cs new file mode 100644 index 0000000..6c276fa --- /dev/null +++ b/src/ObservableCollections/Internal/AlternateIndexList.cs @@ -0,0 +1,211 @@ +#pragma warning disable CS0436 + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.InteropServices; + +namespace ObservableCollections.Internal +{ + public class AlternateIndexList : IEnumerable + { + List list; // alternate index is ordered + + public AlternateIndexList() + { + this.list = new(); + } + + public AlternateIndexList(IEnumerable<(int OrderedAlternateIndex, T Value)> values) + { + this.list = values.Select(x => new IndexedValue(x.OrderedAlternateIndex, x.Value)).ToList(); + } + + void UpdateAlternateIndex(int startIndex, int incr) + { + var span = CollectionsMarshal.AsSpan(list); + for (int i = startIndex; i < span.Length; i++) + { + span[i].AlternateIndex += incr; + } + } + + public T this[int index] + { + get => list[index].Value; + } + + public int Count => list.Count; + + public void Insert(int alternateIndex, T value) + { + var index = list.BinarySearch(alternateIndex); + if (index < 0) + { + index = ~index; + } + list.Insert(index, new(alternateIndex, value)); + UpdateAlternateIndex(index + 1, 1); + } + + public void InsertRange(int startingAlternateIndex, IEnumerable values) + { + var index = list.BinarySearch(startingAlternateIndex); + if (index < 0) + { + index = ~index; + } + + using var iter = new InsertIterator(startingAlternateIndex, values); + list.InsertRange(index, iter); + UpdateAlternateIndex(index + iter.ConsumedCount, iter.ConsumedCount); + } + + public void Remove(T value) + { + var index = list.FindIndex(x => EqualityComparer.Default.Equals(x.Value, value)); + if (index != -1) + { + list.RemoveAt(index); + UpdateAlternateIndex(index, -1); + } + } + + public void RemoveAt(int alternateIndex) + { + var index = list.BinarySearch(alternateIndex); + if (index != -1) + { + list.RemoveAt(index); + UpdateAlternateIndex(index, -1); + } + } + + public void RemoveRange(int alternateIndex, int count) + { + var index = list.BinarySearch(alternateIndex); + if (index < 0) + { + index = ~index; + } + + list.RemoveRange(index, count); + UpdateAlternateIndex(index, -count); + } + + public bool TryGetAtAlternateIndex(int alternateIndex, [MaybeNullWhen(true)] out T value) + { + var index = list.BinarySearch(alternateIndex); + if (index < 0) + { + value = default!; + return false; + } + value = list[index].Value!; + return true; + } + + public bool TrySetAtAlternateIndex(int alternateIndex, T value) + { + var index = list.BinarySearch(alternateIndex); + if (index < 0) + { + return false; + } + CollectionsMarshal.AsSpan(list)[index].Value = value; + return true; + } + + public void Clear() + { + list.Clear(); + } + + // Can't implement add, reverse and sort because alternate index is unknown + // void Add(); + // void AddRange(); + // void Reverse(); + // void Sort(); + + public IEnumerator GetEnumerator() + { + foreach (var item in list) + { + yield return item.Value; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerable<(int AlternateIndex, T Value)> GetIndexedValues() + { + foreach (var item in list) + { + yield return (item.AlternateIndex, item.Value); + } + } + + class InsertIterator(int startingIndex, IEnumerable values) : IEnumerable, IEnumerator + { + IEnumerator iter = values.GetEnumerator(); + IndexedValue current; + + public int ConsumedCount { get; private set; } + + public IndexedValue Current => current; + + object IEnumerator.Current => Current; + + public void Dispose() => iter.Reset(); + + public bool MoveNext() + { + if (iter.MoveNext()) + { + ConsumedCount++; + current = new(startingIndex++, iter.Current); + return true; + } + return false; + } + + public void Reset() => iter.Reset(); + + public IEnumerator GetEnumerator() => this; + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + struct IndexedValue : IComparable + { + public int AlternateIndex; // mutable + public T Value; // mutable + + public IndexedValue(int alternateIndex, T value) + { + this.AlternateIndex = alternateIndex; + this.Value = value; + } + + public static implicit operator IndexedValue(int alternateIndex) // for query + { + return new IndexedValue(alternateIndex, default!); + } + + public int CompareTo(IndexedValue other) + { + return AlternateIndex.CompareTo(other.AlternateIndex); + } + + public override string ToString() + { + return (AlternateIndex, Value).ToString(); + } + } + } +} diff --git a/src/ObservableCollections/ObservableCollections.csproj b/src/ObservableCollections/ObservableCollections.csproj index 07827b9..afa1786 100644 --- a/src/ObservableCollections/ObservableCollections.csproj +++ b/src/ObservableCollections/ObservableCollections.csproj @@ -25,4 +25,11 @@ + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/tests/ObservableCollections.Tests/AlternateIndexListTest.cs b/tests/ObservableCollections.Tests/AlternateIndexListTest.cs new file mode 100644 index 0000000..28eda2f --- /dev/null +++ b/tests/ObservableCollections.Tests/AlternateIndexListTest.cs @@ -0,0 +1,100 @@ +using ObservableCollections.Internal; + +namespace ObservableCollections.Tests; + +public class AlternateIndexListTest +{ + [Fact] + public void Insert() + { + var list = new AlternateIndexList(); + + list.Insert(0, "foo"); + list.Insert(1, "bar"); + list.Insert(2, "baz"); + list.GetIndexedValues().Should().Equal((0, "foo"), (1, "bar"), (2, "baz")); + + list.Insert(1, "new-bar"); + list.GetIndexedValues().Should().Equal((0, "foo"), (1, "new-bar"), (2, "bar"), (3, "baz")); + + + list.Insert(6, "zoo"); + list.GetIndexedValues().Should().Equal((0, "foo"), (1, "new-bar"), (2, "bar"), (3, "baz"), (6, "zoo")); + } + + [Fact] + public void InsertRange() + { + var list = new AlternateIndexList(); + + list.Insert(0, "foo"); + list.Insert(1, "bar"); + list.Insert(2, "baz"); + + list.InsertRange(1, new[] { "new-foo", "new-bar", "new-baz" }); + list.GetIndexedValues().Should().Equal((0, "foo"), (1, "new-foo"), (2, "new-bar"), (3, "new-baz"), (4, "bar"), (5, "baz")); + } + + [Fact] + public void InsertSparsed() + { + var list = new AlternateIndexList(); + + list.Insert(2, "foo"); + list.Insert(8, "baz"); // baz + list.Insert(4, "bar"); + list.GetIndexedValues().Should().Equal((2, "foo"), (4, "bar"), (9, "baz")); + + list.InsertRange(3, new[] { "new-foo", "new-bar", "new-baz" }); + list.GetIndexedValues().Should().Equal((2, "foo"), (3, "new-foo"), (4, "new-bar"), (5, "new-baz"), (7, "bar"), (12, "baz")); + + list.InsertRange(1, new[] { "zoo" }); + list.GetIndexedValues().Should().Equal((1, "zoo"), (3, "foo"), (4, "new-foo"), (5, "new-bar"), (6, "new-baz"), (8, "bar"), (13, "baz")); + } + + [Fact] + public void Remove() + { + var list = new AlternateIndexList(); + + list.Insert(0, "foo"); + list.Insert(1, "bar"); + list.Insert(2, "baz"); + + list.Remove("bar"); + list.GetIndexedValues().Should().Equal((0, "foo"), (1, "baz")); + + list.RemoveAt(0); + list.GetIndexedValues().Should().Equal((0, "baz")); + } + + [Fact] + public void RemoveRange() + { + var list = new AlternateIndexList(); + + list.Insert(0, "foo"); + list.Insert(1, "bar"); + list.Insert(2, "baz"); + + list.RemoveRange(1, 2); + list.GetIndexedValues().Should().Equal((0, "foo")); + } + + [Fact] + public void TryGetSet() + { + var list = new AlternateIndexList(); + + list.Insert(0, "foo"); + list.Insert(2, "bar"); + list.Insert(4, "baz"); + + list.TryGetAtAlternateIndex(2, out var bar).Should().BeTrue(); + bar.Should().Be("bar"); + + list.TrySetAtAlternateIndex(4, "new-baz").Should().BeTrue(); + list.TryGetAtAlternateIndex(4, out var baz).Should().BeTrue(); + baz.Should().Be("new-baz"); + } +} From c0c9cd48d7b4efbe4201016ac5f5a6ed4192be35 Mon Sep 17 00:00:00 2001 From: neuecc Date: Mon, 2 Sep 2024 19:31:29 +0900 Subject: [PATCH 15/22] replace SynchronizedViewList backed uses AlternateIndexList --- .../AlternateIndexList.cs | 222 ++++++ .../Internal/AlternateIndexList.cs | 211 ----- .../Internal/RemoveAllMatcher.cs | 30 - src/ObservableCollections/ObservableList.cs | 13 +- .../SynchronizedViewList.cs | 738 +++++++++--------- .../AlternateIndexListTest.cs | 13 + 6 files changed, 602 insertions(+), 625 deletions(-) create mode 100644 src/ObservableCollections/AlternateIndexList.cs delete mode 100644 src/ObservableCollections/Internal/AlternateIndexList.cs delete mode 100644 src/ObservableCollections/Internal/RemoveAllMatcher.cs diff --git a/src/ObservableCollections/AlternateIndexList.cs b/src/ObservableCollections/AlternateIndexList.cs new file mode 100644 index 0000000..964905b --- /dev/null +++ b/src/ObservableCollections/AlternateIndexList.cs @@ -0,0 +1,222 @@ +#pragma warning disable CS0436 + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.InteropServices; + +namespace ObservableCollections; + +public class AlternateIndexList : IEnumerable +{ + List list; // alternate index is ordered + + public AlternateIndexList() + { + this.list = new(); + } + + public AlternateIndexList(IEnumerable<(int OrderedAlternateIndex, T Value)> values) + { + this.list = values.Select(x => new IndexedValue(x.OrderedAlternateIndex, x.Value)).ToList(); + } + + void UpdateAlternateIndex(int startIndex, int incr) + { + var span = CollectionsMarshal.AsSpan(list); + for (int i = startIndex; i < span.Length; i++) + { + span[i].AlternateIndex += incr; + } + } + + public T this[int index] + { + get => list[index].Value; + } + + public int Count => list.Count; + + public void Insert(int alternateIndex, T value) + { + var index = list.BinarySearch(alternateIndex); + if (index < 0) + { + index = ~index; + } + list.Insert(index, new(alternateIndex, value)); + UpdateAlternateIndex(index + 1, 1); + } + + public void InsertRange(int startingAlternateIndex, IEnumerable values) + { + var index = list.BinarySearch(startingAlternateIndex); + if (index < 0) + { + index = ~index; + } + + using var iter = new InsertIterator(startingAlternateIndex, values); + list.InsertRange(index, iter); + UpdateAlternateIndex(index + iter.ConsumedCount, iter.ConsumedCount); + } + + public void Remove(T value) + { + var index = list.FindIndex(x => EqualityComparer.Default.Equals(x.Value, value)); + if (index != -1) + { + list.RemoveAt(index); + UpdateAlternateIndex(index, -1); + } + } + + public void RemoveAt(int alternateIndex) + { + var index = list.BinarySearch(alternateIndex); + if (index != -1) + { + list.RemoveAt(index); + UpdateAlternateIndex(index, -1); + } + } + + public void RemoveRange(int alternateIndex, int count) + { + var index = list.BinarySearch(alternateIndex); + if (index < 0) + { + index = ~index; + } + + list.RemoveRange(index, count); + UpdateAlternateIndex(index, -count); + } + + public bool TryGetAtAlternateIndex(int alternateIndex, [MaybeNullWhen(true)] out T value) + { + var index = list.BinarySearch(alternateIndex); + if (index < 0) + { + value = default!; + return false; + } + value = list[index].Value!; + return true; + } + + public bool TrySetAtAlternateIndex(int alternateIndex, T value) + { + var index = list.BinarySearch(alternateIndex); + if (index < 0) + { + return false; + } + CollectionsMarshal.AsSpan(list)[index].Value = value; + return true; + } + + public bool TryReplaceByValue(T searchValue, T replaceValue) + { + var index = list.FindIndex(x => EqualityComparer.Default.Equals(x.Value, searchValue)); + if (index != -1) + { + CollectionsMarshal.AsSpan(list)[index].Value = replaceValue; + return true; + } + return false; + } + + public void Clear() + { + list.Clear(); + } + + public void Clear(IEnumerable<(int OrderedAlternateIndex, T Value)> values) + { + list.Clear(); + list.AddRange(values.Select(x => new IndexedValue(x.OrderedAlternateIndex, x.Value))); + } + + public IEnumerator GetEnumerator() + { + foreach (var item in list) + { + yield return item.Value; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerable<(int AlternateIndex, T Value)> GetIndexedValues() + { + foreach (var item in list) + { + yield return (item.AlternateIndex, item.Value); + } + } + + class InsertIterator(int startingIndex, IEnumerable values) : IEnumerable, IEnumerator + { + IEnumerator iter = values.GetEnumerator(); + IndexedValue current; + + public int ConsumedCount { get; private set; } + + public IndexedValue Current => current; + + object IEnumerator.Current => Current; + + public void Dispose() => iter.Reset(); + + public bool MoveNext() + { + if (iter.MoveNext()) + { + ConsumedCount++; + current = new(startingIndex++, iter.Current); + return true; + } + return false; + } + + public void Reset() => iter.Reset(); + + public IEnumerator GetEnumerator() => this; + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + struct IndexedValue : IComparable + { + public int AlternateIndex; // mutable + public T Value; // mutable + + public IndexedValue(int alternateIndex, T value) + { + this.AlternateIndex = alternateIndex; + this.Value = value; + } + + public static implicit operator IndexedValue(int alternateIndex) // for query + { + return new IndexedValue(alternateIndex, default!); + } + + public int CompareTo(IndexedValue other) + { + return AlternateIndex.CompareTo(other.AlternateIndex); + } + + public override string ToString() + { + return (AlternateIndex, Value).ToString(); + } + } +} diff --git a/src/ObservableCollections/Internal/AlternateIndexList.cs b/src/ObservableCollections/Internal/AlternateIndexList.cs deleted file mode 100644 index 6c276fa..0000000 --- a/src/ObservableCollections/Internal/AlternateIndexList.cs +++ /dev/null @@ -1,211 +0,0 @@ -#pragma warning disable CS0436 - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Runtime.InteropServices; - -namespace ObservableCollections.Internal -{ - public class AlternateIndexList : IEnumerable - { - List list; // alternate index is ordered - - public AlternateIndexList() - { - this.list = new(); - } - - public AlternateIndexList(IEnumerable<(int OrderedAlternateIndex, T Value)> values) - { - this.list = values.Select(x => new IndexedValue(x.OrderedAlternateIndex, x.Value)).ToList(); - } - - void UpdateAlternateIndex(int startIndex, int incr) - { - var span = CollectionsMarshal.AsSpan(list); - for (int i = startIndex; i < span.Length; i++) - { - span[i].AlternateIndex += incr; - } - } - - public T this[int index] - { - get => list[index].Value; - } - - public int Count => list.Count; - - public void Insert(int alternateIndex, T value) - { - var index = list.BinarySearch(alternateIndex); - if (index < 0) - { - index = ~index; - } - list.Insert(index, new(alternateIndex, value)); - UpdateAlternateIndex(index + 1, 1); - } - - public void InsertRange(int startingAlternateIndex, IEnumerable values) - { - var index = list.BinarySearch(startingAlternateIndex); - if (index < 0) - { - index = ~index; - } - - using var iter = new InsertIterator(startingAlternateIndex, values); - list.InsertRange(index, iter); - UpdateAlternateIndex(index + iter.ConsumedCount, iter.ConsumedCount); - } - - public void Remove(T value) - { - var index = list.FindIndex(x => EqualityComparer.Default.Equals(x.Value, value)); - if (index != -1) - { - list.RemoveAt(index); - UpdateAlternateIndex(index, -1); - } - } - - public void RemoveAt(int alternateIndex) - { - var index = list.BinarySearch(alternateIndex); - if (index != -1) - { - list.RemoveAt(index); - UpdateAlternateIndex(index, -1); - } - } - - public void RemoveRange(int alternateIndex, int count) - { - var index = list.BinarySearch(alternateIndex); - if (index < 0) - { - index = ~index; - } - - list.RemoveRange(index, count); - UpdateAlternateIndex(index, -count); - } - - public bool TryGetAtAlternateIndex(int alternateIndex, [MaybeNullWhen(true)] out T value) - { - var index = list.BinarySearch(alternateIndex); - if (index < 0) - { - value = default!; - return false; - } - value = list[index].Value!; - return true; - } - - public bool TrySetAtAlternateIndex(int alternateIndex, T value) - { - var index = list.BinarySearch(alternateIndex); - if (index < 0) - { - return false; - } - CollectionsMarshal.AsSpan(list)[index].Value = value; - return true; - } - - public void Clear() - { - list.Clear(); - } - - // Can't implement add, reverse and sort because alternate index is unknown - // void Add(); - // void AddRange(); - // void Reverse(); - // void Sort(); - - public IEnumerator GetEnumerator() - { - foreach (var item in list) - { - yield return item.Value; - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public IEnumerable<(int AlternateIndex, T Value)> GetIndexedValues() - { - foreach (var item in list) - { - yield return (item.AlternateIndex, item.Value); - } - } - - class InsertIterator(int startingIndex, IEnumerable values) : IEnumerable, IEnumerator - { - IEnumerator iter = values.GetEnumerator(); - IndexedValue current; - - public int ConsumedCount { get; private set; } - - public IndexedValue Current => current; - - object IEnumerator.Current => Current; - - public void Dispose() => iter.Reset(); - - public bool MoveNext() - { - if (iter.MoveNext()) - { - ConsumedCount++; - current = new(startingIndex++, iter.Current); - return true; - } - return false; - } - - public void Reset() => iter.Reset(); - - public IEnumerator GetEnumerator() => this; - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - - struct IndexedValue : IComparable - { - public int AlternateIndex; // mutable - public T Value; // mutable - - public IndexedValue(int alternateIndex, T value) - { - this.AlternateIndex = alternateIndex; - this.Value = value; - } - - public static implicit operator IndexedValue(int alternateIndex) // for query - { - return new IndexedValue(alternateIndex, default!); - } - - public int CompareTo(IndexedValue other) - { - return AlternateIndex.CompareTo(other.AlternateIndex); - } - - public override string ToString() - { - return (AlternateIndex, Value).ToString(); - } - } - } -} diff --git a/src/ObservableCollections/Internal/RemoveAllMatcher.cs b/src/ObservableCollections/Internal/RemoveAllMatcher.cs deleted file mode 100644 index ecd81a7..0000000 --- a/src/ObservableCollections/Internal/RemoveAllMatcher.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace ObservableCollections.Internal -{ - internal class RemoveAllMatcher - { - readonly HashSet hashSet; - - public RemoveAllMatcher(ReadOnlySpan source) - { -#if !NETSTANDARD2_0 - var set = new HashSet(capacity: source.Length); -#else - var set = new HashSet(); -#endif - foreach (var item in source) - { - set.Add(item); - } - - this.hashSet = set; - } - - public bool Predicate(T value) - { - return hashSet.Contains(value); - } - } -} \ No newline at end of file diff --git a/src/ObservableCollections/ObservableList.cs b/src/ObservableCollections/ObservableList.cs index bf9e950..a7d325c 100644 --- a/src/ObservableCollections/ObservableList.cs +++ b/src/ObservableCollections/ObservableList.cs @@ -100,11 +100,16 @@ namespace ObservableCollections { lock (SyncRoot) { - var index = list.Count; + var index = list.Count; // starting index + +#if NET8_0_OR_GREATER + list.AddRange(items); +#else foreach (var item in items) { list.Add(item); } +#endif CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Add(items, index)); } @@ -204,11 +209,16 @@ namespace ObservableCollections { lock (SyncRoot) { +#if NET8_0_OR_GREATER + list.InsertRange(index, items); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Add(items, index)); +#else using (var xs = new CloneCollection(items)) { list.InsertRange(index, xs.AsEnumerable()); CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Add(xs.Span, index)); } +#endif } } @@ -304,7 +314,6 @@ namespace ObservableCollections } } - public void Reverse(int index, int count) { lock (SyncRoot) diff --git a/src/ObservableCollections/SynchronizedViewList.cs b/src/ObservableCollections/SynchronizedViewList.cs index 5e4fd7e..ab1b445 100644 --- a/src/ObservableCollections/SynchronizedViewList.cs +++ b/src/ObservableCollections/SynchronizedViewList.cs @@ -7,442 +7,416 @@ using System.ComponentModel; using System.Linq; using System.Runtime.InteropServices; -namespace ObservableCollections +namespace ObservableCollections; + +internal class SynchronizedViewList : ISynchronizedViewList { + readonly ISynchronizedView parent; + protected readonly AlternateIndexList listView; + protected readonly object gate = new object(); - internal class SynchronizedViewList : ISynchronizedViewList + public SynchronizedViewList(ISynchronizedView parent) { - readonly ISynchronizedView parent; - protected readonly List listView; - protected readonly object gate = new object(); - - public SynchronizedViewList(ISynchronizedView parent) + this.parent = parent; + lock (parent.SyncRoot) { - this.parent = parent; - lock (parent.SyncRoot) + listView = new AlternateIndexList(IterateFilteredIndexedViewsOfParent()); + parent.ViewChanged += Parent_ViewChanged; + } + } + + IEnumerable<(int, TView)> IterateFilteredIndexedViewsOfParent() + { + var filter = parent.Filter; + var index = 0; + if (filter.IsNullFilter()) + { + foreach (var item in parent.Unfiltered) // use Unfiltered { - listView = parent.ToList(); - parent.ViewChanged += Parent_ViewChanged; + yield return (index, item.View); + index++; } } - - private void Parent_ViewChanged(in SynchronizedViewChangedEventArgs e) + else { - lock (gate) + foreach (var item in parent.Unfiltered) // use Unfiltered { - switch (e.Action) + if (filter.IsMatch(item.Value)) { - case NotifyCollectionChangedAction.Add: // Add or Insert - if (e.IsSingleItem) - { - if (e.NewStartingIndex == -1) - { - listView.Add(e.NewItem.View); - } - else - { - listView.Insert(e.NewStartingIndex, e.NewItem.View); - } - } - else - { - if (e.NewStartingIndex == -1) - { - listView.AddRange(e.NewViews); - } - else - { - // fnew SortedList().sort + yield return (index, item.View); + } + index++; + } + } + } - listView.InsertRange(e.NewStartingIndex, e.NewViews); - } - } - break; - case NotifyCollectionChangedAction.Remove: // Remove - if (e.IsSingleItem) + 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 + { + using var array = new CloneCollection(e.NewViews); + listView.InsertRange(e.NewStartingIndex, array.AsEnumerable()); + } + break; + case NotifyCollectionChangedAction.Remove: // Remove + if (e.IsSingleItem) + { + if (e.OldStartingIndex == -1) // can't gurantee correct remove if index is not provided { - 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 - { - if (e.OldStartingIndex == -1) - { - var matcher = new RemoveAllMatcher(e.OldViews); - listView.RemoveAll(matcher.Predicate); - } - else - { - listView.RemoveRange(e.OldStartingIndex, e.OldViews.Length); - } - } - - - break; - case NotifyCollectionChangedAction.Replace: // Indexer - if (e.NewStartingIndex == -1) - { - var index = listView.IndexOf(e.OldItem.View); - listView[index] = e.NewItem.View; - } - else - { - listView[e.NewStartingIndex] = e.NewItem.View; - } - - break; - case NotifyCollectionChangedAction.Move: //Remove and Insert - if (e.NewStartingIndex == -1) - { - // do nothing + listView.Remove(e.OldItem.View); } else { listView.RemoveAt(e.OldStartingIndex); - listView.Insert(e.NewStartingIndex, e.NewItem.View); } - break; - case NotifyCollectionChangedAction.Reset: // Clear or drastic changes - if (e.SortOperation.IsNull) + } + else + { + if (e.OldStartingIndex == -1) { - listView.Clear(); - foreach (var item in parent) // refresh + foreach (var view in e.OldViews) // index is unknown, can't do batching { - listView.Add(item); + listView.Remove(view); } } - else if (e.SortOperation.IsReverse) - { - listView.Reverse(); - } else { -#if NET6_0_OR_GREATER -#pragma warning disable CS0436 - if (parent is ObservableList.View observableListView) - { - 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); // span.Sort is NET6 or greater - } - else -#pragma warning restore CS0436 -#endif - { - // can not get source Span, do Clear and Refresh - listView.Clear(); - foreach (var item in parent) - { - listView.Add(item); - } - } + listView.RemoveRange(e.OldStartingIndex, e.OldViews.Length); } - break; - default: - break; - } + } + break; + case NotifyCollectionChangedAction.Replace: // Indexer + if (e.NewStartingIndex == -1) + { + listView.TryReplaceByValue(e.OldItem.View, e.NewItem.View); + } + else + { + listView.TrySetAtAlternateIndex(e.NewStartingIndex, e.NewItem.View); + } - OnCollectionChanged(e); + break; + case NotifyCollectionChangedAction.Move: //Remove and Insert + if (e.NewStartingIndex == -1) + { + // do nothing + } + else + { + listView.RemoveAt(e.OldStartingIndex); + listView.Insert(e.NewStartingIndex, e.NewItem.View); + } + break; + case NotifyCollectionChangedAction.Reset: // Clear or drastic changes + listView.Clear(IterateFilteredIndexedViewsOfParent()); // clear and fill refresh + break; + default: + break; } - } - 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; + OnCollectionChanged(e); } } - internal class NotifyCollectionChangedSynchronizedView : - SynchronizedViewList, - INotifyCollectionChangedSynchronizedView, - IList, IList + protected virtual void OnCollectionChanged(in SynchronizedViewChangedEventArgs args) { - static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count"); - static readonly Action raiseChangedEventInvoke = RaiseChangedEvent; + } - readonly ICollectionEventDispatcher eventDispatcher; - - public event NotifyCollectionChangedEventHandler? CollectionChanged; - public event PropertyChangedEventHandler? PropertyChanged; - - public NotifyCollectionChangedSynchronizedView(ISynchronizedView parent, ICollectionEventDispatcher? eventDispatcher) - : base(parent) + public TView this[int index] + { + get { - this.eventDispatcher = eventDispatcher ?? InlineCollectionEventDispatcher.Instance; - } - - protected override void OnCollectionChanged(in SynchronizedViewChangedEventArgs args) - { - if (CollectionChanged == null && PropertyChanged == null) return; - - switch (args.Action) + lock (gate) { - 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) + 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; + } +} + +internal class NotifyCollectionChangedSynchronizedView : + SynchronizedViewList, + 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 NotifyCollectionChangedSynchronizedView(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 }); - break; - case NotifyCollectionChangedAction.Replace: - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Replace, args.NewItem.View, args.OldItem.View, args.NewStartingIndex) + } + else + { + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewViews.ToArray(), args.NewStartingIndex) { Collection = this, Invoker = raiseChangedEventInvoke, IsInvokeCollectionChanged = true, - IsInvokePropertyChanged = false + IsInvokePropertyChanged = true }); - break; - case NotifyCollectionChangedAction.Move: - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Move, args.NewItem.View, args.NewStartingIndex, args.OldStartingIndex) + } + 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 = false + IsInvokePropertyChanged = true }); - break; - } - } - - static void RaiseChangedEvent(NotifyCollectionChangedEventArgs e) - { - var e2 = (CollectionEventDispatcherEventArgs)e; - var self = (NotifyCollectionChangedSynchronizedView)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 T || value == null && default(T) == 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) + else { - if (EqualityComparer.Default.Equals(listItem, item)) + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldViews.ToArray(), args.OldStartingIndex) { - return index; - } - index++; + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); } - } - 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(); + 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 = (NotifyCollectionChangedSynchronizedView)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 T || value == null && default(T) == 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(); + } } \ No newline at end of file diff --git a/tests/ObservableCollections.Tests/AlternateIndexListTest.cs b/tests/ObservableCollections.Tests/AlternateIndexListTest.cs index 28eda2f..52b00c4 100644 --- a/tests/ObservableCollections.Tests/AlternateIndexListTest.cs +++ b/tests/ObservableCollections.Tests/AlternateIndexListTest.cs @@ -97,4 +97,17 @@ public class AlternateIndexListTest list.TryGetAtAlternateIndex(4, out var baz).Should().BeTrue(); baz.Should().Be("new-baz"); } + + [Fact] + public void TryReplaceByValue() + { + var list = new AlternateIndexList(); + + list.Insert(0, "foo"); + list.Insert(2, "bar"); + list.Insert(4, "baz"); + + list.TryReplaceByValue("bar", "new-bar"); + list.GetIndexedValues().Should().Equal((0, "foo"), (2, "new-bar"), (4, "baz")); + } } From 90e6a542186628023bad095c12971c02b2ae60d9 Mon Sep 17 00:00:00 2001 From: neuecc Date: Mon, 2 Sep 2024 19:50:59 +0900 Subject: [PATCH 16/22] Add ObserveClear, ObserveReverse, ObserverSort --- .../ObservableCollectionR3Extensions.cs | 108 ++++++++++++++++-- .../NotifyCollectionChangedEventArgs.cs | 9 ++ .../ObservableListTest.cs | 6 +- 3 files changed, 108 insertions(+), 15 deletions(-) diff --git a/src/ObservableCollections.R3/ObservableCollectionR3Extensions.cs b/src/ObservableCollections.R3/ObservableCollectionR3Extensions.cs index 724e2b0..2b72713 100644 --- a/src/ObservableCollections.R3/ObservableCollectionR3Extensions.cs +++ b/src/ObservableCollections.R3/ObservableCollectionR3Extensions.cs @@ -34,17 +34,32 @@ public static class ObservableCollectionR3Extensions { return new ObservableCollectionReplace(source, cancellationToken); } - + public static Observable> ObserveMove(this IObservableCollection source, CancellationToken cancellationToken = default) { return new ObservableCollectionMove(source, cancellationToken); } - - public static Observable ObserveReset(this IObservableCollection source, CancellationToken cancellationToken = default) + + public static Observable> ObserveReset(this IObservableCollection source, CancellationToken cancellationToken = default) { return new ObservableCollectionReset(source, cancellationToken); } + public static Observable ObserveClear(this IObservableCollection source, CancellationToken cancellationToken = default) + { + return new ObservableCollectionClear(source, cancellationToken); + } + + public static Observable ObserveReverse(this IObservableCollection source, CancellationToken cancellationToken = default) + { + return new ObservableCollectionReverse(source, cancellationToken); + } + + public static Observable<(int Index, int Count, IComparer Comparer)> ObserveSort(this IObservableCollection source, CancellationToken cancellationToken = default) + { + return new ObservableCollectionSort(source, cancellationToken); + } + public static Observable ObserveCountChanged(this IObservableCollection source, bool notifyCurrentCount = false, CancellationToken cancellationToken = default) { return new ObservableCollectionCountChanged(source, notifyCurrentCount, cancellationToken); @@ -148,7 +163,7 @@ sealed class ObservableCollectionReplace(IObservableCollection collection, { return new _ObservableCollectionReplace(collection, observer, cancellationToken); } - + sealed class _ObservableCollectionReplace( IObservableCollection collection, Observer> observer, @@ -172,7 +187,7 @@ sealed class ObservableCollectionMove(IObservableCollection collection, Ca { return new _ObservableCollectionMove(collection, observer, cancellationToken); } - + sealed class _ObservableCollectionMove( IObservableCollection collection, Observer> observer, @@ -188,16 +203,39 @@ sealed class ObservableCollectionMove(IObservableCollection collection, Ca } } } - sealed class ObservableCollectionReset(IObservableCollection collection, CancellationToken cancellationToken) + : Observable> +{ + protected override IDisposable SubscribeCore(Observer> observer) + { + return new _ObservableCollectionReset(collection, observer, cancellationToken); + } + + sealed class _ObservableCollectionReset( + IObservableCollection collection, + Observer> observer, + CancellationToken cancellationToken) + : ObservableCollectionObserverBase>(collection, observer, cancellationToken) + { + protected override void Handler(in NotifyCollectionChangedEventArgs eventArgs) + { + if (eventArgs.Action == NotifyCollectionChangedAction.Reset) + { + observer.OnNext(eventArgs.SortOperation); + } + } + } +} + +sealed class ObservableCollectionClear(IObservableCollection collection, CancellationToken cancellationToken) : Observable { protected override IDisposable SubscribeCore(Observer observer) { - return new _ObservableCollectionReset(collection, observer, cancellationToken); + return new _ObservableCollectionClear(collection, observer, cancellationToken); } - - sealed class _ObservableCollectionReset( + + sealed class _ObservableCollectionClear( IObservableCollection collection, Observer observer, CancellationToken cancellationToken) @@ -205,7 +243,7 @@ sealed class ObservableCollectionReset(IObservableCollection collection, C { protected override void Handler(in NotifyCollectionChangedEventArgs eventArgs) { - if (eventArgs.Action == NotifyCollectionChangedAction.Reset) + if (eventArgs.Action == NotifyCollectionChangedAction.Reset && eventArgs.SortOperation.IsNull) { observer.OnNext(Unit.Default); } @@ -213,6 +251,52 @@ sealed class ObservableCollectionReset(IObservableCollection collection, C } } +sealed class ObservableCollectionReverse(IObservableCollection collection, CancellationToken cancellationToken) : Observable +{ + protected override IDisposable SubscribeCore(Observer observer) + { + return new _ObservableCollectionReverse(collection, observer, cancellationToken); + } + + sealed class _ObservableCollectionReverse( + IObservableCollection collection, + Observer observer, + CancellationToken cancellationToken) + : ObservableCollectionObserverBase(collection, observer, cancellationToken) + { + protected override void Handler(in NotifyCollectionChangedEventArgs eventArgs) + { + if (eventArgs.Action == NotifyCollectionChangedAction.Reset && eventArgs.SortOperation.IsReverse) + { + observer.OnNext(Unit.Default); + } + } + } +} + +sealed class ObservableCollectionSort(IObservableCollection collection, CancellationToken cancellationToken) : Observable<(int Index, int Count, IComparer Comparer)> +{ + protected override IDisposable SubscribeCore(Observer<(int Index, int Count, IComparer Comparer)> observer) + { + return new _ObservableCollectionSort(collection, observer, cancellationToken); + } + + sealed class _ObservableCollectionSort( + IObservableCollection collection, + Observer<(int Index, int Count, IComparer Comparer)> observer, + CancellationToken cancellationToken) + : ObservableCollectionObserverBase Comparer)>(collection, observer, cancellationToken) + { + protected override void Handler(in NotifyCollectionChangedEventArgs eventArgs) + { + if (eventArgs.Action == NotifyCollectionChangedAction.Reset && eventArgs.SortOperation.IsSort) + { + observer.OnNext(eventArgs.SortOperation.AsTuple()); + } + } + } +} + sealed class ObservableCollectionCountChanged(IObservableCollection collection, bool notifyCurrentCount, CancellationToken cancellationToken) : Observable { @@ -220,7 +304,7 @@ sealed class ObservableCollectionCountChanged(IObservableCollection collec { return new _ObservableCollectionCountChanged(collection, notifyCurrentCount, observer, cancellationToken); } - + sealed class _ObservableCollectionCountChanged : ObservableCollectionObserverBase { int countPrev; @@ -372,7 +456,7 @@ abstract class ObservableCollectionObserverBase : IDisposable this.handlerDelegate = Handler; collection.CollectionChanged += handlerDelegate; - + if (cancellationToken.CanBeCanceled) { cancellationTokenRegistration = cancellationToken.UnsafeRegister(static state => diff --git a/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs b/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs index 05b13fd..4ad53a0 100644 --- a/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs +++ b/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; namespace ObservableCollections @@ -16,6 +17,9 @@ 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) { Index = index; @@ -23,6 +27,11 @@ namespace ObservableCollections Comparer = comparer ?? NullComparerSentinel.Instance; } + public (int Index, int Count, IComparer Comparer) AsTuple() + { + return (Index, Count, Comparer!); + } + public static SortOperation CreateReverse(int index, int count) { return new SortOperation(index, count, ReverseSentinel.Instance); diff --git a/tests/ObservableCollections.Tests/ObservableListTest.cs b/tests/ObservableCollections.Tests/ObservableListTest.cs index 77e5b1a..f6b867b 100644 --- a/tests/ObservableCollections.Tests/ObservableListTest.cs +++ b/tests/ObservableCollections.Tests/ObservableListTest.cs @@ -157,8 +157,8 @@ namespace ObservableCollections.Tests // list.AddRange(new[] { 10, 21, 30, 44 }); // var filter1 = new TestFilter((x, v) => x % 2 == 0); - // view1.AttachFilter(filter1); - + // view1.AttachFilter((x, v) => x % 2 == 0)); + // filter1.CalledOnCollectionChanged[0].Action.Should().Be(NotifyCollectionChangedAction.Add); // filter1.CalledOnCollectionChanged[0].NewValue.Should().Be(10); // filter1.CalledOnCollectionChanged[1].Action.Should().Be(NotifyCollectionChangedAction.Add); @@ -170,6 +170,6 @@ namespace ObservableCollections.Tests // filter1.CalledWhenTrue.Count.Should().Be(3); // filter1.CalledWhenFalse.Count.Should().Be(1); - //} + //} } } \ No newline at end of file From a981b3121f2274f5d15855dc65abf08bf3eb73ed Mon Sep 17 00:00:00 2001 From: neuecc Date: Mon, 2 Sep 2024 19:55:13 +0900 Subject: [PATCH 17/22] merge master --- src/ObservableCollections/IObservableCollection.cs | 1 - src/ObservableCollections/SynchronizedViewList.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ObservableCollections/IObservableCollection.cs b/src/ObservableCollections/IObservableCollection.cs index b4cf153..0568de1 100644 --- a/src/ObservableCollections/IObservableCollection.cs +++ b/src/ObservableCollections/IObservableCollection.cs @@ -49,6 +49,5 @@ namespace ObservableCollections public interface INotifyCollectionChangedSynchronizedView : IReadOnlyCollection, INotifyCollectionChanged, INotifyPropertyChanged, IDisposable { - void Refresh(); } } \ No newline at end of file diff --git a/src/ObservableCollections/SynchronizedViewList.cs b/src/ObservableCollections/SynchronizedViewList.cs index ab1b445..fe78868 100644 --- a/src/ObservableCollections/SynchronizedViewList.cs +++ b/src/ObservableCollections/SynchronizedViewList.cs @@ -309,7 +309,7 @@ internal class NotifyCollectionChangedSynchronizedView : static bool IsCompatibleObject(object? value) { - return value is T || value == null && default(T) == null; + return value is TView || value == null && default(TView) == null; } public bool IsReadOnly => true; From d84965e20e70d1f71c8fb708af2772185675ed04 Mon Sep 17 00:00:00 2001 From: neuecc Date: Mon, 2 Sep 2024 21:19:15 +0900 Subject: [PATCH 18/22] optimize for ObservableList --- sandbox/WpfApp/MainWindow.xaml | 6 + sandbox/WpfApp/MainWindow.xaml.cs | 48 +- .../ObservableCollectionR3Extensions.cs | 12 +- .../AlternateIndexList.cs | 31 +- .../NotifyCollectionChangedEventArgs.cs | 4 +- .../ObservableDictionary.Views.cs | 2 +- .../ObservableHashSet.Views.cs | 2 +- .../ObservableList.Views.cs | 23 +- src/ObservableCollections/ObservableList.cs | 8 +- .../ObservableQueue.Views.cs | 2 +- .../ObservableRingBuffer.Views.cs | 2 +- .../ObservableStack.Views.cs | 2 +- .../SynchronizedViewChangedEventArgs.cs | 50 ++ .../SynchronizedViewList.cs | 547 +++++++++++++++++- 14 files changed, 668 insertions(+), 71 deletions(-) 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 @@ + + + @foreach (var item in ItemsView) + { + + + + } +
@item
``` -WPF(XAML based UI platforms) +WPF/Avalonia/WinUI (XAML based UI platforms) --- Because of data binding in WPF, it is important that the collection is Observable. ObservableCollections high-performance `IObservableCollection` cannot be bind to WPF. Call `ToNotifyCollectionChanged()` to convert it to `INotifyCollectionChanged`. Also, although ObservableCollections and Views are thread-safe, the WPF UI does not support change notifications from different threads. To`ToNotifyCollectionChanged(IColllectionEventDispatcher)` allows multi thread changed. @@ -214,7 +317,7 @@ Because of data binding in WPF, it is important that the collection is Observabl // WPF simple sample. ObservableList list; -public INotifyCollectionChangedSynchronizedView ItemsView { get; set; } +public INotifyCollectionChangedSynchronizedViewList ItemsView { get; set; } public MainWindow() { @@ -224,10 +327,10 @@ public MainWindow() list = new ObservableList(); // for ui synchronization safety of viewmodel - ItemsView = list.CreateView(x => x).ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current); + ItemsView = list.ToNotifyCollectionChanged(SynchronizationContextCollectionEventDispatcher.Current); // if collection is changed only from ui-thread, can use this overload - // ItemsView = list.CreateView(x => x).ToNotifyCollectionChanged(); + // ItemsView = list.ToNotifyCollectionChanged(); } protected override void OnClosed(EventArgs e) @@ -236,8 +339,6 @@ protected override void OnClosed(EventArgs e) } ``` -> WPF can not use SortedView because SortedView can not provide sort event to INotifyCollectionChanged. - `SynchronizationContextCollectionEventDispatcher.Current` is default implementation of `IColllectionEventDispatcher`, it is used `SynchronizationContext.Current` for dispatche ui thread. You can create custom `ICollectionEventDispatcher` to use custom dispatcher object. For example use WPF Dispatcher: ```csharp @@ -258,12 +359,9 @@ Unity --- In Unity projects, you can installing `ObservableCollections` with [NugetForUnity](https://github.com/GlitchEnzo/NuGetForUnity). If R3 integration is required, similarly install `ObservableCollections.R3` via NuGetForUnity. -In Unity, ObservableCollections and Views are useful as CollectionManagers, since they need to convert T to Prefab for display. - -Since we need to have side effects on GameObjects, we will prepare a filter and apply an action on changes. +In Unity, ObservableCollections and Views are useful as CollectionManagers, since they need to convert T to Prefab for display. Since View objects are generated only once, it's possible to complement GameObjects tied to the collection. ```csharp -// Unity, with filter sample. public class SampleScript : MonoBehaviour { public Button prefab; @@ -280,186 +378,187 @@ public class SampleScript : MonoBehaviour item.GetComponentInChildren().text = x.ToString(); return item.gameObject; }); - view.AttachFilter(new GameObjectFilter(root)); + view.ViewChanged += View_ViewChanged; + } + + void View_ViewChanged(in SynchronizedViewChangedEventArgs eventArgs) + { + if (eventArgs.Action == NotifyCollectionChangedAction.Add) + { + eventArgs.NewItem.View.transform.SetParent(root.transform); + } + else if (NotifyCollectionChangedAction.Remove) + { + GameObject.Destroy(eventArgs.OldItem.View); + } } void OnDestroy() { view.Dispose(); } +} +``` - public class GameObjectFilter : ISynchronizedViewFilter +Reference +--- +ObservableCollections provides these collections. + +```csharp +class ObservableList : IList, IReadOnlyList, IObservableCollection, IReadOnlyObservableList +class ObservableDictionary : IDictionary, IReadOnlyDictionary, IObservableCollection> where TKey : notnull +class ObservableHashSet : IReadOnlySet, IReadOnlyCollection, IObservableCollection where T : notnull +class ObservableQueue : IReadOnlyCollection, IObservableCollection +class ObservableStack : IReadOnlyCollection, IObservableCollection +class ObservableRingBuffer : IList, IReadOnlyList, IObservableCollection +class RingBuffer : IList, IReadOnlyList +class ObservableFixedSizeRingBuffer : IList, IReadOnlyList, IObservableCollection +class AlternateIndexList : IEnumerable +``` + +The `IObservableCollection` is the base interface for all, containing the `CollectionChanged` event and the `CreateView` method. + +```csharp +public delegate void NotifyCollectionChangedEventHandler(in NotifyCollectionChangedEventArgs e); + +public interface IObservableCollection : IReadOnlyCollection +{ + object SyncRoot { get; } + event NotifyCollectionChangedEventHandler? CollectionChanged; + ISynchronizedView CreateView(Func transform); +} +``` + +The notification event `NotifyCollectionChangedEventArgs` has the following definition: + +```csharp +/// +/// Contract: +/// IsSingleItem ? (NewItem, OldItem) : (NewItems, OldItems) +/// Action.Add +/// NewItem, NewItems, NewStartingIndex +/// Action.Remove +/// OldItem, OldItems, OldStartingIndex +/// Action.Replace +/// NewItem, NewItems, OldItem, OldItems, (NewStartingIndex, OldStartingIndex = samevalue) +/// Action.Move +/// NewStartingIndex, OldStartingIndex +/// Action.Reset +/// SortOperation(IsClear, IsReverse, IsSort) +/// +[StructLayout(LayoutKind.Auto)] +public readonly ref struct NotifyCollectionChangedEventArgs +{ + public readonly NotifyCollectionChangedAction Action; + public readonly bool IsSingleItem; + public readonly T NewItem; + public readonly T OldItem; + public readonly ReadOnlySpan NewItems; + public readonly ReadOnlySpan OldItems; + public readonly int NewStartingIndex; + public readonly int OldStartingIndex; + public readonly SortOperation SortOperation; +} +``` + +This is the interface for View: + +```csharp +public delegate void NotifyViewChangedEventHandler(in SynchronizedViewChangedEventArgs e); + +public interface ISynchronizedView : IReadOnlyCollection, IDisposable +{ + object SyncRoot { get; } + ISynchronizedViewFilter Filter { get; } + IEnumerable<(T Value, TView View)> Filtered { get; } + IEnumerable<(T Value, TView View)> Unfiltered { get; } + int UnfilteredCount { get; } + + event NotifyViewChangedEventHandler? ViewChanged; + event Action? CollectionStateChanged; + + void AttachFilter(ISynchronizedViewFilter filter); + void ResetFilter(); + ISynchronizedViewList ToViewList(); + INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(); + INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher); +} +``` + +The `Count` of the View returns the filtered value, but if you need the unfiltered value, use `UnfilteredCount`. Also, normal enumeration returns only `TView`, but if you need `T` or want to enumerate pre-filtered values, you can get them with `Filtered` and `Unfiltered`. + +The View's notification event `SynchronizedViewChangedEventArgs` has the following definition: + +```csharp +public readonly ref struct SynchronizedViewChangedEventArgs +{ + public readonly NotifyCollectionChangedAction Action; + public readonly bool IsSingleItem; + public readonly (T Value, TView View) NewItem; + public readonly (T Value, TView View) OldItem; + public readonly ReadOnlySpan NewValues; + public readonly ReadOnlySpan NewViews; + public readonly ReadOnlySpan OldValues; + public readonly ReadOnlySpan OldViews; + public readonly int NewStartingIndex; + public readonly int OldStartingIndex; + public readonly SortOperation SortOperation; +} +``` + +When `NotifyCollectionChangedAction` is `Reset`, additional determination can be made with `SortOperation`. + +```csharp +public readonly struct SortOperation +{ + public readonly int Index; + public readonly int Count; + public readonly IComparer? Comparer; + + public bool IsReverse { get; } + public bool IsClear { get; } + public bool IsSort { get; } +} +``` + +When `IsReverse` is true, you need to use `Index` and `Count`. When `IsSort` is true, you need to use `Index`, `Count`, and `Comparer` values. + +For Filter, you can either create one that implements this interface or generate one from a lambda expression using extension methods. + +```csharp +public interface ISynchronizedViewFilter +{ + bool IsMatch(T value); +} + +public static class SynchronizedViewExtensions +{ + public static void AttachFilter(this ISynchronizedView source, Func filter) { - readonly GameObject root; - - public GameObjectFilter(GameObject root) - { - this.root = root; - } - - public void OnCollectionChanged(in SynchronizedViewChangedEventArgs eventArgs) - { - if (eventArgs.Action == NotifyCollectionChangedAction.Add) - { - eventArgs.NewView.transform.SetParent(root.transform); - } - else if (NotifyCollectionChangedAction.Remove) - { - GameObject.Destroy(eventArgs.OldView); - } - } - - public bool IsMatch(int value, GameObject view) - { - return true; - } - - public void WhenTrue(int value, GameObject view) - { - view.SetActive(true); - } - - public void WhenFalse(int value, GameObject view) - { - view.SetActive(false); - } } } ``` -It is also possible to manage Order by managing indexes inserted from eventArgs, but it is very difficult with many caveats. If you don't have major performance issues, you can foreach the View itself on CollectionStateChanged (like Blazor) and reorder the transforms. If you have such a architecture, you can also use SortedView. - -View/SortedView ---- -View can create from `IObservableCollection`, it completely synchronized and thread-safe. +Here are definitions for other collections: ```csharp -public interface IObservableCollection : IReadOnlyCollection +public interface IReadOnlyObservableList : + IReadOnlyList, IObservableCollection { - // snip... - ISynchronizedView CreateView(Func transform, bool reverse = false); -} -``` - -When reverse = true, foreach view as reverse order(Dictionary, etc. are not supported). - -`ISynchronizedView` is `IReadOnlyCollection` and hold both value and view(transformed value when added). - -```csharp -public interface ISynchronizedView : IReadOnlyCollection<(T Value, TView View)>, IDisposable -{ - object SyncRoot { get; } - - event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; - event Action? CollectionStateChanged; - - void AttachFilter(ISynchronizedViewFilter filter); - void ResetFilter(Action? resetAction); - INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(); -} -``` - - - -see [filter](#filter) section. - - - -```csharp -var view = transform(value); -if (filter.IsMatch(value, view)) -{ - filter.WhenTrue(value, view); -} -else -{ - filter.WhenFalse(value, view); -} -AddToCollectionInnerStructure(value, view); -filter.OnCollectionChanged(ChangeKind.Add, value, view, eventArgs); -RoutingCollectionChanged(eventArgs); -CollectionStateChanged(); -``` - - -```csharp -public static ISynchronizedView CreateSortedView(this IObservableCollection source, Func identitySelector, Func transform, IComparer comparer) - where TKey : notnull - -public static ISynchronizedView CreateSortedView(this IObservableCollection source, Func identitySelector, Func transform, IComparer viewComparer) - where TKey : notnull - -public static ISynchronizedView CreateSortedView(this IObservableCollection source, Func identitySelector, Func transform, Func compareSelector, bool ascending = true) - where TKey : notnull -``` - -> Notice: foreach ObservableCollections and Views are thread-safe but it uses lock at iterating. In other words, the obtained Enumerator must be Dispose. foreach and LINQ are guaranteed to be Dispose, but be careful when you extract the Enumerator by yourself. - -Filter ---- - -```csharp -public interface ISynchronizedViewFilter -{ - bool IsMatch(T value, TView view); - void WhenTrue(T value, TView view); - void WhenFalse(T value, TView view); - void OnCollectionChanged(in SynchronizedViewChangedEventArgs eventArgs); } -public readonly struct SynchronizedViewChangedEventArgs +public interface IReadOnlyObservableDictionary : + IReadOnlyDictionary, IObservableCollection> { - 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; -} -``` - - -Collections ---- - -```csharp -public sealed partial class ObservableDictionary : IDictionary, IReadOnlyDictionary, IObservableCollection> where TKey : notnull -public sealed partial class ObservableFixedSizeRingBuffer : IList, IReadOnlyList, IObservableCollection -public sealed partial class ObservableHashSet : IReadOnlySet, IReadOnlyCollection, IObservableCollection where T : notnull - -public sealed partial class ObservableHashSet : IReadOnlySet, IReadOnlyCollection, IObservableCollection - where T : notnull - -public sealed partial class ObservableList : IList, IReadOnlyList, IObservableCollection - -public sealed partial class ObservableQueue : IReadOnlyCollection, IObservableCollection -public sealed partial class ObservableRingBuffer : IList, IReadOnlyList, IObservableCollection - -public sealed partial class ObservableStack : IReadOnlyCollection, IObservableCollection - -public sealed class RingBuffer : IList, IReadOnlyList -``` - -Freezed ---- - - -```csharp -public sealed class FreezedList : IReadOnlyList, IFreezedCollection -public sealed class FreezedDictionary : IReadOnlyDictionary, IFreezedCollection> where TKey : notnull - - -public interface IFreezedCollection -{ - ISynchronizedView CreateView(Func transform, bool reverse = false); - ISortableSynchronizedView CreateSortableView(Func transform); } -public static ISortableSynchronizedView CreateSortableView(this IFreezedCollection source, Func transform, IComparer initialSort) -public static ISortableSynchronizedView CreateSortableView(this IFreezedCollection source, Func transform, IComparer initialViewSort) -public static ISortableSynchronizedView CreateSortableView(this IFreezedCollection source, Func transform, Func initialCompareSelector, bool ascending = true) -public static void Sort(this ISortableSynchronizedView source, Func compareSelector, bool ascending = true) +public interface ISynchronizedViewList : IReadOnlyList, IDisposable +{ +} + +public interface INotifyCollectionChangedSynchronizedViewList : ISynchronizedViewList, INotifyCollectionChanged, INotifyPropertyChanged +{ +} ``` License diff --git a/docs/assets.pptx b/docs/assets.pptx index bc1016cf77434579e268f8868822f7439e869efd..1a61a6388c290f20a1bc59a0cea645b9c09eee2a 100644 GIT binary patch delta 11877 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 observableList { get; } = new ObservableList(); - public INotifyCollectionChangedSynchronizedView ItemsView { get; } + public INotifyCollectionChangedSynchronizedViewList ItemsView { get; } public ReactiveCommand AddCommand { get; } = new ReactiveCommand(); public ReactiveCommand ClearCommand { get; } = new ReactiveCommand(); diff --git a/sandbox/BlazorApp/Pages/Index.razor.cs b/sandbox/BlazorApp/Pages/Index.razor.cs index d79fbb5..29188e2 100644 --- a/sandbox/BlazorApp/Pages/Index.razor.cs +++ b/sandbox/BlazorApp/Pages/Index.razor.cs @@ -7,7 +7,7 @@ public partial class Index : IDisposable { ObservableList list; public ISynchronizedView ItemsView { get; set; } - int adder = 99; + int count = 99; protected override void OnInitialized() { @@ -20,19 +20,13 @@ public partial class Index : IDisposable }; } + void OnClick() + { + list.Add(count++); + } + public void Dispose() { ItemsView.Dispose(); } - - - - - void OnClick() - { - ThreadPool.QueueUserWorkItem(_ => - { - list.Add(adder++); - }); - } } diff --git a/sandbox/ConsoleApp/Program.cs b/sandbox/ConsoleApp/Program.cs index c13a736..f547f51 100644 --- a/sandbox/ConsoleApp/Program.cs +++ b/sandbox/ConsoleApp/Program.cs @@ -3,43 +3,38 @@ using System.Collections.Specialized; using R3; using System.Linq; using ObservableCollections; +using System.Collections; +using System.Collections.Generic; -var list = new ObservableList(); -list.ObserveAdd() - .Subscribe(x => +// Queue <-> List Synchronization +var queue = new ObservableQueue(); + +queue.Enqueue(1); +queue.Enqueue(10); +queue.Enqueue(100); +queue.Enqueue(1000); +queue.Enqueue(10000); + +using var view = queue.CreateView(x => x.ToString() + "$"); + +using var viewList = view.ToViewList(); + +Console.WriteLine(viewList[2]); // 100$ + + +view.ViewChanged += View_ViewChanged; + +void View_ViewChanged(in SynchronizedViewChangedEventArgs eventArgs) +{ + if (eventArgs.Action == NotifyCollectionChangedAction.Add) { - Console.WriteLine(x); - }); + // eventArgs.OldItem.View. + } -list.Add(10); -list.Add(20); -list.AddRange(new[] { 10, 20, 30 }); - - - - - - - - -var models = new ObservableList(Enumerable.Range(0, 10)); - -var viewModels = models.CreateView(x => new ViewModel -{ - Id = x, - Value = "@" + x -}); - -viewModels.AttachFilter(new HogeFilter()); - -models.Add(100); - -foreach (var x in viewModels) -{ - System.Console.WriteLine(x); + throw new NotImplementedException(); } class ViewModel diff --git a/sandbox/WpfApp/MainWindow.xaml.cs b/sandbox/WpfApp/MainWindow.xaml.cs index 8ba517b..148e950 100644 --- a/sandbox/WpfApp/MainWindow.xaml.cs +++ b/sandbox/WpfApp/MainWindow.xaml.cs @@ -74,7 +74,7 @@ namespace WpfApp public class ViewModel { private ObservableList observableList { get; } = new ObservableList(); - public INotifyCollectionChangedSynchronizedView ItemsView { get; } + public INotifyCollectionChangedSynchronizedViewList ItemsView { get; } public ReactiveCommand AddCommand { get; } = new ReactiveCommand(); public ReactiveCommand InsertAtRandomCommand { get; } = new ReactiveCommand(); public ReactiveCommand RemoveAtRandomCommand { get; } = new ReactiveCommand(); diff --git a/src/ObservableCollections.R3/ObservableCollectionR3Extensions.cs b/src/ObservableCollections.R3/ObservableCollectionR3Extensions.cs index 1b31df7..67d5da7 100644 --- a/src/ObservableCollections.R3/ObservableCollectionR3Extensions.cs +++ b/src/ObservableCollections.R3/ObservableCollectionR3Extensions.cs @@ -10,6 +10,22 @@ public readonly record struct CollectionAddEvent(int Index, T Value); public readonly record struct CollectionRemoveEvent(int Index, T Value); public readonly record struct CollectionReplaceEvent(int Index, T OldValue, T NewValue); public readonly record struct CollectionMoveEvent(int OldIndex, int NewIndex, T Value); +public readonly record struct CollectionResetEvent +{ + readonly SortOperation sortOperation; + + public bool IsClear => sortOperation.IsClear; + public bool IsSort => sortOperation.IsSort; + public bool IsReverse => sortOperation.IsReverse; + public int Index => sortOperation.Index; + public int Count => sortOperation.Count; + public IComparer? Comparer => sortOperation.Comparer; + + public CollectionResetEvent(SortOperation sortOperation) + { + this.sortOperation = sortOperation; + } +} public readonly record struct DictionaryAddEvent(TKey Key, TValue Value); @@ -40,7 +56,7 @@ public static class ObservableCollectionR3Extensions return new ObservableCollectionMove(source, cancellationToken); } - public static Observable> ObserveReset(this IObservableCollection source, CancellationToken cancellationToken = default) + public static Observable> ObserveReset(this IObservableCollection source, CancellationToken cancellationToken = default) { return new ObservableCollectionReset(source, cancellationToken); } @@ -204,24 +220,24 @@ sealed class ObservableCollectionMove(IObservableCollection collection, Ca } } sealed class ObservableCollectionReset(IObservableCollection collection, CancellationToken cancellationToken) - : Observable> + : Observable> { - protected override IDisposable SubscribeCore(Observer> observer) + protected override IDisposable SubscribeCore(Observer> observer) { return new _ObservableCollectionReset(collection, observer, cancellationToken); } sealed class _ObservableCollectionReset( IObservableCollection collection, - Observer> observer, + Observer> observer, CancellationToken cancellationToken) - : ObservableCollectionObserverBase>(collection, observer, cancellationToken) + : ObservableCollectionObserverBase>(collection, observer, cancellationToken) { protected override void Handler(in NotifyCollectionChangedEventArgs eventArgs) { if (eventArgs.Action == NotifyCollectionChangedAction.Reset) { - observer.OnNext(eventArgs.SortOperation); + observer.OnNext(new CollectionResetEvent(eventArgs.SortOperation)); } } } @@ -243,7 +259,7 @@ sealed class ObservableCollectionClear(IObservableCollection collection, C { protected override void Handler(in NotifyCollectionChangedEventArgs eventArgs) { - if (eventArgs.Action == NotifyCollectionChangedAction.Reset && eventArgs.SortOperation.IsNull) + if (eventArgs.Action == NotifyCollectionChangedAction.Reset && eventArgs.SortOperation.IsClear) { observer.OnNext(Unit.Default); } diff --git a/src/ObservableCollections/IObservableCollection.cs b/src/ObservableCollections/IObservableCollection.cs index 629627e..5ef0f92 100644 --- a/src/ObservableCollections/IObservableCollection.cs +++ b/src/ObservableCollections/IObservableCollection.cs @@ -39,15 +39,15 @@ namespace ObservableCollections void AttachFilter(ISynchronizedViewFilter filter); void ResetFilter(); ISynchronizedViewList ToViewList(); - INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(); - INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher); + INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(); + INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher); } public interface ISynchronizedViewList : IReadOnlyList, IDisposable { } - public interface INotifyCollectionChangedSynchronizedView : IReadOnlyCollection, INotifyCollectionChanged, INotifyPropertyChanged, IDisposable + public interface INotifyCollectionChangedSynchronizedViewList : ISynchronizedViewList, INotifyCollectionChanged, INotifyPropertyChanged { } @@ -64,20 +64,25 @@ namespace ObservableCollections return new NonFilteredSynchronizedViewList(collection.CreateView(transform)); } - public static INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(this IObservableCollection collection) + public static INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(this IObservableCollection collection) { return ToNotifyCollectionChanged(collection, null); } - public static INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(this IObservableCollection collection, ICollectionEventDispatcher? collectionEventDispatcher) + public static INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(this IObservableCollection collection, ICollectionEventDispatcher? collectionEventDispatcher) { return ToNotifyCollectionChanged(collection, static x => x, collectionEventDispatcher); } - public static INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(this IObservableCollection collection, Func transform, ICollectionEventDispatcher? collectionEventDispatcher) + public static INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(this IObservableCollection collection, Func transform) + { + return ToNotifyCollectionChanged(collection, transform, null!); + } + + public static INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(this IObservableCollection collection, Func transform, ICollectionEventDispatcher? collectionEventDispatcher) { // Optimized for non filtered - return new NonFilteredNotifyCollectionChangedSynchronizedView(collection.CreateView(transform), collectionEventDispatcher); + return new NonFilteredNotifyCollectionChangedSynchronizedViewList(collection.CreateView(transform), collectionEventDispatcher); } } } \ No newline at end of file diff --git a/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs b/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs index 45b5fdb..790c606 100644 --- a/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs +++ b/src/ObservableCollections/NotifyCollectionChangedEventArgs.cs @@ -15,8 +15,8 @@ namespace ObservableCollections public readonly IComparer? Comparer; public bool IsReverse => Comparer == ReverseSentinel.Instance; - public bool IsNull => Comparer == null; - public bool IsSort => !IsNull && !IsReverse; + public bool IsClear => Comparer == null; + public bool IsSort => !IsClear && !IsReverse; public SortOperation(int index, int count, IComparer? comparer) { @@ -68,7 +68,7 @@ namespace ObservableCollections /// Action.Move /// NewStartingIndex, OldStartingIndex /// Action.Reset - /// SortOperation(IsNull, IsReverse, Comparer) + /// SortOperation(IsClear, IsReverse, Comparer) /// [StructLayout(LayoutKind.Auto)] public readonly ref struct NotifyCollectionChangedEventArgs diff --git a/src/ObservableCollections/ObservableDictionary.Views.cs b/src/ObservableCollections/ObservableDictionary.Views.cs index 8ce2992..4f0f77f 100644 --- a/src/ObservableCollections/ObservableDictionary.Views.cs +++ b/src/ObservableCollections/ObservableDictionary.Views.cs @@ -113,14 +113,14 @@ namespace ObservableCollections return new FiltableSynchronizedViewList, TView>(this); } - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() + public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() { - return new NotifyCollectionChangedSynchronizedView, TView>(this, null); + return new NotifyCollectionChangedSynchronizedViewList, TView>(this, null); } - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) + public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) { - return new NotifyCollectionChangedSynchronizedView, TView>(this, collectionEventDispatcher); + return new NotifyCollectionChangedSynchronizedViewList, TView>(this, collectionEventDispatcher); } public IEnumerator GetEnumerator() diff --git a/src/ObservableCollections/ObservableHashSet.Views.cs b/src/ObservableCollections/ObservableHashSet.Views.cs index f24fa55..8388138 100644 --- a/src/ObservableCollections/ObservableHashSet.Views.cs +++ b/src/ObservableCollections/ObservableHashSet.Views.cs @@ -108,14 +108,14 @@ namespace ObservableCollections return new FiltableSynchronizedViewList(this); } - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() + public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() { - return new NotifyCollectionChangedSynchronizedView(this, null); + return new NotifyCollectionChangedSynchronizedViewList(this, null); } - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) + public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) { - return new NotifyCollectionChangedSynchronizedView(this, collectionEventDispatcher); + return new NotifyCollectionChangedSynchronizedViewList(this, collectionEventDispatcher); } public IEnumerator GetEnumerator() diff --git a/src/ObservableCollections/ObservableList.OptimizeView.cs b/src/ObservableCollections/ObservableList.OptimizeView.cs new file mode 100644 index 0000000..15c5ce0 --- /dev/null +++ b/src/ObservableCollections/ObservableList.OptimizeView.cs @@ -0,0 +1,284 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Text; + +namespace ObservableCollections; + +public sealed partial class ObservableList : IList, IReadOnlyObservableList +{ + // override extension methods(IObservableCollection.cs ObservableCollectionExtensions) + + //public ISynchronizedViewList ToViewList(this IObservableCollection collection) + //{ + // return ToViewList(collection, static x => x); + //} + + //public static ISynchronizedViewList ToViewList(this IObservableCollection collection, Func transform) + //{ + // return new NonFilteredSynchronizedViewList(collection.CreateView(transform)); + //} + + public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() + { + return new ObservableListSynchronizedViewList(this, null); + } + + public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) + { + return new ObservableListSynchronizedViewList(this, collectionEventDispatcher); + } + + //public static INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(this IObservableCollection collection, Func transform) + //{ + // return ToNotifyCollectionChanged(collection, transform, null!); + //} + + //public static INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(this IObservableCollection collection, Func transform, ICollectionEventDispatcher? collectionEventDispatcher) + //{ + // return new NonFilteredNotifyCollectionChangedSynchronizedViewList(collection.CreateView(transform), collectionEventDispatcher); + //} +} + +internal sealed class ObservableListSynchronizedViewList : INotifyCollectionChangedSynchronizedViewList, IList, IList +{ + static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count"); + static readonly Action raiseChangedEventInvoke = RaiseChangedEvent; + + readonly ObservableList parent; + readonly ICollectionEventDispatcher eventDispatcher; + + public event NotifyCollectionChangedEventHandler? CollectionChanged; + public event PropertyChangedEventHandler? PropertyChanged; + + public ObservableListSynchronizedViewList(ObservableList parent, ICollectionEventDispatcher? eventDispatcher) + { + this.parent = parent; + this.eventDispatcher = eventDispatcher ?? InlineCollectionEventDispatcher.Instance; + parent.CollectionChanged += Parent_CollectionChanged; + } + + private void Parent_CollectionChanged(in NotifyCollectionChangedEventArgs args) + { + if (CollectionChanged == null && PropertyChanged == null) return; + + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + if (args.IsSingleItem) + { + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewItem, args.NewStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + } + else + { + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewItems.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, args.OldStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + } + else + { + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldItems.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, args.OldItem, args.NewStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = false + }); + break; + case NotifyCollectionChangedAction.Move: + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Move, args.NewItem, args.NewStartingIndex, args.OldStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = false + }); + break; + } + } + + static void RaiseChangedEvent(NotifyCollectionChangedEventArgs e) + { + var e2 = (CollectionEventDispatcherEventArgs)e; + var self = (ObservableListSynchronizedViewList)e2.Collection; + + if (e2.IsInvokeCollectionChanged) + { + self.CollectionChanged?.Invoke(self, e); + } + if (e2.IsInvokePropertyChanged) + { + self.PropertyChanged?.Invoke(self, CountPropertyChangedEventArgs); + } + } + + public T this[int index] => parent[index]; + + public int Count => parent.Count; + + public IEnumerator GetEnumerator() + { + return parent.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return parent.GetEnumerator(); + } + + public void Dispose() + { + parent.CollectionChanged -= Parent_CollectionChanged; + } + + // IList, IList implementation + + T 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 T || value == null && default(T) == null; + } + + public bool IsReadOnly => true; + + public bool IsFixedSize => false; + + public bool IsSynchronized => true; + + public object SyncRoot => parent.SyncRoot; + + public void Add(T item) + { + throw new NotSupportedException(); + } + + public int Add(object? value) + { + throw new NotImplementedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(T item) + { + return parent.Contains(item); + } + + public bool Contains(object? value) + { + if (IsCompatibleObject(value)) + { + return Contains((T)value!); + } + return false; + } + + public void CopyTo(T[] array, int arrayIndex) + { + throw new NotSupportedException(); + } + + public void CopyTo(Array array, int index) + { + throw new NotImplementedException(); + } + + public int IndexOf(T item) + { + return parent.IndexOf(item); + } + + public int IndexOf(object? item) + { + if (IsCompatibleObject(item)) + { + return IndexOf((T)item!); + } + return -1; + } + + public void Insert(int index, T item) + { + throw new NotSupportedException(); + } + + public void Insert(int index, object? value) + { + throw new NotImplementedException(); + } + + public bool Remove(T item) + { + throw new NotSupportedException(); + } + + public void Remove(object? value) + { + throw new NotImplementedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index c965ee3..fad0903 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -111,14 +111,14 @@ namespace ObservableCollections return new FiltableSynchronizedViewList(this); } - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() + public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() { - return new NotifyCollectionChangedSynchronizedView(this, null); + return new NotifyCollectionChangedSynchronizedViewList(this, null); } - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) + public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) { - return new NotifyCollectionChangedSynchronizedView(this, collectionEventDispatcher); + return new NotifyCollectionChangedSynchronizedViewList(this, collectionEventDispatcher); } public IEnumerator GetEnumerator() @@ -273,7 +273,7 @@ namespace ObservableCollections } break; case NotifyCollectionChangedAction.Reset: - if (e.SortOperation.IsNull) + if (e.SortOperation.IsClear) { // None(Clear) list.Clear(); diff --git a/src/ObservableCollections/ObservableQueue.Views.cs b/src/ObservableCollections/ObservableQueue.Views.cs index 852c3fd..c4aa63c 100644 --- a/src/ObservableCollections/ObservableQueue.Views.cs +++ b/src/ObservableCollections/ObservableQueue.Views.cs @@ -108,14 +108,14 @@ namespace ObservableCollections return new FiltableSynchronizedViewList(this); } - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() + public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() { - return new NotifyCollectionChangedSynchronizedView(this, null); + return new NotifyCollectionChangedSynchronizedViewList(this, null); } - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) + public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) { - return new NotifyCollectionChangedSynchronizedView(this, collectionEventDispatcher); + return new NotifyCollectionChangedSynchronizedViewList(this, collectionEventDispatcher); } public IEnumerator GetEnumerator() diff --git a/src/ObservableCollections/ObservableRingBuffer.Views.cs b/src/ObservableCollections/ObservableRingBuffer.Views.cs index 4281b0b..c539d1b 100644 --- a/src/ObservableCollections/ObservableRingBuffer.Views.cs +++ b/src/ObservableCollections/ObservableRingBuffer.Views.cs @@ -110,19 +110,19 @@ namespace ObservableCollections return new FiltableSynchronizedViewList(this); } - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() + public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() { lock (SyncRoot) { - return new NotifyCollectionChangedSynchronizedView(this, null); + return new NotifyCollectionChangedSynchronizedViewList(this, null); } } - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) + public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) { lock (SyncRoot) { - return new NotifyCollectionChangedSynchronizedView(this, collectionEventDispatcher); + return new NotifyCollectionChangedSynchronizedViewList(this, collectionEventDispatcher); } } diff --git a/src/ObservableCollections/ObservableStack.Views.cs b/src/ObservableCollections/ObservableStack.Views.cs index 000082d..81250a8 100644 --- a/src/ObservableCollections/ObservableStack.Views.cs +++ b/src/ObservableCollections/ObservableStack.Views.cs @@ -107,19 +107,19 @@ namespace ObservableCollections return new FiltableSynchronizedViewList(this); } - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() + public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() { lock (SyncRoot) { - return new NotifyCollectionChangedSynchronizedView(this, null); + return new NotifyCollectionChangedSynchronizedViewList(this, null); } } - public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) + public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) { lock (SyncRoot) { - return new NotifyCollectionChangedSynchronizedView(this, collectionEventDispatcher); + return new NotifyCollectionChangedSynchronizedViewList(this, collectionEventDispatcher); } } diff --git a/src/ObservableCollections/SynchronizedViewList.cs b/src/ObservableCollections/SynchronizedViewList.cs index 4957208..eb16d64 100644 --- a/src/ObservableCollections/SynchronizedViewList.cs +++ b/src/ObservableCollections/SynchronizedViewList.cs @@ -306,7 +306,7 @@ internal class NonFilteredSynchronizedViewList : ISynchronizedViewList } break; case NotifyCollectionChangedAction.Reset: // Clear or drastic changes - if (e.SortOperation.IsNull) + if (e.SortOperation.IsClear) { listView.Clear(); foreach (var item in parent.Unfiltered) // refresh @@ -418,9 +418,9 @@ internal class NonFilteredSynchronizedViewList : ISynchronizedViewList } } -internal class NotifyCollectionChangedSynchronizedView : +internal class NotifyCollectionChangedSynchronizedViewList : FiltableSynchronizedViewList, - INotifyCollectionChangedSynchronizedView, + INotifyCollectionChangedSynchronizedViewList, IList, IList { static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count"); @@ -431,7 +431,7 @@ internal class NotifyCollectionChangedSynchronizedView : public event NotifyCollectionChangedEventHandler? CollectionChanged; public event PropertyChangedEventHandler? PropertyChanged; - public NotifyCollectionChangedSynchronizedView(ISynchronizedView parent, ICollectionEventDispatcher? eventDispatcher) + public NotifyCollectionChangedSynchronizedViewList(ISynchronizedView parent, ICollectionEventDispatcher? eventDispatcher) : base(parent) { this.eventDispatcher = eventDispatcher ?? InlineCollectionEventDispatcher.Instance; @@ -520,7 +520,7 @@ internal class NotifyCollectionChangedSynchronizedView : static void RaiseChangedEvent(NotifyCollectionChangedEventArgs e) { var e2 = (CollectionEventDispatcherEventArgs)e; - var self = (NotifyCollectionChangedSynchronizedView)e2.Collection; + var self = (NotifyCollectionChangedSynchronizedViewList)e2.Collection; if (e2.IsInvokeCollectionChanged) { @@ -663,9 +663,9 @@ internal class NotifyCollectionChangedSynchronizedView : } } -internal class NonFilteredNotifyCollectionChangedSynchronizedView : +internal class NonFilteredNotifyCollectionChangedSynchronizedViewList : NonFilteredSynchronizedViewList, - INotifyCollectionChangedSynchronizedView, + INotifyCollectionChangedSynchronizedViewList, IList, IList { static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count"); @@ -676,7 +676,7 @@ internal class NonFilteredNotifyCollectionChangedSynchronizedView : public event NotifyCollectionChangedEventHandler? CollectionChanged; public event PropertyChangedEventHandler? PropertyChanged; - public NonFilteredNotifyCollectionChangedSynchronizedView(ISynchronizedView parent, ICollectionEventDispatcher? eventDispatcher) + public NonFilteredNotifyCollectionChangedSynchronizedViewList(ISynchronizedView parent, ICollectionEventDispatcher? eventDispatcher) : base(parent) { this.eventDispatcher = eventDispatcher ?? InlineCollectionEventDispatcher.Instance; @@ -765,7 +765,7 @@ internal class NonFilteredNotifyCollectionChangedSynchronizedView : static void RaiseChangedEvent(NotifyCollectionChangedEventArgs e) { var e2 = (CollectionEventDispatcherEventArgs)e; - var self = (NonFilteredNotifyCollectionChangedSynchronizedView)e2.Collection; + var self = (NonFilteredNotifyCollectionChangedSynchronizedViewList)e2.Collection; if (e2.IsInvokeCollectionChanged) {