From 327850a0db10d1708403b6ba2648a17dfe2cb8bf Mon Sep 17 00:00:00 2001 From: neuecc Date: Wed, 4 Aug 2021 15:49:48 +0900 Subject: [PATCH] Observable Dict --- src/ObservableCollections/FreezedList.cs | 422 +++++++++--------- .../IObservableCollection.cs | 6 +- ...NotifyCollectionChangedSynchronizedView.cs | 17 + .../Internal/SynchronizedEnumerator.cs | 46 ++ .../Internal/SynchronizedViewEnumerator.cs | 14 +- .../ObservableDictionary.Views.cs | 297 ++++++++++++ .../ObservableDictionary.cs | 206 +++++++++ .../ObservableList.Views.cs | 4 +- src/ObservableCollections/ObservableList.cs | 23 +- .../ObservableCollections.Tests/UnitTest1.cs | 3 +- 10 files changed, 801 insertions(+), 237 deletions(-) create mode 100644 src/ObservableCollections/Internal/SynchronizedEnumerator.cs create mode 100644 src/ObservableCollections/ObservableDictionary.Views.cs create mode 100644 src/ObservableCollections/ObservableDictionary.cs diff --git a/src/ObservableCollections/FreezedList.cs b/src/ObservableCollections/FreezedList.cs index ab38ed3..5cc8240 100644 --- a/src/ObservableCollections/FreezedList.cs +++ b/src/ObservableCollections/FreezedList.cs @@ -1,249 +1,257 @@ -//using ObservableCollections.Internal; -//using ObservableCollections; -//using System; -//using System.Collections; -//using System.Collections.Generic; -//using System.Collections.Specialized; -//using System.Linq; -//using System.Runtime.InteropServices; -//using System.Text; -//using System.Threading.Tasks; +using ObservableCollections.Internal; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; -//namespace ObservableCollections -//{ -// public sealed class FreezedList : IReadOnlyList, IFreezedCollection -// { -// readonly IReadOnlyList list; +namespace ObservableCollections +{ + public sealed class FreezedList : IReadOnlyList, IFreezedCollection + { + readonly IReadOnlyList list; -// public T this[int index] -// { -// get -// { -// return list[index]; -// } -// } + public T this[int index] + { + get + { + return list[index]; + } + } -// public int Count -// { -// get -// { -// return list.Count; -// } -// } + public int Count + { + get + { + return list.Count; + } + } -// public bool IsReadOnly => true; + public bool IsReadOnly => true; -// public FreezedList(IReadOnlyList list) -// { -// this.list = list; -// } + public FreezedList(IReadOnlyList list) + { + this.list = list; + } -// public ISynchronizedView CreateView(Func transform, bool reverse = false) -// { -// return new View(this, transform, reverse); -// } + public ISynchronizedView CreateView(Func transform, bool reverse = false) + { + return new View(this, transform, reverse); + } -// public ISortableSynchronizedView CreateSortableView(Func transform) -// { -// return new SortableView(this, transform); -// } + public ISortableSynchronizedView CreateSortableView(Func transform) + { + return new SortableView(this, transform); + } -// public bool Contains(T item) -// { -// return list.Contains(item); -// } + public bool Contains(T item) + { + return list.Contains(item); + } -// public IEnumerator GetEnumerator() -// { -// return list.GetEnumerator(); -// } + public IEnumerator GetEnumerator() + { + return list.GetEnumerator(); + } -// IEnumerator IEnumerable.GetEnumerator() -// { -// return GetEnumerator(); -// } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } -// class View : ISynchronizedView -// { -// readonly bool reverse; -// readonly List<(T, TView)> list; + class View : ISynchronizedView + { + readonly bool reverse; + readonly List<(T, TView)> list; -// ISynchronizedViewFilter filter; + ISynchronizedViewFilter filter; -// public event Action? CollectionStateChanged; + public event Action? CollectionStateChanged; + public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; -// public object SyncRoot { get; } = new object(); + public object SyncRoot { get; } = new object(); -// public View(FreezedList source, Func selector, bool reverse) -// { -// this.reverse = reverse; -// this.filter = TrueViewFilter.Instance; -// this.list = source.Select(x => (x, selector(x))).ToList(); -// } + 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 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 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 = TrueViewFilter.Instance; -// if (resetAction != null) -// { -// foreach (var (item, view) in list) -// { -// resetAction(item, 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); -// } -// } + 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(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); -// public void Dispose() -// { + public void Dispose() + { -// } -// } + } -// class SortableView : ISortableSynchronizedView -// { -// readonly (T, TView)[] array; + public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() + { + return new NotifyCollectionChangedSynchronizedView(this); + } + } -// ISynchronizedViewFilter filter; + class SortableView : ISortableSynchronizedView + { + readonly (T, TView)[] array; -// public event Action? CollectionStateChanged; + ISynchronizedViewFilter filter; -// public object SyncRoot { get; } = new object(); + public event Action? CollectionStateChanged; + public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; -// public SortableView(FreezedList source, Func selector) -// { -// this.filter = TrueViewFilter.Instance; -// this.array = source.Select(x => (x, selector(x))).ToArray(); -// } + public object SyncRoot { get; } = new object(); -// public int Count -// { -// get -// { -// lock (SyncRoot) -// { -// return array.Length; -// } -// } -// } + public SortableView(FreezedList source, Func selector) + { + this.filter = SynchronizedViewFilter.AlwaysTrue; + this.array = source.Select(x => (x, selector(x))).ToArray(); + } -// public void AttachFilter(ISynchronizedViewFilter filter) -// { -// lock (SyncRoot) -// { -// this.filter = filter; -// foreach (var (value, view) in array) -// { -// filter.Invoke(value, view); -// } -// } -// } + public int Count + { + get + { + lock (SyncRoot) + { + return array.Length; + } + } + } -// public void ResetFilter(Action? resetAction) -// { -// lock (SyncRoot) -// { -// this.filter = TrueViewFilter.Instance; -// if (resetAction != null) -// { -// foreach (var (item, view) in array) -// { -// resetAction(item, view); -// } -// } -// } -// } + public void AttachFilter(ISynchronizedViewFilter filter) + { + lock (SyncRoot) + { + this.filter = filter; + foreach (var (value, view) in array) + { + filter.Invoke(value, view); + } + } + } -// public IEnumerator<(T, TView)> GetEnumerator() -// { -// return new SynchronizedViewEnumerator(SyncRoot, array.AsEnumerable().GetEnumerator(), filter); -// } + public void ResetFilter(Action? resetAction) + { + lock (SyncRoot) + { + this.filter = SynchronizedViewFilter.AlwaysTrue; + if (resetAction != null) + { + foreach (var (item, view) in array) + { + resetAction(item, view); + } + } + } + } -// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator<(T, TView)> GetEnumerator() + { + return new SynchronizedViewEnumerator(SyncRoot, array.AsEnumerable().GetEnumerator(), filter); + } -// public void Dispose() -// { + 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 void Sort(IComparer comparer) + { + Array.Sort(array, new TComparer(comparer)); + } -// class TComparer : IComparer<(T, TView)> -// { -// readonly IComparer comparer; + public void Sort(IComparer viewComparer) + { + Array.Sort(array, new TViewComparer(viewComparer)); + } -// public TComparer(IComparer comparer) -// { -// this.comparer = comparer; -// } + public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() + { + return new NotifyCollectionChangedSynchronizedView(this); + } -// public int Compare((T, TView) x, (T, TView) y) -// { -// return comparer.Compare(x.Item1, y.Item1); -// } -// } + class TComparer : IComparer<(T, TView)> + { + readonly IComparer comparer; -// class TViewComparer : IComparer<(T, TView)> -// { -// readonly IComparer comparer; + public TComparer(IComparer comparer) + { + this.comparer = comparer; + } -// public TViewComparer(IComparer comparer) -// { -// this.comparer = comparer; -// } + public int Compare((T, TView) x, (T, TView) y) + { + return comparer.Compare(x.Item1, y.Item1); + } + } -// public int Compare((T, TView) x, (T, TView) y) -// { -// return comparer.Compare(x.Item2, y.Item2); -// } -// } -// } -// } -//} \ No newline at end of file + 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/IObservableCollection.cs b/src/ObservableCollections/IObservableCollection.cs index 717b8dd..e3b6026 100644 --- a/src/ObservableCollections/IObservableCollection.cs +++ b/src/ObservableCollections/IObservableCollection.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.ComponentModel; namespace ObservableCollections { @@ -13,9 +14,6 @@ namespace ObservableCollections ISynchronizedView CreateView(Func transform, bool reverse = false); ISynchronizedView CreateSortedView(Func transform, IComparer comparer); ISynchronizedView CreateSortedView(Func transform, IComparer viewComparer); - - // TODO:Grouping - // IGroupedSynchronizedView CreateGroupedView(Func keySelector, Func transform); } public interface IFreezedCollection @@ -42,7 +40,7 @@ namespace ObservableCollections void Sort(IComparer viewComparer); } - public interface INotifyCollectionChangedSynchronizedView : ISynchronizedView, INotifyCollectionChanged + public interface INotifyCollectionChangedSynchronizedView : ISynchronizedView, INotifyCollectionChanged, INotifyPropertyChanged { } } \ No newline at end of file diff --git a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs index 4b7a28b..327de38 100644 --- a/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs +++ b/src/ObservableCollections/Internal/NotifyCollectionChangedSynchronizedView.cs @@ -2,12 +2,14 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; +using System.ComponentModel; namespace ObservableCollections.Internal { internal class NotifyCollectionChangedSynchronizedView : INotifyCollectionChangedSynchronizedView { readonly ISynchronizedView parent; + static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new PropertyChangedEventArgs("Count"); public NotifyCollectionChangedSynchronizedView(ISynchronizedView parent) { @@ -18,6 +20,20 @@ namespace ObservableCollections.Internal 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; @@ -25,6 +41,7 @@ namespace ObservableCollections.Internal public int Count => parent.Count; public event NotifyCollectionChangedEventHandler? CollectionChanged; + public event PropertyChangedEventHandler? PropertyChanged; public event Action? CollectionStateChanged { diff --git a/src/ObservableCollections/Internal/SynchronizedEnumerator.cs b/src/ObservableCollections/Internal/SynchronizedEnumerator.cs new file mode 100644 index 0000000..0f177df --- /dev/null +++ b/src/ObservableCollections/Internal/SynchronizedEnumerator.cs @@ -0,0 +1,46 @@ +using System.Collections; +using System.Collections.Generic; +using System.Threading; + +namespace ObservableCollections.Internal +{ + internal class SynchronizedEnumerator : IEnumerator + { + bool isDisposed; + readonly object gate; + readonly bool lockTaken; + readonly IEnumerator enumerator; + + public SynchronizedEnumerator(object gate, IEnumerator enumerator) + { + this.gate = gate; + this.enumerator = enumerator; + Monitor.Enter(gate, ref lockTaken); + } + + public T Current => enumerator.Current; + + object IEnumerator.Current => Current!; + public bool MoveNext() => enumerator.MoveNext(); + public void Reset() => enumerator.Reset(); + + public void Dispose() + { + if (!isDisposed) + { + isDisposed = true; + try + { + enumerator.Dispose(); + } + finally + { + if (lockTaken) + { + Monitor.Exit(gate); + } + } + } + } + } +} diff --git a/src/ObservableCollections/Internal/SynchronizedViewEnumerator.cs b/src/ObservableCollections/Internal/SynchronizedViewEnumerator.cs index 38a659b..57fd267 100644 --- a/src/ObservableCollections/Internal/SynchronizedViewEnumerator.cs +++ b/src/ObservableCollections/Internal/SynchronizedViewEnumerator.cs @@ -8,8 +8,7 @@ namespace ObservableCollections.Internal internal class SynchronizedViewEnumerator : IEnumerator<(T, TView)>, IDisposable { bool isDisposed; - bool startEnumerate; - bool lockTaken; + readonly bool lockTaken; readonly object gate; readonly IEnumerator<(T, TView)> enumerator; readonly ISynchronizedViewFilter filter; @@ -22,8 +21,7 @@ namespace ObservableCollections.Internal this.filter = filter; this.current = default; this.isDisposed = false; - this.startEnumerate = false; - this.lockTaken = false; + Monitor.Enter(gate, ref lockTaken); } public (T, TView) Current => current; @@ -31,12 +29,6 @@ namespace ObservableCollections.Internal public bool MoveNext() { - if (!startEnumerate) // TODO: check is this work correctly? - { - startEnumerate = true; - Monitor.Enter(gate, ref lockTaken); - } - while (enumerator.MoveNext()) { current = enumerator.Current; @@ -51,7 +43,7 @@ namespace ObservableCollections.Internal public void Dispose() { - if (isDisposed) + if (!isDisposed) { isDisposed = true; try diff --git a/src/ObservableCollections/ObservableDictionary.Views.cs b/src/ObservableCollections/ObservableDictionary.Views.cs new file mode 100644 index 0000000..6ff8f5b --- /dev/null +++ b/src/ObservableCollections/ObservableDictionary.Views.cs @@ -0,0 +1,297 @@ +using ObservableCollections.Internal; +using System; +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) + { + // reverse is no used. + throw new NotImplementedException(); + } + + public ISynchronizedView, TView> CreateSortedView(Func, TView> transform, IComparer> comparer) + { + throw new NotImplementedException(); + } + + public ISynchronizedView, TView> CreateSortedView(Func, TView> transform, IComparer viewComparer) + { + throw new NotImplementedException(); + } + + class View : ISynchronizedView, TView> + { + readonly ObservableDictionary source; + readonly Func, TView> selector; + ISynchronizedViewFilter, TView> filter; + readonly Dictionary dict; + + public View(ObservableDictionary source, Func, TView> selector) + { + this.source = source; + this.selector = selector; + this.filter = SynchronizedViewFilter, TView>.AlwaysTrue; + this.SyncRoot = new object(); + lock (source.SyncRoot) + { + this.dict = source.dictionary.ToDictionary(x => x.Key, x => (x.Value, selector(x))); + 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(new KeyValuePair(v.Key, v.Value.Item1), v.Value.Item2); + } + } + } + + public void ResetFilter(Action, TView>? resetAction) + { + lock (SyncRoot) + { + this.filter = SynchronizedViewFilter, TView>.AlwaysTrue; + if (resetAction != null) + { + foreach (var v in dict) + { + resetAction(new KeyValuePair(v.Key, v.Value.Item1), v.Value.Item2); + } + } + } + } + + 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, x.Value.Item1), x.Value.Item2)).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); + dict.Add(e.NewItem.Key, (e.NewItem.Value, v)); + filter.Invoke(new KeyValuePair(e.NewItem.Key, e.NewItem.Value), v); + } + break; + case NotifyCollectionChangedAction.Remove: + { + dict.Remove(e.OldItem.Key); + } + break; + case NotifyCollectionChangedAction.Move: + case NotifyCollectionChangedAction.Replace: + { + dict.Remove(e.OldItem.Key); + var v = selector(e.NewItem); + dict.Add(e.NewItem.Key, (e.NewItem.Value, v)); + filter.Invoke(new KeyValuePair(e.NewItem.Key, e.NewItem.Value), v); + } + break; + case NotifyCollectionChangedAction.Reset: + { + dict.Clear(); + } + break; + default: + break; + } + + RoutingCollectionChanged?.Invoke(e); + CollectionStateChanged?.Invoke(e.Action); + } + } + } + + 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>.AlwaysTrue; + 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.Invoke(new KeyValuePair(v.Key.Key, v.Key.Value), v.Value); + } + } + } + + public void ResetFilter(Action, TView>? resetAction) + { + lock (SyncRoot) + { + this.filter = SynchronizedViewFilter, TView>.AlwaysTrue; + 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.Invoke(k, v); + } + break; + case NotifyCollectionChangedAction.Remove: + { + dict.Remove(e.OldItem); + } + break; + case NotifyCollectionChangedAction.Move: + case NotifyCollectionChangedAction.Replace: + { + var k = new KeyValuePair(e.OldItem.Key, e.OldItem.Value); + dict.Remove(k); + var v = selector(e.NewItem); + var nk = new KeyValuePair(e.NewItem.Key, e.NewItem.Value); + dict.Add(nk, v); + filter.Invoke(nk, v); + } + break; + case NotifyCollectionChangedAction.Reset: + { + dict.Clear(); + } + break; + default: + break; + } + + RoutingCollectionChanged?.Invoke(e); + CollectionStateChanged?.Invoke(e.Action); + } + } + } + } +} diff --git a/src/ObservableCollections/ObservableDictionary.cs b/src/ObservableCollections/ObservableDictionary.cs new file mode 100644 index 0000000..dbbc295 --- /dev/null +++ b/src/ObservableCollections/ObservableDictionary.cs @@ -0,0 +1,206 @@ +using ObservableCollections.Internal; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace ObservableCollections +{ + public sealed partial class ObservableDictionary + : IDictionary, IReadOnlyDictionary, IObservableCollection> + where TKey : notnull + { + readonly Dictionary dictionary; + public readonly object SyncRoot = new object(); + + public ObservableDictionary() + { + this.dictionary = new Dictionary(); + } + + public ObservableDictionary(Dictionary dictionary) + { + this.dictionary = dictionary; + } + + public event NotifyCollectionChangedEventHandler>? CollectionChanged; + + public TValue this[TKey key] + { + get + { + lock (SyncRoot) + { + return dictionary[key]; + } + } + set + { + lock (SyncRoot) + { + var oldValue = dictionary[key]; + dictionary[key] = value; + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs>.Replace( + new KeyValuePair(key, value), + new KeyValuePair(key, oldValue), + -1)); + } + } + } + + // for lock synchronization, hide keys and values. + ICollection IDictionary.Keys + { + get + { + lock (SyncRoot) + { + return dictionary.Keys; + } + } + } + + ICollection IDictionary.Values + { + get + { + lock (SyncRoot) + { + return dictionary.Values; + } + } + } + + public int Count + { + get + { + lock (SyncRoot) + { + return dictionary.Count; + } + } + } + + public bool IsReadOnly => false; + + IEnumerable IReadOnlyDictionary.Keys + { + get + { + lock (SyncRoot) + { + return dictionary.Keys; + } + } + } + + IEnumerable IReadOnlyDictionary.Values + { + get + { + lock (SyncRoot) + { + return dictionary.Values; + } + } + } + + public void Add(TKey key, TValue value) + { + lock (SyncRoot) + { + dictionary.Add(key, value); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs>.Add(new KeyValuePair(key, value), -1)); + } + } + + public void Add(KeyValuePair item) + { + Add(item.Key, item.Value); + } + + public void Clear() + { + lock (SyncRoot) + { + dictionary.Clear(); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs>.Reset()); + } + } + + public bool Contains(KeyValuePair item) + { + lock (SyncRoot) + { + return ((ICollection>)dictionary).Contains(item); + } + } + + public bool ContainsKey(TKey key) + { + lock (SyncRoot) + { + return ((IDictionary)dictionary).ContainsKey(key); + } + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + lock (SyncRoot) + { + ((ICollection>)dictionary).CopyTo(array, arrayIndex); + } + } + + public bool Remove(TKey key) + { + lock (SyncRoot) + { + if (dictionary.Remove(key, out var value)) + { + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs>.Remove(new KeyValuePair(key, value), -1)); + return true; + } + return false; + } + } + + public bool Remove(KeyValuePair item) + { + lock (SyncRoot) + { + if (dictionary.TryGetValue(item.Key, out var value)) + { + if (EqualityComparer.Default.Equals(value, item.Value)) + { + if (dictionary.Remove(item.Key, out var value2)) + { + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs>.Remove(new KeyValuePair(item.Key, value2), -1)); + return true; + } + } + } + return false; + } + } + + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) + { + lock (SyncRoot) + { + return dictionary.TryGetValue(key, out value); + } + } + + public IEnumerator> GetEnumerator() + { + return new SynchronizedEnumerator>(SyncRoot, dictionary.GetEnumerator()); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index 638e5de..b005614 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -36,7 +36,7 @@ namespace ObservableCollections public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; public event Action? CollectionStateChanged; - public object SyncRoot { get; } = new object(); + public object SyncRoot { get; } public View(ObservableList source, Func selector, bool reverse) { @@ -47,7 +47,7 @@ namespace ObservableCollections this.SyncRoot = new object(); lock (source.SyncRoot) { - this.list = source.Select(x => (x, selector(x))).ToList(); + this.list = source.list.Select(x => (x, selector(x))).ToList(); this.source.CollectionChanged += SourceCollectionChanged; } } diff --git a/src/ObservableCollections/ObservableList.cs b/src/ObservableCollections/ObservableList.cs index c840457..e280e9d 100644 --- a/src/ObservableCollections/ObservableList.cs +++ b/src/ObservableCollections/ObservableList.cs @@ -2,7 +2,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Collections.Specialized; using System.Linq; using System.Runtime.InteropServices; @@ -13,6 +12,16 @@ namespace ObservableCollections readonly List list; public readonly object SyncRoot = new object(); + public ObservableList() + { + list = new List(); + } + + public ObservableList(IEnumerable source) + { + list = source.ToList(); + } + public T this[int index] { get @@ -49,16 +58,6 @@ namespace ObservableCollections public event NotifyCollectionChangedEventHandler? CollectionChanged; - public ObservableList() - { - list = new List(); - } - - public ObservableList(IEnumerable source) - { - list = source.ToList(); - } - public void Add(T item) { lock (SyncRoot) @@ -136,7 +135,7 @@ namespace ObservableCollections public IEnumerator GetEnumerator() { - return list.GetEnumerator(); + return new SynchronizedEnumerator(SyncRoot, list.GetEnumerator()); } IEnumerator IEnumerable.GetEnumerator() diff --git a/tests/ObservableCollections.Tests/UnitTest1.cs b/tests/ObservableCollections.Tests/UnitTest1.cs index 5cf4d9d..c82df28 100644 --- a/tests/ObservableCollections.Tests/UnitTest1.cs +++ b/tests/ObservableCollections.Tests/UnitTest1.cs @@ -8,7 +8,8 @@ namespace ObservableCollections.Tests [Fact] public void Test1() { - + } + } }