From fed5184f474bc406e16c3549a6ccc39a6a04ddb7 Mon Sep 17 00:00:00 2001 From: neuecc Date: Fri, 13 Aug 2021 19:10:50 +0900 Subject: [PATCH] q is ok --- src/ObservableCollections/ObservableQueue.cs | 18 +- .../ObservableStack.View.cs | 181 ++++++++++++++++ src/ObservableCollections/ObservableStack.cs | 199 +++++++++++++++++- .../ObservableStackTest.cs | 84 ++++++++ 4 files changed, 461 insertions(+), 21 deletions(-) create mode 100644 src/ObservableCollections/ObservableStack.View.cs create mode 100644 tests/ObservableCollections.Tests/ObservableStackTest.cs diff --git a/src/ObservableCollections/ObservableQueue.cs b/src/ObservableCollections/ObservableQueue.cs index 7ee738e..978cf3f 100644 --- a/src/ObservableCollections/ObservableQueue.cs +++ b/src/ObservableCollections/ObservableQueue.cs @@ -94,9 +94,8 @@ namespace ObservableCollections { lock (SyncRoot) { - var index = queue.Count - 1; var v = queue.Dequeue(); - CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(v, index)); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(v, 0)); return v; } } @@ -105,10 +104,9 @@ namespace ObservableCollections { lock (SyncRoot) { - var index = queue.Count - 1; if (queue.TryDequeue(out result)) { - CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(result, index)); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(result, 0)); return true; } return false; @@ -119,17 +117,15 @@ namespace ObservableCollections { lock (SyncRoot) { - var startIndex = queue.Count - count; - var dest = ArrayPool.Shared.Rent(count); try { for (int i = 0; i < count; i++) { - dest[0] = queue.Dequeue(); + dest[i] = queue.Dequeue(); } - CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(dest.AsSpan(0, count), startIndex)); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(dest.AsSpan(0, count), 0)); } finally { @@ -142,14 +138,12 @@ namespace ObservableCollections { lock (SyncRoot) { - var count = queue.Count; - var destCount = dest.Length; for (int i = 0; i < dest.Length; i++) { - dest[0] = queue.Dequeue(); + dest[i] = queue.Dequeue(); } - CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(dest, count - queue.Count)); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(dest, 0)); } } diff --git a/src/ObservableCollections/ObservableStack.View.cs b/src/ObservableCollections/ObservableStack.View.cs new file mode 100644 index 0000000..3e043d5 --- /dev/null +++ b/src/ObservableCollections/ObservableStack.View.cs @@ -0,0 +1,181 @@ +using ObservableCollections.Internal; +using System.Collections; +using System.Collections.Specialized; + +namespace ObservableCollections +{ + public sealed partial class ObservableStack : 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, stack, identitySelector, transform, comparer); + } + + public ISynchronizedView CreateSortedView(Func identitySelector, Func transform, IComparer viewComparer) where TKey : notnull + { + return new SortedViewViewComparer(this, SyncRoot, stack, identitySelector, transform, viewComparer); + } + + class View : ISynchronizedView + { + readonly ObservableStack source; + readonly Func selector; + readonly bool reverse; + protected readonly Stack<(T, TView)> stack; + + ISynchronizedViewFilter filter; + + public event NotifyCollectionChangedEventHandler? RoutingCollectionChanged; + public event Action? CollectionStateChanged; + + public object SyncRoot { get; } + + public View(ObservableStack 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.stack = new Stack<(T, TView)>(source.stack.Select(x => (x, selector(x)))); + this.source.CollectionChanged += SourceCollectionChanged; + } + } + + public int Count + { + get + { + lock (SyncRoot) + { + return stack.Count; + } + } + } + + public void AttachFilter(ISynchronizedViewFilter filter) + { + lock (SyncRoot) + { + this.filter = filter; + foreach (var (value, view) in stack) + { + filter.InvokeOnAttach(value, view); + } + } + } + + public void ResetFilter(Action? resetAction) + { + lock (SyncRoot) + { + this.filter = SynchronizedViewFilter.Null; + if (resetAction != null) + { + foreach (var (item, view) in stack) + { + resetAction(item, view); + } + } + } + } + + public INotifyCollectionChangedSynchronizedView WithINotifyCollectionChanged() + { + lock (SyncRoot) + { + return new NotifyCollectionChangedSynchronizedView(this); + } + } + + public IEnumerator<(T, TView)> GetEnumerator() + { + if (!reverse) + { + return new SynchronizedViewEnumerator(SyncRoot, stack.GetEnumerator(), filter); + } + else + { + return new SynchronizedViewEnumerator(SyncRoot, stack.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(Push, PushRange) + if (e.IsSingleItem) + { + var v = (e.NewItem, selector(e.NewItem)); + stack.Push(v); + filter.InvokeOnAdd(v); + } + else + { + stack.EnsureCapacity(e.NewItems.Length); + foreach (var item in e.NewItems) + { + var v = (item, selector(item)); + stack.Push(v); + filter.InvokeOnAdd(v); + } + } + break; + case NotifyCollectionChangedAction.Remove: + // Pop, PopRange + if (e.IsSingleItem) + { + var v = stack.Pop(); + filter.InvokeOnRemove(v.Item1, v.Item2); + } + else + { + var len = e.OldItems.Length; + for (int i = 0; i < len; i++) + { + var v = stack.Pop(); + filter.InvokeOnRemove(v.Item1, v.Item2); + } + } + break; + case NotifyCollectionChangedAction.Reset: + if (!filter.IsNullFilter()) + { + foreach (var item in stack) + { + filter.InvokeOnRemove(item); + } + } + stack.Clear(); + break; + case NotifyCollectionChangedAction.Replace: + case NotifyCollectionChangedAction.Move: + default: + break; + } + + RoutingCollectionChanged?.Invoke(e); + CollectionStateChanged?.Invoke(e.Action); + } + } + } + } +} diff --git a/src/ObservableCollections/ObservableStack.cs b/src/ObservableCollections/ObservableStack.cs index 010a691..3803f0c 100644 --- a/src/ObservableCollections/ObservableStack.cs +++ b/src/ObservableCollections/ObservableStack.cs @@ -1,25 +1,206 @@ -using System.Collections.Generic; +using ObservableCollections.Internal; +using System.Buffers; +using System.Collections; +using System.Diagnostics.CodeAnalysis; namespace ObservableCollections { - public sealed partial class ObservableStack + public sealed partial class ObservableStack : IReadOnlyCollection, IObservableCollection { - // TODO:not yet. readonly Stack stack; + public readonly object SyncRoot = new object(); - public ObservableStack(Stack stack) + public ObservableStack() { - this.stack = stack; + this.stack = new Stack(); + } + + public ObservableStack(int capacity) + { + this.stack = new Stack(capacity); + } + + public ObservableStack(IEnumerable collection) + { + this.stack = new Stack(collection); + } + + public event NotifyCollectionChangedEventHandler? CollectionChanged; + + public int Count + { + get + { + lock (SyncRoot) + { + return stack.Count; + } + } } public void Push(T item) { - stack.Push(item); + lock (SyncRoot) + { + var index = stack.Count; + stack.Push(item); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Add(item, index)); + } } - public void Pop(T item) + public void PushRange(IEnumerable items) { - stack.Pop(); + lock (SyncRoot) + { + using (var xs = new CopyedCollection(items)) + { + foreach (var item in xs.Span) + { + stack.Push(item); + } + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Add(xs.Span, 0)); + } + } + } + + public void PushRange(T[] items) + { + lock (SyncRoot) + { + foreach (var item in items) + { + stack.Push(item); + } + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Add(items, 0)); + } + } + + public void PushRange(ReadOnlySpan items) + { + lock (SyncRoot) + { + foreach (var item in items) + { + stack.Push(item); + } + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Add(items, 0)); + } + } + + public T Pop() + { + lock (SyncRoot) + { + var v = stack.Pop(); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(v, 0)); + return v; + } + } + + public bool TryPop([MaybeNullWhen(false)] out T result) + { + lock (SyncRoot) + { + if (stack.TryPop(out result)) + { + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(result, 0)); + return true; + } + return false; + } + } + + public void PopRange(int count) + { + lock (SyncRoot) + { + var dest = ArrayPool.Shared.Rent(count); + try + { + for (int i = 0; i < count; i++) + { + dest[i] = stack.Pop(); + } + + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(dest.AsSpan(0, count), 0)); + } + finally + { + ArrayPool.Shared.Return(dest); + } + } + } + + public void PopRange(Span dest) + { + lock (SyncRoot) + { + for (int i = 0; i < dest.Length; i++) + { + dest[i] = stack.Pop(); + } + + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Remove(dest, 0)); + } + } + + public void Clear() + { + lock (SyncRoot) + { + stack.Clear(); + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs.Reset()); + } + } + + public T Peek() + { + lock (SyncRoot) + { + return stack.Peek(); + } + } + + public bool TryPeek([MaybeNullWhen(false)] T result) + { + lock (SyncRoot) + { + return stack.TryPeek(out result); + } + } + + public T[] ToArray() + { + lock (SyncRoot) + { + return stack.ToArray(); + } + } + + public void TrimExcess() + { + lock (SyncRoot) + { + stack.TrimExcess(); + } + } + + public void EnsureCapacity(int capacity) + { + lock (SyncRoot) + { + stack.EnsureCapacity(capacity); + } + } + + public IEnumerator GetEnumerator() + { + return new SynchronizedEnumerator(SyncRoot, stack.GetEnumerator()); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); } } -} +} \ No newline at end of file diff --git a/tests/ObservableCollections.Tests/ObservableStackTest.cs b/tests/ObservableCollections.Tests/ObservableStackTest.cs new file mode 100644 index 0000000..0da4131 --- /dev/null +++ b/tests/ObservableCollections.Tests/ObservableStackTest.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 ObservableStackTests + { + [Fact] + public void View() + { + var stack = new ObservableStack(); + var view = stack.CreateView(x => new ViewContainer(x)); + + stack.Push(10); + stack.Push(50); + stack.Push(30); + stack.Push(20); + stack.Push(40); + + void Equal(params int[] expected) + { + stack.Should().Equal(expected); + view.Select(x => x.Value).Should().Equal(expected); + view.Select(x => x.View).Should().Equal(expected.Select(x => new ViewContainer(x))); + } + + Equal(40, 20, 30, 50, 10); + + stack.PushRange(new[] { 1, 2, 3, 4, 5 }); + Equal(5, 4, 3, 2, 1, 40, 20, 30, 50, 10); + + stack.Pop().Should().Be(5); + Equal(4, 3, 2, 1, 40, 20, 30, 50, 10); + + stack.TryPop(out var q).Should().BeTrue(); + q.Should().Be(4); + Equal(3, 2, 1, 40, 20, 30, 50, 10); + + stack.PopRange(4); + Equal(20, 30, 50, 10); + + stack.Clear(); + + Equal(); + } + + [Fact] + public void Filter() + { + var stack = new ObservableStack(); + var view = stack.CreateView(x => new ViewContainer(x)); + var filter = new TestFilter((x, v) => x % 3 == 0); + + stack.Push(10); + stack.Push(50); + stack.Push(30); + stack.Push(20); + stack.Push(40); + + view.AttachFilter(filter); + filter.CalledWhenTrue.Select(x => x.Item1).Should().Equal(30); + filter.CalledWhenFalse.Select(x => x.Item1).Should().Equal(40, 20, 50, 10); + + view.Select(x => x.Value).Should().Equal(30); + + filter.Clear(); + + stack.Push(33); + stack.PushRange(new[] { 98 }); + + filter.CalledOnCollectionChanged.Select(x => (x.changedKind, x.value)).Should().Equal((ChangedKind.Add, 33), (ChangedKind.Add, 98)); + filter.Clear(); + + stack.Pop(); + stack.PopRange(2); + filter.CalledOnCollectionChanged.Select(x => (x.changedKind, x.value)).Should().Equal((ChangedKind.Remove, 98), (ChangedKind.Remove, 33), (ChangedKind.Remove, 40)); + } + } +}