q
This commit is contained in:
parent
e08cce94b0
commit
db803f83ed
213
src/ObservableCollections/Internal/SortedView.cs
Normal file
213
src/ObservableCollections/Internal/SortedView.cs
Normal file
@ -0,0 +1,213 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace ObservableCollections.Internal
|
||||
{
|
||||
internal class SortedView<T, TKey, TView> : ISynchronizedView<T, TView>
|
||||
where TKey : notnull
|
||||
{
|
||||
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;
|
||||
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T>? RoutingCollectionChanged;
|
||||
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
||||
public SortedView(IObservableCollection<T> source, object syncRoot, IEnumerable<T> sourceEnumerable, Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<T> comparer)
|
||||
{
|
||||
this.source = source;
|
||||
this.identitySelector = identitySelector;
|
||||
this.transform = transform;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
lock (syncRoot)
|
||||
{
|
||||
var dict = new SortedDictionary<(T, TKey), (T, TView)>(new Comparer(comparer));
|
||||
foreach (var v in sourceEnumerable)
|
||||
{
|
||||
dict.Add((v, identitySelector(v)), (v, transform(v)));
|
||||
}
|
||||
|
||||
this.dict = dict;
|
||||
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:
|
||||
{
|
||||
// Add, Insert
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var value = e.NewItem;
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
dict.Add((value, id), (value, view));
|
||||
filter.InvokeOnAdd(value, view);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var value in e.NewItems)
|
||||
{
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
dict.Add((value, id), (value, view));
|
||||
filter.InvokeOnAdd(value, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
{
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var value = e.OldItem;
|
||||
var id = identitySelector(value);
|
||||
dict.Remove((value, id), out var v);
|
||||
filter.InvokeOnRemove(v.Value, v.View);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var value in e.OldItems)
|
||||
{
|
||||
var id = identitySelector(value);
|
||||
dict.Remove((value, id), out var v);
|
||||
filter.InvokeOnRemove(v.Value, v.View);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
// ReplaceRange is not supported in all ObservableCollections collections
|
||||
// Replace is remove old item and insert new item.
|
||||
{
|
||||
var oldValue = e.OldItem;
|
||||
dict.Remove((oldValue, identitySelector(oldValue)), out var oldView);
|
||||
|
||||
var value = e.NewItem;
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
dict.Add((value, id), (value, view));
|
||||
|
||||
filter.InvokeOnRemove(oldView);
|
||||
filter.InvokeOnAdd(value, view);
|
||||
}
|
||||
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))
|
||||
{
|
||||
filter.InvokeOnMove(view);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
if (!filter.IsNullFilter())
|
||||
{
|
||||
foreach (var item in dict)
|
||||
{
|
||||
filter.InvokeOnRemove(item.Value);
|
||||
}
|
||||
}
|
||||
dict.Clear();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
RoutingCollectionChanged?.Invoke(e);
|
||||
CollectionStateChanged?.Invoke(e.Action);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Comparer : IComparer<(T value, TKey id)>
|
||||
{
|
||||
readonly IComparer<T> comparer;
|
||||
|
||||
public Comparer(IComparer<T> comparer)
|
||||
{
|
||||
this.comparer = comparer;
|
||||
}
|
||||
|
||||
public int Compare((T value, TKey id) x, (T value, TKey id) y)
|
||||
{
|
||||
var compare = comparer.Compare(x.value, y.value);
|
||||
if (compare == 0)
|
||||
{
|
||||
compare = Comparer<TKey>.Default.Compare(x.id, y.id);
|
||||
}
|
||||
|
||||
return compare;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
231
src/ObservableCollections/Internal/SortedViewViewComparer.cs
Normal file
231
src/ObservableCollections/Internal/SortedViewViewComparer.cs
Normal file
@ -0,0 +1,231 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace ObservableCollections.Internal
|
||||
{
|
||||
internal class SortedViewViewComparer<T, TKey, TView> : ISynchronizedView<T, TView>
|
||||
where TKey : notnull
|
||||
{
|
||||
readonly IObservableCollection<T> source;
|
||||
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)> list;
|
||||
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T>? RoutingCollectionChanged;
|
||||
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
||||
public SortedViewViewComparer(IObservableCollection<T> source, object syncRoot, IEnumerable<T> sourceEnumerable, Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<TView> comparer)
|
||||
{
|
||||
this.source = source;
|
||||
this.identitySelector = identitySelector;
|
||||
this.transform = transform;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
lock (syncRoot)
|
||||
{
|
||||
var dict = new SortedDictionary<(TView, TKey), (T, TView)>(new Comparer(comparer));
|
||||
this.viewMap = new Dictionary<TKey, TView>();
|
||||
foreach (var value in sourceEnumerable)
|
||||
{
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
dict.Add((view, id), (value, view));
|
||||
viewMap.Add(id, view);
|
||||
}
|
||||
this.list = dict;
|
||||
this.source.CollectionChanged += SourceCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return list.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = filter;
|
||||
foreach (var (_, (value, view)) in list)
|
||||
{
|
||||
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 list)
|
||||
{
|
||||
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, list.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:
|
||||
{
|
||||
// Add, Insert
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var value = e.NewItem;
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
list.Add((view, id), (value, view));
|
||||
viewMap.Add(id, view);
|
||||
filter.InvokeOnAdd(value, view);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var value in e.NewItems)
|
||||
{
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
list.Add((view, id), (value, view));
|
||||
viewMap.Add(id, view);
|
||||
filter.InvokeOnAdd(value, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
{
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var value = e.OldItem;
|
||||
var id = identitySelector(value);
|
||||
if (viewMap.Remove(id, out var view))
|
||||
{
|
||||
list.Remove((view, id), out var v);
|
||||
filter.InvokeOnRemove(v);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var value in e.OldItems)
|
||||
{
|
||||
var id = identitySelector(value);
|
||||
if (viewMap.Remove(id, out var view))
|
||||
{
|
||||
list.Remove((view, id), out var v);
|
||||
filter.InvokeOnRemove(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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))
|
||||
{
|
||||
list.Remove((oldView, oldKey));
|
||||
filter.InvokeOnRemove(oldValue, oldView);
|
||||
}
|
||||
|
||||
var value = e.NewItem;
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
list.Add((view, id), (value, view));
|
||||
viewMap.Add(id, view);
|
||||
|
||||
filter.InvokeOnAdd(value, view);
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
// Move(index change) does not affect soreted dict.
|
||||
{
|
||||
var value = e.OldItem;
|
||||
var key = identitySelector(value);
|
||||
if (viewMap.TryGetValue(key, out var view))
|
||||
{
|
||||
filter.InvokeOnMove(value, view);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
if (!filter.IsNullFilter())
|
||||
{
|
||||
foreach (var item in list)
|
||||
{
|
||||
filter.InvokeOnRemove(item.Value);
|
||||
}
|
||||
}
|
||||
list.Clear();
|
||||
viewMap.Clear();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
RoutingCollectionChanged?.Invoke(e);
|
||||
CollectionStateChanged?.Invoke(e.Action);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Comparer : IComparer<(TView view, TKey id)>
|
||||
{
|
||||
readonly IComparer<TView> comparer;
|
||||
|
||||
public Comparer(IComparer<TView> comparer)
|
||||
{
|
||||
this.comparer = comparer;
|
||||
}
|
||||
|
||||
public int Compare((TView view, TKey id) x, (TView view, TKey id) y)
|
||||
{
|
||||
var compare = comparer.Compare(x.view, y.view);
|
||||
if (compare == 0)
|
||||
{
|
||||
compare = Comparer<TKey>.Default.Compare(x.id, y.id);
|
||||
}
|
||||
|
||||
return compare;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
using ObservableCollections.Internal;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
@ -17,23 +14,23 @@ namespace ObservableCollections
|
||||
|
||||
public ISynchronizedView<KeyValuePair<TKey, TValue>, TView> CreateSortedView<TView>(Func<KeyValuePair<TKey, TValue>, TView> transform, IComparer<KeyValuePair<TKey, TValue>> comparer)
|
||||
{
|
||||
return new SortedView<TView>(this, transform, comparer);
|
||||
return new SortedView<KeyValuePair<TKey, TValue>, TKey, TView>(this, this.SyncRoot, dictionary, x => x.Key, transform, comparer);
|
||||
}
|
||||
|
||||
public ISynchronizedView<KeyValuePair<TKey, TValue>, TView> CreateSortedView<TView>(Func<KeyValuePair<TKey, TValue>, TView> transform, IComparer<TView> viewComparer)
|
||||
{
|
||||
return new ViewComparerSortedView<TView>(this, transform, viewComparer);
|
||||
return new SortedViewViewComparer<KeyValuePair<TKey, TValue>, TKey, TView>(this, this.SyncRoot, dictionary, x => x.Key, 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);
|
||||
return new SortedView<KeyValuePair<TKey, TValue>, TKey, TView>(this, this.SyncRoot, dictionary, x => x.Key, 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);
|
||||
return new SortedViewViewComparer<KeyValuePair<TKey, TValue>, TKey, TView>(this, this.SyncRoot, dictionary, x => x.Key, transform, viewComparer);
|
||||
}
|
||||
|
||||
class View<TView> : ISynchronizedView<KeyValuePair<TKey, TValue>, TView>
|
||||
@ -180,321 +177,5 @@ namespace ObservableCollections
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SortedView<TView> : ISynchronizedView<KeyValuePair<TKey, TValue>, TView>
|
||||
{
|
||||
readonly ObservableDictionary<TKey, TValue> source;
|
||||
readonly Func<KeyValuePair<TKey, TValue>, TView> selector;
|
||||
ISynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView> filter;
|
||||
readonly SortedDictionary<KeyValuePair<TKey, TValue>, TView> dict;
|
||||
|
||||
public SortedView(ObservableDictionary<TKey, TValue> source, Func<KeyValuePair<TKey, TValue>, TView> selector, IComparer<KeyValuePair<TKey, TValue>> comparer)
|
||||
{
|
||||
this.source = source;
|
||||
this.selector = selector;
|
||||
this.filter = SynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView>.Null;
|
||||
this.SyncRoot = new object();
|
||||
lock (source.SyncRoot)
|
||||
{
|
||||
this.dict = new SortedDictionary<KeyValuePair<TKey, TValue>, TView>(comparer);
|
||||
foreach (var item in source.dictionary)
|
||||
{
|
||||
dict.Add(item, selector(item));
|
||||
}
|
||||
this.source.CollectionChanged += SourceCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public object SyncRoot { get; }
|
||||
public event NotifyCollectionChangedEventHandler<KeyValuePair<TKey, TValue>>? RoutingCollectionChanged;
|
||||
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return dict.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.source.CollectionChanged -= SourceCollectionChanged;
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView> filter)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = filter;
|
||||
foreach (var v in dict)
|
||||
{
|
||||
filter.InvokeOnAttach(new KeyValuePair<TKey, TValue>(v.Key.Key, v.Key.Value), v.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetFilter(Action<KeyValuePair<TKey, TValue>, TView>? resetAction)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView>.Null;
|
||||
if (resetAction != null)
|
||||
{
|
||||
foreach (var v in dict)
|
||||
{
|
||||
resetAction(new KeyValuePair<TKey, TValue>(v.Key.Key, v.Key.Value), v.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<KeyValuePair<TKey, TValue>, TView> WithINotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedView<KeyValuePair<TKey, TValue>, TView>(this);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<(KeyValuePair<TKey, TValue>, TView)> GetEnumerator()
|
||||
{
|
||||
return new SynchronizedViewEnumerator<KeyValuePair<TKey, TValue>, TView>(SyncRoot,
|
||||
dict.Select(x => (new KeyValuePair<TKey, TValue>(x.Key.Key, x.Key.Value), x.Value)).GetEnumerator(),
|
||||
filter);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs<KeyValuePair<TKey, TValue>> e)
|
||||
{
|
||||
// ObservableDictionary only provides single item operation and does not use int index.
|
||||
lock (SyncRoot)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
{
|
||||
var v = selector(e.NewItem);
|
||||
var k = new KeyValuePair<TKey, TValue>(e.NewItem.Key, e.NewItem.Value);
|
||||
dict.Add(k, v);
|
||||
filter.InvokeOnAdd(k, v);
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
{
|
||||
if (dict.Remove(e.OldItem, out var value))
|
||||
{
|
||||
filter.InvokeOnRemove(e.OldItem, value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
{
|
||||
var k = new KeyValuePair<TKey, TValue>(e.OldItem.Key, e.OldItem.Value);
|
||||
if (dict.Remove(k, out var oldValue))
|
||||
{
|
||||
filter.InvokeOnRemove(k, oldValue);
|
||||
}
|
||||
|
||||
var v = selector(e.NewItem);
|
||||
var nk = new KeyValuePair<TKey, TValue>(e.NewItem.Key, e.NewItem.Value);
|
||||
dict[nk] = v;
|
||||
filter.InvokeOnAdd(nk, v);
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
{
|
||||
if (!filter.IsNullFilter())
|
||||
{
|
||||
foreach (var item in dict)
|
||||
{
|
||||
filter.InvokeOnRemove(item.Key, item.Value);
|
||||
}
|
||||
}
|
||||
dict.Clear();
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Move: // ObservableDictionary have no Move operation.
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
RoutingCollectionChanged?.Invoke(e);
|
||||
CollectionStateChanged?.Invoke(e.Action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable CS8714 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint.
|
||||
|
||||
class ViewComparerSortedView<TView> : ISynchronizedView<KeyValuePair<TKey, TValue>, TView>
|
||||
{
|
||||
readonly ObservableDictionary<TKey, TValue> source;
|
||||
readonly Func<KeyValuePair<TKey, TValue>, TView> selector;
|
||||
readonly Dictionary<TKey, TView> viewMap;
|
||||
readonly SortedDictionary<TView, KeyValuePair<TKey, TValue>> dict;
|
||||
ISynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView> filter;
|
||||
|
||||
public ViewComparerSortedView(ObservableDictionary<TKey, TValue> source, Func<KeyValuePair<TKey, TValue>, TView> selector, IComparer<TView> viewComparer)
|
||||
{
|
||||
this.source = source;
|
||||
this.selector = selector;
|
||||
this.filter = SynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView>.Null;
|
||||
this.SyncRoot = new object();
|
||||
lock (source.SyncRoot)
|
||||
{
|
||||
this.viewMap = new Dictionary<TKey, TView>(source.Count);
|
||||
this.dict = new SortedDictionary<TView, KeyValuePair<TKey, TValue>>(viewComparer);
|
||||
foreach (var item in source.dictionary)
|
||||
{
|
||||
var v = selector(item);
|
||||
dict.Add(v, item);
|
||||
viewMap.Add(item.Key, v);
|
||||
}
|
||||
this.source.CollectionChanged += SourceCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public object SyncRoot { get; }
|
||||
public event NotifyCollectionChangedEventHandler<KeyValuePair<TKey, TValue>>? RoutingCollectionChanged;
|
||||
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return dict.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.source.CollectionChanged -= SourceCollectionChanged;
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView> filter)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = filter;
|
||||
foreach (var v in dict)
|
||||
{
|
||||
filter.InvokeOnAttach(v.Value, v.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetFilter(Action<KeyValuePair<TKey, TValue>, TView>? resetAction)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = SynchronizedViewFilter<KeyValuePair<TKey, TValue>, TView>.Null;
|
||||
if (resetAction != null)
|
||||
{
|
||||
foreach (var v in dict)
|
||||
{
|
||||
resetAction(v.Value, v.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public INotifyCollectionChangedSynchronizedView<KeyValuePair<TKey, TValue>, TView> WithINotifyCollectionChanged()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return new NotifyCollectionChangedSynchronizedView<KeyValuePair<TKey, TValue>, TView>(this);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<(KeyValuePair<TKey, TValue>, TView)> GetEnumerator()
|
||||
{
|
||||
return new SynchronizedViewEnumerator<KeyValuePair<TKey, TValue>, TView>(SyncRoot,
|
||||
dict.Select(x => (x.Value, x.Key)).GetEnumerator(),
|
||||
filter);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
private void SourceCollectionChanged(in NotifyCollectionChangedEventArgs<KeyValuePair<TKey, TValue>> e)
|
||||
{
|
||||
// ObservableDictionary only provides single item operation and does not use int index.
|
||||
lock (SyncRoot)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
{
|
||||
var v = selector(e.NewItem);
|
||||
var k = new KeyValuePair<TKey, TValue>(e.NewItem.Key, e.NewItem.Value);
|
||||
dict.Add(v, k);
|
||||
viewMap.Add(e.NewItem.Key, v);
|
||||
filter.InvokeOnAdd(k, v);
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
{
|
||||
if (viewMap.Remove(e.OldItem.Key, out var view))
|
||||
{
|
||||
dict.Remove(view);
|
||||
filter.InvokeOnRemove(e.OldItem, view);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
{
|
||||
if (viewMap.Remove(e.OldItem.Key, out var view))
|
||||
{
|
||||
if (dict.Remove(view, out var oldView))
|
||||
{
|
||||
filter.InvokeOnRemove(e.OldItem, 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.InvokeOnAdd(k, v);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
{
|
||||
if (!filter.IsNullFilter())
|
||||
{
|
||||
foreach (var item in dict)
|
||||
{
|
||||
filter.InvokeOnRemove(item.Value, item.Key);
|
||||
}
|
||||
}
|
||||
dict.Clear();
|
||||
viewMap.Clear();
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Move: // ObservableDictionary have no Move operation.
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
RoutingCollectionChanged?.Invoke(e);
|
||||
CollectionStateChanged?.Invoke(e.Action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore CS8714
|
||||
}
|
||||
}
|
||||
|
@ -14,13 +14,13 @@ namespace ObservableCollections
|
||||
public ISynchronizedView<T, TView> CreateSortedView<TKey, TView>(Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<T> comparer)
|
||||
where TKey : notnull
|
||||
{
|
||||
return new SortedView<TKey, TView>(this, identitySelector, transform, comparer);
|
||||
return new SortedView<T, TKey, TView>(this, SyncRoot, list, 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 ViewComparerSortedView<TKey, TView>(this, identitySelector, transform, viewComparer);
|
||||
return new SortedViewViewComparer<T, TKey, TView>(this, SyncRoot, list, identitySelector, transform, viewComparer);
|
||||
}
|
||||
|
||||
class View<TView> : ISynchronizedView<T, TView>
|
||||
@ -176,8 +176,6 @@ namespace ObservableCollections
|
||||
filter.InvokeOnRemove(v.Item1, v.Item2);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!filter.IsNullFilter())
|
||||
{
|
||||
var len = e.OldStartingIndex + e.OldItems.Length;
|
||||
for (int i = e.OldStartingIndex; i < len; i++)
|
||||
@ -185,7 +183,6 @@ namespace ObservableCollections
|
||||
var v = list[i];
|
||||
filter.InvokeOnRemove(v.Item1, v.Item2);
|
||||
}
|
||||
}
|
||||
|
||||
list.RemoveRange(e.OldStartingIndex, e.OldItems.Length);
|
||||
}
|
||||
@ -230,444 +227,5 @@ namespace ObservableCollections
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SortedView<TKey, TView> : ISynchronizedView<T, TView>
|
||||
where TKey : notnull
|
||||
{
|
||||
readonly ObservableList<T> source;
|
||||
readonly Func<T, TView> transform;
|
||||
readonly Func<T, TKey> identitySelector;
|
||||
readonly SortedDictionary<(T Value, TKey Key), (T Value, TView View)> list;
|
||||
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T>? RoutingCollectionChanged;
|
||||
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
||||
public SortedView(ObservableList<T> source, Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<T> comparer)
|
||||
{
|
||||
this.source = source;
|
||||
this.identitySelector = identitySelector;
|
||||
this.transform = transform;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
lock (source.SyncRoot)
|
||||
{
|
||||
var dict = new SortedDictionary<(T, TKey), (T, TView)>(new Comparer(comparer));
|
||||
var count = source.list.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var v = source.list[i];
|
||||
dict.Add((v, identitySelector(v)), (v, transform(v)));
|
||||
}
|
||||
|
||||
this.list = dict;
|
||||
this.source.CollectionChanged += SourceCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return list.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = filter;
|
||||
foreach (var (_, (value, view)) in list)
|
||||
{
|
||||
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 list)
|
||||
{
|
||||
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, list.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:
|
||||
{
|
||||
// Add, Insert
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var value = e.NewItem;
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
list.Add((value, id), (value, view));
|
||||
filter.InvokeOnAdd(value, view);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var value in e.NewItems)
|
||||
{
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
list.Add((value, id), (value, view));
|
||||
filter.InvokeOnAdd(value, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
{
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var value = e.OldItem;
|
||||
var id = identitySelector(value);
|
||||
list.Remove((value, id), out var v);
|
||||
filter.InvokeOnRemove(v.Value, v.View);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var value in e.OldItems)
|
||||
{
|
||||
var id = identitySelector(value);
|
||||
list.Remove((value, id), out var v);
|
||||
filter.InvokeOnRemove(v.Value, v.View);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
// ObservableList does not support replace range
|
||||
// Replace is remove old item and insert new item.
|
||||
{
|
||||
var oldValue = e.OldItem;
|
||||
list.Remove((oldValue, identitySelector(oldValue)), out var oldView);
|
||||
|
||||
var value = e.NewItem;
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
list.Add((value, id), (value, view));
|
||||
|
||||
filter.InvokeOnRemove(oldView);
|
||||
filter.InvokeOnAdd(value, view);
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
{
|
||||
// Move(index change) does not affect sorted list.
|
||||
var oldValue = e.OldItem;
|
||||
if (list.TryGetValue((oldValue, identitySelector(oldValue)), out var view))
|
||||
{
|
||||
filter.InvokeOnMove(view);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
if (!filter.IsNullFilter())
|
||||
{
|
||||
foreach (var item in list)
|
||||
{
|
||||
filter.InvokeOnRemove(item.Value);
|
||||
}
|
||||
}
|
||||
list.Clear();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
RoutingCollectionChanged?.Invoke(e);
|
||||
CollectionStateChanged?.Invoke(e.Action);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Comparer : IComparer<(T value, TKey id)>
|
||||
{
|
||||
readonly IComparer<T> comparer;
|
||||
|
||||
public Comparer(IComparer<T> comparer)
|
||||
{
|
||||
this.comparer = comparer;
|
||||
}
|
||||
|
||||
public int Compare((T value, TKey id) x, (T value, TKey id) y)
|
||||
{
|
||||
var compare = comparer.Compare(x.value, y.value);
|
||||
if (compare == 0)
|
||||
{
|
||||
compare = Comparer<TKey>.Default.Compare(x.id, y.id);
|
||||
}
|
||||
|
||||
return compare;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ViewComparerSortedView<TKey, TView> : ISynchronizedView<T, TView>
|
||||
where TKey : notnull
|
||||
{
|
||||
readonly ObservableList<T> source;
|
||||
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)> list;
|
||||
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T>? RoutingCollectionChanged;
|
||||
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
|
||||
public object SyncRoot { get; } = new object();
|
||||
|
||||
public ViewComparerSortedView(ObservableList<T> source, Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<TView> comparer)
|
||||
{
|
||||
this.source = source;
|
||||
this.identitySelector = identitySelector;
|
||||
this.transform = transform;
|
||||
this.filter = SynchronizedViewFilter<T, TView>.Null;
|
||||
lock (source.SyncRoot)
|
||||
{
|
||||
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;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var value = source.list[i];
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
dict.Add((view, id), (value, view));
|
||||
viewMap.Add(id, view);
|
||||
}
|
||||
this.list = dict;
|
||||
this.source.CollectionChanged += SourceCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return list.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = filter;
|
||||
foreach (var (_, (value, view)) in list)
|
||||
{
|
||||
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 list)
|
||||
{
|
||||
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, list.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:
|
||||
{
|
||||
// Add, Insert
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var value = e.NewItem;
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
list.Add((view, id), (value, view));
|
||||
viewMap.Add(id, view);
|
||||
filter.InvokeOnAdd(value, view);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var value in e.NewItems)
|
||||
{
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
list.Add((view, id), (value, view));
|
||||
viewMap.Add(id, view);
|
||||
filter.InvokeOnAdd(value, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
{
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var value = e.OldItem;
|
||||
var id = identitySelector(value);
|
||||
if (viewMap.Remove(id, out var view))
|
||||
{
|
||||
list.Remove((view, id), out var v);
|
||||
filter.InvokeOnRemove(v);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var value in e.OldItems)
|
||||
{
|
||||
var id = identitySelector(value);
|
||||
if (viewMap.Remove(id, out var view))
|
||||
{
|
||||
list.Remove((view, id), out var v);
|
||||
filter.InvokeOnRemove(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
// ObservableList does not support replace range
|
||||
// Replace is remove old item and insert new item.
|
||||
{
|
||||
var oldValue = e.OldItem;
|
||||
var oldKey = identitySelector(oldValue);
|
||||
if (viewMap.Remove(oldKey, out var oldView))
|
||||
{
|
||||
list.Remove((oldView, oldKey));
|
||||
filter.InvokeOnRemove(oldValue, oldView);
|
||||
}
|
||||
|
||||
var value = e.NewItem;
|
||||
var view = transform(value);
|
||||
var id = identitySelector(value);
|
||||
list.Add((view, id), (value, view));
|
||||
viewMap.Add(id, view);
|
||||
|
||||
filter.InvokeOnAdd(value, view);
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
// Move(index change) does not affect soreted dict.
|
||||
{
|
||||
var value = e.OldItem;
|
||||
var key = identitySelector(value);
|
||||
if (viewMap.TryGetValue(key, out var view))
|
||||
{
|
||||
filter.InvokeOnMove(value, view);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
if (!filter.IsNullFilter())
|
||||
{
|
||||
foreach (var item in list)
|
||||
{
|
||||
filter.InvokeOnRemove(item.Value);
|
||||
}
|
||||
}
|
||||
list.Clear();
|
||||
viewMap.Clear();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
RoutingCollectionChanged?.Invoke(e);
|
||||
CollectionStateChanged?.Invoke(e.Action);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Comparer : IComparer<(TView view, TKey id)>
|
||||
{
|
||||
readonly IComparer<TView> comparer;
|
||||
|
||||
public Comparer(IComparer<TView> comparer)
|
||||
{
|
||||
this.comparer = comparer;
|
||||
}
|
||||
|
||||
public int Compare((TView view, TKey id) x, (TView view, TKey id) y)
|
||||
{
|
||||
var compare = comparer.Compare(x.view, y.view);
|
||||
if (compare == 0)
|
||||
{
|
||||
compare = Comparer<TKey>.Default.Compare(x.id, y.id);
|
||||
}
|
||||
|
||||
return compare;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
181
src/ObservableCollections/ObservableQueue.View.cs
Normal file
181
src/ObservableCollections/ObservableQueue.View.cs
Normal file
@ -0,0 +1,181 @@
|
||||
using ObservableCollections.Internal;
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
public sealed partial class ObservableQueue<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, queue, 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, queue, identitySelector, transform, viewComparer);
|
||||
}
|
||||
|
||||
class View<TView> : ISynchronizedView<T, TView>
|
||||
{
|
||||
readonly ObservableQueue<T> source;
|
||||
readonly Func<T, TView> selector;
|
||||
readonly bool reverse;
|
||||
protected readonly Queue<(T, TView)> queue;
|
||||
|
||||
ISynchronizedViewFilter<T, TView> filter;
|
||||
|
||||
public event NotifyCollectionChangedEventHandler<T>? RoutingCollectionChanged;
|
||||
public event Action<NotifyCollectionChangedAction>? CollectionStateChanged;
|
||||
|
||||
public object SyncRoot { get; }
|
||||
|
||||
public View(ObservableQueue<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.queue = new Queue<(T, TView)>(source.queue.Select(x => (x, selector(x))));
|
||||
this.source.CollectionChanged += SourceCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return queue.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AttachFilter(ISynchronizedViewFilter<T, TView> filter)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
this.filter = filter;
|
||||
foreach (var (value, view) in queue)
|
||||
{
|
||||
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 queue)
|
||||
{
|
||||
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, queue.GetEnumerator(), filter);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new SynchronizedViewEnumerator<T, TView>(SyncRoot, queue.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(Enqueue, EnqueueRange)
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var v = (e.NewItem, selector(e.NewItem));
|
||||
queue.Enqueue(v);
|
||||
filter.InvokeOnAdd(v);
|
||||
}
|
||||
else
|
||||
{
|
||||
queue.EnsureCapacity(e.NewItems.Length);
|
||||
foreach (var item in e.NewItems)
|
||||
{
|
||||
var v = (item, selector(item));
|
||||
queue.Enqueue(v);
|
||||
filter.InvokeOnAdd(v);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
// Dequeue, DequeuRange
|
||||
if (e.IsSingleItem)
|
||||
{
|
||||
var v = queue.Dequeue();
|
||||
filter.InvokeOnRemove(v.Item1, v.Item2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var len = e.OldItems.Length;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
var v = queue.Dequeue();
|
||||
filter.InvokeOnRemove(v.Item1, v.Item2);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
if (!filter.IsNullFilter())
|
||||
{
|
||||
foreach (var item in queue)
|
||||
{
|
||||
filter.InvokeOnRemove(item);
|
||||
}
|
||||
}
|
||||
queue.Clear();
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
RoutingCollectionChanged?.Invoke(e);
|
||||
CollectionStateChanged?.Invoke(e.Action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,7 @@
|
||||
using ObservableCollections.Internal;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ObservableCollections
|
||||
{
|
||||
@ -158,53 +153,63 @@ namespace ObservableCollections
|
||||
}
|
||||
}
|
||||
|
||||
// TODO:
|
||||
void Clear()
|
||||
public void Clear()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
queue.Clear();
|
||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Reset());
|
||||
}
|
||||
}
|
||||
|
||||
//bool Contains(T item)
|
||||
//{
|
||||
//}
|
||||
public T Peek()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return queue.Peek();
|
||||
}
|
||||
}
|
||||
|
||||
//void CopyTo(T[] array, int arrayIndex);
|
||||
public bool TryPeek([MaybeNullWhen(false)] T result)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return queue.TryPeek(out result);
|
||||
}
|
||||
}
|
||||
|
||||
// Peek
|
||||
public T[] ToArray()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return queue.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
// ToArray
|
||||
|
||||
// TrimExcess
|
||||
|
||||
// EnsureCapacity
|
||||
|
||||
|
||||
|
||||
// TryPeek
|
||||
public void TrimExcess()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
queue.TrimExcess();
|
||||
}
|
||||
}
|
||||
|
||||
public void EnsureCapacity(int capacity)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
queue.EnsureCapacity(capacity);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return new SynchronizedEnumerator<T>(SyncRoot, queue.GetEnumerator());
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ISynchronizedView<T, TView> CreateView<TView>(Func<T, TView> transform, bool reverse = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ISynchronizedView<T, TView> CreateSortedView<TKey, TView>(Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<T> comparer) where TKey : notnull
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ISynchronizedView<T, TView> CreateSortedView<TKey, TView>(Func<T, TKey> identitySelector, Func<T, TView> transform, IComparer<TView> viewComparer) where TKey : notnull
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
84
tests/ObservableCollections.Tests/ObservableQueueTest.cs
Normal file
84
tests/ObservableCollections.Tests/ObservableQueueTest.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 ObservableQueueTests
|
||||
{
|
||||
[Fact]
|
||||
public void View()
|
||||
{
|
||||
var queue = new ObservableQueue<int>();
|
||||
var view = queue.CreateView(x => new ViewContainer<int>(x));
|
||||
|
||||
queue.Enqueue(10);
|
||||
queue.Enqueue(50);
|
||||
queue.Enqueue(30);
|
||||
queue.Enqueue(20);
|
||||
queue.Enqueue(40);
|
||||
|
||||
void Equal(params int[] expected)
|
||||
{
|
||||
queue.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);
|
||||
|
||||
queue.EnqueueRange(new[] { 1, 2, 3, 4, 5 });
|
||||
Equal(10, 50, 30, 20, 40, 1, 2, 3, 4, 5);
|
||||
|
||||
queue.Dequeue().Should().Be(10);
|
||||
Equal(50, 30, 20, 40, 1, 2, 3, 4, 5);
|
||||
|
||||
queue.TryDequeue(out var q).Should().BeTrue();
|
||||
q.Should().Be(50);
|
||||
Equal(30, 20, 40, 1, 2, 3, 4, 5);
|
||||
|
||||
queue.DequeueRange(4);
|
||||
Equal(2, 3, 4, 5);
|
||||
|
||||
queue.Clear();
|
||||
|
||||
Equal();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Filter()
|
||||
{
|
||||
var queue = new ObservableQueue<int>();
|
||||
var view = queue.CreateView(x => new ViewContainer<int>(x));
|
||||
var filter = new TestFilter<int>((x, v) => x % 3 == 0);
|
||||
|
||||
queue.Enqueue(10);
|
||||
queue.Enqueue(50);
|
||||
queue.Enqueue(30);
|
||||
queue.Enqueue(20);
|
||||
queue.Enqueue(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();
|
||||
|
||||
queue.Enqueue(33);
|
||||
queue.EnqueueRange(new[] { 98 });
|
||||
|
||||
filter.CalledOnCollectionChanged.Select(x => (x.changedKind, x.value)).Should().Equal((ChangedKind.Add, 33), (ChangedKind.Add, 98));
|
||||
filter.Clear();
|
||||
|
||||
queue.Dequeue();
|
||||
queue.DequeueRange(2);
|
||||
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