list and dict tests
This commit is contained in:
parent
25ae432a40
commit
c0fb7375e0
8
OnlySln.slnf
Normal file
8
OnlySln.slnf
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"solution": {
|
||||||
|
"path": "ObservableCollections.sln",
|
||||||
|
"projects": [
|
||||||
|
"src\\ObservableCollections\\ObservableCollections.csproj"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -12,8 +12,10 @@ namespace ObservableCollections
|
|||||||
event NotifyCollectionChangedEventHandler<T>? CollectionChanged;
|
event NotifyCollectionChangedEventHandler<T>? CollectionChanged;
|
||||||
|
|
||||||
ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false);
|
ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false);
|
||||||
ISynchronizedView<T, TView> CreateSortedView<TView>(Func<T, TView> transform, IComparer<T> comparer);
|
ISynchronizedView<T, TView> CreateSortedView<TKey, TView>(Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<T> comparer)
|
||||||
ISynchronizedView<T, TView> CreateSortedView<TView>(Func<T, TView> transform, IComparer<TView> viewComparer);
|
where TKey : notnull;
|
||||||
|
ISynchronizedView<T, TView> CreateSortedView<TKey, TView>(Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<TView> viewComparer)
|
||||||
|
where TKey : notnull;
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IFreezedCollection<T>
|
public interface IFreezedCollection<T>
|
||||||
@ -43,4 +45,60 @@ namespace ObservableCollections
|
|||||||
public interface INotifyCollectionChangedSynchronizedView<T, TView> : ISynchronizedView<T, TView>, INotifyCollectionChanged, INotifyPropertyChanged
|
public interface INotifyCollectionChangedSynchronizedView<T, TView> : ISynchronizedView<T, TView>, INotifyCollectionChanged, INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ObservableCollectionsExtensions
|
||||||
|
{
|
||||||
|
public static ISynchronizedView<T, TView> CreateSortedView<T, TKey, TView, TCompare>(this IObservableCollection<T> source, Func<T, TKey> identitySelector, Func<T, TView> transform, Func<T, TCompare> compareSelector, bool ascending = true)
|
||||||
|
where TKey : notnull
|
||||||
|
{
|
||||||
|
return source.CreateSortedView(identitySelector, transform, new AnonymousComparer<T, TCompare>(compareSelector, ascending));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ISortableSynchronizedView<T, TView> CreateSortableView<T, TView>(this IFreezedCollection<T> source, Func<T, TView> transform, IComparer<T> initialSort)
|
||||||
|
{
|
||||||
|
var view = source.CreateSortableView(transform);
|
||||||
|
view.Sort(initialSort);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ISortableSynchronizedView<T, TView> CreateSortableView<T, TView>(this IFreezedCollection<T> source, Func<T, TView> transform, IComparer<TView> initialViewSort)
|
||||||
|
{
|
||||||
|
var view = source.CreateSortableView(transform);
|
||||||
|
view.Sort(initialViewSort);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ISortableSynchronizedView<T, TView> CreateSortableView<T, TView, TCompare>(this IFreezedCollection<T> source, Func<T, TView> transform, Func<T, TCompare> initialCompareSelector, bool ascending = true)
|
||||||
|
{
|
||||||
|
var view = source.CreateSortableView(transform);
|
||||||
|
view.Sort(initialCompareSelector, ascending);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Sort<T, TView, TCompare>(this ISortableSynchronizedView<T, TView> source, Func<T, TCompare> compareSelector, bool ascending = true)
|
||||||
|
{
|
||||||
|
source.Sort(new AnonymousComparer<T, TCompare>(compareSelector, ascending));
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnonymousComparer<T, TCompare> : IComparer<T>
|
||||||
|
{
|
||||||
|
readonly Func<T, TCompare> selector;
|
||||||
|
readonly int f;
|
||||||
|
|
||||||
|
public AnonymousComparer(Func<T, TCompare> selector, bool ascending)
|
||||||
|
{
|
||||||
|
this.selector = selector;
|
||||||
|
this.f = ascending ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Compare(T? x, T? y)
|
||||||
|
{
|
||||||
|
if (x == null && y == null) return 0;
|
||||||
|
if (x == null) return 1 * f;
|
||||||
|
if (y == null) return -1 * f;
|
||||||
|
|
||||||
|
return Comparer<TCompare>.Default.Compare(selector(x), selector(y)) * f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,4 +1,6 @@
|
|||||||
using System;
|
#pragma warning disable CS0067
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
|
@ -25,6 +25,17 @@ namespace ObservableCollections
|
|||||||
return new ViewComparerSortedView<TView>(this, transform, viewComparer);
|
return new ViewComparerSortedView<TView>(this, transform, viewComparer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// identity selector is ignored
|
||||||
|
ISynchronizedView<KeyValuePair<TKey, TValue>, TView> IObservableCollection<KeyValuePair<TKey, TValue>>.CreateSortedView<TKey1, TView>(Func<KeyValuePair<TKey, TValue>, TKey1> identitySelector, Func<KeyValuePair<TKey, TValue>, TView> transform, IComparer<KeyValuePair<TKey, TValue>> comparer)
|
||||||
|
{
|
||||||
|
return new SortedView<TView>(this, transform, comparer);
|
||||||
|
}
|
||||||
|
|
||||||
|
ISynchronizedView<KeyValuePair<TKey, TValue>, TView> IObservableCollection<KeyValuePair<TKey, TValue>>.CreateSortedView<TKey1, TView>(Func<KeyValuePair<TKey, TValue>, TKey1> identitySelector, Func<KeyValuePair<TKey, TValue>, TView> transform, IComparer<TView> viewComparer)
|
||||||
|
{
|
||||||
|
return new ViewComparerSortedView<TView>(this, transform, viewComparer);
|
||||||
|
}
|
||||||
|
|
||||||
class View<TView> : ISynchronizedView<KeyValuePair<TKey, TValue>, TView>
|
class View<TView> : ISynchronizedView<KeyValuePair<TKey, TValue>, TView>
|
||||||
{
|
{
|
||||||
readonly ObservableDictionary<TKey, TValue> source;
|
readonly ObservableDictionary<TKey, TValue> source;
|
||||||
@ -136,7 +147,7 @@ namespace ObservableCollections
|
|||||||
{
|
{
|
||||||
dict.Remove(e.OldItem.Key);
|
dict.Remove(e.OldItem.Key);
|
||||||
var v = selector(e.NewItem);
|
var v = selector(e.NewItem);
|
||||||
dict.Add(e.NewItem.Key, (e.NewItem.Value, v));
|
dict[e.NewItem.Key] = (e.NewItem.Value, v);
|
||||||
filter.Invoke(new KeyValuePair<TKey, TValue>(e.NewItem.Key, e.NewItem.Value), v);
|
filter.Invoke(new KeyValuePair<TKey, TValue>(e.NewItem.Key, e.NewItem.Value), v);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -273,7 +284,7 @@ namespace ObservableCollections
|
|||||||
dict.Remove(k);
|
dict.Remove(k);
|
||||||
var v = selector(e.NewItem);
|
var v = selector(e.NewItem);
|
||||||
var nk = new KeyValuePair<TKey, TValue>(e.NewItem.Key, e.NewItem.Value);
|
var nk = new KeyValuePair<TKey, TValue>(e.NewItem.Key, e.NewItem.Value);
|
||||||
dict.Add(nk, v);
|
dict[nk] = v;
|
||||||
filter.Invoke(nk, v);
|
filter.Invoke(nk, v);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -419,8 +430,14 @@ namespace ObservableCollections
|
|||||||
if (viewMap.Remove(e.OldItem.Key, out var view))
|
if (viewMap.Remove(e.OldItem.Key, out var view))
|
||||||
{
|
{
|
||||||
dict.Remove(view);
|
dict.Remove(view);
|
||||||
|
|
||||||
|
var v = selector(e.NewItem);
|
||||||
|
var k = new KeyValuePair<TKey, TValue>(e.NewItem.Key, e.NewItem.Value);
|
||||||
|
dict[v] = k;
|
||||||
|
viewMap[e.NewItem.Key] = v;
|
||||||
|
filter.Invoke(k, v);
|
||||||
}
|
}
|
||||||
goto case NotifyCollectionChangedAction.Add;
|
break;
|
||||||
}
|
}
|
||||||
case NotifyCollectionChangedAction.Reset:
|
case NotifyCollectionChangedAction.Reset:
|
||||||
{
|
{
|
||||||
|
@ -38,12 +38,18 @@ namespace ObservableCollections
|
|||||||
{
|
{
|
||||||
lock (SyncRoot)
|
lock (SyncRoot)
|
||||||
{
|
{
|
||||||
var oldValue = dictionary[key];
|
if (dictionary.TryGetValue(key, out var oldValue))
|
||||||
dictionary[key] = value;
|
{
|
||||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<KeyValuePair<TKey, TValue>>.Replace(
|
dictionary[key] = value;
|
||||||
new KeyValuePair<TKey, TValue>(key, value),
|
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<KeyValuePair<TKey, TValue>>.Replace(
|
||||||
new KeyValuePair<TKey, TValue>(key, oldValue),
|
new KeyValuePair<TKey, TValue>(key, value),
|
||||||
-1));
|
new KeyValuePair<TKey, TValue>(key, oldValue),
|
||||||
|
-1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Add(key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,14 +14,16 @@ namespace ObservableCollections
|
|||||||
return new View<TView>(this, transform, reverse);
|
return new View<TView>(this, transform, reverse);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ISynchronizedView<T, TView> CreateSortedView<TView>(Func<T, TView> transform, IComparer<T> comparer)
|
public ISynchronizedView<T, TView> CreateSortedView<TKey, TView>(Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<T> comparer)
|
||||||
|
where TKey : notnull
|
||||||
{
|
{
|
||||||
return new SortedView<TView>(this, transform, comparer);
|
return new SortedView<TKey, TView>(this, identitySelector, transform, comparer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ISynchronizedView<T, TView> CreateSortedView<TView>(Func<T, TView> transform, IComparer<TView> viewComparer)
|
public ISynchronizedView<T, TView> CreateSortedView<TKey, TView>(Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<TView> viewComparer)
|
||||||
|
where TKey : notnull
|
||||||
{
|
{
|
||||||
return new ViewComparerSortedView<TView>(this, transform, viewComparer);
|
return new ViewComparerSortedView<TKey, TView>(this, identitySelector, transform, viewComparer);
|
||||||
}
|
}
|
||||||
|
|
||||||
class View<TView> : ISynchronizedView<T, TView>
|
class View<TView> : ISynchronizedView<T, TView>
|
||||||
@ -29,7 +31,7 @@ namespace ObservableCollections
|
|||||||
readonly ObservableList<T> source;
|
readonly ObservableList<T> source;
|
||||||
readonly Func<T, TView> selector;
|
readonly Func<T, TView> selector;
|
||||||
readonly bool reverse;
|
readonly bool reverse;
|
||||||
readonly List<(T, TView)> list;
|
protected readonly List<(T, TView)> list;
|
||||||
|
|
||||||
ISynchronizedViewFilter<T, TView> filter;
|
ISynchronizedViewFilter<T, TView> filter;
|
||||||
|
|
||||||
@ -52,6 +54,10 @@ namespace ObservableCollections
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void DoSort()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public int Count
|
public int Count
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -201,20 +207,20 @@ namespace ObservableCollections
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DoSort();
|
||||||
RoutingCollectionChanged?.Invoke(e);
|
RoutingCollectionChanged?.Invoke(e);
|
||||||
CollectionStateChanged?.Invoke(e.Action);
|
CollectionStateChanged?.Invoke(e.Action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SortedView<TView> : ISynchronizedView<T, TView>
|
class SortedView<TKey, TView> : ISynchronizedView<T, TView>
|
||||||
|
where TKey : notnull
|
||||||
{
|
{
|
||||||
readonly ObservableList<T> source;
|
readonly ObservableList<T> source;
|
||||||
readonly Func<T, TView> selector;
|
readonly Func<T, TView> transform;
|
||||||
|
readonly Func<T, TKey> identitySelector;
|
||||||
// SortedList is array-based, SortedDictionary is red-black tree based.
|
readonly SortedDictionary<(T Value, TKey Key), (T Value, TView View)> list;
|
||||||
// key as with index to keep uniqueness
|
|
||||||
readonly SortedDictionary<(T value, int index), TView> list;
|
|
||||||
|
|
||||||
ISynchronizedViewFilter<T, TView> filter;
|
ISynchronizedViewFilter<T, TView> filter;
|
||||||
|
|
||||||
@ -223,19 +229,20 @@ namespace ObservableCollections
|
|||||||
|
|
||||||
public object SyncRoot { get; } = new object();
|
public object SyncRoot { get; } = new object();
|
||||||
|
|
||||||
public SortedView(ObservableList<T> source, Func<T, TView> selector, IComparer<T> comparer)
|
public SortedView(ObservableList<T> source, Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<T> comparer)
|
||||||
{
|
{
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.selector = selector;
|
this.identitySelector = identitySelector;
|
||||||
|
this.transform = transform;
|
||||||
this.filter = SynchronizedViewFilter<T, TView>.AlwaysTrue;
|
this.filter = SynchronizedViewFilter<T, TView>.AlwaysTrue;
|
||||||
lock (source.SyncRoot)
|
lock (source.SyncRoot)
|
||||||
{
|
{
|
||||||
var dict = new SortedDictionary<(T, int), TView>(new WithIndexComparer(comparer));
|
var dict = new SortedDictionary<(T, TKey), (T, TView)>(new Comparer(comparer));
|
||||||
var count = source.list.Count;
|
var count = source.list.Count;
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
var v = source.list[i];
|
var v = source.list[i];
|
||||||
dict.Add((v, i), selector(v));
|
dict.Add((v, identitySelector(v)), (v, transform(v)));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.list = dict;
|
this.list = dict;
|
||||||
@ -259,9 +266,9 @@ namespace ObservableCollections
|
|||||||
lock (SyncRoot)
|
lock (SyncRoot)
|
||||||
{
|
{
|
||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
foreach (var (item, view) in list)
|
foreach (var (_, (value, view)) in list)
|
||||||
{
|
{
|
||||||
filter.Invoke(item.value, view);
|
filter.Invoke(value, view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -273,9 +280,9 @@ namespace ObservableCollections
|
|||||||
this.filter = SynchronizedViewFilter<T, TView>.AlwaysTrue;
|
this.filter = SynchronizedViewFilter<T, TView>.AlwaysTrue;
|
||||||
if (resetAction != null)
|
if (resetAction != null)
|
||||||
{
|
{
|
||||||
foreach (var (item, view) in list)
|
foreach (var (_, (value, view)) in list)
|
||||||
{
|
{
|
||||||
resetAction(item.value, view);
|
resetAction(value, view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -291,7 +298,7 @@ namespace ObservableCollections
|
|||||||
|
|
||||||
public IEnumerator<(T, TView)> GetEnumerator()
|
public IEnumerator<(T, TView)> GetEnumerator()
|
||||||
{
|
{
|
||||||
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, list.Select(x => (x.Key.value, x.Value)).GetEnumerator(), filter);
|
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, list.Select(x => x.Value).GetEnumerator(), filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
@ -308,22 +315,24 @@ namespace ObservableCollections
|
|||||||
switch (e.Action)
|
switch (e.Action)
|
||||||
{
|
{
|
||||||
case NotifyCollectionChangedAction.Add:
|
case NotifyCollectionChangedAction.Add:
|
||||||
// Add, Insert
|
|
||||||
{
|
{
|
||||||
|
// Add, Insert
|
||||||
if (e.IsSingleItem)
|
if (e.IsSingleItem)
|
||||||
{
|
{
|
||||||
var view = selector(e.NewItem);
|
var value = e.NewItem;
|
||||||
list.Add((e.NewItem, e.NewStartingIndex), view);
|
var view = transform(value);
|
||||||
filter.Invoke(e.NewItem, view);
|
var id = identitySelector(value);
|
||||||
|
list.Add((value, id), (value, view));
|
||||||
|
filter.Invoke(value, view);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var index = e.NewStartingIndex;
|
foreach (var value in e.NewItems)
|
||||||
foreach (var item in e.NewItems)
|
|
||||||
{
|
{
|
||||||
var view = selector(item);
|
var view = transform(value);
|
||||||
list.Add((item, index++), view);
|
var id = identitySelector(value);
|
||||||
filter.Invoke(item, view);
|
list.Add((value, id), (value, view));
|
||||||
|
filter.Invoke(value, view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -332,14 +341,16 @@ namespace ObservableCollections
|
|||||||
{
|
{
|
||||||
if (e.IsSingleItem)
|
if (e.IsSingleItem)
|
||||||
{
|
{
|
||||||
list.Remove((e.OldItem, e.OldStartingIndex));
|
var value = e.OldItem;
|
||||||
|
var id = identitySelector(value);
|
||||||
|
list.Remove((value, id));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var index = e.OldStartingIndex;
|
foreach (var value in e.OldItems)
|
||||||
foreach (var item in e.OldItems)
|
|
||||||
{
|
{
|
||||||
list.Remove((item, index++));
|
var id = identitySelector(value);
|
||||||
|
list.Remove((value, id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -349,10 +360,14 @@ namespace ObservableCollections
|
|||||||
// ObservableList does not support replace range
|
// ObservableList does not support replace range
|
||||||
// Replace is remove old item and insert new item(same index on replace, difference index on move).
|
// Replace is remove old item and insert new item(same index on replace, difference index on move).
|
||||||
{
|
{
|
||||||
var view = selector(e.NewItem);
|
var oldValue = e.OldItem;
|
||||||
list.Remove((e.OldItem, e.OldStartingIndex));
|
list.Remove((oldValue, identitySelector(oldValue)));
|
||||||
list.Add((e.NewItem, e.NewStartingIndex), view);
|
|
||||||
filter.Invoke(e.NewItem, view);
|
var value = e.NewItem;
|
||||||
|
var view = transform(value);
|
||||||
|
var id = identitySelector(value);
|
||||||
|
list.Add((value, id), (value, view));
|
||||||
|
filter.Invoke(value, view);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NotifyCollectionChangedAction.Reset:
|
case NotifyCollectionChangedAction.Reset:
|
||||||
@ -367,33 +382,36 @@ namespace ObservableCollections
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class WithIndexComparer : IComparer<(T value, int index)>
|
sealed class Comparer : IComparer<(T value, TKey id)>
|
||||||
{
|
{
|
||||||
readonly IComparer<T> comparer;
|
readonly IComparer<T> comparer;
|
||||||
|
|
||||||
public WithIndexComparer(IComparer<T> comparer)
|
public Comparer(IComparer<T> comparer)
|
||||||
{
|
{
|
||||||
this.comparer = comparer;
|
this.comparer = comparer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Compare((T value, int index) x, (T value, int index) y)
|
public int Compare((T value, TKey id) x, (T value, TKey id) y)
|
||||||
{
|
{
|
||||||
var v = comparer.Compare(x.value, y.value);
|
var compare = comparer.Compare(x.value, y.value);
|
||||||
if (v == 0)
|
if (compare == 0)
|
||||||
{
|
{
|
||||||
v = x.index.CompareTo(y.index);
|
compare = Comparer<TKey>.Default.Compare(x.id, y.id);
|
||||||
}
|
}
|
||||||
return v;
|
|
||||||
|
return compare;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewComparerSortedView<TView> : ISynchronizedView<T, TView>
|
class ViewComparerSortedView<TKey, TView> : ISynchronizedView<T, TView>
|
||||||
|
where TKey : notnull
|
||||||
{
|
{
|
||||||
readonly ObservableList<T> source;
|
readonly ObservableList<T> source;
|
||||||
readonly Func<T, TView> selector;
|
readonly Func<T, TView> transform;
|
||||||
readonly Dictionary<(T, int), TView> viewMap; // view-map needs to use in remove.
|
readonly Func<T, TKey> identitySelector;
|
||||||
readonly SortedDictionary<(TView view, int index), T> list;
|
readonly Dictionary<TKey, TView> viewMap; // view-map needs to use in remove.
|
||||||
|
readonly SortedDictionary<(TView View, TKey Key), (T Value, TView View)> list;
|
||||||
|
|
||||||
ISynchronizedViewFilter<T, TView> filter;
|
ISynchronizedViewFilter<T, TView> filter;
|
||||||
|
|
||||||
@ -402,25 +420,25 @@ namespace ObservableCollections
|
|||||||
|
|
||||||
public object SyncRoot { get; } = new object();
|
public object SyncRoot { get; } = new object();
|
||||||
|
|
||||||
public ViewComparerSortedView(ObservableList<T> source, Func<T, TView> selector, IComparer<TView> comparer)
|
public ViewComparerSortedView(ObservableList<T> source, Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<TView> comparer)
|
||||||
{
|
{
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.selector = selector;
|
this.identitySelector = identitySelector;
|
||||||
this.viewMap = new Dictionary<(T, int), TView>(source.Count);
|
this.transform = transform;
|
||||||
this.filter = SynchronizedViewFilter<T, TView>.AlwaysTrue;
|
this.filter = SynchronizedViewFilter<T, TView>.AlwaysTrue;
|
||||||
lock (source.SyncRoot)
|
lock (source.SyncRoot)
|
||||||
{
|
{
|
||||||
var dict = new SortedDictionary<(TView, int), T>(new WithIndexComparer(comparer));
|
var dict = new SortedDictionary<(TView, TKey), (T, TView)>(new Comparer(comparer));
|
||||||
|
this.viewMap = new Dictionary<TKey, TView>(source.list.Count);
|
||||||
var count = source.list.Count;
|
var count = source.list.Count;
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
var v = source.list[i];
|
var value = source.list[i];
|
||||||
var v2 = selector(v);
|
var view = transform(value);
|
||||||
|
var id = identitySelector(value);
|
||||||
dict.Add((v2, i), v);
|
dict.Add((view, id), (value, view));
|
||||||
viewMap.Add((v, i), v2);
|
viewMap.Add(id, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.list = dict;
|
this.list = dict;
|
||||||
this.source.CollectionChanged += SourceCollectionChanged;
|
this.source.CollectionChanged += SourceCollectionChanged;
|
||||||
}
|
}
|
||||||
@ -442,9 +460,9 @@ namespace ObservableCollections
|
|||||||
lock (SyncRoot)
|
lock (SyncRoot)
|
||||||
{
|
{
|
||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
foreach (var item in list)
|
foreach (var (_, (value, view)) in list)
|
||||||
{
|
{
|
||||||
filter.Invoke(item.Value, item.Key.view);
|
filter.Invoke(value, view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -456,9 +474,9 @@ namespace ObservableCollections
|
|||||||
this.filter = SynchronizedViewFilter<T, TView>.AlwaysTrue;
|
this.filter = SynchronizedViewFilter<T, TView>.AlwaysTrue;
|
||||||
if (resetAction != null)
|
if (resetAction != null)
|
||||||
{
|
{
|
||||||
foreach (var item in list)
|
foreach (var (_, (value, view)) in list)
|
||||||
{
|
{
|
||||||
resetAction(item.Value, item.Key.view);
|
resetAction(value, view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -474,7 +492,7 @@ namespace ObservableCollections
|
|||||||
|
|
||||||
public IEnumerator<(T, TView)> GetEnumerator()
|
public IEnumerator<(T, TView)> GetEnumerator()
|
||||||
{
|
{
|
||||||
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, list.Select(x => (x.Value, x.Key.view)).GetEnumerator(), filter);
|
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, list.Select(x => x.Value).GetEnumerator(), filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
@ -491,25 +509,26 @@ namespace ObservableCollections
|
|||||||
switch (e.Action)
|
switch (e.Action)
|
||||||
{
|
{
|
||||||
case NotifyCollectionChangedAction.Add:
|
case NotifyCollectionChangedAction.Add:
|
||||||
// Add, Insert
|
|
||||||
{
|
{
|
||||||
|
// Add, Insert
|
||||||
if (e.IsSingleItem)
|
if (e.IsSingleItem)
|
||||||
{
|
{
|
||||||
var v = selector(e.NewItem);
|
var value = e.NewItem;
|
||||||
list.Add((v, e.NewStartingIndex), e.NewItem);
|
var view = transform(value);
|
||||||
viewMap.Add((e.NewItem, e.NewStartingIndex), v);
|
var id = identitySelector(value);
|
||||||
filter.Invoke(e.NewItem, v);
|
list.Add((view, id), (value, view));
|
||||||
|
viewMap.Add(id, view);
|
||||||
|
filter.Invoke(value, view);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var index = e.NewStartingIndex;
|
foreach (var value in e.NewItems)
|
||||||
foreach (var item in e.NewItems)
|
|
||||||
{
|
{
|
||||||
var v = selector(item);
|
var view = transform(value);
|
||||||
list.Add((v, index), item);
|
var id = identitySelector(value);
|
||||||
viewMap.Add((item, index), v);
|
list.Add((view, id), (value, view));
|
||||||
filter.Invoke(item, v);
|
viewMap.Add(id, view);
|
||||||
index++;
|
filter.Invoke(value, view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -518,21 +537,22 @@ namespace ObservableCollections
|
|||||||
{
|
{
|
||||||
if (e.IsSingleItem)
|
if (e.IsSingleItem)
|
||||||
{
|
{
|
||||||
if (viewMap.Remove((e.OldItem, e.OldStartingIndex), out var view))
|
var value = e.OldItem;
|
||||||
|
var id = identitySelector(value);
|
||||||
|
if (viewMap.Remove(id, out var view))
|
||||||
{
|
{
|
||||||
list.Remove((view, e.OldStartingIndex));
|
list.Remove((view, id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var index = e.OldStartingIndex;
|
foreach (var value in e.OldItems)
|
||||||
foreach (var item in e.OldItems)
|
|
||||||
{
|
{
|
||||||
if (viewMap.Remove((item, index), out var view))
|
var id = identitySelector(value);
|
||||||
|
if (viewMap.Remove(id, out var view))
|
||||||
{
|
{
|
||||||
list.Remove((view, index));
|
list.Remove((view, id));
|
||||||
}
|
}
|
||||||
index++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -540,22 +560,25 @@ namespace ObservableCollections
|
|||||||
case NotifyCollectionChangedAction.Replace:
|
case NotifyCollectionChangedAction.Replace:
|
||||||
case NotifyCollectionChangedAction.Move:
|
case NotifyCollectionChangedAction.Move:
|
||||||
// ObservableList does not support replace range
|
// ObservableList does not support replace range
|
||||||
// Replace is remove old item and insert new item(same index on replace, diffrence index on move).
|
// Replace is remove old item and insert new item(same index on replace, difference index on move).
|
||||||
{
|
{
|
||||||
if (viewMap.Remove((e.OldItem, e.OldStartingIndex), out var oldView))
|
var oldValue = e.OldItem;
|
||||||
|
var oldKey = identitySelector(oldValue);
|
||||||
|
if (viewMap.Remove(oldKey, out var oldView))
|
||||||
{
|
{
|
||||||
list.Remove((oldView, e.OldStartingIndex));
|
list.Remove((oldView, oldKey));
|
||||||
|
|
||||||
var newView = selector(e.NewItem);
|
|
||||||
list.Add((newView, e.NewStartingIndex), e.NewItem);
|
|
||||||
viewMap.Add((e.NewItem, e.NewStartingIndex), newView);
|
|
||||||
filter.Invoke(e.NewItem, newView);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var value = e.NewItem;
|
||||||
|
var view = transform(value);
|
||||||
|
var id = identitySelector(value);
|
||||||
|
list.Add((view, id), (value, view));
|
||||||
|
viewMap.Add(id, view);
|
||||||
|
filter.Invoke(value, view);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NotifyCollectionChangedAction.Reset:
|
case NotifyCollectionChangedAction.Reset:
|
||||||
list.Clear();
|
list.Clear();
|
||||||
viewMap.Clear();
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -566,27 +589,26 @@ namespace ObservableCollections
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class WithIndexComparer : IComparer<(TView value, int index)>
|
sealed class Comparer : IComparer<(TView view, TKey id)>
|
||||||
{
|
{
|
||||||
readonly IComparer<TView> comparer;
|
readonly IComparer<TView> comparer;
|
||||||
|
|
||||||
public WithIndexComparer(IComparer<TView> comparer)
|
public Comparer(IComparer<TView> comparer)
|
||||||
{
|
{
|
||||||
this.comparer = comparer;
|
this.comparer = comparer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Compare((TView value, int index) x, (TView value, int index) y)
|
public int Compare((TView view, TKey id) x, (TView view, TKey id) y)
|
||||||
{
|
{
|
||||||
var v = comparer.Compare(x.value, y.value);
|
var compare = comparer.Compare(x.view, y.view);
|
||||||
if (v == 0)
|
if (compare == 0)
|
||||||
{
|
{
|
||||||
v = x.index.CompareTo(y.index);
|
compare = Comparer<TKey>.Default.Compare(x.id, y.id);
|
||||||
}
|
}
|
||||||
return v;
|
|
||||||
|
return compare;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
103
tests/ObservableCollections.Tests/ObservableDictionaryTest.cs
Normal file
103
tests/ObservableCollections.Tests/ObservableDictionaryTest.cs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace ObservableCollections.Tests
|
||||||
|
{
|
||||||
|
public class ObservableDictionaryTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void View()
|
||||||
|
{
|
||||||
|
var dict = new ObservableDictionary<int, int>();
|
||||||
|
var view = dict.CreateView(x => new ViewContainer<int>(x.Value));
|
||||||
|
|
||||||
|
dict.Add(10, -10); // 0
|
||||||
|
dict.Add(50, -50); // 1
|
||||||
|
dict.Add(30, -30); // 2
|
||||||
|
dict.Add(20, -20); // 3
|
||||||
|
dict.Add(40, -40); // 4
|
||||||
|
|
||||||
|
void Equal(params int[] expected)
|
||||||
|
{
|
||||||
|
dict.Select(x => x.Value).OrderByDescending(x => x).Should().Equal(expected);
|
||||||
|
view.Select(x => x.Value.Value).OrderByDescending(x => x).Should().Equal(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
Equal(-10, -20, -30, -40, -50);
|
||||||
|
|
||||||
|
dict[99] = -100;
|
||||||
|
Equal(-10, -20, -30, -40, -50, -100);
|
||||||
|
|
||||||
|
dict[10] = -5;
|
||||||
|
Equal(-5, -20, -30, -40, -50, -100);
|
||||||
|
|
||||||
|
dict.Remove(20);
|
||||||
|
Equal(-5, -30, -40, -50, -100);
|
||||||
|
|
||||||
|
dict.Clear();
|
||||||
|
Equal(new int[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ViewSorted()
|
||||||
|
{
|
||||||
|
var dict = new ObservableDictionary<int, int>();
|
||||||
|
var view1 = dict.CreateSortedView(x => x.Key, x => new ViewContainer<int>(x.Value), x => x.Value, true);
|
||||||
|
var view2 = dict.CreateSortedView(x => x.Key, x => new ViewContainer<int>(x.Value), x => x.Value, false);
|
||||||
|
|
||||||
|
dict.Add(10, 10); // 0
|
||||||
|
dict.Add(50, 50); // 1
|
||||||
|
dict.Add(30, 30); // 2
|
||||||
|
dict.Add(20, 20); // 3
|
||||||
|
dict.Add(40, 40); // 4
|
||||||
|
|
||||||
|
void Equal(params int[] expected)
|
||||||
|
{
|
||||||
|
dict.Select(x => x.Value).OrderBy(x => x).Should().Equal(expected);
|
||||||
|
view1.Select(x => x.Value.Value).Should().Equal(expected);
|
||||||
|
view2.Select(x => x.Value.Value).Should().Equal(expected.OrderByDescending(x => x));
|
||||||
|
}
|
||||||
|
|
||||||
|
Equal(10, 20, 30, 40, 50);
|
||||||
|
|
||||||
|
dict[99] = 100;
|
||||||
|
Equal(10, 20, 30, 40, 50, 100);
|
||||||
|
|
||||||
|
dict[10] = -5;
|
||||||
|
Equal(-5, 20, 30, 40, 50, 100);
|
||||||
|
|
||||||
|
dict.Remove(20);
|
||||||
|
Equal(-5, 30, 40, 50, 100);
|
||||||
|
|
||||||
|
dict.Clear();
|
||||||
|
Equal(new int[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Freezed()
|
||||||
|
{
|
||||||
|
var dict = new FreezedDictionary<int, int>(new Dictionary<int, int>
|
||||||
|
{
|
||||||
|
[10] = 10,
|
||||||
|
[50] = 50,
|
||||||
|
[30] = 30,
|
||||||
|
[20] = 20,
|
||||||
|
[40] = 40,
|
||||||
|
[60] = 60
|
||||||
|
});
|
||||||
|
|
||||||
|
var view = dict.CreateSortableView(x => new ViewContainer<int>(x.Value));
|
||||||
|
|
||||||
|
view.Sort(x => x.Key, true);
|
||||||
|
view.Select(x => x.Value.Value).Should().Equal(10, 20, 30, 40, 50, 60);
|
||||||
|
view.Select(x => x.View).Should().Equal(10, 20, 30, 40, 50, 60);
|
||||||
|
|
||||||
|
view.Sort(x => x.Key, false);
|
||||||
|
view.Select(x => x.Value.Value).Should().Equal(60, 50, 40, 30, 20, 10);
|
||||||
|
view.Select(x => x.View).Should().Equal(60, 50, 40, 30, 20, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@ -28,6 +29,13 @@ namespace ObservableCollections.Tests
|
|||||||
view.Select(x => x.View).Should().Equal(expected.Select(x => new ViewContainer<int>(x)));
|
view.Select(x => x.View).Should().Equal(expected.Select(x => new ViewContainer<int>(x)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Equal2(params int[] expected)
|
||||||
|
{
|
||||||
|
list.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(10, 50, 30, 20, 40);
|
Equal(10, 50, 30, 20, 40);
|
||||||
|
|
||||||
reference.Move(3, 1);
|
reference.Move(3, 1);
|
||||||
@ -37,6 +45,96 @@ namespace ObservableCollections.Tests
|
|||||||
reference.Insert(2, 99);
|
reference.Insert(2, 99);
|
||||||
list.Insert(2, 99);
|
list.Insert(2, 99);
|
||||||
Equal(10, 20, 99, 50, 30, 40);
|
Equal(10, 20, 99, 50, 30, 40);
|
||||||
|
|
||||||
|
reference.RemoveAt(2);
|
||||||
|
list.RemoveAt(2);
|
||||||
|
Equal(10, 20, 50, 30, 40);
|
||||||
|
|
||||||
|
reference[3] = 88;
|
||||||
|
list[3] = 88;
|
||||||
|
Equal(10, 20, 50, 88, 40);
|
||||||
|
|
||||||
|
reference.Clear();
|
||||||
|
list.Clear();
|
||||||
|
Equal(new int[0]);
|
||||||
|
|
||||||
|
list.AddRange(new[] { 100, 200, 300 });
|
||||||
|
Equal2(100, 200, 300);
|
||||||
|
|
||||||
|
list.InsertRange(1, new[] { 400, 500, 600 });
|
||||||
|
Equal2(100, 400, 500, 600, 200, 300);
|
||||||
|
|
||||||
|
list.RemoveRange(2, 2);
|
||||||
|
Equal2(100, 400, 200, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ViewSorted()
|
||||||
|
{
|
||||||
|
var list = new ObservableList<int>();
|
||||||
|
var view1 = list.CreateSortedView(x => x, x => new ViewContainer<int>(x), comparer: Comparer<int>.Default);
|
||||||
|
var view2 = list.CreateSortedView(x => x, x => new ViewContainer<int>(x), viewComparer: Comparer<ViewContainer<int>>.Default);
|
||||||
|
var view3 = list.CreateSortedView(x => x, x => new ViewContainer<int>(x), x => x, ascending: true);
|
||||||
|
var view4 = list.CreateSortedView(x => x, x => new ViewContainer<int>(x), x => x, ascending: false);
|
||||||
|
|
||||||
|
list.Add(10); // 0
|
||||||
|
list.Add(50); // 1
|
||||||
|
list.Add(30); // 2
|
||||||
|
list.Add(20); // 3
|
||||||
|
list.Add(40); // 4
|
||||||
|
|
||||||
|
void Equal(params int[] expected)
|
||||||
|
{
|
||||||
|
list.Should().Equal(expected);
|
||||||
|
|
||||||
|
var sorted = expected.OrderBy(x => x).ToArray();
|
||||||
|
view1.Select(x => x.Value).Should().Equal(sorted);
|
||||||
|
view2.Select(x => x.View).Should().Equal(sorted.Select(x => new ViewContainer<int>(x)));
|
||||||
|
view3.Select(x => x.Value).Should().Equal(sorted);
|
||||||
|
view4.Select(x => x.Value).Should().Equal(expected.OrderByDescending(x => x).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
Equal(10, 50, 30, 20, 40);
|
||||||
|
|
||||||
|
list.Move(3, 1);
|
||||||
|
Equal(10, 20, 50, 30, 40);
|
||||||
|
|
||||||
|
list.Insert(2, 99);
|
||||||
|
Equal(10, 20, 99, 50, 30, 40);
|
||||||
|
|
||||||
|
list.RemoveAt(2);
|
||||||
|
Equal(10, 20, 50, 30, 40);
|
||||||
|
|
||||||
|
list[3] = 88;
|
||||||
|
Equal(10, 20, 50, 88, 40);
|
||||||
|
|
||||||
|
list.Clear();
|
||||||
|
Equal(new int[0]);
|
||||||
|
|
||||||
|
list.AddRange(new[] { 100, 200, 300 });
|
||||||
|
Equal(100, 200, 300);
|
||||||
|
|
||||||
|
list.InsertRange(1, new[] { 400, 500, 600 });
|
||||||
|
Equal(100, 400, 500, 600, 200, 300);
|
||||||
|
|
||||||
|
list.RemoveRange(2, 2);
|
||||||
|
Equal(100, 400, 200, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Freezed()
|
||||||
|
{
|
||||||
|
var list = new FreezedList<int>(new[] { 10, 20, 50, 30, 40, 60 });
|
||||||
|
|
||||||
|
var view = list.CreateSortableView(x => new ViewContainer<int>(x));
|
||||||
|
|
||||||
|
view.Sort(x => x, true);
|
||||||
|
view.Select(x => x.Value).Should().Equal(10, 20, 30, 40, 50, 60);
|
||||||
|
view.Select(x => x.View).Should().Equal(10, 20, 30, 40, 50, 60);
|
||||||
|
|
||||||
|
view.Sort(x => x, false);
|
||||||
|
view.Select(x => x.Value).Should().Equal(60, 50, 40, 30, 20, 10);
|
||||||
|
view.Select(x => x.View).Should().Equal(60, 50, 40, 30, 20, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace ObservableCollections.Tests
|
namespace ObservableCollections.Tests
|
||||||
{
|
{
|
||||||
public struct ViewContainer<T> : IEquatable<T>
|
public struct ViewContainer<T> : IEquatable<ViewContainer<T>>, IComparable<ViewContainer<T>>
|
||||||
{
|
{
|
||||||
public ViewContainer(T value)
|
public ViewContainer(T value)
|
||||||
{
|
{
|
||||||
@ -19,9 +19,14 @@ namespace ObservableCollections.Tests
|
|||||||
return Value.GetHashCode();
|
return Value.GetHashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Equals(T other)
|
public int CompareTo(ViewContainer<T> other)
|
||||||
{
|
{
|
||||||
return EqualityComparer<T>.Default.Equals(Value, other);
|
return Comparer<T>.Default.Compare(Value, other.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(ViewContainer<T> other)
|
||||||
|
{
|
||||||
|
return EqualityComparer<T>.Default.Equals(Value, other.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user