replace SynchronizedViewList backed uses AlternateIndexList

This commit is contained in:
neuecc 2024-09-02 19:31:29 +09:00
parent 08b328c16f
commit c0c9cd48d7
6 changed files with 602 additions and 625 deletions

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

View File

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

View File

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

View File

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

View File

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

View File

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