This commit is contained in:
neuecc 2021-08-13 17:59:55 +09:00
parent e08cce94b0
commit db803f83ed
7 changed files with 762 additions and 809 deletions

View 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;
}
}
}
}

View 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;
}
}
}
}

View File

@ -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
}
}

View File

@ -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;
}
}
}
}
}

View 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);
}
}
}
}
}

View File

@ -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();
}
}
}

View 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));
}
}
}