From db803f83ed124655a56a65b75779dfdb66e90930 Mon Sep 17 00:00:00 2001 From: neuecc Date: Fri, 13 Aug 2021 17:59:55 +0900 Subject: [PATCH] q --- .../Internal/SortedView.cs | 213 ++++++++ .../Internal/SortedViewViewComparer.cs | 231 +++++++++ .../ObservableDictionary.Views.cs | 327 +------------ .../ObservableList.Views.cs | 454 +----------------- .../ObservableQueue.View.cs | 181 +++++++ src/ObservableCollections/ObservableQueue.cs | 81 ++-- .../ObservableQueueTest.cs | 84 ++++ 7 files changed, 762 insertions(+), 809 deletions(-) create mode 100644 src/ObservableCollections/Internal/SortedView.cs create mode 100644 src/ObservableCollections/Internal/SortedViewViewComparer.cs create mode 100644 src/ObservableCollections/ObservableQueue.View.cs create mode 100644 tests/ObservableCollections.Tests/ObservableQueueTest.cs diff --git a/src/ObservableCollections/Internal/SortedView.cs b/src/ObservableCollections/Internal/SortedView.cs new file mode 100644 index 0000000..b400da9 --- /dev/null +++ b/src/ObservableCollections/Internal/SortedView.cs @@ -0,0 +1,213 @@ +using System.Collections; +using System.Collections.Specialized; + +namespace ObservableCollections.Internal +{ + internal class SortedView : ISynchronizedView + where TKey : notnull + { + readonly IObservableCollection source; + readonly Func transform; + readonly Func identitySelector; + readonly SortedDictionary<(T Value, TKey Key), (T Value, TView View)> dict; + + ISynchronizedViewFilter filter; + + public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; + public event Action? CollectionStateChanged; + + public object SyncRoot { get; } = new object(); + + public SortedView(IObservableCollection source, object syncRoot, IEnumerable sourceEnumerable, Func identitySelector, Func transform, IComparer comparer) + { + this.source = source; + this.identitySelector = identitySelector; + this.transform = transform; + this.filter = SynchronizedViewFilter.Null; + lock (syncRoot) + { + var dict = new SortedDictionary<(T, TKey), (T, TView)>(new Comparer(comparer)); + foreach (var v in sourceEnumerable) + { + dict.Add((v, identitySelector(v)), (v, transform(v))); + } + + this.dict = dict; + this.source.CollectionChanged += SourceCollectionChanged; + } + } + + public int Count + { + get + { + lock (SyncRoot) + { + return dict.Count; + } + } + } + + public void AttachFilter(ISynchronizedViewFilter filter) + { + lock (SyncRoot) + { + this.filter = filter; + foreach (var (_, (value, view)) in dict) + { + filter.InvokeOnAttach(value, view); + } + } + } + + public void ResetFilter(Action? resetAction) + { + lock (SyncRoot) + { + this.filter = SynchronizedViewFilter.Null; + if (resetAction != null) + { + foreach (var (_, (value, view)) in dict) + { + resetAction(value, view); + } + } + } + } + + public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() + { + lock (SyncRoot) + { + return new NotifyCollectionChangedSynchronizedView(this); + } + } + + public IEnumerator<(T, TView)> GetEnumerator() + { + return new SynchronizedViewEnumerator(SyncRoot, dict.Select(x => x.Value).GetEnumerator(), filter); + } + + 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); + dict.Add((value, id), (value, view)); + filter.InvokeOnAdd(value, view); + } + else + { + foreach (var value in e.NewItems) + { + var view = transform(value); + var id = identitySelector(value); + dict.Add((value, id), (value, view)); + filter.InvokeOnAdd(value, view); + } + } + } + break; + case NotifyCollectionChangedAction.Remove: + { + if (e.IsSingleItem) + { + var value = e.OldItem; + var id = identitySelector(value); + dict.Remove((value, id), out var v); + filter.InvokeOnRemove(v.Value, v.View); + } + else + { + foreach (var value in e.OldItems) + { + var id = identitySelector(value); + dict.Remove((value, id), out var v); + filter.InvokeOnRemove(v.Value, v.View); + } + } + } + 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; + dict.Remove((oldValue, identitySelector(oldValue)), out var oldView); + + var value = e.NewItem; + var view = transform(value); + var id = identitySelector(value); + dict.Add((value, id), (value, view)); + + filter.InvokeOnRemove(oldView); + filter.InvokeOnAdd(value, view); + } + break; + case NotifyCollectionChangedAction.Move: + { + // Move(index change) does not affect sorted list. + var oldValue = e.OldItem; + if (dict.TryGetValue((oldValue, identitySelector(oldValue)), out var view)) + { + filter.InvokeOnMove(view); + } + } + break; + case NotifyCollectionChangedAction.Reset: + if (!filter.IsNullFilter()) + { + foreach (var item in dict) + { + filter.InvokeOnRemove(item.Value); + } + } + dict.Clear(); + 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; + } + } + } +} diff --git a/src/ObservableCollections/Internal/SortedViewViewComparer.cs b/src/ObservableCollections/Internal/SortedViewViewComparer.cs new file mode 100644 index 0000000..9064bc8 --- /dev/null +++ b/src/ObservableCollections/Internal/SortedViewViewComparer.cs @@ -0,0 +1,231 @@ +using System.Collections; +using System.Collections.Specialized; + +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 SortedDictionary<(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 SortedViewViewComparer(IObservableCollection source, object syncRoot, IEnumerable sourceEnumerable, Func identitySelector, Func transform, IComparer comparer) + { + this.source = source; + this.identitySelector = identitySelector; + this.transform = transform; + this.filter = SynchronizedViewFilter.Null; + lock (syncRoot) + { + var dict = new SortedDictionary<(TView, TKey), (T, TView)>(new Comparer(comparer)); + this.viewMap = new Dictionary(); + foreach (var value in sourceEnumerable) + { + var view = transform(value); + var id = identitySelector(value); + dict.Add((view, id), (value, view)); + viewMap.Add(id, view); + } + this.list = dict; + this.source.CollectionChanged += SourceCollectionChanged; + } + } + + public int Count + { + get + { + lock (SyncRoot) + { + return list.Count; + } + } + } + + public void AttachFilter(ISynchronizedViewFilter filter) + { + lock (SyncRoot) + { + this.filter = filter; + foreach (var (_, (value, view)) in list) + { + 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 WithINotifyCollectionChanged() + { + lock (SyncRoot) + { + return new NotifyCollectionChangedSynchronizedView(this); + } + } + + public IEnumerator<(T, TView)> GetEnumerator() + { + return new SynchronizedViewEnumerator(SyncRoot, list.Select(x => x.Value).GetEnumerator(), filter); + } + + 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); + filter.InvokeOnAdd(value, view); + } + 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); + filter.InvokeOnAdd(value, view); + } + } + } + break; + case NotifyCollectionChangedAction.Remove: + { + if (e.IsSingleItem) + { + var value = e.OldItem; + var id = identitySelector(value); + if (viewMap.Remove(id, out var view)) + { + list.Remove((view, id), out var v); + filter.InvokeOnRemove(v); + } + } + else + { + foreach (var value in e.OldItems) + { + var id = identitySelector(value); + if (viewMap.Remove(id, out var view)) + { + list.Remove((view, id), out var v); + filter.InvokeOnRemove(v); + } + } + } + } + break; + case NotifyCollectionChangedAction.Replace: + // Replace is remove old item and insert new item. + { + var oldValue = e.OldItem; + var oldKey = identitySelector(oldValue); + if (viewMap.Remove(oldKey, out var oldView)) + { + list.Remove((oldView, oldKey)); + filter.InvokeOnRemove(oldValue, oldView); + } + + var value = e.NewItem; + var view = transform(value); + var id = identitySelector(value); + list.Add((view, id), (value, view)); + viewMap.Add(id, view); + + filter.InvokeOnAdd(value, view); + } + break; + case NotifyCollectionChangedAction.Move: + // Move(index change) does not affect soreted dict. + { + var value = e.OldItem; + var key = identitySelector(value); + if (viewMap.TryGetValue(key, out var view)) + { + filter.InvokeOnMove(value, view); + } + } + break; + case NotifyCollectionChangedAction.Reset: + if (!filter.IsNullFilter()) + { + foreach (var item in list) + { + filter.InvokeOnRemove(item.Value); + } + } + list.Clear(); + viewMap.Clear(); + 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 a1e046b..24939c8 100644 --- a/src/ObservableCollections/ObservableDictionary.Views.cs +++ b/src/ObservableCollections/ObservableDictionary.Views.cs @@ -1,9 +1,6 @@ using ObservableCollections.Internal; -using System; using System.Collections; -using System.Collections.Generic; using System.Collections.Specialized; -using System.Linq; namespace ObservableCollections { @@ -17,23 +14,23 @@ namespace ObservableCollections public ISynchronizedView, TView> CreateSortedView(Func, TView> transform, IComparer> comparer) { - return new SortedView(this, transform, comparer); + return new SortedView, TKey, TView>(this, this.SyncRoot, dictionary, x => x.Key, transform, comparer); } public ISynchronizedView, TView> CreateSortedView(Func, TView> transform, IComparer viewComparer) { - return new ViewComparerSortedView(this, transform, viewComparer); + return new SortedViewViewComparer, TKey, TView>(this, this.SyncRoot, dictionary, x => x.Key, transform, viewComparer); } // identity selector is ignored ISynchronizedView, TView> IObservableCollection>.CreateSortedView(Func, TKey1> identitySelector, Func, TView> transform, IComparer> comparer) { - return new SortedView(this, transform, comparer); + return new SortedView, TKey, TView>(this, this.SyncRoot, dictionary, x => x.Key, transform, comparer); } ISynchronizedView, TView> IObservableCollection>.CreateSortedView(Func, TKey1> identitySelector, Func, TView> transform, IComparer viewComparer) { - return new ViewComparerSortedView(this, transform, viewComparer); + return new SortedViewViewComparer, TKey, TView>(this, this.SyncRoot, dictionary, x => x.Key, transform, viewComparer); } class View : ISynchronizedView, TView> @@ -180,321 +177,5 @@ namespace ObservableCollections } } } - - class SortedView : ISynchronizedView, TView> - { - readonly ObservableDictionary source; - readonly Func, TView> selector; - ISynchronizedViewFilter, TView> filter; - readonly SortedDictionary, TView> dict; - - public SortedView(ObservableDictionary source, Func, TView> selector, IComparer> comparer) - { - this.source = source; - this.selector = selector; - this.filter = SynchronizedViewFilter, TView>.Null; - this.SyncRoot = new object(); - lock (source.SyncRoot) - { - this.dict = new SortedDictionary, TView>(comparer); - foreach (var item in source.dictionary) - { - dict.Add(item, selector(item)); - } - this.source.CollectionChanged += SourceCollectionChanged; - } - } - - public object SyncRoot { get; } - public event NotifyCollectionChangedEventHandler>? RoutingCollectionChanged; - public event Action? CollectionStateChanged; - - public int Count - { - get - { - lock (SyncRoot) - { - return dict.Count; - } - } - } - - public void Dispose() - { - this.source.CollectionChanged -= SourceCollectionChanged; - } - - public void AttachFilter(ISynchronizedViewFilter, TView> filter) - { - lock (SyncRoot) - { - this.filter = filter; - foreach (var v in dict) - { - filter.InvokeOnAttach(new KeyValuePair(v.Key.Key, v.Key.Value), v.Value); - } - } - } - - public void ResetFilter(Action, TView>? resetAction) - { - lock (SyncRoot) - { - this.filter = SynchronizedViewFilter, TView>.Null; - if (resetAction != null) - { - foreach (var v in dict) - { - resetAction(new KeyValuePair(v.Key.Key, v.Key.Value), v.Value); - } - } - } - } - - public INotifyCollectionChangedSynchronizedView, TView> WithINotifyCollectionChanged() - { - lock (SyncRoot) - { - return new NotifyCollectionChangedSynchronizedView, TView>(this); - } - } - - public IEnumerator<(KeyValuePair, TView)> GetEnumerator() - { - return new SynchronizedViewEnumerator, TView>(SyncRoot, - dict.Select(x => (new KeyValuePair(x.Key.Key, x.Key.Value), x.Value)).GetEnumerator(), - filter); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs> e) - { - // ObservableDictionary only provides single item operation and does not use int index. - lock (SyncRoot) - { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - { - var v = selector(e.NewItem); - var k = new KeyValuePair(e.NewItem.Key, e.NewItem.Value); - dict.Add(k, v); - filter.InvokeOnAdd(k, v); - } - break; - case NotifyCollectionChangedAction.Remove: - { - if (dict.Remove(e.OldItem, out var value)) - { - filter.InvokeOnRemove(e.OldItem, value); - } - } - break; - case NotifyCollectionChangedAction.Replace: - { - var k = new KeyValuePair(e.OldItem.Key, e.OldItem.Value); - if (dict.Remove(k, out var oldValue)) - { - filter.InvokeOnRemove(k, oldValue); - } - - var v = selector(e.NewItem); - var nk = new KeyValuePair(e.NewItem.Key, e.NewItem.Value); - dict[nk] = v; - filter.InvokeOnAdd(nk, v); - } - break; - case NotifyCollectionChangedAction.Reset: - { - if (!filter.IsNullFilter()) - { - foreach (var item in dict) - { - filter.InvokeOnRemove(item.Key, item.Value); - } - } - dict.Clear(); - } - break; - case NotifyCollectionChangedAction.Move: // ObservableDictionary have no Move operation. - default: - break; - } - - RoutingCollectionChanged?.Invoke(e); - CollectionStateChanged?.Invoke(e.Action); - } - } - } - -#pragma warning disable CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint. - - class ViewComparerSortedView : ISynchronizedView, TView> - { - readonly ObservableDictionary source; - readonly Func, TView> selector; - readonly Dictionary viewMap; - readonly SortedDictionary> dict; - ISynchronizedViewFilter, TView> filter; - - public ViewComparerSortedView(ObservableDictionary source, Func, TView> selector, IComparer viewComparer) - { - this.source = source; - this.selector = selector; - this.filter = SynchronizedViewFilter, TView>.Null; - this.SyncRoot = new object(); - lock (source.SyncRoot) - { - this.viewMap = new Dictionary(source.Count); - this.dict = new SortedDictionary>(viewComparer); - foreach (var item in source.dictionary) - { - var v = selector(item); - dict.Add(v, item); - viewMap.Add(item.Key, v); - } - this.source.CollectionChanged += SourceCollectionChanged; - } - } - - public object SyncRoot { get; } - public event NotifyCollectionChangedEventHandler>? RoutingCollectionChanged; - public event Action? CollectionStateChanged; - - public int Count - { - get - { - lock (SyncRoot) - { - return dict.Count; - } - } - } - - public void Dispose() - { - this.source.CollectionChanged -= SourceCollectionChanged; - } - - public void AttachFilter(ISynchronizedViewFilter, TView> filter) - { - lock (SyncRoot) - { - this.filter = filter; - foreach (var v in dict) - { - filter.InvokeOnAttach(v.Value, v.Key); - } - } - } - - public void ResetFilter(Action, TView>? resetAction) - { - lock (SyncRoot) - { - this.filter = SynchronizedViewFilter, TView>.Null; - if (resetAction != null) - { - foreach (var v in dict) - { - resetAction(v.Value, v.Key); - } - } - } - } - - public INotifyCollectionChangedSynchronizedView, TView> WithINotifyCollectionChanged() - { - lock (SyncRoot) - { - return new NotifyCollectionChangedSynchronizedView, TView>(this); - } - } - - public IEnumerator<(KeyValuePair, TView)> GetEnumerator() - { - return new SynchronizedViewEnumerator, TView>(SyncRoot, - dict.Select(x => (x.Value, x.Key)).GetEnumerator(), - filter); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs> e) - { - // ObservableDictionary only provides single item operation and does not use int index. - lock (SyncRoot) - { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - { - var v = selector(e.NewItem); - var k = new KeyValuePair(e.NewItem.Key, e.NewItem.Value); - dict.Add(v, k); - viewMap.Add(e.NewItem.Key, v); - filter.InvokeOnAdd(k, v); - } - break; - case NotifyCollectionChangedAction.Remove: - { - if (viewMap.Remove(e.OldItem.Key, out var view)) - { - dict.Remove(view); - filter.InvokeOnRemove(e.OldItem, view); - } - } - break; - case NotifyCollectionChangedAction.Replace: - { - if (viewMap.Remove(e.OldItem.Key, out var view)) - { - if (dict.Remove(view, out var oldView)) - { - filter.InvokeOnRemove(e.OldItem, view); - } - - var v = selector(e.NewItem); - var k = new KeyValuePair(e.NewItem.Key, e.NewItem.Value); - dict[v] = k; - viewMap[e.NewItem.Key] = v; - filter.InvokeOnAdd(k, v); - } - break; - } - case NotifyCollectionChangedAction.Reset: - { - if (!filter.IsNullFilter()) - { - foreach (var item in dict) - { - filter.InvokeOnRemove(item.Value, item.Key); - } - } - dict.Clear(); - viewMap.Clear(); - } - break; - case NotifyCollectionChangedAction.Move: // ObservableDictionary have no Move operation. - default: - break; - } - - RoutingCollectionChanged?.Invoke(e); - CollectionStateChanged?.Invoke(e.Action); - } - } - } - -#pragma warning restore CS8714 } } diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index 09e9c5f..6f26559 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -14,13 +14,13 @@ namespace ObservableCollections public ISynchronizedView CreateSortedView(Func identitySelector, Func transform, IComparer comparer) where TKey : notnull { - return new SortedView(this, identitySelector, transform, comparer); + return new SortedView(this, SyncRoot, list, identitySelector, transform, comparer); } public ISynchronizedView CreateSortedView(Func identitySelector, Func transform, IComparer viewComparer) where TKey : notnull { - return new ViewComparerSortedView(this, identitySelector, transform, viewComparer); + return new SortedViewViewComparer(this, SyncRoot, list, identitySelector, transform, viewComparer); } class View : ISynchronizedView @@ -177,14 +177,11 @@ namespace ObservableCollections } else { - if (!filter.IsNullFilter()) + var len = e.OldStartingIndex + e.OldItems.Length; + for (int i = e.OldStartingIndex; i < len; i++) { - var len = e.OldStartingIndex + e.OldItems.Length; - for (int i = e.OldStartingIndex; i < len; i++) - { - var v = list[i]; - filter.InvokeOnRemove(v.Item1, v.Item2); - } + var v = list[i]; + filter.InvokeOnRemove(v.Item1, v.Item2); } list.RemoveRange(e.OldStartingIndex, e.OldItems.Length); @@ -230,444 +227,5 @@ namespace ObservableCollections } } } - - class SortedView : ISynchronizedView - where TKey : notnull - { - readonly ObservableList source; - readonly Func transform; - readonly Func identitySelector; - readonly SortedDictionary<(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(ObservableList 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 SortedDictionary<(T, TKey), (T, TView)>(new Comparer(comparer)); - var count = source.list.Count; - for (int i = 0; i < count; i++) - { - var v = source.list[i]; - dict.Add((v, identitySelector(v)), (v, transform(v))); - } - - this.list = dict; - this.source.CollectionChanged += SourceCollectionChanged; - } - } - - public int Count - { - get - { - lock (SyncRoot) - { - return list.Count; - } - } - } - - public void AttachFilter(ISynchronizedViewFilter filter) - { - lock (SyncRoot) - { - this.filter = filter; - foreach (var (_, (value, view)) in list) - { - 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 WithINotifyCollectionChanged() - { - lock (SyncRoot) - { - return new NotifyCollectionChangedSynchronizedView(this); - } - } - - public IEnumerator<(T, TView)> GetEnumerator() - { - return new SynchronizedViewEnumerator(SyncRoot, list.Select(x => x.Value).GetEnumerator(), filter); - } - - 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)); - filter.InvokeOnAdd(value, view); - } - else - { - foreach (var value in e.NewItems) - { - var view = transform(value); - var id = identitySelector(value); - list.Add((value, id), (value, view)); - filter.InvokeOnAdd(value, view); - } - } - } - break; - case NotifyCollectionChangedAction.Remove: - { - if (e.IsSingleItem) - { - var value = e.OldItem; - var id = identitySelector(value); - list.Remove((value, id), out var v); - filter.InvokeOnRemove(v.Value, v.View); - } - else - { - foreach (var value in e.OldItems) - { - var id = identitySelector(value); - list.Remove((value, id), out var v); - filter.InvokeOnRemove(v.Value, v.View); - } - } - } - break; - case NotifyCollectionChangedAction.Replace: - // ObservableList does not support replace range - // Replace is remove old item and insert new item. - { - var oldValue = e.OldItem; - list.Remove((oldValue, identitySelector(oldValue)), out var oldView); - - var value = e.NewItem; - var view = transform(value); - var id = identitySelector(value); - list.Add((value, id), (value, view)); - - filter.InvokeOnRemove(oldView); - filter.InvokeOnAdd(value, view); - } - break; - case NotifyCollectionChangedAction.Move: - { - // Move(index change) does not affect sorted list. - var oldValue = e.OldItem; - if (list.TryGetValue((oldValue, identitySelector(oldValue)), out var view)) - { - filter.InvokeOnMove(view); - } - } - break; - case NotifyCollectionChangedAction.Reset: - if (!filter.IsNullFilter()) - { - foreach (var item in list) - { - filter.InvokeOnRemove(item.Value); - } - } - list.Clear(); - 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; - } - } - } - - class ViewComparerSortedView : ISynchronizedView - where TKey : notnull - { - readonly ObservableList source; - readonly Func transform; - readonly Func identitySelector; - readonly Dictionary viewMap; // view-map needs to use in remove. - readonly SortedDictionary<(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 ViewComparerSortedView(ObservableList 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 SortedDictionary<(TView, TKey), (T, TView)>(new Comparer(comparer)); - this.viewMap = new Dictionary(source.list.Count); - var count = source.list.Count; - for (int i = 0; i < count; i++) - { - var value = source.list[i]; - var view = transform(value); - var id = identitySelector(value); - dict.Add((view, id), (value, view)); - viewMap.Add(id, view); - } - this.list = dict; - this.source.CollectionChanged += SourceCollectionChanged; - } - } - - public int Count - { - get - { - lock (SyncRoot) - { - return list.Count; - } - } - } - - public void AttachFilter(ISynchronizedViewFilter filter) - { - lock (SyncRoot) - { - this.filter = filter; - foreach (var (_, (value, view)) in list) - { - 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 WithINotifyCollectionChanged() - { - lock (SyncRoot) - { - return new NotifyCollectionChangedSynchronizedView(this); - } - } - - public IEnumerator<(T, TView)> GetEnumerator() - { - return new SynchronizedViewEnumerator(SyncRoot, list.Select(x => x.Value).GetEnumerator(), filter); - } - - 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); - filter.InvokeOnAdd(value, view); - } - 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); - filter.InvokeOnAdd(value, view); - } - } - } - break; - case NotifyCollectionChangedAction.Remove: - { - if (e.IsSingleItem) - { - var value = e.OldItem; - var id = identitySelector(value); - if (viewMap.Remove(id, out var view)) - { - list.Remove((view, id), out var v); - filter.InvokeOnRemove(v); - } - } - else - { - foreach (var value in e.OldItems) - { - var id = identitySelector(value); - if (viewMap.Remove(id, out var view)) - { - list.Remove((view, id), out var v); - filter.InvokeOnRemove(v); - } - } - } - } - break; - case NotifyCollectionChangedAction.Replace: - // ObservableList does not support replace range - // Replace is remove old item and insert new item. - { - var oldValue = e.OldItem; - var oldKey = identitySelector(oldValue); - if (viewMap.Remove(oldKey, out var oldView)) - { - list.Remove((oldView, oldKey)); - filter.InvokeOnRemove(oldValue, oldView); - } - - var value = e.NewItem; - var view = transform(value); - var id = identitySelector(value); - list.Add((view, id), (value, view)); - viewMap.Add(id, view); - - filter.InvokeOnAdd(value, view); - } - break; - case NotifyCollectionChangedAction.Move: - // Move(index change) does not affect soreted dict. - { - var value = e.OldItem; - var key = identitySelector(value); - if (viewMap.TryGetValue(key, out var view)) - { - filter.InvokeOnMove(value, view); - } - } - break; - case NotifyCollectionChangedAction.Reset: - if (!filter.IsNullFilter()) - { - foreach (var item in list) - { - filter.InvokeOnRemove(item.Value); - } - } - list.Clear(); - viewMap.Clear(); - 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/ObservableQueue.View.cs b/src/ObservableCollections/ObservableQueue.View.cs new file mode 100644 index 0000000..1d3efdb --- /dev/null +++ b/src/ObservableCollections/ObservableQueue.View.cs @@ -0,0 +1,181 @@ +using ObservableCollections.Internal; +using System.Collections; +using System.Collections.Specialized; + +namespace ObservableCollections +{ + public sealed partial class ObservableQueue : IReadOnlyCollection, IObservableCollection + { + public ISynchronizedView CreateView(Func transform, bool reverse = false) + { + return new View(this, transform, reverse); + } + + public ISynchronizedView CreateSortedView(Func identitySelector, Func transform, IComparer comparer) where TKey : notnull + { + return new SortedView(this, SyncRoot, queue, identitySelector, transform, comparer); + } + + public ISynchronizedView CreateSortedView(Func identitySelector, Func transform, IComparer viewComparer) where TKey : notnull + { + return new SortedViewViewComparer(this, SyncRoot, queue, identitySelector, transform, viewComparer); + } + + class View : ISynchronizedView + { + readonly ObservableQueue source; + readonly Func selector; + readonly bool reverse; + protected readonly Queue<(T, TView)> queue; + + ISynchronizedViewFilter filter; + + public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; + public event Action? CollectionStateChanged; + + public object SyncRoot { get; } + + public View(ObservableQueue source, Func selector, bool reverse) + { + this.source = source; + this.selector = selector; + this.reverse = reverse; + 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.source.CollectionChanged += SourceCollectionChanged; + } + } + + public int Count + { + get + { + lock (SyncRoot) + { + return queue.Count; + } + } + } + + public void AttachFilter(ISynchronizedViewFilter filter) + { + lock (SyncRoot) + { + this.filter = filter; + foreach (var (value, view) in queue) + { + filter.InvokeOnAttach(value, view); + } + } + } + + public void ResetFilter(Action? resetAction) + { + lock (SyncRoot) + { + this.filter = SynchronizedViewFilter.Null; + if (resetAction != null) + { + foreach (var (item, view) in queue) + { + resetAction(item, view); + } + } + } + } + + public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() + { + lock (SyncRoot) + { + return new NotifyCollectionChangedSynchronizedView(this); + } + } + + public IEnumerator<(T, TView)> GetEnumerator() + { + if (!reverse) + { + return new SynchronizedViewEnumerator(SyncRoot, queue.GetEnumerator(), filter); + } + else + { + return new SynchronizedViewEnumerator(SyncRoot, queue.AsEnumerable().Reverse().GetEnumerator(), filter); + } + } + + 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(Enqueue, EnqueueRange) + if (e.IsSingleItem) + { + var v = (e.NewItem, selector(e.NewItem)); + queue.Enqueue(v); + filter.InvokeOnAdd(v); + } + else + { + queue.EnsureCapacity(e.NewItems.Length); + foreach (var item in e.NewItems) + { + var v = (item, selector(item)); + queue.Enqueue(v); + filter.InvokeOnAdd(v); + } + } + break; + case NotifyCollectionChangedAction.Remove: + // Dequeue, DequeuRange + if (e.IsSingleItem) + { + var v = queue.Dequeue(); + filter.InvokeOnRemove(v.Item1, v.Item2); + } + else + { + var len = e.OldItems.Length; + for (int i = 0; i < len; i++) + { + var v = queue.Dequeue(); + filter.InvokeOnRemove(v.Item1, v.Item2); + } + } + break; + case NotifyCollectionChangedAction.Reset: + if (!filter.IsNullFilter()) + { + foreach (var item in queue) + { + filter.InvokeOnRemove(item); + } + } + queue.Clear(); + break; + case NotifyCollectionChangedAction.Replace: + case NotifyCollectionChangedAction.Move: + default: + break; + } + + RoutingCollectionChanged?.Invoke(e); + CollectionStateChanged?.Invoke(e.Action); + } + } + } + } +} \ No newline at end of file diff --git a/src/ObservableCollections/ObservableQueue.cs b/src/ObservableCollections/ObservableQueue.cs index c4a9de0..7ee738e 100644 --- a/src/ObservableCollections/ObservableQueue.cs +++ b/src/ObservableCollections/ObservableQueue.cs @@ -1,12 +1,7 @@ using ObservableCollections.Internal; -using System; using System.Buffers; using System.Collections; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace ObservableCollections { @@ -158,53 +153,63 @@ namespace ObservableCollections } } - // TODO: - void Clear() + public void Clear() { + lock (SyncRoot) + { + queue.Clear(); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Reset()); + } } - //bool Contains(T item) - //{ - //} + public T Peek() + { + lock (SyncRoot) + { + return queue.Peek(); + } + } - //void CopyTo(T[] array, int arrayIndex); + public bool TryPeek([MaybeNullWhen(false)] T result) + { + lock (SyncRoot) + { + return queue.TryPeek(out result); + } + } - // Peek + public T[] ToArray() + { + lock (SyncRoot) + { + return queue.ToArray(); + } + } - // ToArray - - // TrimExcess - - // EnsureCapacity - - - - // TryPeek + public void TrimExcess() + { + lock (SyncRoot) + { + queue.TrimExcess(); + } + } + public void EnsureCapacity(int capacity) + { + lock (SyncRoot) + { + queue.EnsureCapacity(capacity); + } + } public IEnumerator GetEnumerator() { - throw new NotImplementedException(); + return new SynchronizedEnumerator(SyncRoot, queue.GetEnumerator()); } IEnumerator IEnumerable.GetEnumerator() { - throw new NotImplementedException(); - } - - public ISynchronizedView CreateView(Func transform, bool reverse = false) - { - throw new NotImplementedException(); - } - - public ISynchronizedView CreateSortedView(Func identitySelector, Func transform, IComparer comparer) where TKey : notnull - { - throw new NotImplementedException(); - } - - public ISynchronizedView CreateSortedView(Func identitySelector, Func transform, IComparer viewComparer) where TKey : notnull - { - throw new NotImplementedException(); + return GetEnumerator(); } } } diff --git a/tests/ObservableCollections.Tests/ObservableQueueTest.cs b/tests/ObservableCollections.Tests/ObservableQueueTest.cs new file mode 100644 index 0000000..6cd07cd --- /dev/null +++ b/tests/ObservableCollections.Tests/ObservableQueueTest.cs @@ -0,0 +1,84 @@ +using FluentAssertions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace ObservableCollections.Tests +{ + public class ObservableQueueTests + { + [Fact] + public void View() + { + var queue = new ObservableQueue(); + var view = queue.CreateView(x => new ViewContainer(x)); + + queue.Enqueue(10); + queue.Enqueue(50); + queue.Enqueue(30); + queue.Enqueue(20); + queue.Enqueue(40); + + void Equal(params int[] expected) + { + 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); + + queue.EnqueueRange(new[] { 1, 2, 3, 4, 5 }); + Equal(10, 50, 30, 20, 40, 1, 2, 3, 4, 5); + + queue.Dequeue().Should().Be(10); + Equal(50, 30, 20, 40, 1, 2, 3, 4, 5); + + queue.TryDequeue(out var q).Should().BeTrue(); + q.Should().Be(50); + Equal(30, 20, 40, 1, 2, 3, 4, 5); + + queue.DequeueRange(4); + Equal(2, 3, 4, 5); + + queue.Clear(); + + 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); + + 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.Select(x => x.Value).Should().Equal(30); + + filter.Clear(); + + queue.Enqueue(33); + queue.EnqueueRange(new[] { 98 }); + + filter.CalledOnCollectionChanged.Select(x => (x.changedKind, x.value)).Should().Equal((ChangedKind.Add, 33), (ChangedKind.Add, 98)); + filter.Clear(); + + queue.Dequeue(); + queue.DequeueRange(2); + filter.CalledOnCollectionChanged.Select(x => (x.changedKind, x.value)).Should().Equal((ChangedKind.Remove, 10), (ChangedKind.Remove, 50), (ChangedKind.Remove, 30)); + } + } +}