From e2dcbd804b11d3e1e9613c9777aa2ecf4c29a38a Mon Sep 17 00:00:00 2001 From: hadashiA Date: Thu, 15 Feb 2024 14:01:48 +0900 Subject: [PATCH 1/3] Change the INotifyCollectionChaneged to IE from IE<(T, View)> --- .../IObservableCollection.cs | 5 +- .../Internal/FreezedView.cs | 14 +++- ...NotifyCollectionChangedSynchronizedView.cs | 67 +++++++++++-------- .../Internal/SortedView.cs | 10 ++- .../Internal/SortedViewViewComparer.cs | 9 ++- .../ObservableCollections.csproj | 4 +- .../ObservableDictionary.Views.cs | 9 ++- .../ObservableHashSet.Views.cs | 7 +- .../ObservableList.Views.cs | 10 ++- .../ObservableQueue.Views.cs | 7 +- .../ObservableRingBuffer.Views.cs | 7 +- .../ObservableStack.Views.cs | 7 +- 12 files changed, 109 insertions(+), 47 deletions(-) diff --git a/src/ObservableCollections/IObservableCollection.cs b/src/ObservableCollections/IObservableCollection.cs index abc38b5..ca9d4de 100644 --- a/src/ObservableCollections/IObservableCollection.cs +++ b/src/ObservableCollections/IObservableCollection.cs @@ -24,13 +24,14 @@ public interface IFreezedCollection public interface ISynchronizedView : IReadOnlyCollection<(T Value, TView View)>, IDisposable { object SyncRoot { get; } + ISynchronizedViewFilter CurrentFilter { get; } event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; event Action? CollectionStateChanged; void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForInitialElements = false); void ResetFilter(Action? resetAction); - INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged(); + INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged(); } public interface ISortableSynchronizedView : ISynchronizedView @@ -44,7 +45,7 @@ public interface ISortableSynchronizedView : ISynchronizedView : ISynchronizedView, INotifyCollectionChanged, INotifyPropertyChanged + public interface INotifyCollectionChangedSynchronizedView : IReadOnlyCollection, INotifyCollectionChanged, INotifyPropertyChanged { } diff --git a/src/ObservableCollections/Internal/FreezedView.cs b/src/ObservableCollections/Internal/FreezedView.cs index f9cd98a..1fc1cfc 100644 --- a/src/ObservableCollections/Internal/FreezedView.cs +++ b/src/ObservableCollections/Internal/FreezedView.cs @@ -15,6 +15,11 @@ internal sealed class FreezedView : ISynchronizedView ISynchronizedViewFilter filter; + public ISynchronizedViewFilter CurrentFilter + { + get { lock (SyncRoot) return filter; } + } + public event Action? CollectionStateChanged; public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; @@ -107,7 +112,7 @@ public void Dispose() } - public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() + public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() { return new NotifyCollectionChangedSynchronizedView(this); } @@ -119,6 +124,11 @@ internal sealed class FreezedSortableView : ISortableSynchronizedView< ISynchronizedViewFilter filter; + public ISynchronizedViewFilter CurrentFilter + { + get { lock (SyncRoot) return filter; } + } + public event Action? CollectionStateChanged; public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; @@ -206,7 +216,7 @@ public void Sort(IComparer viewComparer) Array.Sort(array, new TViewComparer(viewComparer)); } - public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() + public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() { return new NotifyCollectionChangedSynchronizedView(this); } diff --git a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs index 25efb63..eaf1cb4 100644 --- a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs +++ b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs @@ -6,38 +6,20 @@ namespace ObservableCollections.Internal { - internal class NotifyCollectionChangedSynchronizedView : INotifyCollectionChangedSynchronizedView + internal class NotifyCollectionChangedSynchronizedView : + INotifyCollectionChangedSynchronizedView, + ISynchronizedViewFilter { + static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count"); + readonly ISynchronizedView parent; - static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new PropertyChangedEventArgs("Count"); public NotifyCollectionChangedSynchronizedView(ISynchronizedView parent) { this.parent = parent; - this.parent.RoutingCollectionChanged += Parent_RoutingCollectionChanged; + parent.AttachFilter(this); } - private void Parent_RoutingCollectionChanged(in NotifyCollectionChangedEventArgs e) - { - CollectionChanged?.Invoke(this, e.ToStandardEventArgs()); - - switch (e.Action) - { - // add, remove, reset will change the count. - case NotifyCollectionChangedAction.Add: - case NotifyCollectionChangedAction.Remove: - case NotifyCollectionChangedAction.Reset: - PropertyChanged?.Invoke(this, CountPropertyChangedEventArgs); - break; - case NotifyCollectionChangedAction.Replace: - case NotifyCollectionChangedAction.Move: - default: - break; - } - } - - public object SyncRoot => parent.SyncRoot; - public int Count => parent.Count; public event NotifyCollectionChangedEventHandler? CollectionChanged; @@ -55,16 +37,43 @@ public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged remove { parent.RoutingCollectionChanged -= value; } } - public void AttachFilter(ISynchronizedViewFilter filter, bool invokeAddEventForCurrentElements = false) => parent.AttachFilter(filter, invokeAddEventForCurrentElements); - public void ResetFilter(Action? resetAction) => parent.ResetFilter(resetAction); - public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() => this; public void Dispose() { - this.parent.RoutingCollectionChanged -= Parent_RoutingCollectionChanged; parent.Dispose(); } - public IEnumerator<(T, TView)> GetEnumerator() => parent.GetEnumerator(); + public IEnumerator GetEnumerator() + { + foreach (var (value, view) in parent) + { + yield return view; + } + } + IEnumerator IEnumerable.GetEnumerator() => parent.GetEnumerator(); + + public bool IsMatch(T value, TView view) => parent.CurrentFilter.IsMatch(value, view); + public void WhenTrue(T value, TView view) => parent.CurrentFilter.WhenTrue(value, view); + public void WhenFalse(T value, TView view) => parent.CurrentFilter.WhenFalse(value, view); + + public void OnCollectionChanged(ChangedKind changedKind, T value, TView view, in NotifyCollectionChangedEventArgs eventArgs) + { + parent.CurrentFilter.OnCollectionChanged(changedKind, value, view, in eventArgs); + + switch (changedKind) + { + case ChangedKind.Add: + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, view, eventArgs.NewStartingIndex)); + return; + case ChangedKind.Remove: + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, view, eventArgs.OldStartingIndex)); + break; + case ChangedKind.Move: + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, view, eventArgs.NewStartingIndex, eventArgs.OldStartingIndex)); + break; + default: + throw new ArgumentOutOfRangeException(nameof(changedKind), changedKind, null); + } + } } } \ No newline at end of file diff --git a/src/ObservableCollections/Internal/SortedView.cs b/src/ObservableCollections/Internal/SortedView.cs index 435e483..747d822 100644 --- a/src/ObservableCollections/Internal/SortedView.cs +++ b/src/ObservableCollections/Internal/SortedView.cs @@ -2,13 +2,17 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; -using System.Linq; 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; @@ -85,7 +89,7 @@ public void ResetFilter(Action? resetAction) } } - public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() + public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() { lock (SyncRoot) { @@ -192,7 +196,7 @@ private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs e) var view = transform(value); var id = identitySelector(value); list.Add((value, id), (value, view)); - var newIndex = list.IndexOfKey((value, id)); + var newIndex = list.IndexOfKey((value, id)); filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, newIndex)); } diff --git a/src/ObservableCollections/Internal/SortedViewViewComparer.cs b/src/ObservableCollections/Internal/SortedViewViewComparer.cs index 5f91980..ab47dc9 100644 --- a/src/ObservableCollections/Internal/SortedViewViewComparer.cs +++ b/src/ObservableCollections/Internal/SortedViewViewComparer.cs @@ -22,6 +22,11 @@ internal class SortedViewViewComparer : ISynchronizedView CurrentFilter + { + get { lock (SyncRoot) return filter; } + } + public SortedViewViewComparer(IObservableCollection source, Func identitySelector, Func transform, IComparer comparer) { this.source = source; @@ -89,7 +94,7 @@ public void ResetFilter(Action? resetAction) } } - public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() + public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() { lock (SyncRoot) { @@ -146,7 +151,7 @@ private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs e) var id = identitySelector(value); list.Add((view, id), (value, view)); viewMap.Add(id, view); - var index = list.IndexOfKey((view, id)); + var index = list.IndexOfKey((view, id)); filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs.Add(value, index)); } } diff --git a/src/ObservableCollections/ObservableCollections.csproj b/src/ObservableCollections/ObservableCollections.csproj index 8f698d0..b6404b1 100644 --- a/src/ObservableCollections/ObservableCollections.csproj +++ b/src/ObservableCollections/ObservableCollections.csproj @@ -1,9 +1,9 @@  - netstandard2.0;netstandard2.1;net5.0;net6.0 + netstandard2.0;netstandard2.1;net6.0;net8.0 enable - 10.0 + 12.0 disable diff --git a/src/ObservableCollections/ObservableDictionary.Views.cs b/src/ObservableCollections/ObservableDictionary.Views.cs index 0dd794a..ed250e1 100644 --- a/src/ObservableCollections/ObservableDictionary.Views.cs +++ b/src/ObservableCollections/ObservableDictionary.Views.cs @@ -14,7 +14,7 @@ public ISynchronizedView, TView> CreateView(Fu // reverse is no used. return new View(this, transform); } - + class View : ISynchronizedView, TView> { readonly ObservableDictionary source; @@ -39,6 +39,11 @@ public View(ObservableDictionary source, Func>? RoutingCollectionChanged; public event Action? CollectionStateChanged; + public ISynchronizedViewFilter, TView> CurrentFilter + { + get { lock (SyncRoot) return filter; } + } + public int Count { get @@ -91,7 +96,7 @@ public void ResetFilter(Action, TView>? resetAction) } } - public INotifyCollectionChangedSynchronizedView, TView> WithINotifyCollectionChanged() + public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() { lock (SyncRoot) { diff --git a/src/ObservableCollections/ObservableHashSet.Views.cs b/src/ObservableCollections/ObservableHashSet.Views.cs index 8223634..3039bef 100644 --- a/src/ObservableCollections/ObservableHashSet.Views.cs +++ b/src/ObservableCollections/ObservableHashSet.Views.cs @@ -16,6 +16,11 @@ public ISynchronizedView CreateView(Func transform, b sealed class View : ISynchronizedView { + public ISynchronizedViewFilter CurrentFilter + { + get { lock (SyncRoot) return filter; } + } + readonly ObservableHashSet source; readonly Func selector; readonly Dictionary dict; @@ -85,7 +90,7 @@ public void ResetFilter(Action? resetAction) } } - public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() + public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() { lock (SyncRoot) { diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index ed95711..b699171 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -16,6 +16,14 @@ public ISynchronizedView CreateView(Func transform, b sealed class View : ISynchronizedView { + public ISynchronizedViewFilter CurrentFilter + { + get + { + lock (SyncRoot) { return filter; } + } + } + readonly ObservableList source; readonly Func selector; readonly bool reverse; @@ -89,7 +97,7 @@ public void ResetFilter(Action? resetAction) } } - public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() + public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() { lock (SyncRoot) { diff --git a/src/ObservableCollections/ObservableQueue.Views.cs b/src/ObservableCollections/ObservableQueue.Views.cs index 5f551b6..bcb9e93 100644 --- a/src/ObservableCollections/ObservableQueue.Views.cs +++ b/src/ObservableCollections/ObservableQueue.Views.cs @@ -28,6 +28,11 @@ class View : ISynchronizedView public object SyncRoot { get; } + public ISynchronizedViewFilter CurrentFilter + { + get { lock (SyncRoot) return filter; } + } + public View(ObservableQueue source, Func selector, bool reverse) { this.source = source; @@ -89,7 +94,7 @@ public void ResetFilter(Action? resetAction) } } - public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() + public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() { lock (SyncRoot) { diff --git a/src/ObservableCollections/ObservableRingBuffer.Views.cs b/src/ObservableCollections/ObservableRingBuffer.Views.cs index 3562549..a0866b4 100644 --- a/src/ObservableCollections/ObservableRingBuffer.Views.cs +++ b/src/ObservableCollections/ObservableRingBuffer.Views.cs @@ -17,6 +17,11 @@ public ISynchronizedView CreateView(Func transform, b // used with ObservableFixedSizeRingBuffer internal sealed class View : ISynchronizedView { + public ISynchronizedViewFilter CurrentFilter + { + get { lock (SyncRoot) return filter; } + } + readonly IObservableCollection source; readonly Func selector; readonly bool reverse; @@ -89,7 +94,7 @@ public void ResetFilter(Action? resetAction) } } - public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() + public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() { lock (SyncRoot) { diff --git a/src/ObservableCollections/ObservableStack.Views.cs b/src/ObservableCollections/ObservableStack.Views.cs index 5175732..a21c1e0 100644 --- a/src/ObservableCollections/ObservableStack.Views.cs +++ b/src/ObservableCollections/ObservableStack.Views.cs @@ -28,6 +28,11 @@ class View : ISynchronizedView public object SyncRoot { get; } + public ISynchronizedViewFilter CurrentFilter + { + get { lock (SyncRoot) return filter; } + } + public View(ObservableStack source, Func selector, bool reverse) { this.source = source; @@ -89,7 +94,7 @@ public void ResetFilter(Action? resetAction) } } - public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() + public INotifyCollectionChangedSynchronizedView ToNotifyCollectionChanged() { lock (SyncRoot) { From 76de096069336d43ea81bbe52ecc6b34a62d8e37 Mon Sep 17 00:00:00 2001 From: hadashiA Date: Thu, 15 Feb 2024 14:17:38 +0900 Subject: [PATCH 2/3] Add test --- .../ToNotifyCollectionChangedTest.cs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/ObservableCollections.Tests/ToNotifyCollectionChangedTest.cs diff --git a/tests/ObservableCollections.Tests/ToNotifyCollectionChangedTest.cs b/tests/ObservableCollections.Tests/ToNotifyCollectionChangedTest.cs new file mode 100644 index 0000000..746edb6 --- /dev/null +++ b/tests/ObservableCollections.Tests/ToNotifyCollectionChangedTest.cs @@ -0,0 +1,57 @@ +namespace ObservableCollections.Tests; + +public class ToNotifyCollectionChangedTest +{ + [Fact] + public void ToNotifyCollectionChanged() + { + var list = new ObservableList(); + + list.Add(10); + list.Add(50); + list.Add(30); + + var notify = list.CreateView(x => $"${x}").ToNotifyCollectionChanged(); + + list.Add(20); + list.Add(40); + + using var e = notify.GetEnumerator(); + e.MoveNext().Should().BeTrue(); + e.Current.Should().Be("$10"); + e.MoveNext().Should().BeTrue(); + e.Current.Should().Be("$20"); + e.MoveNext().Should().BeTrue(); + e.Current.Should().Be("$30"); + e.MoveNext().Should().BeTrue(); + e.Current.Should().Be("$40"); + e.MoveNext().Should().BeTrue(); + e.Current.Should().Be("$50"); + e.MoveNext().Should().BeFalse(); + } + + [Fact] + public void ToNotifyCollectionChanged_Filter() + { + var list = new ObservableList(); + + list.Add(1); + list.Add(2); + list.Add(5); + list.Add(3); + + var view = list.CreateView(x => $"${x}"); + var notify = view.ToNotifyCollectionChanged(); + + view.AttachFilter((value, view) => value % 2 == 0); + + list.Add(4); + + using var e = notify.GetEnumerator(); + e.MoveNext().Should().BeTrue(); + e.Current.Should().Be("$2"); + e.MoveNext().Should().BeTrue(); + e.Current.Should().Be("$4"); + e.MoveNext().Should().BeFalse(); + } +} \ No newline at end of file From ba5b572d2e6862b7a67d8e60b604b872f24df4c1 Mon Sep 17 00:00:00 2001 From: hadashiA Date: Thu, 15 Feb 2024 14:36:55 +0900 Subject: [PATCH 3/3] Fix test --- .../NotifyCollectionChangedSynchronizedView.cs | 10 ++++++---- src/ObservableCollections/ObservableList.cs | 1 - .../ToNotifyCollectionChangedTest.cs | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs index eaf1cb4..46234db 100644 --- a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs +++ b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs @@ -13,10 +13,12 @@ internal class NotifyCollectionChangedSynchronizedView : static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count"); readonly ISynchronizedView parent; + readonly ISynchronizedViewFilter currentFilter; public NotifyCollectionChangedSynchronizedView(ISynchronizedView parent) { this.parent = parent; + currentFilter = parent.CurrentFilter; parent.AttachFilter(this); } @@ -52,13 +54,13 @@ public IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => parent.GetEnumerator(); - public bool IsMatch(T value, TView view) => parent.CurrentFilter.IsMatch(value, view); - public void WhenTrue(T value, TView view) => parent.CurrentFilter.WhenTrue(value, view); - public void WhenFalse(T value, TView view) => parent.CurrentFilter.WhenFalse(value, view); + 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(ChangedKind changedKind, T value, TView view, in NotifyCollectionChangedEventArgs eventArgs) { - parent.CurrentFilter.OnCollectionChanged(changedKind, value, view, in eventArgs); + currentFilter.OnCollectionChanged(changedKind, value, view, in eventArgs); switch (changedKind) { diff --git a/src/ObservableCollections/ObservableList.cs b/src/ObservableCollections/ObservableList.cs index cc024c4..dc33723 100644 --- a/src/ObservableCollections/ObservableList.cs +++ b/src/ObservableCollections/ObservableList.cs @@ -62,7 +62,6 @@ public int Count public event NotifyCollectionChangedEventHandler? CollectionChanged; - public void Add(T item) { lock (SyncRoot) diff --git a/tests/ObservableCollections.Tests/ToNotifyCollectionChangedTest.cs b/tests/ObservableCollections.Tests/ToNotifyCollectionChangedTest.cs index 746edb6..106b6ec 100644 --- a/tests/ObservableCollections.Tests/ToNotifyCollectionChangedTest.cs +++ b/tests/ObservableCollections.Tests/ToNotifyCollectionChangedTest.cs @@ -8,13 +8,13 @@ public void ToNotifyCollectionChanged() var list = new ObservableList(); list.Add(10); - list.Add(50); + list.Add(20); list.Add(30); var notify = list.CreateView(x => $"${x}").ToNotifyCollectionChanged(); - list.Add(20); list.Add(40); + list.Add(50); using var e = notify.GetEnumerator(); e.MoveNext().Should().BeTrue();