HashSet
This commit is contained in:
parent
2a85ffdac8
commit
d7d621ab0d
@ -3,6 +3,7 @@ using System.Buffers;
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace ObservableCollections.Internal
|
namespace ObservableCollections.Internal
|
||||||
{
|
{
|
||||||
@ -69,7 +70,7 @@ namespace ObservableCollections.Internal
|
|||||||
{
|
{
|
||||||
if (array.Length == index)
|
if (array.Length == index)
|
||||||
{
|
{
|
||||||
ArrayPool<T>.Shared.Return(array);
|
ArrayPool<T>.Shared.Return(array, RuntimeHelpers.IsReferenceOrContainsReferences<T>());
|
||||||
}
|
}
|
||||||
array = ArrayPool<T>.Shared.Rent(index * 2);
|
array = ArrayPool<T>.Shared.Rent(index * 2);
|
||||||
}
|
}
|
||||||
@ -78,7 +79,7 @@ namespace ObservableCollections.Internal
|
|||||||
{
|
{
|
||||||
if (array != null)
|
if (array != null)
|
||||||
{
|
{
|
||||||
ArrayPool<T>.Shared.Return(array);
|
ArrayPool<T>.Shared.Return(array, RuntimeHelpers.IsReferenceOrContainsReferences<T>());
|
||||||
array = null;
|
array = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -346,7 +346,8 @@ namespace ObservableCollections.Internal
|
|||||||
var value = e.OldItem;
|
var value = e.OldItem;
|
||||||
var key = keySelector(value);
|
var key = keySelector(value);
|
||||||
|
|
||||||
lookup
|
// TODO:...
|
||||||
|
// lookup
|
||||||
|
|
||||||
//var removeItems = lookup[key];
|
//var removeItems = lookup[key];
|
||||||
//foreach (var v in removeItems)
|
//foreach (var v in removeItems)
|
||||||
|
54
src/ObservableCollections/Internal/ResizableArray.cs
Normal file
54
src/ObservableCollections/Internal/ResizableArray.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
using System.Buffers;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace ObservableCollections.Internal
|
||||||
|
{
|
||||||
|
internal ref struct ResizableArray<T>
|
||||||
|
{
|
||||||
|
T[]? array;
|
||||||
|
int count;
|
||||||
|
|
||||||
|
public ReadOnlySpan<T> Span => array.AsSpan(0, count);
|
||||||
|
|
||||||
|
public ResizableArray(int initialCapacity)
|
||||||
|
{
|
||||||
|
array = ArrayPool<T>.Shared.Rent(initialCapacity);
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Add(T item)
|
||||||
|
{
|
||||||
|
if (array == null) Throw();
|
||||||
|
if (array.Length == count)
|
||||||
|
{
|
||||||
|
EnsureCapacity();
|
||||||
|
}
|
||||||
|
array[count++] = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
void EnsureCapacity()
|
||||||
|
{
|
||||||
|
var newArray = array.AsSpan().ToArray();
|
||||||
|
ArrayPool<T>.Shared.Return(array!, RuntimeHelpers.IsReferenceOrContainsReferences<T>());
|
||||||
|
array = newArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (array != null)
|
||||||
|
{
|
||||||
|
ArrayPool<T>.Shared.Return(array, RuntimeHelpers.IsReferenceOrContainsReferences<T>());
|
||||||
|
array = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DoesNotReturn]
|
||||||
|
void Throw()
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException("ResizableArray");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,162 @@
|
|||||||
using System.Collections.Generic;
|
using ObservableCollections.Internal;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
|
||||||
namespace ObservableCollections
|
namespace ObservableCollections
|
||||||
{
|
{
|
||||||
public sealed partial class ObservableHashSet<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
public sealed partial class ObservableHashSet<T> : IReadOnlyCollection<T>, IObservableCollection<T>
|
||||||
{
|
{
|
||||||
// TODO:
|
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool _ = false)
|
||||||
|
|
||||||
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false)
|
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
return new View<TView>(this, transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class View<TView> : ISynchronizedView<T, TView>
|
||||||
|
{
|
||||||
|
readonly ObservableHashSet<T> source;
|
||||||
|
readonly Func<T, TView> selector;
|
||||||
|
readonly Dictionary<T, (T, TView)> dict;
|
||||||
|
|
||||||
|
ISynchronizedViewFilter<T, TView> filter;
|
||||||
|
|
||||||
|
public event NotifyCollectionChangedEventHandler<T>? RoutingCollectionChanged;
|
||||||
|
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||||
|
|
||||||
|
public object SyncRoot { get; }
|
||||||
|
|
||||||
|
public View(ObservableHashSet<T> source, Func<T, TView> selector)
|
||||||
|
{
|
||||||
|
this.source = source;
|
||||||
|
this.selector = selector;
|
||||||
|
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||||
|
this.SyncRoot = new object();
|
||||||
|
lock (source.SyncRoot)
|
||||||
|
{
|
||||||
|
this.dict = source.set.ToDictionary(x => x, x => (x, selector(x)));
|
||||||
|
this.source.CollectionChanged += SourceCollectionChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
return dict.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||||
|
{
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
this.filter = filter;
|
||||||
|
foreach (var (_, (value, view)) in dict)
|
||||||
|
{
|
||||||
|
filter.InvokeOnAttach(value, view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetFilter(Action<T, TView>? resetAction)
|
||||||
|
{
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||||
|
if (resetAction != null)
|
||||||
|
{
|
||||||
|
foreach (var (_, (value, view)) in dict)
|
||||||
|
{
|
||||||
|
resetAction(value, view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public INotifyCollectionChangedSynchronizedView<T, TView> WithINotifyCollectionChanged()
|
||||||
|
{
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
return new NotifyCollectionChangedSynchronizedView<T, TView>(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<(T, TView)> GetEnumerator()
|
||||||
|
{
|
||||||
|
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, dict.Select(x => x.Value).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:
|
||||||
|
if (e.IsSingleItem)
|
||||||
|
{
|
||||||
|
var v = (e.NewItem, selector(e.NewItem));
|
||||||
|
dict.Add(e.NewItem, v);
|
||||||
|
filter.InvokeOnAdd(v);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var item in e.NewItems)
|
||||||
|
{
|
||||||
|
var v = (item, selector(item));
|
||||||
|
dict.Add(item, v);
|
||||||
|
filter.InvokeOnAdd(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NotifyCollectionChangedAction.Remove:
|
||||||
|
if (e.IsSingleItem)
|
||||||
|
{
|
||||||
|
if (dict.Remove(e.OldItem, out var value))
|
||||||
|
{
|
||||||
|
filter.InvokeOnRemove(value.Item1, value.Item2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var item in e.OldItems)
|
||||||
|
{
|
||||||
|
if (dict.Remove(item, out var value))
|
||||||
|
{
|
||||||
|
filter.InvokeOnRemove(value.Item1, value.Item2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NotifyCollectionChangedAction.Reset:
|
||||||
|
if (!filter.IsNullFilter())
|
||||||
|
{
|
||||||
|
foreach (var item in dict)
|
||||||
|
{
|
||||||
|
filter.InvokeOnRemove(item.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dict.Clear();
|
||||||
|
break;
|
||||||
|
case NotifyCollectionChangedAction.Replace:
|
||||||
|
case NotifyCollectionChangedAction.Move:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
RoutingCollectionChanged?.Invoke(e);
|
||||||
|
CollectionStateChanged?.Invoke(e.Action);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
using ObservableCollections.Internal;
|
using ObservableCollections.Internal;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace ObservableCollections
|
namespace ObservableCollections
|
||||||
{
|
{
|
||||||
// can not implements ISet<T> because set operation can not get added/removed values.
|
// can not implements ISet<T> because set operation can not get added/removed values.
|
||||||
public sealed partial class ObservableHashSet<T> : IReadOnlySet<T>, IReadOnlyCollection<T>, IObservableCollection<T>
|
public sealed partial class ObservableHashSet<T> : IReadOnlySet<T>, IReadOnlyCollection<T>, IObservableCollection<T>
|
||||||
|
where T : notnull
|
||||||
{
|
{
|
||||||
readonly HashSet<T> set;
|
readonly HashSet<T> set;
|
||||||
public object SyncRoot { get; } = new object();
|
public object SyncRoot { get; } = new object();
|
||||||
@ -40,8 +41,143 @@ namespace ObservableCollections
|
|||||||
|
|
||||||
public bool IsReadOnly => false;
|
public bool IsReadOnly => false;
|
||||||
|
|
||||||
// TODO: Add, Remove, Set operations.
|
public bool Add(T item)
|
||||||
|
{
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
if (set.Add(item))
|
||||||
|
{
|
||||||
|
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(item, -1));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRange(IEnumerable<T> items)
|
||||||
|
{
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
if (!items.TryGetNonEnumeratedCount(out var capacity))
|
||||||
|
{
|
||||||
|
capacity = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var list = new ResizableArray<T>(capacity))
|
||||||
|
{
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (set.Add(item))
|
||||||
|
{
|
||||||
|
list.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(list.Span, -1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRange(T[] items)
|
||||||
|
{
|
||||||
|
AddRange(items.AsSpan());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRange(ReadOnlySpan<T> items)
|
||||||
|
{
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
using (var list = new ResizableArray<T>(items.Length))
|
||||||
|
{
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (set.Add(item))
|
||||||
|
{
|
||||||
|
list.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(list.Span, -1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(T item)
|
||||||
|
{
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
if (set.Remove(item))
|
||||||
|
{
|
||||||
|
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(item, -1));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveRange(IEnumerable<T> items)
|
||||||
|
{
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
if (!items.TryGetNonEnumeratedCount(out var capacity))
|
||||||
|
{
|
||||||
|
capacity = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var list = new ResizableArray<T>(capacity))
|
||||||
|
{
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (set.Remove(item))
|
||||||
|
{
|
||||||
|
list.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(list.Span, -1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveRange(T[] items)
|
||||||
|
{
|
||||||
|
RemoveRange(items.AsSpan());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveRange(ReadOnlySpan<T> items)
|
||||||
|
{
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
using (var list = new ResizableArray<T>(items.Length))
|
||||||
|
{
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (set.Remove(item))
|
||||||
|
{
|
||||||
|
list.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Remove(list.Span, -1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
lock (SyncRoot)
|
||||||
|
{
|
||||||
|
set.Clear();
|
||||||
|
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Reset());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue(T equalValue, [MaybeNullWhen(false)] out T actualValue)
|
||||||
|
{
|
||||||
|
return set.TryGetValue(equalValue, out actualValue);
|
||||||
|
}
|
||||||
|
|
||||||
public bool Contains(T item)
|
public bool Contains(T item)
|
||||||
{
|
{
|
||||||
|
@ -11,12 +11,12 @@ namespace ObservableCollections
|
|||||||
return new View<TView>(this, transform, reverse);
|
return new View<TView>(this, transform, reverse);
|
||||||
}
|
}
|
||||||
|
|
||||||
class View<TView> : ISynchronizedView<T, TView>
|
sealed class View<TView> : ISynchronizedView<T, TView>
|
||||||
{
|
{
|
||||||
readonly ObservableList<T> source;
|
readonly ObservableList<T> source;
|
||||||
readonly Func<T, TView> selector;
|
readonly Func<T, TView> selector;
|
||||||
readonly bool reverse;
|
readonly bool reverse;
|
||||||
protected readonly List<(T, TView)> list;
|
readonly List<(T, TView)> list;
|
||||||
|
|
||||||
ISynchronizedViewFilter<T, TView> filter;
|
ISynchronizedViewFilter<T, TView> filter;
|
||||||
|
|
||||||
@ -111,8 +111,6 @@ namespace ObservableCollections
|
|||||||
switch (e.Action)
|
switch (e.Action)
|
||||||
{
|
{
|
||||||
case NotifyCollectionChangedAction.Add:
|
case NotifyCollectionChangedAction.Add:
|
||||||
list.EnsureCapacity(e.NewItems.Length);
|
|
||||||
|
|
||||||
// Add
|
// Add
|
||||||
if (e.NewStartingIndex == list.Count)
|
if (e.NewStartingIndex == list.Count)
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace ObservableCollections
|
namespace ObservableCollections
|
||||||
{
|
{
|
||||||
@ -129,7 +130,7 @@ namespace ObservableCollections
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
ArrayPool<T>.Shared.Return(dest);
|
ArrayPool<T>.Shared.Return(dest, RuntimeHelpers.IsReferenceOrContainsReferences<T>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace ObservableCollections
|
namespace ObservableCollections
|
||||||
{
|
{
|
||||||
@ -126,7 +127,7 @@ namespace ObservableCollections
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
ArrayPool<T>.Shared.Return(dest);
|
ArrayPool<T>.Shared.Return(dest, RuntimeHelpers.IsReferenceOrContainsReferences<T>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
80
tests/ObservableCollections.Tests/ObservableHashSetTest.cs
Normal file
80
tests/ObservableCollections.Tests/ObservableHashSetTest.cs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
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 ObservableHashSetTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void View()
|
||||||
|
{
|
||||||
|
var set = new ObservableHashSet<int>();
|
||||||
|
var view = set.CreateView(x => new ViewContainer<int>(x));
|
||||||
|
|
||||||
|
set.Add(10);
|
||||||
|
set.Add(50);
|
||||||
|
set.Add(30);
|
||||||
|
set.Add(20);
|
||||||
|
set.Add(40);
|
||||||
|
|
||||||
|
void Equal(params int[] expected)
|
||||||
|
{
|
||||||
|
set.Should().BeEquivalentTo(expected);
|
||||||
|
view.Select(x => x.Value).Should().BeEquivalentTo(expected);
|
||||||
|
view.Select(x => x.View.Value).Should().BeEquivalentTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
Equal(10, 50, 30, 20, 40);
|
||||||
|
|
||||||
|
set.AddRange(new[] { 1, 2, 3, 4, 5 });
|
||||||
|
Equal(10, 50, 30, 20, 40, 1, 2, 3, 4, 5);
|
||||||
|
|
||||||
|
set.Remove(10);
|
||||||
|
Equal(50, 30, 20, 40, 1, 2, 3, 4, 5);
|
||||||
|
|
||||||
|
set.RemoveRange(new[] { 50, 40 });
|
||||||
|
Equal(30, 20, 1, 2, 3, 4, 5);
|
||||||
|
|
||||||
|
set.Clear();
|
||||||
|
|
||||||
|
Equal();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Filter()
|
||||||
|
{
|
||||||
|
var set = new ObservableHashSet<int>();
|
||||||
|
var view = set.CreateView(x => new ViewContainer<int>(x));
|
||||||
|
var filter = new TestFilter<int>((x, v) => x % 3 == 0);
|
||||||
|
|
||||||
|
set.Add(10);
|
||||||
|
set.Add(50);
|
||||||
|
set.Add(30);
|
||||||
|
set.Add(20);
|
||||||
|
set.Add(40);
|
||||||
|
|
||||||
|
view.AttachFilter(filter);
|
||||||
|
filter.CalledWhenTrue.Select(x => x.Item1).Should().Equal(30);
|
||||||
|
filter.CalledWhenFalse.Select(x => x.Item1).Should().Equal(10, 50, 20, 40);
|
||||||
|
|
||||||
|
view.Select(x => x.Value).Should().Equal(30);
|
||||||
|
|
||||||
|
filter.Clear();
|
||||||
|
|
||||||
|
set.Add(33);
|
||||||
|
set.AddRange(new[] { 98 });
|
||||||
|
|
||||||
|
filter.CalledOnCollectionChanged.Select(x => (x.changedKind, x.value)).Should().Equal((ChangedKind.Add, 33), (ChangedKind.Add, 98));
|
||||||
|
filter.Clear();
|
||||||
|
|
||||||
|
set.Remove(10);
|
||||||
|
set.RemoveRange(new[] { 50, 30 });
|
||||||
|
filter.CalledOnCollectionChanged.Select(x => (x.changedKind, x.value)).Should().Equal((ChangedKind.Remove, 10), (ChangedKind.Remove, 50), (ChangedKind.Remove, 30));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user