From c7cf88a57dea1e8ce8e4c230854bd07759a5ee64 Mon Sep 17 00:00:00 2001 From: neuecc Date: Thu, 5 Aug 2021 19:06:37 +0900 Subject: [PATCH] Freezed --- ...llections.sln => ObservableCollections.sln | 0 .../FreezedDictionary.cs | 57 +++++ src/ObservableCollections/FreezedList.cs | 200 +---------------- .../Internal/FreezedView.cs | 203 ++++++++++++++++++ .../ObservableDictionary.Views.cs | 157 +++++++++++++- 5 files changed, 413 insertions(+), 204 deletions(-) rename ObservbaleCollections.sln => ObservableCollections.sln (100%) create mode 100644 src/ObservableCollections/FreezedDictionary.cs create mode 100644 src/ObservableCollections/Internal/FreezedView.cs diff --git a/ObservbaleCollections.sln b/ObservableCollections.sln similarity index 100% rename from ObservbaleCollections.sln rename to ObservableCollections.sln diff --git a/src/ObservableCollections/FreezedDictionary.cs b/src/ObservableCollections/FreezedDictionary.cs new file mode 100644 index 0000000..4a80455 --- /dev/null +++ b/src/ObservableCollections/FreezedDictionary.cs @@ -0,0 +1,57 @@ +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 index 5cc8240..9ec87ae 100644 --- a/src/ObservableCollections/FreezedList.cs +++ b/src/ObservableCollections/FreezedList.cs @@ -2,7 +2,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Collections.Specialized; using System.Linq; namespace ObservableCollections @@ -36,12 +35,12 @@ namespace ObservableCollections public ISynchronizedView CreateView(Func transform, bool reverse = false) { - return new View(this, transform, reverse); + return new FreezedView(list, transform, reverse); } public ISortableSynchronizedView CreateSortableView(Func transform) { - return new SortableView(this, transform); + return new FreezedSortableView(list, transform); } public bool Contains(T item) @@ -58,200 +57,5 @@ namespace ObservableCollections { return GetEnumerator(); } - - class View : ISynchronizedView - { - readonly bool reverse; - readonly List<(T, TView)> list; - - ISynchronizedViewFilter filter; - - public event Action? CollectionStateChanged; - public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; - - public object SyncRoot { get; } = new object(); - - public View(FreezedList source, Func selector, bool reverse) - { - this.reverse = reverse; - this.filter = SynchronizedViewFilter.AlwaysTrue; - this.list = source.Select(x => (x, selector(x))).ToList(); - } - - 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.Invoke(value, view); - } - } - } - - public void ResetFilter(Action? resetAction) - { - lock (SyncRoot) - { - this.filter = SynchronizedViewFilter.AlwaysTrue; - if (resetAction != null) - { - foreach (var (item, view) in list) - { - resetAction(item, view); - } - } - } - } - - public IEnumerator<(T, TView)> GetEnumerator() - { - if (!reverse) - { - return new SynchronizedViewEnumerator(SyncRoot, list.GetEnumerator(), filter); - } - else - { - return new SynchronizedViewEnumerator(SyncRoot, list.AsEnumerable().Reverse().GetEnumerator(), filter); - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public void Dispose() - { - - } - - public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() - { - return new NotifyCollectionChangedSynchronizedView(this); - } - } - - class SortableView : ISortableSynchronizedView - { - readonly (T, TView)[] array; - - ISynchronizedViewFilter filter; - - public event Action? CollectionStateChanged; - public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; - - public object SyncRoot { get; } = new object(); - - public SortableView(FreezedList source, Func selector) - { - this.filter = SynchronizedViewFilter.AlwaysTrue; - this.array = source.Select(x => (x, selector(x))).ToArray(); - } - - public int Count - { - get - { - lock (SyncRoot) - { - return array.Length; - } - } - } - - public void AttachFilter(ISynchronizedViewFilter filter) - { - lock (SyncRoot) - { - this.filter = filter; - foreach (var (value, view) in array) - { - filter.Invoke(value, view); - } - } - } - - public void ResetFilter(Action? resetAction) - { - lock (SyncRoot) - { - this.filter = SynchronizedViewFilter.AlwaysTrue; - if (resetAction != null) - { - foreach (var (item, view) in array) - { - resetAction(item, view); - } - } - } - } - - public IEnumerator<(T, TView)> GetEnumerator() - { - return new SynchronizedViewEnumerator(SyncRoot, array.AsEnumerable().GetEnumerator(), filter); - } - - 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 WithINotifyCollectionChanged() - { - return new NotifyCollectionChangedSynchronizedView(this); - } - - 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/FreezedView.cs b/src/ObservableCollections/Internal/FreezedView.cs new file mode 100644 index 0000000..e9d5f93 --- /dev/null +++ b/src/ObservableCollections/Internal/FreezedView.cs @@ -0,0 +1,203 @@ +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 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.AlwaysTrue; + this.list = source.Select(x => (x, selector(x))).ToList(); + } + + 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.Invoke(value, view); + } + } + } + + public void ResetFilter(Action? resetAction) + { + lock (SyncRoot) + { + this.filter = SynchronizedViewFilter.AlwaysTrue; + if (resetAction != null) + { + foreach (var (item, view) in list) + { + resetAction(item, view); + } + } + } + } + + public IEnumerator<(T, TView)> GetEnumerator() + { + if (!reverse) + { + return new SynchronizedViewEnumerator(SyncRoot, list.GetEnumerator(), filter); + } + else + { + return new SynchronizedViewEnumerator(SyncRoot, list.AsEnumerable().Reverse().GetEnumerator(), filter); + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public void Dispose() + { + + } + + public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() + { + return new NotifyCollectionChangedSynchronizedView(this); + } + } + + internal sealed class FreezedSortableView : ISortableSynchronizedView + { + readonly (T, TView)[] array; + + ISynchronizedViewFilter filter; + + public event Action? CollectionStateChanged; + public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; + + public object SyncRoot { get; } = new object(); + + public FreezedSortableView(IEnumerable source, Func selector) + { + this.filter = SynchronizedViewFilter.AlwaysTrue; + this.array = source.Select(x => (x, selector(x))).ToArray(); + } + + public int Count + { + get + { + lock (SyncRoot) + { + return array.Length; + } + } + } + + public void AttachFilter(ISynchronizedViewFilter filter) + { + lock (SyncRoot) + { + this.filter = filter; + foreach (var (value, view) in array) + { + filter.Invoke(value, view); + } + } + } + + public void ResetFilter(Action? resetAction) + { + lock (SyncRoot) + { + this.filter = SynchronizedViewFilter.AlwaysTrue; + if (resetAction != null) + { + foreach (var (item, view) in array) + { + resetAction(item, view); + } + } + } + } + + public IEnumerator<(T, TView)> GetEnumerator() + { + return new SynchronizedViewEnumerator(SyncRoot, array.AsEnumerable().GetEnumerator(), filter); + } + + 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 WithINotifyCollectionChanged() + { + return new NotifyCollectionChangedSynchronizedView(this); + } + + 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/ObservableDictionary.Views.cs b/src/ObservableCollections/ObservableDictionary.Views.cs index 6ff8f5b..16ada84 100644 --- a/src/ObservableCollections/ObservableDictionary.Views.cs +++ b/src/ObservableCollections/ObservableDictionary.Views.cs @@ -4,27 +4,25 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace ObservableCollections { public sealed partial class ObservableDictionary { - public ISynchronizedView, TView> CreateView(Func, TView> transform, bool reverse = false) + public ISynchronizedView, TView> CreateView(Func, TView> transform, bool _ = false) { // reverse is no used. - throw new NotImplementedException(); + return new View(this, transform); } public ISynchronizedView, TView> CreateSortedView(Func, TView> transform, IComparer> comparer) { - throw new NotImplementedException(); + return new SortedView(this, transform, comparer); } public ISynchronizedView, TView> CreateSortedView(Func, TView> transform, IComparer viewComparer) { - throw new NotImplementedException(); + return new ViewComparerSortedView(this, transform, viewComparer); } class View : ISynchronizedView, TView> @@ -293,5 +291,152 @@ namespace ObservableCollections } } } + +#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>.AlwaysTrue; + 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.Invoke(v.Value, v.Key); + } + } + } + + public void ResetFilter(Action, TView>? resetAction) + { + lock (SyncRoot) + { + this.filter = SynchronizedViewFilter, TView>.AlwaysTrue; + 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.Invoke(k, v); + } + break; + case NotifyCollectionChangedAction.Remove: + { + if (viewMap.Remove(e.OldItem.Key, out var view)) + { + dict.Remove(view); + } + } + break; + case NotifyCollectionChangedAction.Move: + case NotifyCollectionChangedAction.Replace: + { + if (viewMap.Remove(e.OldItem.Key, out var view)) + { + dict.Remove(view); + } + goto case NotifyCollectionChangedAction.Add; + } + case NotifyCollectionChangedAction.Reset: + { + dict.Clear(); + } + break; + default: + break; + } + + RoutingCollectionChanged?.Invoke(e); + CollectionStateChanged?.Invoke(e.Action); + } + } + } + +#pragma warning restore CS8714 } }