Merge pull request #22 from Cysharp/hadashiA/sorted-index

Fix the index provided by SortedView to filters
This commit is contained in:
Yoshifumi Kawai 2024-02-15 10:47:07 +09:00 committed by GitHub
commit 2d35d946e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 432 additions and 222 deletions

View File

@ -12,7 +12,7 @@ namespace ObservableCollections.Internal
readonly IObservableCollection<T> source;
readonly Func<T, TView> transform;
readonly Func<T, TKey> identitySelector;
readonly SortedDictionary<(T Value, TKey Key), (T Value, TView View)> dict;
readonly SortedList<(T Value, TKey Key), (T Value, TView View)> list;
ISynchronizedViewFilter<T, TView> filter;
@ -29,13 +29,13 @@ namespace ObservableCollections.Internal
this.filter = SynchronizedViewFilter<T, TView>.Null;
lock (source.SyncRoot)
{
var dict = new SortedDictionary<(T, TKey), (T, TView)>(new Comparer(comparer));
var dict = new Dictionary<(T, TKey), (T, TView)>(source.Count);
foreach (var v in source)
{
dict.Add((v, identitySelector(v)), (v, transform(v)));
}
this.dict = dict;
this.list = new SortedList<(T Value, TKey Key), (T Value, TView View)>(dict, new Comparer(comparer));
this.source.CollectionChanged += SourceCollectionChanged;
}
}
@ -46,7 +46,7 @@ namespace ObservableCollections.Internal
{
lock (SyncRoot)
{
return dict.Count;
return list.Count;
}
}
}
@ -56,7 +56,7 @@ namespace ObservableCollections.Internal
lock (SyncRoot)
{
this.filter = filter;
foreach (var (_, (value, view)) in dict)
foreach (var (_, (value, view)) in list)
{
if (invokeAddEventForCurrentElements)
{
@ -77,7 +77,7 @@ namespace ObservableCollections.Internal
this.filter = SynchronizedViewFilter<T, TView>.Null;
if (resetAction != null)
{
foreach (var (_, (value, view)) in dict)
foreach (var (_, (value, view)) in list)
{
resetAction(value, view);
}
@ -97,7 +97,7 @@ namespace ObservableCollections.Internal
{
lock (SyncRoot)
{
foreach (var item in dict)
foreach (var item in list)
{
if (filter.IsMatch(item.Value.Value, item.Value.View))
{
@ -128,8 +128,9 @@ namespace ObservableCollections.Internal
var value = e.NewItem;
var view = transform(value);
var id = identitySelector(value);
dict.Add((value, id), (value, view));
filter.InvokeOnAdd(value, view, e);
list.Add((value, id), (value, view));
var index = list.IndexOfKey((value, id));
filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs<T>.Add(value, index));
}
else
{
@ -137,8 +138,9 @@ namespace ObservableCollections.Internal
{
var view = transform(value);
var id = identitySelector(value);
dict.Add((value, id), (value, view));
filter.InvokeOnAdd(value, view, e);
list.Add((value, id), (value, view));
var index = list.IndexOfKey((value, id));
filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs<T>.Add(value, index));
}
}
}
@ -149,16 +151,26 @@ namespace ObservableCollections.Internal
{
var value = e.OldItem;
var id = identitySelector(value);
dict.Remove((value, id), out var v);
filter.InvokeOnRemove(v.Value, v.View, e);
var key = (value, id);
if (list.TryGetValue(key, out var v))
{
var index = list.IndexOfKey(key);
list.RemoveAt(index);
filter.InvokeOnRemove(v.Value, v.View, NotifyCollectionChangedEventArgs<T>.Remove(v.Value, index));
}
}
else
{
foreach (var value in e.OldItems)
{
var id = identitySelector(value);
dict.Remove((value, id), out var v);
filter.InvokeOnRemove(v.Value, v.View, e);
var key = (value, id);
if (list.TryGetValue(key, out var v))
{
var index = list.IndexOfKey((value, id));
list.RemoveAt(index);
filter.InvokeOnRemove(v.Value, v.View, NotifyCollectionChangedEventArgs<T>.Remove(v.Value, index));
}
}
}
}
@ -168,22 +180,28 @@ namespace ObservableCollections.Internal
// Replace is remove old item and insert new item.
{
var oldValue = e.OldItem;
dict.Remove((oldValue, identitySelector(oldValue)), out var oldView);
var oldKey = (oldValue, identitySelector(oldValue));
if (list.TryGetValue(oldKey, out var o))
{
var oldIndex = list.IndexOfKey(oldKey);
list.RemoveAt(oldIndex);
filter.InvokeOnRemove(o, NotifyCollectionChangedEventArgs<T>.Remove(oldValue, oldIndex));
}
var value = e.NewItem;
var view = transform(value);
var id = identitySelector(value);
dict.Add((value, id), (value, view));
list.Add((value, id), (value, view));
var newIndex = list.IndexOfKey((value, id));
filter.InvokeOnRemove(oldView, e);
filter.InvokeOnAdd(value, view, e);
filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs<T>.Add(value, newIndex));
}
break;
case NotifyCollectionChangedAction.Move:
{
// Move(index change) does not affect sorted list.
var oldValue = e.OldItem;
if (dict.TryGetValue((oldValue, identitySelector(oldValue)), out var view))
if (list.TryGetValue((oldValue, identitySelector(oldValue)), out var view))
{
filter.InvokeOnMove(view, e);
}
@ -192,12 +210,12 @@ namespace ObservableCollections.Internal
case NotifyCollectionChangedAction.Reset:
if (!filter.IsNullFilter())
{
foreach (var item in dict)
foreach (var item in list)
{
filter.InvokeOnRemove(item.Value, e);
}
}
dict.Clear();
list.Clear();
break;
default:
break;

View File

@ -13,7 +13,7 @@ namespace ObservableCollections.Internal
readonly Func<T, TView> transform;
readonly Func<T, TKey> identitySelector;
readonly Dictionary<TKey, TView> viewMap; // view-map needs to use in remove.
readonly SortedDictionary<(TView View, TKey Key), (T Value, TView View)> dict;
readonly SortedList<(TView View, TKey Key), (T Value, TView View)> list;
ISynchronizedViewFilter<T, TView> filter;
@ -30,7 +30,7 @@ namespace ObservableCollections.Internal
this.filter = SynchronizedViewFilter<T, TView>.Null;
lock (source.SyncRoot)
{
var dict = new SortedDictionary<(TView, TKey), (T, TView)>(new Comparer(comparer));
var dict = new Dictionary<(TView, TKey), (T, TView)>(source.Count);
this.viewMap = new Dictionary<TKey, TView>();
foreach (var value in source)
{
@ -39,7 +39,7 @@ namespace ObservableCollections.Internal
dict.Add((view, id), (value, view));
viewMap.Add(id, view);
}
this.dict = dict;
this.list = new SortedList<(TView View, TKey Key), (T Value, TView View)>(dict, new Comparer(comparer));
this.source.CollectionChanged += SourceCollectionChanged;
}
}
@ -50,7 +50,7 @@ namespace ObservableCollections.Internal
{
lock (SyncRoot)
{
return dict.Count;
return list.Count;
}
}
}
@ -60,7 +60,7 @@ namespace ObservableCollections.Internal
lock (SyncRoot)
{
this.filter = filter;
foreach (var (_, (value, view)) in dict)
foreach (var (_, (value, view)) in list)
{
if (invokeAddEventForCurrentElements)
{
@ -81,7 +81,7 @@ namespace ObservableCollections.Internal
this.filter = SynchronizedViewFilter<T, TView>.Null;
if (resetAction != null)
{
foreach (var (_, (value, view)) in dict)
foreach (var (_, (value, view)) in list)
{
resetAction(value, view);
}
@ -102,7 +102,7 @@ namespace ObservableCollections.Internal
lock (SyncRoot)
{
foreach (var item in dict)
foreach (var item in list)
{
if (filter.IsMatch(item.Value.Value, item.Value.View))
{
@ -133,9 +133,10 @@ namespace ObservableCollections.Internal
var value = e.NewItem;
var view = transform(value);
var id = identitySelector(value);
dict.Add((view, id), (value, view));
list.Add((view, id), (value, view));
viewMap.Add(id, view);
filter.InvokeOnAdd(value, view, e);
var index = list.IndexOfKey((view, id));
filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs<T>.Add(value, index));
}
else
{
@ -143,13 +144,14 @@ namespace ObservableCollections.Internal
{
var view = transform(value);
var id = identitySelector(value);
dict.Add((view, id), (value, view));
list.Add((view, id), (value, view));
viewMap.Add(id, view);
filter.InvokeOnAdd(value, view, e);
}
var index = list.IndexOfKey((view, id));
filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs<T>.Add(value, index));
}
}
break;
}
case NotifyCollectionChangedAction.Remove:
{
if (e.IsSingleItem)
@ -158,8 +160,13 @@ namespace ObservableCollections.Internal
var id = identitySelector(value);
if (viewMap.Remove(id, out var view))
{
dict.Remove((view, id), out var v);
filter.InvokeOnRemove(v, e);
var key = (view, id);
if (list.TryGetValue(key, out var v))
{
var index = list.IndexOfKey(key);
list.RemoveAt(index);
filter.InvokeOnRemove(v, NotifyCollectionChangedEventArgs<T>.Remove(v.Value, index));
}
}
}
else
@ -169,33 +176,44 @@ namespace ObservableCollections.Internal
var id = identitySelector(value);
if (viewMap.Remove(id, out var view))
{
dict.Remove((view, id), out var v);
filter.InvokeOnRemove(v, e);
var key = (view, id);
if (list.TryGetValue(key, out var v))
{
var index = list.IndexOfKey((view, id));
list.RemoveAt(index);
filter.InvokeOnRemove(v, NotifyCollectionChangedEventArgs<T>.Remove(v.Value, index));
}
}
}
}
break;
}
case NotifyCollectionChangedAction.Replace:
// Replace is remove old item and insert new item.
{
var oldValue = e.OldItem;
var oldKey = identitySelector(oldValue);
if (viewMap.Remove(oldKey, out var oldView))
var oldId = identitySelector(oldValue);
if (viewMap.Remove(oldId, out var oldView))
{
dict.Remove((oldView, oldKey));
filter.InvokeOnRemove(oldValue, oldView, e);
var oldKey = (oldView, oldId);
if (list.TryGetValue(oldKey, out var v))
{
var oldIndex = list.IndexOfKey(oldKey);
list.RemoveAt(oldIndex);
filter.InvokeOnRemove(oldValue, oldView, NotifyCollectionChangedEventArgs<T>.Remove(v.Value, oldIndex));
}
}
var value = e.NewItem;
var view = transform(value);
var id = identitySelector(value);
dict.Add((view, id), (value, view));
list.Add((view, id), (value, view));
viewMap.Add(id, view);
filter.InvokeOnAdd(value, view, e);
}
var index = list.IndexOfKey((view, id));
filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs<T>.Add(value, index));
break;
}
case NotifyCollectionChangedAction.Move:
// Move(index change) does not affect soreted dict.
{
@ -205,17 +223,17 @@ namespace ObservableCollections.Internal
{
filter.InvokeOnMove(value, view, e);
}
}
break;
}
case NotifyCollectionChangedAction.Reset:
if (!filter.IsNullFilter())
{
foreach (var item in dict)
foreach (var item in list)
{
filter.InvokeOnRemove(item.Value, e);
}
}
dict.Clear();
list.Clear();
viewMap.Clear();
break;
default:

View File

@ -12,7 +12,7 @@ namespace ObservableCollections.Internal
readonly IObservableCollection<T> source;
readonly Func<T, TView> transform;
readonly Func<T, TKey> identitySelector;
readonly SortedDictionary<(T Value, TKey Key), (T Value, TView View)> dict;
readonly SortedList<(T Value, TKey Key), (T Value, TView View)> list;
ISynchronizedViewFilter<T, TView> filter;
@ -29,13 +29,13 @@ namespace ObservableCollections.Internal
this.filter = SynchronizedViewFilter<T, TView>.Null;
lock (source.SyncRoot)
{
var dict = new SortedDictionary<(T, TKey), (T, TView)>(new Comparer(comparer));
var dict = new Dictionary<(T, TKey), (T, TView)>(source.Count);
foreach (var v in source)
{
dict.Add((v, identitySelector(v)), (v, transform(v)));
}
this.dict = dict;
this.list = new SortedList<(T Value, TKey Key), (T Value, TView View)>(dict, new Comparer(comparer));
this.source.CollectionChanged += SourceCollectionChanged;
}
}
@ -46,7 +46,7 @@ namespace ObservableCollections.Internal
{
lock (SyncRoot)
{
return dict.Count;
return list.Count;
}
}
}
@ -56,7 +56,7 @@ namespace ObservableCollections.Internal
lock (SyncRoot)
{
this.filter = filter;
foreach (var (_, (value, view)) in dict)
foreach (var (_, (value, view)) in list)
{
if (invokeAddEventForCurrentElements)
{
@ -77,7 +77,7 @@ namespace ObservableCollections.Internal
this.filter = SynchronizedViewFilter<T, TView>.Null;
if (resetAction != null)
{
foreach (var (_, (value, view)) in dict)
foreach (var (_, (value, view)) in list)
{
resetAction(value, view);
}
@ -97,7 +97,7 @@ namespace ObservableCollections.Internal
{
lock (SyncRoot)
{
foreach (var item in dict)
foreach (var item in list)
{
if (filter.IsMatch(item.Value.Value, item.Value.View))
{
@ -128,8 +128,9 @@ namespace ObservableCollections.Internal
var value = e.NewItem;
var view = transform(value);
var id = identitySelector(value);
dict.Add((value, id), (value, view));
filter.InvokeOnAdd(value, view, e);
list.Add((value, id), (value, view));
var index = list.IndexOfKey((value, id));
filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs<T>.Add(value, index));
}
else
{
@ -137,8 +138,9 @@ namespace ObservableCollections.Internal
{
var view = transform(value);
var id = identitySelector(value);
dict.Add((value, id), (value, view));
filter.InvokeOnAdd(value, view, e);
list.Add((value, id), (value, view));
var index = list.IndexOfKey((value, id));
filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs<T>.Add(value, index));
}
}
}
@ -149,16 +151,26 @@ namespace ObservableCollections.Internal
{
var value = e.OldItem;
var id = identitySelector(value);
dict.Remove((value, id), out var v);
filter.InvokeOnRemove(v.Value, v.View, e);
var key = (value, id);
if (list.TryGetValue(key, out var v))
{
var index = list.IndexOfKey(key);
list.RemoveAt(index);
filter.InvokeOnRemove(v.Value, v.View, NotifyCollectionChangedEventArgs<T>.Remove(v.Value, index));
}
}
else
{
foreach (var value in e.OldItems)
{
var id = identitySelector(value);
dict.Remove((value, id), out var v);
filter.InvokeOnRemove(v.Value, v.View, e);
var key = (value, id);
if (list.TryGetValue(key, out var v))
{
var index = list.IndexOfKey((value, id));
list.RemoveAt(index);
filter.InvokeOnRemove(v.Value, v.View, NotifyCollectionChangedEventArgs<T>.Remove(v.Value, index));
}
}
}
}
@ -168,22 +180,28 @@ namespace ObservableCollections.Internal
// Replace is remove old item and insert new item.
{
var oldValue = e.OldItem;
dict.Remove((oldValue, identitySelector(oldValue)), out var oldView);
var oldKey = (oldValue, identitySelector(oldValue));
if (list.TryGetValue(oldKey, out var o))
{
var oldIndex = list.IndexOfKey(oldKey);
list.RemoveAt(oldIndex);
filter.InvokeOnRemove(o, NotifyCollectionChangedEventArgs<T>.Remove(oldValue, oldIndex));
}
var value = e.NewItem;
var view = transform(value);
var id = identitySelector(value);
dict.Add((value, id), (value, view));
list.Add((value, id), (value, view));
var newIndex = list.IndexOfKey((value, id));
filter.InvokeOnRemove(oldView, e);
filter.InvokeOnAdd(value, view, e);
filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs<T>.Add(value, newIndex));
}
break;
case NotifyCollectionChangedAction.Move:
{
// Move(index change) does not affect sorted list.
var oldValue = e.OldItem;
if (dict.TryGetValue((oldValue, identitySelector(oldValue)), out var view))
if (list.TryGetValue((oldValue, identitySelector(oldValue)), out var view))
{
filter.InvokeOnMove(view, e);
}
@ -192,12 +210,12 @@ namespace ObservableCollections.Internal
case NotifyCollectionChangedAction.Reset:
if (!filter.IsNullFilter())
{
foreach (var item in dict)
foreach (var item in list)
{
filter.InvokeOnRemove(item.Value, e);
}
}
dict.Clear();
list.Clear();
break;
default:
break;

View File

@ -13,7 +13,7 @@ namespace ObservableCollections.Internal
readonly Func<T, TView> transform;
readonly Func<T, TKey> identitySelector;
readonly Dictionary<TKey, TView> viewMap; // view-map needs to use in remove.
readonly SortedDictionary<(TView View, TKey Key), (T Value, TView View)> dict;
readonly SortedList<(TView View, TKey Key), (T Value, TView View)> list;
ISynchronizedViewFilter<T, TView> filter;
@ -30,7 +30,7 @@ namespace ObservableCollections.Internal
this.filter = SynchronizedViewFilter<T, TView>.Null;
lock (source.SyncRoot)
{
var dict = new SortedDictionary<(TView, TKey), (T, TView)>(new Comparer(comparer));
var dict = new Dictionary<(TView, TKey), (T, TView)>(source.Count);
this.viewMap = new Dictionary<TKey, TView>();
foreach (var value in source)
{
@ -39,7 +39,7 @@ namespace ObservableCollections.Internal
dict.Add((view, id), (value, view));
viewMap.Add(id, view);
}
this.dict = dict;
this.list = new SortedList<(TView View, TKey Key), (T Value, TView View)>(dict, new Comparer(comparer));
this.source.CollectionChanged += SourceCollectionChanged;
}
}
@ -50,7 +50,7 @@ namespace ObservableCollections.Internal
{
lock (SyncRoot)
{
return dict.Count;
return list.Count;
}
}
}
@ -60,7 +60,7 @@ namespace ObservableCollections.Internal
lock (SyncRoot)
{
this.filter = filter;
foreach (var (_, (value, view)) in dict)
foreach (var (_, (value, view)) in list)
{
if (invokeAddEventForCurrentElements)
{
@ -81,7 +81,7 @@ namespace ObservableCollections.Internal
this.filter = SynchronizedViewFilter<T, TView>.Null;
if (resetAction != null)
{
foreach (var (_, (value, view)) in dict)
foreach (var (_, (value, view)) in list)
{
resetAction(value, view);
}
@ -102,7 +102,7 @@ namespace ObservableCollections.Internal
lock (SyncRoot)
{
foreach (var item in dict)
foreach (var item in list)
{
if (filter.IsMatch(item.Value.Value, item.Value.View))
{
@ -133,9 +133,10 @@ namespace ObservableCollections.Internal
var value = e.NewItem;
var view = transform(value);
var id = identitySelector(value);
dict.Add((view, id), (value, view));
list.Add((view, id), (value, view));
viewMap.Add(id, view);
filter.InvokeOnAdd(value, view, e);
var index = list.IndexOfKey((view, id));
filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs<T>.Add(value, index));
}
else
{
@ -143,13 +144,14 @@ namespace ObservableCollections.Internal
{
var view = transform(value);
var id = identitySelector(value);
dict.Add((view, id), (value, view));
list.Add((view, id), (value, view));
viewMap.Add(id, view);
filter.InvokeOnAdd(value, view, e);
}
var index = list.IndexOfKey((view, id));
filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs<T>.Add(value, index));
}
}
break;
}
case NotifyCollectionChangedAction.Remove:
{
if (e.IsSingleItem)
@ -158,8 +160,13 @@ namespace ObservableCollections.Internal
var id = identitySelector(value);
if (viewMap.Remove(id, out var view))
{
dict.Remove((view, id), out var v);
filter.InvokeOnRemove(v, e);
var key = (view, id);
if (list.TryGetValue(key, out var v))
{
var index = list.IndexOfKey(key);
list.RemoveAt(index);
filter.InvokeOnRemove(v, NotifyCollectionChangedEventArgs<T>.Remove(v.Value, index));
}
}
}
else
@ -169,33 +176,44 @@ namespace ObservableCollections.Internal
var id = identitySelector(value);
if (viewMap.Remove(id, out var view))
{
dict.Remove((view, id), out var v);
filter.InvokeOnRemove(v, e);
var key = (view, id);
if (list.TryGetValue(key, out var v))
{
var index = list.IndexOfKey((view, id));
list.RemoveAt(index);
filter.InvokeOnRemove(v, NotifyCollectionChangedEventArgs<T>.Remove(v.Value, index));
}
}
}
}
break;
}
case NotifyCollectionChangedAction.Replace:
// Replace is remove old item and insert new item.
{
var oldValue = e.OldItem;
var oldKey = identitySelector(oldValue);
if (viewMap.Remove(oldKey, out var oldView))
var oldId = identitySelector(oldValue);
if (viewMap.Remove(oldId, out var oldView))
{
dict.Remove((oldView, oldKey));
filter.InvokeOnRemove(oldValue, oldView, e);
var oldKey = (oldView, oldId);
if (list.TryGetValue(oldKey, out var v))
{
var oldIndex = list.IndexOfKey(oldKey);
list.RemoveAt(oldIndex);
filter.InvokeOnRemove(oldValue, oldView, NotifyCollectionChangedEventArgs<T>.Remove(v.Value, oldIndex));
}
}
var value = e.NewItem;
var view = transform(value);
var id = identitySelector(value);
dict.Add((view, id), (value, view));
list.Add((view, id), (value, view));
viewMap.Add(id, view);
filter.InvokeOnAdd(value, view, e);
}
var index = list.IndexOfKey((view, id));
filter.InvokeOnAdd(value, view, NotifyCollectionChangedEventArgs<T>.Add(value, index));
break;
}
case NotifyCollectionChangedAction.Move:
// Move(index change) does not affect soreted dict.
{
@ -205,17 +223,17 @@ namespace ObservableCollections.Internal
{
filter.InvokeOnMove(value, view, e);
}
}
break;
}
case NotifyCollectionChangedAction.Reset:
if (!filter.IsNullFilter())
{
foreach (var item in dict)
foreach (var item in list)
{
filter.InvokeOnRemove(item.Value, e);
}
}
dict.Clear();
list.Clear();
viewMap.Clear();
break;
default:

View File

@ -0,0 +1,69 @@
using System.Collections.Generic;
namespace ObservableCollections.Tests;
public class SortedViewTest
{
[Fact]
public void Sort()
{
var list = new ObservableList<int>();
var sortedView = list.CreateSortedView(
x => x,
x => new ViewContainer<int>(x),
Comparer<int>.Default);
list.Add(10);
list.Add(50);
list.Add(30);
list.Add(20);
list.Add(40);
using var e = sortedView.GetEnumerator();
e.MoveNext().Should().BeTrue();
e.Current.Value.Should().Be(10);
e.MoveNext().Should().BeTrue();
e.Current.Value.Should().Be(20);
e.MoveNext().Should().BeTrue();
e.Current.Value.Should().Be(30);
e.MoveNext().Should().BeTrue();
e.Current.Value.Should().Be(40);
e.MoveNext().Should().BeTrue();
e.Current.Value.Should().Be(50);
e.MoveNext().Should().BeFalse();
}
[Fact]
public void ObserveIndex()
{
var list = new ObservableList<int>();
var sortedView = list.CreateSortedView(
x => x,
x => new ViewContainer<int>(x),
Comparer<int>.Default);
var filter = new TestFilter<int>((value, view) => value % 2 == 0);
list.Add(50);
list.Add(10);
sortedView.AttachFilter(filter);
list.Add(20);
filter.CalledOnCollectionChanged[0].changedKind.Should().Be(ChangedKind.Add);
filter.CalledOnCollectionChanged[0].value.Should().Be(20);
filter.CalledOnCollectionChanged[0].index.Should().Be(1);
list.Remove(20);
filter.CalledOnCollectionChanged[1].changedKind.Should().Be(ChangedKind.Remove);
filter.CalledOnCollectionChanged[1].value.Should().Be(20);
filter.CalledOnCollectionChanged[1].oldIndex.Should().Be(1);
list[1] = 999; // from 10(at 0 in original) to 999
filter.CalledOnCollectionChanged[2].changedKind.Should().Be(ChangedKind.Remove);
filter.CalledOnCollectionChanged[2].value.Should().Be(10);
filter.CalledOnCollectionChanged[2].oldIndex.Should().Be(0);
filter.CalledOnCollectionChanged[3].changedKind.Should().Be(ChangedKind.Add);
filter.CalledOnCollectionChanged[3].value.Should().Be(999);
filter.CalledOnCollectionChanged[3].index.Should().Be(1);
}
}

View File

@ -0,0 +1,69 @@
using System.Collections.Generic;
namespace ObservableCollections.Tests;
public class SortedViewViewComparerTest
{
[Fact]
public void Sort()
{
var list = new ObservableList<int>();
var sortedView = list.CreateSortedView(
x => x,
x => new ViewContainer<int>(x),
Comparer<ViewContainer<int>>.Default);
list.Add(10);
list.Add(50);
list.Add(30);
list.Add(20);
list.Add(40);
using var e = sortedView.GetEnumerator();
e.MoveNext().Should().BeTrue();
e.Current.Value.Should().Be(10);
e.MoveNext().Should().BeTrue();
e.Current.Value.Should().Be(20);
e.MoveNext().Should().BeTrue();
e.Current.Value.Should().Be(30);
e.MoveNext().Should().BeTrue();
e.Current.Value.Should().Be(40);
e.MoveNext().Should().BeTrue();
e.Current.Value.Should().Be(50);
e.MoveNext().Should().BeFalse();
}
[Fact]
public void ObserveIndex()
{
var list = new ObservableList<int>();
var sortedView = list.CreateSortedView(
x => x,
x => new ViewContainer<int>(x),
Comparer<ViewContainer<int>>.Default);
var filter = new TestFilter<int>((value, view) => value % 2 == 0);
list.Add(50);
list.Add(10);
sortedView.AttachFilter(filter);
list.Add(20);
filter.CalledOnCollectionChanged[0].changedKind.Should().Be(ChangedKind.Add);
filter.CalledOnCollectionChanged[0].value.Should().Be(20);
filter.CalledOnCollectionChanged[0].index.Should().Be(1);
list.Remove(20);
filter.CalledOnCollectionChanged[1].changedKind.Should().Be(ChangedKind.Remove);
filter.CalledOnCollectionChanged[1].value.Should().Be(20);
filter.CalledOnCollectionChanged[1].oldIndex.Should().Be(1);
list[1] = 999; // from 10(at 0 in original) to 999
filter.CalledOnCollectionChanged[2].changedKind.Should().Be(ChangedKind.Remove);
filter.CalledOnCollectionChanged[2].value.Should().Be(10);
filter.CalledOnCollectionChanged[2].oldIndex.Should().Be(0);
filter.CalledOnCollectionChanged[3].changedKind.Should().Be(ChangedKind.Add);
filter.CalledOnCollectionChanged[3].value.Should().Be(999);
filter.CalledOnCollectionChanged[3].index.Should().Be(1);
}
}

View File

@ -35,7 +35,7 @@ namespace ObservableCollections.Tests
readonly Func<T, ViewContainer<T>, bool> filter;
public List<(T, ViewContainer<T>)> CalledWhenTrue = new();
public List<(T, ViewContainer<T>)> CalledWhenFalse = new();
public List<(ChangedKind changedKind, T value, ViewContainer<T> view)> CalledOnCollectionChanged = new();
public List<(ChangedKind changedKind, T value, ViewContainer<T> view, int index, int oldIndex)> CalledOnCollectionChanged = new();
public TestFilter(Func<T, ViewContainer<T>, bool> filter)
{
@ -56,7 +56,7 @@ namespace ObservableCollections.Tests
public void OnCollectionChanged(ChangedKind changedKind, T value, ViewContainer<T> view, in NotifyCollectionChangedEventArgs<T> eventArgs)
{
CalledOnCollectionChanged.Add((changedKind, value, view));
CalledOnCollectionChanged.Add((changedKind, value, view, eventArgs.NewStartingIndex, eventArgs.OldStartingIndex));
}
public void WhenTrue(T value, ViewContainer<T> view)