q is ok
This commit is contained in:
parent
ef0689ec4e
commit
fed5184f47
@ -94,9 +94,8 @@ namespace ObservableCollections
|
|||||||
{
|
{
|
||||||
lock (SyncRoot)
|
lock (SyncRoot)
|
||||||
{
|
{
|
||||||
var index = queue.Count - 1;
|
|
||||||
var v = queue.Dequeue();
|
var v = queue.Dequeue();
|
||||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(v, index));
|
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(v, 0));
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,10 +104,9 @@ namespace ObservableCollections
|
|||||||
{
|
{
|
||||||
lock (SyncRoot)
|
lock (SyncRoot)
|
||||||
{
|
{
|
||||||
var index = queue.Count - 1;
|
|
||||||
if (queue.TryDequeue(out result))
|
if (queue.TryDequeue(out result))
|
||||||
{
|
{
|
||||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(result, index));
|
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(result, 0));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -119,17 +117,15 @@ namespace ObservableCollections
|
|||||||
{
|
{
|
||||||
lock (SyncRoot)
|
lock (SyncRoot)
|
||||||
{
|
{
|
||||||
var startIndex = queue.Count - count;
|
|
||||||
|
|
||||||
var dest = ArrayPool<T>.Shared.Rent(count);
|
var dest = ArrayPool<T>.Shared.Rent(count);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
for (int i = 0; i < count; i++)
|
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
|
finally
|
||||||
{
|
{
|
||||||
@ -142,14 +138,12 @@ namespace ObservableCollections
|
|||||||
{
|
{
|
||||||
lock (SyncRoot)
|
lock (SyncRoot)
|
||||||
{
|
{
|
||||||
var count = queue.Count;
|
|
||||||
var destCount = dest.Length;
|
|
||||||
for (int i = 0; i < dest.Length; i++)
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
181
src/ObservableCollections/ObservableStack.View.cs
Normal file
181
src/ObservableCollections/ObservableStack.View.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +1,206 @@
|
|||||||
using System.Collections.Generic;
|
using ObservableCollections.Internal;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace ObservableCollections
|
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;
|
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)
|
public void Push(T item)
|
||||||
{
|
{
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
var index = stack.Count;
|
||||||
stack.Push(item);
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
84
tests/ObservableCollections.Tests/ObservableStackTest.cs
Normal file
84
tests/ObservableCollections.Tests/ObservableStackTest.cs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user