This commit is contained in:
neuecc 2021-08-13 19:10:50 +09:00
parent ef0689ec4e
commit fed5184f47
4 changed files with 461 additions and 21 deletions

View File

@ -94,9 +94,8 @@ namespace ObservableCollections
{
lock (SyncRoot)
{
var index = queue.Count - 1;
var v = queue.Dequeue();
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(v, index));
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.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<T>.Remove(result, index));
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(result, 0));
return true;
}
return false;
@ -119,17 +117,15 @@ namespace ObservableCollections
{
lock (SyncRoot)
{
var startIndex = queue.Count - count;
var dest = ArrayPool<T>.Shared.Rent(count);
try
{
for (int i = 0; i < count; i++)
{
dest[0] = queue.Dequeue();
dest[i] = queue.Dequeue();
}
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(dest.AsSpan(0, count), startIndex));
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.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<T>.Remove(dest, count - queue.Count));
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(dest, 0));
}
}

View File

@ -0,0 +1,181 @@
using ObservableCollections.Internal;
using System.Collections;
using System.Collections.Specialized;
namespace ObservableCollections
{
public sealed partial class ObservableStack<T> : IReadOnlyCollection<T>, IObservableCollection<T>
{
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false)
{
return new View<TView>(this, transform, reverse);
}
public ISynchronizedView<T, TView> CreateSortedView<TKey, TView>(Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<T> comparer) where TKey : notnull
{
return new SortedView<T, TKey, TView>(this, SyncRoot, stack, identitySelector, transform, comparer);
}
public ISynchronizedView<T, TView> CreateSortedView<TKey, TView>(Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<TView> viewComparer) where TKey : notnull
{
return new SortedViewViewComparer<T, TKey, TView>(this, SyncRoot, stack, identitySelector, transform, viewComparer);
}
class View<TView> : ISynchronizedView<T, TView>
{
readonly ObservableStack<T> source;
readonly Func<T, TView> selector;
readonly bool reverse;
protected readonly Stack<(T, TView)> stack;
ISynchronizedViewFilter<T, TView> filter;
public event NotifyCollectionChangedEventHandler<T>? RoutingCollectionChanged;
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
public object SyncRoot { get; }
public View(ObservableStack<T> source, Func<T, TView> selector, bool reverse)
{
this.source = source;
this.selector = selector;
this.reverse = reverse;
this.filter = SynchronizedViewFilter<T, TView>.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<T, TView> filter)
{
lock (SyncRoot)
{
this.filter = filter;
foreach (var (value, view) in stack)
{
filter.InvokeOnAttach(value, view);
}
}
}
public void ResetFilter(Action<T, TView>? resetAction)
{
lock (SyncRoot)
{
this.filter = SynchronizedViewFilter<T, TView>.Null;
if (resetAction != null)
{
foreach (var (item, view) in stack)
{
resetAction(item, view);
}
}
}
}
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
{
lock (SyncRoot)
{
return new NotifyCollectionChangedSynchronizedView<T, TView>(this);
}
}
public IEnumerator<(T, TView)> GetEnumerator()
{
if (!reverse)
{
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, stack.GetEnumerator(), filter);
}
else
{
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, stack.AsEnumerable().Reverse().GetEnumerator(), filter);
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Dispose()
{
this.source.CollectionChanged -= SourceCollectionChanged;
}
private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs<T> 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);
}
}
}
}
}

View File

@ -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<T>
public sealed partial class ObservableStack<T> : IReadOnlyCollection<T>, IObservableCollection<T>
{
// TODO:not yet.
readonly Stack<T> stack;
public readonly object SyncRoot = new object();
public ObservableStack(Stack<T> stack)
public ObservableStack()
{
this.stack = stack;
this.stack = new Stack<T>();
}
public ObservableStack(int capacity)
{
this.stack = new Stack<T>(capacity);
}
public ObservableStack(IEnumerable<T> collection)
{
this.stack = new Stack<T>(collection);
}
public event NotifyCollectionChangedEventHandler<T>? 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<T>.Add(item, index));
}
}
public void Pop(T item)
public void PushRange(IEnumerable<T> items)
{
stack.Pop();
lock (SyncRoot)
{
using (var xs = new CopyedCollection<T>(items))
{
foreach (var item in xs.Span)
{
stack.Push(item);
}
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(xs.Span, 0));
}
}
}
public void PushRange(T[] items)
{
lock (SyncRoot)
{
foreach (var item in items)
{
stack.Push(item);
}
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(items, 0));
}
}
public void PushRange(ReadOnlySpan<T> items)
{
lock (SyncRoot)
{
foreach (var item in items)
{
stack.Push(item);
}
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(items, 0));
}
}
public T Pop()
{
lock (SyncRoot)
{
var v = stack.Pop();
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(v, 0));
return v;
}
}
public bool TryPop([MaybeNullWhen(false)] out T result)
{
lock (SyncRoot)
{
if (stack.TryPop(out result))
{
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(result, 0));
return true;
}
return false;
}
}
public void PopRange(int count)
{
lock (SyncRoot)
{
var dest = ArrayPool<T>.Shared.Rent(count);
try
{
for (int i = 0; i < count; i++)
{
dest[i] = stack.Pop();
}
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(dest.AsSpan(0, count), 0));
}
finally
{
ArrayPool<T>.Shared.Return(dest);
}
}
}
public void PopRange(Span<T> dest)
{
lock (SyncRoot)
{
for (int i = 0; i < dest.Length; i++)
{
dest[i] = stack.Pop();
}
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(dest, 0));
}
}
public void Clear()
{
lock (SyncRoot)
{
stack.Clear();
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.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<T> GetEnumerator()
{
return new SynchronizedEnumerator<T>(SyncRoot, stack.GetEnumerator());
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
}

View File

@ -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<int>();
var view = stack.CreateView(x => new ViewContainer<int>(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<int>(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<int>();
var view = stack.CreateView(x => new ViewContainer<int>(x));
var filter = new TestFilter<int>((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));
}
}
}