q is ok
This commit is contained in:
parent
ef0689ec4e
commit
fed5184f47
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
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
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
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