replace SynchronizedViewList backed uses AlternateIndexList
This commit is contained in:
parent
08b328c16f
commit
c0c9cd48d7
222
src/ObservableCollections/AlternateIndexList.cs
Normal file
222
src/ObservableCollections/AlternateIndexList.cs
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
#pragma warning disable CS0436
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace ObservableCollections;
|
||||||
|
|
||||||
|
public class AlternateIndexList<T> : IEnumerable<T>
|
||||||
|
{
|
||||||
|
List<IndexedValue> list; // alternate index is ordered
|
||||||
|
|
||||||
|
public AlternateIndexList()
|
||||||
|
{
|
||||||
|
this.list = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlternateIndexList(IEnumerable<(int OrderedAlternateIndex, T Value)> values)
|
||||||
|
{
|
||||||
|
this.list = values.Select(x => new IndexedValue(x.OrderedAlternateIndex, x.Value)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateAlternateIndex(int startIndex, int incr)
|
||||||
|
{
|
||||||
|
var span = CollectionsMarshal.AsSpan(list);
|
||||||
|
for (int i = startIndex; i < span.Length; i++)
|
||||||
|
{
|
||||||
|
span[i].AlternateIndex += incr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T this[int index]
|
||||||
|
{
|
||||||
|
get => list[index].Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count => list.Count;
|
||||||
|
|
||||||
|
public void Insert(int alternateIndex, T value)
|
||||||
|
{
|
||||||
|
var index = list.BinarySearch(alternateIndex);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
index = ~index;
|
||||||
|
}
|
||||||
|
list.Insert(index, new(alternateIndex, value));
|
||||||
|
UpdateAlternateIndex(index + 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InsertRange(int startingAlternateIndex, IEnumerable<T> values)
|
||||||
|
{
|
||||||
|
var index = list.BinarySearch(startingAlternateIndex);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
index = ~index;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var iter = new InsertIterator(startingAlternateIndex, values);
|
||||||
|
list.InsertRange(index, iter);
|
||||||
|
UpdateAlternateIndex(index + iter.ConsumedCount, iter.ConsumedCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(T value)
|
||||||
|
{
|
||||||
|
var index = list.FindIndex(x => EqualityComparer<T>.Default.Equals(x.Value, value));
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
list.RemoveAt(index);
|
||||||
|
UpdateAlternateIndex(index, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAt(int alternateIndex)
|
||||||
|
{
|
||||||
|
var index = list.BinarySearch(alternateIndex);
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
list.RemoveAt(index);
|
||||||
|
UpdateAlternateIndex(index, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveRange(int alternateIndex, int count)
|
||||||
|
{
|
||||||
|
var index = list.BinarySearch(alternateIndex);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
index = ~index;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.RemoveRange(index, count);
|
||||||
|
UpdateAlternateIndex(index, -count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetAtAlternateIndex(int alternateIndex, [MaybeNullWhen(true)] out T value)
|
||||||
|
{
|
||||||
|
var index = list.BinarySearch(alternateIndex);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
value = default!;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
value = list[index].Value!;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TrySetAtAlternateIndex(int alternateIndex, T value)
|
||||||
|
{
|
||||||
|
var index = list.BinarySearch(alternateIndex);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CollectionsMarshal.AsSpan(list)[index].Value = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryReplaceByValue(T searchValue, T replaceValue)
|
||||||
|
{
|
||||||
|
var index = list.FindIndex(x => EqualityComparer<T>.Default.Equals(x.Value, searchValue));
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
CollectionsMarshal.AsSpan(list)[index].Value = replaceValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
list.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear(IEnumerable<(int OrderedAlternateIndex, T Value)> values)
|
||||||
|
{
|
||||||
|
list.Clear();
|
||||||
|
list.AddRange(values.Select(x => new IndexedValue(x.OrderedAlternateIndex, x.Value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator()
|
||||||
|
{
|
||||||
|
foreach (var item in list)
|
||||||
|
{
|
||||||
|
yield return item.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<(int AlternateIndex, T Value)> GetIndexedValues()
|
||||||
|
{
|
||||||
|
foreach (var item in list)
|
||||||
|
{
|
||||||
|
yield return (item.AlternateIndex, item.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InsertIterator(int startingIndex, IEnumerable<T> values) : IEnumerable<IndexedValue>, IEnumerator<IndexedValue>
|
||||||
|
{
|
||||||
|
IEnumerator<T> iter = values.GetEnumerator();
|
||||||
|
IndexedValue current;
|
||||||
|
|
||||||
|
public int ConsumedCount { get; private set; }
|
||||||
|
|
||||||
|
public IndexedValue Current => current;
|
||||||
|
|
||||||
|
object IEnumerator.Current => Current;
|
||||||
|
|
||||||
|
public void Dispose() => iter.Reset();
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
if (iter.MoveNext())
|
||||||
|
{
|
||||||
|
ConsumedCount++;
|
||||||
|
current = new(startingIndex++, iter.Current);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset() => iter.Reset();
|
||||||
|
|
||||||
|
public IEnumerator<IndexedValue> GetEnumerator() => this;
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IndexedValue : IComparable<IndexedValue>
|
||||||
|
{
|
||||||
|
public int AlternateIndex; // mutable
|
||||||
|
public T Value; // mutable
|
||||||
|
|
||||||
|
public IndexedValue(int alternateIndex, T value)
|
||||||
|
{
|
||||||
|
this.AlternateIndex = alternateIndex;
|
||||||
|
this.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator IndexedValue(int alternateIndex) // for query
|
||||||
|
{
|
||||||
|
return new IndexedValue(alternateIndex, default!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(IndexedValue other)
|
||||||
|
{
|
||||||
|
return AlternateIndex.CompareTo(other.AlternateIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return (AlternateIndex, Value).ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,211 +0,0 @@
|
|||||||
#pragma warning disable CS0436
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace ObservableCollections.Internal
|
|
||||||
{
|
|
||||||
public class AlternateIndexList<T> : IEnumerable<T>
|
|
||||||
{
|
|
||||||
List<IndexedValue> list; // alternate index is ordered
|
|
||||||
|
|
||||||
public AlternateIndexList()
|
|
||||||
{
|
|
||||||
this.list = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public AlternateIndexList(IEnumerable<(int OrderedAlternateIndex, T Value)> values)
|
|
||||||
{
|
|
||||||
this.list = values.Select(x => new IndexedValue(x.OrderedAlternateIndex, x.Value)).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateAlternateIndex(int startIndex, int incr)
|
|
||||||
{
|
|
||||||
var span = CollectionsMarshal.AsSpan(list);
|
|
||||||
for (int i = startIndex; i < span.Length; i++)
|
|
||||||
{
|
|
||||||
span[i].AlternateIndex += incr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public T this[int index]
|
|
||||||
{
|
|
||||||
get => list[index].Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Count => list.Count;
|
|
||||||
|
|
||||||
public void Insert(int alternateIndex, T value)
|
|
||||||
{
|
|
||||||
var index = list.BinarySearch(alternateIndex);
|
|
||||||
if (index < 0)
|
|
||||||
{
|
|
||||||
index = ~index;
|
|
||||||
}
|
|
||||||
list.Insert(index, new(alternateIndex, value));
|
|
||||||
UpdateAlternateIndex(index + 1, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void InsertRange(int startingAlternateIndex, IEnumerable<T> values)
|
|
||||||
{
|
|
||||||
var index = list.BinarySearch(startingAlternateIndex);
|
|
||||||
if (index < 0)
|
|
||||||
{
|
|
||||||
index = ~index;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var iter = new InsertIterator(startingAlternateIndex, values);
|
|
||||||
list.InsertRange(index, iter);
|
|
||||||
UpdateAlternateIndex(index + iter.ConsumedCount, iter.ConsumedCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Remove(T value)
|
|
||||||
{
|
|
||||||
var index = list.FindIndex(x => EqualityComparer<T>.Default.Equals(x.Value, value));
|
|
||||||
if (index != -1)
|
|
||||||
{
|
|
||||||
list.RemoveAt(index);
|
|
||||||
UpdateAlternateIndex(index, -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveAt(int alternateIndex)
|
|
||||||
{
|
|
||||||
var index = list.BinarySearch(alternateIndex);
|
|
||||||
if (index != -1)
|
|
||||||
{
|
|
||||||
list.RemoveAt(index);
|
|
||||||
UpdateAlternateIndex(index, -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveRange(int alternateIndex, int count)
|
|
||||||
{
|
|
||||||
var index = list.BinarySearch(alternateIndex);
|
|
||||||
if (index < 0)
|
|
||||||
{
|
|
||||||
index = ~index;
|
|
||||||
}
|
|
||||||
|
|
||||||
list.RemoveRange(index, count);
|
|
||||||
UpdateAlternateIndex(index, -count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGetAtAlternateIndex(int alternateIndex, [MaybeNullWhen(true)] out T value)
|
|
||||||
{
|
|
||||||
var index = list.BinarySearch(alternateIndex);
|
|
||||||
if (index < 0)
|
|
||||||
{
|
|
||||||
value = default!;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
value = list[index].Value!;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TrySetAtAlternateIndex(int alternateIndex, T value)
|
|
||||||
{
|
|
||||||
var index = list.BinarySearch(alternateIndex);
|
|
||||||
if (index < 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
CollectionsMarshal.AsSpan(list)[index].Value = value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
list.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can't implement add, reverse and sort because alternate index is unknown
|
|
||||||
// void Add();
|
|
||||||
// void AddRange();
|
|
||||||
// void Reverse();
|
|
||||||
// void Sort();
|
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator()
|
|
||||||
{
|
|
||||||
foreach (var item in list)
|
|
||||||
{
|
|
||||||
yield return item.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
{
|
|
||||||
return GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<(int AlternateIndex, T Value)> GetIndexedValues()
|
|
||||||
{
|
|
||||||
foreach (var item in list)
|
|
||||||
{
|
|
||||||
yield return (item.AlternateIndex, item.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class InsertIterator(int startingIndex, IEnumerable<T> values) : IEnumerable<IndexedValue>, IEnumerator<IndexedValue>
|
|
||||||
{
|
|
||||||
IEnumerator<T> iter = values.GetEnumerator();
|
|
||||||
IndexedValue current;
|
|
||||||
|
|
||||||
public int ConsumedCount { get; private set; }
|
|
||||||
|
|
||||||
public IndexedValue Current => current;
|
|
||||||
|
|
||||||
object IEnumerator.Current => Current;
|
|
||||||
|
|
||||||
public void Dispose() => iter.Reset();
|
|
||||||
|
|
||||||
public bool MoveNext()
|
|
||||||
{
|
|
||||||
if (iter.MoveNext())
|
|
||||||
{
|
|
||||||
ConsumedCount++;
|
|
||||||
current = new(startingIndex++, iter.Current);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Reset() => iter.Reset();
|
|
||||||
|
|
||||||
public IEnumerator<IndexedValue> GetEnumerator() => this;
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
struct IndexedValue : IComparable<IndexedValue>
|
|
||||||
{
|
|
||||||
public int AlternateIndex; // mutable
|
|
||||||
public T Value; // mutable
|
|
||||||
|
|
||||||
public IndexedValue(int alternateIndex, T value)
|
|
||||||
{
|
|
||||||
this.AlternateIndex = alternateIndex;
|
|
||||||
this.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator IndexedValue(int alternateIndex) // for query
|
|
||||||
{
|
|
||||||
return new IndexedValue(alternateIndex, default!);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int CompareTo(IndexedValue other)
|
|
||||||
{
|
|
||||||
return AlternateIndex.CompareTo(other.AlternateIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return (AlternateIndex, Value).ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace ObservableCollections.Internal
|
|
||||||
{
|
|
||||||
internal class RemoveAllMatcher<T>
|
|
||||||
{
|
|
||||||
readonly HashSet<T> hashSet;
|
|
||||||
|
|
||||||
public RemoveAllMatcher(ReadOnlySpan<T> source)
|
|
||||||
{
|
|
||||||
#if !NETSTANDARD2_0
|
|
||||||
var set = new HashSet<T>(capacity: source.Length);
|
|
||||||
#else
|
|
||||||
var set = new HashSet<T>();
|
|
||||||
#endif
|
|
||||||
foreach (var item in source)
|
|
||||||
{
|
|
||||||
set.Add(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hashSet = set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Predicate(T value)
|
|
||||||
{
|
|
||||||
return hashSet.Contains(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -100,11 +100,16 @@ namespace ObservableCollections
|
|||||||
{
|
{
|
||||||
lock (SyncRoot)
|
lock (SyncRoot)
|
||||||
{
|
{
|
||||||
var index = list.Count;
|
var index = list.Count; // starting index
|
||||||
|
|
||||||
|
#if NET8_0_OR_GREATER
|
||||||
|
list.AddRange(items);
|
||||||
|
#else
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
list.Add(item);
|
list.Add(item);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(items, index));
|
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(items, index));
|
||||||
}
|
}
|
||||||
@ -204,11 +209,16 @@ namespace ObservableCollections
|
|||||||
{
|
{
|
||||||
lock (SyncRoot)
|
lock (SyncRoot)
|
||||||
{
|
{
|
||||||
|
#if NET8_0_OR_GREATER
|
||||||
|
list.InsertRange(index, items);
|
||||||
|
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(items, index));
|
||||||
|
#else
|
||||||
using (var xs = new CloneCollection<T>(items))
|
using (var xs = new CloneCollection<T>(items))
|
||||||
{
|
{
|
||||||
list.InsertRange(index, xs.AsEnumerable());
|
list.InsertRange(index, xs.AsEnumerable());
|
||||||
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(xs.Span, index));
|
CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs<T>.Add(xs.Span, index));
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,7 +314,6 @@ namespace ObservableCollections
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void Reverse(int index, int count)
|
public void Reverse(int index, int count)
|
||||||
{
|
{
|
||||||
lock (SyncRoot)
|
lock (SyncRoot)
|
||||||
|
@ -7,442 +7,416 @@ using System.ComponentModel;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace ObservableCollections
|
namespace ObservableCollections;
|
||||||
|
|
||||||
|
internal class SynchronizedViewList<T, TView> : ISynchronizedViewList<TView>
|
||||||
{
|
{
|
||||||
|
readonly ISynchronizedView<T, TView> parent;
|
||||||
|
protected readonly AlternateIndexList<TView> listView;
|
||||||
|
protected readonly object gate = new object();
|
||||||
|
|
||||||
internal class SynchronizedViewList<T, TView> : ISynchronizedViewList<TView>
|
public SynchronizedViewList(ISynchronizedView<T, TView> parent)
|
||||||
{
|
{
|
||||||
readonly ISynchronizedView<T, TView> parent;
|
this.parent = parent;
|
||||||
protected readonly List<TView> listView;
|
lock (parent.SyncRoot)
|
||||||
protected readonly object gate = new object();
|
|
||||||
|
|
||||||
public SynchronizedViewList(ISynchronizedView<T, TView> parent)
|
|
||||||
{
|
{
|
||||||
this.parent = parent;
|
listView = new AlternateIndexList<TView>(IterateFilteredIndexedViewsOfParent());
|
||||||
lock (parent.SyncRoot)
|
parent.ViewChanged += Parent_ViewChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<(int, TView)> IterateFilteredIndexedViewsOfParent()
|
||||||
|
{
|
||||||
|
var filter = parent.Filter;
|
||||||
|
var index = 0;
|
||||||
|
if (filter.IsNullFilter())
|
||||||
|
{
|
||||||
|
foreach (var item in parent.Unfiltered) // use Unfiltered
|
||||||
{
|
{
|
||||||
listView = parent.ToList();
|
yield return (index, item.View);
|
||||||
parent.ViewChanged += Parent_ViewChanged;
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
private void Parent_ViewChanged(in SynchronizedViewChangedEventArgs<T, TView> e)
|
|
||||||
{
|
{
|
||||||
lock (gate)
|
foreach (var item in parent.Unfiltered) // use Unfiltered
|
||||||
{
|
{
|
||||||
switch (e.Action)
|
if (filter.IsMatch(item.Value))
|
||||||
{
|
{
|
||||||
case NotifyCollectionChangedAction.Add: // Add or Insert
|
yield return (index, item.View);
|
||||||
if (e.IsSingleItem)
|
}
|
||||||
{
|
index++;
|
||||||
if (e.NewStartingIndex == -1)
|
}
|
||||||
{
|
}
|
||||||
listView.Add(e.NewItem.View);
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
listView.Insert(e.NewStartingIndex, e.NewItem.View);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (e.NewStartingIndex == -1)
|
|
||||||
{
|
|
||||||
listView.AddRange(e.NewViews);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// fnew SortedList<int, int>().sort
|
|
||||||
|
|
||||||
listView.InsertRange(e.NewStartingIndex, e.NewViews);
|
private void Parent_ViewChanged(in SynchronizedViewChangedEventArgs<T, TView> e)
|
||||||
}
|
{
|
||||||
}
|
// event is called inside parent lock
|
||||||
break;
|
lock (gate)
|
||||||
case NotifyCollectionChangedAction.Remove: // Remove
|
{
|
||||||
if (e.IsSingleItem)
|
switch (e.Action)
|
||||||
|
{
|
||||||
|
case NotifyCollectionChangedAction.Add: // Add or Insert
|
||||||
|
if (e.IsSingleItem)
|
||||||
|
{
|
||||||
|
listView.Insert(e.NewStartingIndex, e.NewItem.View);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using var array = new CloneCollection<TView>(e.NewViews);
|
||||||
|
listView.InsertRange(e.NewStartingIndex, array.AsEnumerable());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NotifyCollectionChangedAction.Remove: // Remove
|
||||||
|
if (e.IsSingleItem)
|
||||||
|
{
|
||||||
|
if (e.OldStartingIndex == -1) // can't gurantee correct remove if index is not provided
|
||||||
{
|
{
|
||||||
if (e.OldStartingIndex == -1) // can't gurantee correct remove if index is not provided
|
listView.Remove(e.OldItem.View);
|
||||||
{
|
|
||||||
listView.Remove(e.OldItem.View);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
listView.RemoveAt(e.OldStartingIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (e.OldStartingIndex == -1)
|
|
||||||
{
|
|
||||||
var matcher = new RemoveAllMatcher<TView>(e.OldViews);
|
|
||||||
listView.RemoveAll(matcher.Predicate);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
listView.RemoveRange(e.OldStartingIndex, e.OldViews.Length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
break;
|
|
||||||
case NotifyCollectionChangedAction.Replace: // Indexer
|
|
||||||
if (e.NewStartingIndex == -1)
|
|
||||||
{
|
|
||||||
var index = listView.IndexOf(e.OldItem.View);
|
|
||||||
listView[index] = e.NewItem.View;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
listView[e.NewStartingIndex] = e.NewItem.View;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case NotifyCollectionChangedAction.Move: //Remove and Insert
|
|
||||||
if (e.NewStartingIndex == -1)
|
|
||||||
{
|
|
||||||
// do nothing
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
listView.RemoveAt(e.OldStartingIndex);
|
listView.RemoveAt(e.OldStartingIndex);
|
||||||
listView.Insert(e.NewStartingIndex, e.NewItem.View);
|
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case NotifyCollectionChangedAction.Reset: // Clear or drastic changes
|
else
|
||||||
if (e.SortOperation.IsNull)
|
{
|
||||||
|
if (e.OldStartingIndex == -1)
|
||||||
{
|
{
|
||||||
listView.Clear();
|
foreach (var view in e.OldViews) // index is unknown, can't do batching
|
||||||
foreach (var item in parent) // refresh
|
|
||||||
{
|
{
|
||||||
listView.Add(item);
|
listView.Remove(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (e.SortOperation.IsReverse)
|
|
||||||
{
|
|
||||||
listView.Reverse();
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#if NET6_0_OR_GREATER
|
listView.RemoveRange(e.OldStartingIndex, e.OldViews.Length);
|
||||||
#pragma warning disable CS0436
|
|
||||||
if (parent is ObservableList<T>.View<TView> observableListView)
|
|
||||||
{
|
|
||||||
var comparer = new ObservableList<T>.View<TView>.IgnoreViewComparer(e.SortOperation.Comparer ?? Comparer<T>.Default);
|
|
||||||
var viewSpan = CollectionsMarshal.AsSpan(listView).Slice(e.SortOperation.Index, e.SortOperation.Count);
|
|
||||||
var sourceSpan = CollectionsMarshal.AsSpan(observableListView.list).Slice(e.SortOperation.Index, e.SortOperation.Count);
|
|
||||||
sourceSpan.Sort(viewSpan, comparer); // span.Sort is NET6 or greater
|
|
||||||
}
|
|
||||||
else
|
|
||||||
#pragma warning restore CS0436
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
// can not get source Span, do Clear and Refresh
|
|
||||||
listView.Clear();
|
|
||||||
foreach (var item in parent)
|
|
||||||
{
|
|
||||||
listView.Add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
default:
|
break;
|
||||||
break;
|
case NotifyCollectionChangedAction.Replace: // Indexer
|
||||||
}
|
if (e.NewStartingIndex == -1)
|
||||||
|
{
|
||||||
|
listView.TryReplaceByValue(e.OldItem.View, e.NewItem.View);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
listView.TrySetAtAlternateIndex(e.NewStartingIndex, e.NewItem.View);
|
||||||
|
}
|
||||||
|
|
||||||
OnCollectionChanged(e);
|
break;
|
||||||
|
case NotifyCollectionChangedAction.Move: //Remove and Insert
|
||||||
|
if (e.NewStartingIndex == -1)
|
||||||
|
{
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
listView.RemoveAt(e.OldStartingIndex);
|
||||||
|
listView.Insert(e.NewStartingIndex, e.NewItem.View);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NotifyCollectionChangedAction.Reset: // Clear or drastic changes
|
||||||
|
listView.Clear(IterateFilteredIndexedViewsOfParent()); // clear and fill refresh
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnCollectionChanged(in SynchronizedViewChangedEventArgs<T, TView> args)
|
OnCollectionChanged(e);
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public TView this[int index]
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock (gate)
|
|
||||||
{
|
|
||||||
return listView[index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Count
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock (gate)
|
|
||||||
{
|
|
||||||
return listView.Count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<TView> GetEnumerator()
|
|
||||||
{
|
|
||||||
lock (gate)
|
|
||||||
{
|
|
||||||
foreach (var item in listView)
|
|
||||||
{
|
|
||||||
yield return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
{
|
|
||||||
return listView.GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
parent.ViewChanged -= Parent_ViewChanged;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class NotifyCollectionChangedSynchronizedView<T, TView> :
|
protected virtual void OnCollectionChanged(in SynchronizedViewChangedEventArgs<T, TView> args)
|
||||||
SynchronizedViewList<T, TView>,
|
|
||||||
INotifyCollectionChangedSynchronizedView<TView>,
|
|
||||||
IList<TView>, IList
|
|
||||||
{
|
{
|
||||||
static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count");
|
}
|
||||||
static readonly Action<NotifyCollectionChangedEventArgs> raiseChangedEventInvoke = RaiseChangedEvent;
|
|
||||||
|
|
||||||
readonly ICollectionEventDispatcher eventDispatcher;
|
public TView this[int index]
|
||||||
|
{
|
||||||
public event NotifyCollectionChangedEventHandler? CollectionChanged;
|
get
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
|
||||||
|
|
||||||
public NotifyCollectionChangedSynchronizedView(ISynchronizedView<T, TView> parent, ICollectionEventDispatcher? eventDispatcher)
|
|
||||||
: base(parent)
|
|
||||||
{
|
{
|
||||||
this.eventDispatcher = eventDispatcher ?? InlineCollectionEventDispatcher.Instance;
|
lock (gate)
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnCollectionChanged(in SynchronizedViewChangedEventArgs<T, TView> args)
|
|
||||||
{
|
|
||||||
if (CollectionChanged == null && PropertyChanged == null) return;
|
|
||||||
|
|
||||||
switch (args.Action)
|
|
||||||
{
|
{
|
||||||
case NotifyCollectionChangedAction.Add:
|
return listView[index];
|
||||||
if (args.IsSingleItem)
|
}
|
||||||
{
|
}
|
||||||
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewItem.View, args.NewStartingIndex)
|
}
|
||||||
{
|
|
||||||
Collection = this,
|
public int Count
|
||||||
Invoker = raiseChangedEventInvoke,
|
{
|
||||||
IsInvokeCollectionChanged = true,
|
get
|
||||||
IsInvokePropertyChanged = true
|
{
|
||||||
});
|
lock (gate)
|
||||||
}
|
{
|
||||||
else
|
return listView.Count;
|
||||||
{
|
}
|
||||||
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewViews.ToArray(), args.NewStartingIndex)
|
}
|
||||||
{
|
}
|
||||||
Collection = this,
|
|
||||||
Invoker = raiseChangedEventInvoke,
|
public IEnumerator<TView> GetEnumerator()
|
||||||
IsInvokeCollectionChanged = true,
|
{
|
||||||
IsInvokePropertyChanged = true
|
lock (gate)
|
||||||
});
|
{
|
||||||
}
|
foreach (var item in listView)
|
||||||
break;
|
{
|
||||||
case NotifyCollectionChangedAction.Remove:
|
yield return item;
|
||||||
if (args.IsSingleItem)
|
}
|
||||||
{
|
}
|
||||||
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldItem.View, args.OldStartingIndex)
|
}
|
||||||
{
|
|
||||||
Collection = this,
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
Invoker = raiseChangedEventInvoke,
|
{
|
||||||
IsInvokeCollectionChanged = true,
|
return listView.GetEnumerator();
|
||||||
IsInvokePropertyChanged = true
|
}
|
||||||
});
|
|
||||||
}
|
public void Dispose()
|
||||||
else
|
{
|
||||||
{
|
parent.ViewChanged -= Parent_ViewChanged;
|
||||||
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldViews.ToArray(), args.OldStartingIndex)
|
}
|
||||||
{
|
}
|
||||||
Collection = this,
|
|
||||||
Invoker = raiseChangedEventInvoke,
|
internal class NotifyCollectionChangedSynchronizedView<T, TView> :
|
||||||
IsInvokeCollectionChanged = true,
|
SynchronizedViewList<T, TView>,
|
||||||
IsInvokePropertyChanged = true
|
INotifyCollectionChangedSynchronizedView<TView>,
|
||||||
});
|
IList<TView>, IList
|
||||||
}
|
{
|
||||||
break;
|
static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count");
|
||||||
case NotifyCollectionChangedAction.Reset:
|
static readonly Action<NotifyCollectionChangedEventArgs> raiseChangedEventInvoke = RaiseChangedEvent;
|
||||||
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Reset)
|
|
||||||
|
readonly ICollectionEventDispatcher eventDispatcher;
|
||||||
|
|
||||||
|
public event NotifyCollectionChangedEventHandler? CollectionChanged;
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
public NotifyCollectionChangedSynchronizedView(ISynchronizedView<T, TView> parent, ICollectionEventDispatcher? eventDispatcher)
|
||||||
|
: base(parent)
|
||||||
|
{
|
||||||
|
this.eventDispatcher = eventDispatcher ?? InlineCollectionEventDispatcher.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnCollectionChanged(in SynchronizedViewChangedEventArgs<T, TView> args)
|
||||||
|
{
|
||||||
|
if (CollectionChanged == null && PropertyChanged == null) return;
|
||||||
|
|
||||||
|
switch (args.Action)
|
||||||
|
{
|
||||||
|
case NotifyCollectionChangedAction.Add:
|
||||||
|
if (args.IsSingleItem)
|
||||||
|
{
|
||||||
|
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewItem.View, args.NewStartingIndex)
|
||||||
{
|
{
|
||||||
Collection = this,
|
Collection = this,
|
||||||
Invoker = raiseChangedEventInvoke,
|
Invoker = raiseChangedEventInvoke,
|
||||||
IsInvokeCollectionChanged = true,
|
IsInvokeCollectionChanged = true,
|
||||||
IsInvokePropertyChanged = true
|
IsInvokePropertyChanged = true
|
||||||
});
|
});
|
||||||
break;
|
}
|
||||||
case NotifyCollectionChangedAction.Replace:
|
else
|
||||||
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Replace, args.NewItem.View, args.OldItem.View, args.NewStartingIndex)
|
{
|
||||||
|
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewViews.ToArray(), args.NewStartingIndex)
|
||||||
{
|
{
|
||||||
Collection = this,
|
Collection = this,
|
||||||
Invoker = raiseChangedEventInvoke,
|
Invoker = raiseChangedEventInvoke,
|
||||||
IsInvokeCollectionChanged = true,
|
IsInvokeCollectionChanged = true,
|
||||||
IsInvokePropertyChanged = false
|
IsInvokePropertyChanged = true
|
||||||
});
|
});
|
||||||
break;
|
}
|
||||||
case NotifyCollectionChangedAction.Move:
|
break;
|
||||||
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Move, args.NewItem.View, args.NewStartingIndex, args.OldStartingIndex)
|
case NotifyCollectionChangedAction.Remove:
|
||||||
|
if (args.IsSingleItem)
|
||||||
|
{
|
||||||
|
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldItem.View, args.OldStartingIndex)
|
||||||
{
|
{
|
||||||
Collection = this,
|
Collection = this,
|
||||||
Invoker = raiseChangedEventInvoke,
|
Invoker = raiseChangedEventInvoke,
|
||||||
IsInvokeCollectionChanged = true,
|
IsInvokeCollectionChanged = true,
|
||||||
IsInvokePropertyChanged = false
|
IsInvokePropertyChanged = true
|
||||||
});
|
});
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void RaiseChangedEvent(NotifyCollectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
var e2 = (CollectionEventDispatcherEventArgs)e;
|
|
||||||
var self = (NotifyCollectionChangedSynchronizedView<T, TView>)e2.Collection;
|
|
||||||
|
|
||||||
if (e2.IsInvokeCollectionChanged)
|
|
||||||
{
|
|
||||||
self.CollectionChanged?.Invoke(self, e);
|
|
||||||
}
|
|
||||||
if (e2.IsInvokePropertyChanged)
|
|
||||||
{
|
|
||||||
self.PropertyChanged?.Invoke(self, CountPropertyChangedEventArgs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IList<T>, IList implementation
|
|
||||||
|
|
||||||
TView IList<TView>.this[int index]
|
|
||||||
{
|
|
||||||
get => ((IReadOnlyList<TView>)this)[index];
|
|
||||||
set => throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
object? IList.this[int index]
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return this[index];
|
|
||||||
}
|
|
||||||
set => throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool IsCompatibleObject(object? value)
|
|
||||||
{
|
|
||||||
return value is T || value == null && default(T) == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsReadOnly => true;
|
|
||||||
|
|
||||||
public bool IsFixedSize => false;
|
|
||||||
|
|
||||||
public bool IsSynchronized => true;
|
|
||||||
|
|
||||||
public object SyncRoot => gate;
|
|
||||||
|
|
||||||
public void Add(TView item)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Add(object? value)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(TView item)
|
|
||||||
{
|
|
||||||
lock (gate)
|
|
||||||
{
|
|
||||||
foreach (var listItem in listView)
|
|
||||||
{
|
|
||||||
if (EqualityComparer<TView>.Default.Equals(listItem, item))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(object? value)
|
|
||||||
{
|
|
||||||
if (IsCompatibleObject(value))
|
|
||||||
{
|
|
||||||
return Contains((TView)value!);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CopyTo(TView[] array, int arrayIndex)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CopyTo(Array array, int index)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int IndexOf(TView item)
|
|
||||||
{
|
|
||||||
lock (gate)
|
|
||||||
{
|
|
||||||
var index = 0;
|
|
||||||
foreach (var listItem in listView)
|
|
||||||
{
|
{
|
||||||
if (EqualityComparer<TView>.Default.Equals(listItem, item))
|
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldViews.ToArray(), args.OldStartingIndex)
|
||||||
{
|
{
|
||||||
return index;
|
Collection = this,
|
||||||
}
|
Invoker = raiseChangedEventInvoke,
|
||||||
index++;
|
IsInvokeCollectionChanged = true,
|
||||||
|
IsInvokePropertyChanged = true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
return -1;
|
case NotifyCollectionChangedAction.Reset:
|
||||||
}
|
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Reset)
|
||||||
|
{
|
||||||
public int IndexOf(object? item)
|
Collection = this,
|
||||||
{
|
Invoker = raiseChangedEventInvoke,
|
||||||
if (IsCompatibleObject(item))
|
IsInvokeCollectionChanged = true,
|
||||||
{
|
IsInvokePropertyChanged = true
|
||||||
return IndexOf((TView)item!);
|
});
|
||||||
}
|
break;
|
||||||
return -1;
|
case NotifyCollectionChangedAction.Replace:
|
||||||
}
|
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Replace, args.NewItem.View, args.OldItem.View, args.NewStartingIndex)
|
||||||
|
{
|
||||||
public void Insert(int index, TView item)
|
Collection = this,
|
||||||
{
|
Invoker = raiseChangedEventInvoke,
|
||||||
throw new NotSupportedException();
|
IsInvokeCollectionChanged = true,
|
||||||
}
|
IsInvokePropertyChanged = false
|
||||||
|
});
|
||||||
public void Insert(int index, object? value)
|
break;
|
||||||
{
|
case NotifyCollectionChangedAction.Move:
|
||||||
throw new NotImplementedException();
|
eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Move, args.NewItem.View, args.NewStartingIndex, args.OldStartingIndex)
|
||||||
}
|
{
|
||||||
|
Collection = this,
|
||||||
public bool Remove(TView item)
|
Invoker = raiseChangedEventInvoke,
|
||||||
{
|
IsInvokeCollectionChanged = true,
|
||||||
throw new NotSupportedException();
|
IsInvokePropertyChanged = false
|
||||||
}
|
});
|
||||||
|
break;
|
||||||
public void Remove(object? value)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveAt(int index)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void RaiseChangedEvent(NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var e2 = (CollectionEventDispatcherEventArgs)e;
|
||||||
|
var self = (NotifyCollectionChangedSynchronizedView<T, TView>)e2.Collection;
|
||||||
|
|
||||||
|
if (e2.IsInvokeCollectionChanged)
|
||||||
|
{
|
||||||
|
self.CollectionChanged?.Invoke(self, e);
|
||||||
|
}
|
||||||
|
if (e2.IsInvokePropertyChanged)
|
||||||
|
{
|
||||||
|
self.PropertyChanged?.Invoke(self, CountPropertyChangedEventArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IList<T>, IList implementation
|
||||||
|
|
||||||
|
TView IList<TView>.this[int index]
|
||||||
|
{
|
||||||
|
get => ((IReadOnlyList<TView>)this)[index];
|
||||||
|
set => throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
object? IList.this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this[index];
|
||||||
|
}
|
||||||
|
set => throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsCompatibleObject(object? value)
|
||||||
|
{
|
||||||
|
return value is T || value == null && default(T) == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsReadOnly => true;
|
||||||
|
|
||||||
|
public bool IsFixedSize => false;
|
||||||
|
|
||||||
|
public bool IsSynchronized => true;
|
||||||
|
|
||||||
|
public object SyncRoot => gate;
|
||||||
|
|
||||||
|
public void Add(TView item)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Add(object? value)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(TView item)
|
||||||
|
{
|
||||||
|
lock (gate)
|
||||||
|
{
|
||||||
|
foreach (var listItem in listView)
|
||||||
|
{
|
||||||
|
if (EqualityComparer<TView>.Default.Equals(listItem, item))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(object? value)
|
||||||
|
{
|
||||||
|
if (IsCompatibleObject(value))
|
||||||
|
{
|
||||||
|
return Contains((TView)value!);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(TView[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(Array array, int index)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int IndexOf(TView item)
|
||||||
|
{
|
||||||
|
lock (gate)
|
||||||
|
{
|
||||||
|
var index = 0;
|
||||||
|
foreach (var listItem in listView)
|
||||||
|
{
|
||||||
|
if (EqualityComparer<TView>.Default.Equals(listItem, item))
|
||||||
|
{
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int IndexOf(object? item)
|
||||||
|
{
|
||||||
|
if (IsCompatibleObject(item))
|
||||||
|
{
|
||||||
|
return IndexOf((TView)item!);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Insert(int index, TView item)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Insert(int index, object? value)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(TView item)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(object? value)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAt(int index)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
}
|
}
|
@ -97,4 +97,17 @@ public class AlternateIndexListTest
|
|||||||
list.TryGetAtAlternateIndex(4, out var baz).Should().BeTrue();
|
list.TryGetAtAlternateIndex(4, out var baz).Should().BeTrue();
|
||||||
baz.Should().Be("new-baz");
|
baz.Should().Be("new-baz");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TryReplaceByValue()
|
||||||
|
{
|
||||||
|
var list = new AlternateIndexList<string>();
|
||||||
|
|
||||||
|
list.Insert(0, "foo");
|
||||||
|
list.Insert(2, "bar");
|
||||||
|
list.Insert(4, "baz");
|
||||||
|
|
||||||
|
list.TryReplaceByValue("bar", "new-bar");
|
||||||
|
list.GetIndexedValues().Should().Equal((0, "foo"), (2, "new-bar"), (4, "baz"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user