diff --git a/sandbox/ConsoleApp/Program.cs b/sandbox/ConsoleApp/Program.cs index 3c099c9..873c5a4 100644 --- a/sandbox/ConsoleApp/Program.cs +++ b/sandbox/ConsoleApp/Program.cs @@ -17,7 +17,7 @@ var list = new ObservableList() var view = list.CreateWritableView(x => x.Name); view.AttachFilter(x => x.Age >= 20); -IList bindable = view.ToWritableNotifyCollectionChanged((string? newView, Person original, ref bool setValue) => +var bindable = view.ToWritableNotifyCollectionChanged((string? newView, Person original, ref bool setValue) => { if (setValue) { diff --git a/src/ObservableCollections/IObservableCollection.cs b/src/ObservableCollections/IObservableCollection.cs index 7ff0b71..9134cbf 100644 --- a/src/ObservableCollections/IObservableCollection.cs +++ b/src/ObservableCollections/IObservableCollection.cs @@ -47,8 +47,8 @@ namespace ObservableCollections void AttachFilter(ISynchronizedViewFilter filter); void ResetFilter(); ISynchronizedViewList ToViewList(); - INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(); - INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher); + NotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(); + NotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher); } public interface IWritableSynchronizedView : ISynchronizedView @@ -58,9 +58,9 @@ namespace ObservableCollections void SetToSourceCollection(int index, T value); void AddToSourceCollection(T value); IWritableSynchronizedViewList ToWritableViewList(WritableViewChangedEventHandler converter); - INotifyCollectionChangedSynchronizedViewList ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler converter); - INotifyCollectionChangedSynchronizedViewList ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher); - INotifyCollectionChangedSynchronizedViewList ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler converter, ICollectionEventDispatcher? collectionEventDispatcher); + NotifyCollectionChangedSynchronizedViewList ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler converter); + NotifyCollectionChangedSynchronizedViewList ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher); + NotifyCollectionChangedSynchronizedViewList ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler converter, ICollectionEventDispatcher? collectionEventDispatcher); } public interface ISynchronizedViewList : IReadOnlyList, IDisposable @@ -72,10 +72,97 @@ namespace ObservableCollections new TView this[int index] { get; set; } } + // only for compatibility, use NotifyCollectionChangedSynchronizedViewList insetad. + // [Obsolete] in future public interface INotifyCollectionChangedSynchronizedViewList : IList, IList, ISynchronizedViewList, INotifyCollectionChanged, INotifyPropertyChanged { } + // IColleciton.Count and ICollection.Count will be ambigious so use abstract class instead of interface + public abstract class NotifyCollectionChangedSynchronizedViewList : + INotifyCollectionChangedSynchronizedViewList, + IWritableSynchronizedViewList, + IList, + IList + { + protected readonly object gate = new object(); + + public abstract TView this[int index] { get; set; } + + object? IList.this[int index] + { + get + { + return this[index]; + } + set => ((IList)this)[index] = (TView)value!; + } + + public abstract int Count { get; } + public bool IsReadOnly => false; + public bool IsFixedSize => false; + public bool IsSynchronized => true; + public object SyncRoot => gate; + + public abstract event NotifyCollectionChangedEventHandler? CollectionChanged; + public abstract event PropertyChangedEventHandler? PropertyChanged; + + public abstract void Add(TView item); + + int IList.Add(object? value) + { + Add((TView)value!); + return -1; // itself does not add in this collection + } + + public abstract bool Contains(TView item); + + bool IList.Contains(object? value) + { + if (IsCompatibleObject(value)) + { + return Contains((TView)value!); + } + return false; + } + + public abstract void Dispose(); + public abstract IEnumerator GetEnumerator(); + public abstract int IndexOf(TView item); + + int IList.IndexOf(object? item) + { + if (IsCompatibleObject(item)) + { + return IndexOf((TView)item!); + } + return -1; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + static bool IsCompatibleObject(object? value) + { + return value is TView || value == null && default(TView) == null; + } + + void ICollection.Clear() => throw new NotSupportedException(); + void IList.Clear() => throw new NotSupportedException(); + + void ICollection.CopyTo(TView[] array, int arrayIndex) => throw new NotSupportedException(); + void ICollection.CopyTo(Array array, int index) => throw new NotSupportedException(); + + void IList.Insert(int index, TView item) => throw new NotSupportedException(); + void IList.Insert(int index, object? value) => throw new NotSupportedException(); + bool ICollection.Remove(TView item) => throw new NotSupportedException(); + void IList.Remove(object? value) => throw new NotSupportedException(); + void IList.RemoveAt(int index) => throw new NotSupportedException(); + void IList.RemoveAt(int index) => throw new NotSupportedException(); + } + public static class ObservableCollectionExtensions { public static ISynchronizedViewList ToViewList(this IObservableCollection collection) @@ -86,7 +173,7 @@ namespace ObservableCollections public static ISynchronizedViewList ToViewList(this IObservableCollection collection, Func transform) { // Optimized for non filtered - return new NonFilteredSynchronizedViewList(collection.CreateView(transform)); + return new NonFilteredSynchronizedViewList(collection.CreateView(transform), isSupportRangeFeature: true, null, null); } public static INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(this IObservableCollection collection) @@ -107,7 +194,7 @@ namespace ObservableCollections public static INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(this IObservableCollection collection, Func transform, ICollectionEventDispatcher? collectionEventDispatcher) { // Optimized for non filtered - return new NonFilteredNotifyCollectionChangedSynchronizedViewList(collection.CreateView(transform), collectionEventDispatcher); + return new NonFilteredSynchronizedViewList(collection.CreateView(transform), isSupportRangeFeature: false, collectionEventDispatcher, null); } } } \ No newline at end of file diff --git a/src/ObservableCollections/ObservableDictionary.Views.cs b/src/ObservableCollections/ObservableDictionary.Views.cs index 666aa48..ece0950 100644 --- a/src/ObservableCollections/ObservableDictionary.Views.cs +++ b/src/ObservableCollections/ObservableDictionary.Views.cs @@ -111,17 +111,17 @@ namespace ObservableCollections public ISynchronizedViewList ToViewList() { - return new FiltableSynchronizedViewList, TView>(this); + return new FiltableSynchronizedViewList, TView>(this, isSupportRangeFeature: true); } - public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() + public NotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() { - return new NotifyCollectionChangedSynchronizedViewList, TView>(this, null); + return new FiltableSynchronizedViewList, TView>(this, isSupportRangeFeature: false); } - public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) + public NotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) { - return new NotifyCollectionChangedSynchronizedViewList, TView>(this, collectionEventDispatcher); + return new FiltableSynchronizedViewList, TView>(this, isSupportRangeFeature: false, collectionEventDispatcher); } public IEnumerator GetEnumerator() diff --git a/src/ObservableCollections/ObservableHashSet.Views.cs b/src/ObservableCollections/ObservableHashSet.Views.cs index d8adf18..50669d1 100644 --- a/src/ObservableCollections/ObservableHashSet.Views.cs +++ b/src/ObservableCollections/ObservableHashSet.Views.cs @@ -106,17 +106,17 @@ namespace ObservableCollections public ISynchronizedViewList ToViewList() { - return new FiltableSynchronizedViewList(this); + return new FiltableSynchronizedViewList(this, isSupportRangeFeature: true); } - public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() + public NotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() { - return new NotifyCollectionChangedSynchronizedViewList(this, null); + return new FiltableSynchronizedViewList(this, isSupportRangeFeature: false); } - public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) + public NotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) { - return new NotifyCollectionChangedSynchronizedViewList(this, collectionEventDispatcher); + return new FiltableSynchronizedViewList(this, isSupportRangeFeature: false, collectionEventDispatcher); } public IEnumerator GetEnumerator() diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index f20f864..5eea0af 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -43,7 +43,7 @@ namespace ObservableCollections public INotifyCollectionChangedSynchronizedViewList ToWritableNotifyCollectionChanged(Func transform, WritableViewChangedEventHandler? converter, ICollectionEventDispatcher? collectionEventDispatcher) { - return new NonFilteredNotifyCollectionChangedSynchronizedViewList(CreateView(transform), collectionEventDispatcher, converter); + return new NonFilteredSynchronizedViewList(CreateView(transform), isSupportRangeFeature: false, collectionEventDispatcher, converter); } internal sealed class View : ISynchronizedView, IWritableSynchronizedView @@ -141,17 +141,17 @@ namespace ObservableCollections public ISynchronizedViewList ToViewList() { - return new FiltableSynchronizedViewList(this); + return new FiltableSynchronizedViewList(this, isSupportRangeFeature: true); } - public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() + public NotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() { - return new NotifyCollectionChangedSynchronizedViewList(this, null); + return new FiltableSynchronizedViewList(this, isSupportRangeFeature: false); } - public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) + public NotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) { - return new NotifyCollectionChangedSynchronizedViewList(this, collectionEventDispatcher); + return new FiltableSynchronizedViewList(this, isSupportRangeFeature: false, collectionEventDispatcher); } public IEnumerator GetEnumerator() @@ -370,27 +370,29 @@ namespace ObservableCollections public IWritableSynchronizedViewList ToWritableViewList(WritableViewChangedEventHandler converter) { - return new FiltableWritableSynchronizedViewList(this, converter); + return new FiltableSynchronizedViewList(this, isSupportRangeFeature: true, converter: converter); } - public INotifyCollectionChangedSynchronizedViewList ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler converter) + public NotifyCollectionChangedSynchronizedViewList ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler converter) { - return new NotifyCollectionChangedSynchronizedViewList(this, null, converter); + return new FiltableSynchronizedViewList(this, isSupportRangeFeature: false, converter: converter); } - public INotifyCollectionChangedSynchronizedViewList ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) + public NotifyCollectionChangedSynchronizedViewList ToWritableNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) { - return new NotifyCollectionChangedSynchronizedViewList(this, collectionEventDispatcher, - static (TView newView, T originalValue, ref bool setValue) => - { - setValue = true; - return originalValue; - }); + return new FiltableSynchronizedViewList(this, + isSupportRangeFeature: false, + collectionEventDispatcher, + static (TView newView, T originalValue, ref bool setValue) => + { + setValue = true; + return originalValue; + }); } - public INotifyCollectionChangedSynchronizedViewList ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler converter, ICollectionEventDispatcher? collectionEventDispatcher) + public NotifyCollectionChangedSynchronizedViewList ToWritableNotifyCollectionChanged(WritableViewChangedEventHandler converter, ICollectionEventDispatcher? collectionEventDispatcher) { - return new NotifyCollectionChangedSynchronizedViewList(this, collectionEventDispatcher, converter); + return new FiltableSynchronizedViewList(this, isSupportRangeFeature: false, collectionEventDispatcher, converter); } #endregion diff --git a/src/ObservableCollections/ObservableQueue.Views.cs b/src/ObservableCollections/ObservableQueue.Views.cs index 812dee8..e118dcf 100644 --- a/src/ObservableCollections/ObservableQueue.Views.cs +++ b/src/ObservableCollections/ObservableQueue.Views.cs @@ -106,17 +106,17 @@ namespace ObservableCollections public ISynchronizedViewList ToViewList() { - return new FiltableSynchronizedViewList(this); + return new FiltableSynchronizedViewList(this, isSupportRangeFeature: true); } - public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() + public NotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() { - return new NotifyCollectionChangedSynchronizedViewList(this, null); + return new FiltableSynchronizedViewList(this, isSupportRangeFeature: false); } - public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) + public NotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) { - return new NotifyCollectionChangedSynchronizedViewList(this, collectionEventDispatcher); + return new FiltableSynchronizedViewList(this, isSupportRangeFeature: false, collectionEventDispatcher); } public IEnumerator GetEnumerator() diff --git a/src/ObservableCollections/ObservableRingBuffer.Views.cs b/src/ObservableCollections/ObservableRingBuffer.Views.cs index 8b951dd..fed1f17 100644 --- a/src/ObservableCollections/ObservableRingBuffer.Views.cs +++ b/src/ObservableCollections/ObservableRingBuffer.Views.cs @@ -108,22 +108,22 @@ namespace ObservableCollections public ISynchronizedViewList ToViewList() { - return new FiltableSynchronizedViewList(this); + return new FiltableSynchronizedViewList(this, isSupportRangeFeature: true); } - public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() + public NotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() { lock (SyncRoot) { - return new NotifyCollectionChangedSynchronizedViewList(this, null); + return new FiltableSynchronizedViewList(this, isSupportRangeFeature: false); } } - public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) + public NotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) { lock (SyncRoot) { - return new NotifyCollectionChangedSynchronizedViewList(this, collectionEventDispatcher); + return new FiltableSynchronizedViewList(this, isSupportRangeFeature: false, collectionEventDispatcher); } } diff --git a/src/ObservableCollections/ObservableStack.Views.cs b/src/ObservableCollections/ObservableStack.Views.cs index f0feeab..67b9bdb 100644 --- a/src/ObservableCollections/ObservableStack.Views.cs +++ b/src/ObservableCollections/ObservableStack.Views.cs @@ -105,22 +105,22 @@ namespace ObservableCollections public ISynchronizedViewList ToViewList() { - return new FiltableSynchronizedViewList(this); + return new FiltableSynchronizedViewList(this, isSupportRangeFeature: true); } - public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() + public NotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged() { lock (SyncRoot) { - return new NotifyCollectionChangedSynchronizedViewList(this, null); + return new FiltableSynchronizedViewList(this, isSupportRangeFeature: false); } } - public INotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) + public NotifyCollectionChangedSynchronizedViewList ToNotifyCollectionChanged(ICollectionEventDispatcher? collectionEventDispatcher) { lock (SyncRoot) { - return new NotifyCollectionChangedSynchronizedViewList(this, collectionEventDispatcher); + return new FiltableSynchronizedViewList(this, isSupportRangeFeature: false, collectionEventDispatcher); } } diff --git a/src/ObservableCollections/SynchronizedViewList.cs b/src/ObservableCollections/SynchronizedViewList.cs index 9287f03..e6c6c2d 100644 --- a/src/ObservableCollections/SynchronizedViewList.cs +++ b/src/ObservableCollections/SynchronizedViewList.cs @@ -1,27 +1,35 @@ using ObservableCollections.Internal; using System; -using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace ObservableCollections; -internal class FiltableSynchronizedViewList : ISynchronizedViewList +internal sealed class FiltableSynchronizedViewList : NotifyCollectionChangedSynchronizedViewList { - protected readonly ISynchronizedView parent; - protected readonly AlternateIndexList listView; - protected readonly object gate = new object(); + static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count"); + static readonly Action raiseChangedEventInvoke = RaiseChangedEvent; - protected virtual bool IsSupportRangeFeature => true; + readonly ISynchronizedView parent; + readonly AlternateIndexList listView; + readonly bool isSupportRangeFeature; // WPF, Avalonia etc does not support range notification - public FiltableSynchronizedViewList(ISynchronizedView parent) + readonly ICollectionEventDispatcher eventDispatcher; + readonly WritableViewChangedEventHandler? converter; // null = readonly + + public override event NotifyCollectionChangedEventHandler? CollectionChanged; + public override event PropertyChangedEventHandler? PropertyChanged; + + public FiltableSynchronizedViewList(ISynchronizedView parent, bool isSupportRangeFeature, ICollectionEventDispatcher? eventDispatcher = null, WritableViewChangedEventHandler? converter = null) { this.parent = parent; + this.isSupportRangeFeature = isSupportRangeFeature; + this.eventDispatcher = eventDispatcher ?? InlineCollectionEventDispatcher.Instance; + this.converter = converter; lock (parent.SyncRoot) { listView = new AlternateIndexList(IterateFilteredIndexedViewsOfParent()); @@ -82,7 +90,7 @@ internal class FiltableSynchronizedViewList : ISynchronizedViewList(e.NewViews); var index = listView.InsertRange(e.NewStartingIndex, array.AsEnumerable()); @@ -127,7 +135,7 @@ internal class FiltableSynchronizedViewList : ISynchronizedViewList : ISynchronizedViewList args) + void OnCollectionChanged(in SynchronizedViewChangedEventArgs 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, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + } + else + { + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewViews.ToArray(), args.NewStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + } + break; + case NotifyCollectionChangedAction.Remove: + if (args.IsSingleItem) + { + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldItem.View, args.OldStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + } + else + { + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldViews.ToArray(), args.OldStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + } + break; + case NotifyCollectionChangedAction.Reset: + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Reset) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + break; + case NotifyCollectionChangedAction.Replace: + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Replace, args.NewItem.View, args.OldItem.View, args.NewStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = false + }); + break; + case NotifyCollectionChangedAction.Move: + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Move, args.NewItem.View, args.NewStartingIndex, args.OldStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = false + }); + break; + } } - public TView this[int index] + static void RaiseChangedEvent(NotifyCollectionChangedEventArgs e) + { + var e2 = (CollectionEventDispatcherEventArgs)e; + var self = (FiltableSynchronizedViewList)e2.Collection; + + if (e2.IsInvokeCollectionChanged) + { + self.CollectionChanged?.Invoke(self, e); + } + if (e2.IsInvokePropertyChanged) + { + self.PropertyChanged?.Invoke(self, CountPropertyChangedEventArgs); + } + } + + public override TView this[int index] { get { @@ -236,9 +335,33 @@ internal class FiltableSynchronizedViewList : ISynchronizedViewList writableView) + { + throw new NotSupportedException("This CollectionView does not support set. If base type is ObservableList, you can use ToWritableSynchronizedView and ToWritableNotifyCollectionChanged."); + } + else + { + var originalIndex = listView.GetAlternateIndex(index); + var (originalValue, _) = writableView.GetAt(originalIndex); + + // update view + writableView.SetViewAt(originalIndex, value); + listView[index] = value; + + var setValue = true; + var newOriginal = converter(value, originalValue, ref setValue); + + if (setValue) + { + writableView.SetToSourceCollection(originalIndex, newOriginal); + } + } + } } - public int Count + public override int Count { get { @@ -249,7 +372,7 @@ internal class FiltableSynchronizedViewList : ISynchronizedViewList GetEnumerator() + public override IEnumerator GetEnumerator() { lock (gate) { @@ -260,33 +383,88 @@ internal class FiltableSynchronizedViewList : ISynchronizedViewList writableView) + { + throw new NotSupportedException("This CollectionView does not support Add. If base type is ObservableList, you can use ToWritableSynchronizedView and ToWritableNotifyCollectionChanged."); + } + else + { + var setValue = false; + var newOriginal = converter(item, default!, ref setValue); + + // always add + writableView.AddToSourceCollection(newOriginal); + } } - public void Dispose() + public override bool Contains(TView item) + { + lock (gate) + { + foreach (var listItem in listView) + { + if (EqualityComparer.Default.Equals(listItem, item)) + { + return true; + } + } + } + return false; + } + + public override int IndexOf(TView item) + { + lock (gate) + { + var index = 0; + foreach (var listItem in listView) + { + if (EqualityComparer.Default.Equals(listItem, item)) + { + return index; + } + index++; + } + } + return -1; + } + + public override void Dispose() { parent.ViewChanged -= Parent_ViewChanged; parent.RejectedViewChanged -= Parent_RejectedViewChanged; } } -internal class NonFilteredSynchronizedViewList : ISynchronizedViewList +internal sealed class NonFilteredSynchronizedViewList : NotifyCollectionChangedSynchronizedViewList { - protected readonly ISynchronizedView parent; - protected readonly List listView; // no filter can be faster - protected readonly object gate = new object(); + static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count"); + static readonly Action raiseChangedEventInvoke = RaiseChangedEvent; - protected virtual bool IsSupportRangeFeature => true; + readonly ISynchronizedView parent; + readonly List listView; // no filter can be faster + readonly bool isSupportRangeFeature; // WPF, Avalonia etc does not support range notification - public NonFilteredSynchronizedViewList(ISynchronizedView parent) + readonly ICollectionEventDispatcher eventDispatcher; + readonly WritableViewChangedEventHandler? converter; // null = readonly + + public override event NotifyCollectionChangedEventHandler? CollectionChanged; + public override event PropertyChangedEventHandler? PropertyChanged; + + + public NonFilteredSynchronizedViewList(ISynchronizedView parent, bool isSupportRangeFeature, ICollectionEventDispatcher? eventDispatcher, WritableViewChangedEventHandler? converter) { this.parent = parent; + this.isSupportRangeFeature = isSupportRangeFeature; + this.eventDispatcher = eventDispatcher ?? InlineCollectionEventDispatcher.Instance; + this.converter = converter; lock (parent.SyncRoot) { - listView = parent.ToList(); // iterate filtered + listView = parent.ToList(); // guranteed non-filtered parent.ViewChanged += Parent_ViewChanged; + // no register RejectedViewChanged(beacuse non filtered) } } @@ -314,7 +492,7 @@ internal class NonFilteredSynchronizedViewList : ISynchronizedViewList } else { - if (IsSupportRangeFeature) + if (isSupportRangeFeature) { #if NET8_0_OR_GREATER listView.InsertRange(e.NewStartingIndex, e.NewViews); @@ -367,7 +545,7 @@ internal class NonFilteredSynchronizedViewList : ISynchronizedViewList } else { - if (IsSupportRangeFeature) + if (isSupportRangeFeature) { listView.RemoveRange(e.OldStartingIndex, e.OldViews.Length); } @@ -478,11 +656,102 @@ internal class NonFilteredSynchronizedViewList : ISynchronizedViewList } } - protected virtual void OnCollectionChanged(in SynchronizedViewChangedEventArgs args) + void OnCollectionChanged(in SynchronizedViewChangedEventArgs 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, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + } + else + { + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewViews.ToArray(), args.NewStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + } + break; + case NotifyCollectionChangedAction.Remove: + if (args.IsSingleItem) + { + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldItem.View, args.OldStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + } + else + { + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldViews.ToArray(), args.OldStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + } + break; + case NotifyCollectionChangedAction.Reset: + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Reset) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = true + }); + break; + case NotifyCollectionChangedAction.Replace: + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Replace, args.NewItem.View, args.OldItem.View, args.NewStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = false + }); + break; + case NotifyCollectionChangedAction.Move: + eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Move, args.NewItem.View, args.NewStartingIndex, args.OldStartingIndex) + { + Collection = this, + Invoker = raiseChangedEventInvoke, + IsInvokeCollectionChanged = true, + IsInvokePropertyChanged = false + }); + break; + } } - public TView this[int index] + static void RaiseChangedEvent(NotifyCollectionChangedEventArgs e) + { + var e2 = (CollectionEventDispatcherEventArgs)e; + var self = (NonFilteredSynchronizedViewList)e2.Collection; + + if (e2.IsInvokeCollectionChanged) + { + self.CollectionChanged?.Invoke(self, e); + } + if (e2.IsInvokePropertyChanged) + { + self.PropertyChanged?.Invoke(self, CountPropertyChangedEventArgs); + } + } + + public override TView this[int index] { get { @@ -491,498 +760,6 @@ internal class NonFilteredSynchronizedViewList : ISynchronizedViewList return listView[index]; } } - } - - public int Count - { - get - { - lock (gate) - { - return listView.Count; - } - } - } - - public IEnumerator GetEnumerator() - { - lock (gate) - { - foreach (var item in listView) - { - yield return item; - } - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public void Dispose() - { - parent.ViewChanged -= Parent_ViewChanged; - parent.Dispose(); // Dispose parent - } -} - -internal class FiltableWritableSynchronizedViewList : FiltableSynchronizedViewList, IWritableSynchronizedViewList -{ - IWritableSynchronizedView writableView; - WritableViewChangedEventHandler converter; - - public FiltableWritableSynchronizedViewList(IWritableSynchronizedView parent, WritableViewChangedEventHandler converter) : base(parent) - { - this.writableView = parent; - this.converter = converter; - } - - public new TView this[int index] - { - get => base[index]; - set - { - lock (gate) - { - var originalIndex = listView.GetAlternateIndex(index); - var (originalValue, _) = writableView.GetAt(originalIndex); - - // update view - writableView.SetViewAt(originalIndex, value); - listView[index] = value; - - var setValue = true; - var newOriginal = converter(value, originalValue, ref setValue); - - if (setValue) - { - writableView.SetToSourceCollection(originalIndex, newOriginal); - } - } - } - } -} - -internal class NotifyCollectionChangedSynchronizedViewList : - FiltableSynchronizedViewList, - INotifyCollectionChangedSynchronizedViewList, - IList, IList -{ - static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count"); - static readonly Action raiseChangedEventInvoke = RaiseChangedEvent; - - readonly ICollectionEventDispatcher eventDispatcher; - WritableViewChangedEventHandler? converter; // null = readonly - - protected override bool IsSupportRangeFeature => false; // WPF, Avalonia etc does not support range notification - - public event NotifyCollectionChangedEventHandler? CollectionChanged; - public event PropertyChangedEventHandler? PropertyChanged; - - public NotifyCollectionChangedSynchronizedViewList(ISynchronizedView parent, ICollectionEventDispatcher? eventDispatcher) - : base(parent) - { - this.eventDispatcher = eventDispatcher ?? InlineCollectionEventDispatcher.Instance; - } - - public NotifyCollectionChangedSynchronizedViewList(ISynchronizedView parent, ICollectionEventDispatcher? eventDispatcher, WritableViewChangedEventHandler? converter) - : base(parent) - { - this.eventDispatcher = eventDispatcher ?? InlineCollectionEventDispatcher.Instance; - this.converter = converter; - } - - protected override void OnCollectionChanged(in SynchronizedViewChangedEventArgs 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, - Invoker = raiseChangedEventInvoke, - IsInvokeCollectionChanged = true, - IsInvokePropertyChanged = true - }); - } - else - { - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewViews.ToArray(), args.NewStartingIndex) - { - Collection = this, - Invoker = raiseChangedEventInvoke, - IsInvokeCollectionChanged = true, - IsInvokePropertyChanged = true - }); - } - break; - case NotifyCollectionChangedAction.Remove: - if (args.IsSingleItem) - { - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldItem.View, args.OldStartingIndex) - { - Collection = this, - Invoker = raiseChangedEventInvoke, - IsInvokeCollectionChanged = true, - IsInvokePropertyChanged = true - }); - } - else - { - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldViews.ToArray(), args.OldStartingIndex) - { - Collection = this, - Invoker = raiseChangedEventInvoke, - IsInvokeCollectionChanged = true, - IsInvokePropertyChanged = true - }); - } - break; - case NotifyCollectionChangedAction.Reset: - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Reset) - { - Collection = this, - Invoker = raiseChangedEventInvoke, - IsInvokeCollectionChanged = true, - IsInvokePropertyChanged = true - }); - break; - case NotifyCollectionChangedAction.Replace: - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Replace, args.NewItem.View, args.OldItem.View, args.NewStartingIndex) - { - Collection = this, - Invoker = raiseChangedEventInvoke, - IsInvokeCollectionChanged = true, - IsInvokePropertyChanged = false - }); - break; - case NotifyCollectionChangedAction.Move: - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Move, args.NewItem.View, args.NewStartingIndex, args.OldStartingIndex) - { - Collection = this, - Invoker = raiseChangedEventInvoke, - IsInvokeCollectionChanged = true, - IsInvokePropertyChanged = false - }); - break; - } - } - - static void RaiseChangedEvent(NotifyCollectionChangedEventArgs e) - { - var e2 = (CollectionEventDispatcherEventArgs)e; - var self = (NotifyCollectionChangedSynchronizedViewList)e2.Collection; - - if (e2.IsInvokeCollectionChanged) - { - self.CollectionChanged?.Invoke(self, e); - } - if (e2.IsInvokePropertyChanged) - { - self.PropertyChanged?.Invoke(self, CountPropertyChangedEventArgs); - } - } - - // IList, IList implementation - - TView IList.this[int index] - { - get => ((IReadOnlyList)this)[index]; - set - { - if (converter == null || parent is not IWritableSynchronizedView writableView) - { - throw new NotSupportedException("This CollectionView does not support set. If base type is ObservableList, you can use ToWritableSynchronizedView and ToWritableNotifyCollectionChanged."); - } - else - { - var originalIndex = listView.GetAlternateIndex(index); - var (originalValue, _) = writableView.GetAt(originalIndex); - - // update view - writableView.SetViewAt(originalIndex, value); - listView[index] = value; - - var setValue = true; - var newOriginal = converter(value, originalValue, ref setValue); - - if (setValue) - { - writableView.SetToSourceCollection(originalIndex, newOriginal); - } - } - } - } - - object? IList.this[int index] - { - get - { - return this[index]; - } - set => ((IList)this)[index] = (TView)value!; - } - - static bool IsCompatibleObject(object? value) - { - return value is TView || value == null && default(TView) == null; - } - - public bool IsReadOnly => true; - - public bool IsFixedSize => false; - - public bool IsSynchronized => true; - - public object SyncRoot => gate; - - public void Add(TView item) - { - if (converter == null || parent is not IWritableSynchronizedView writableView) - { - throw new NotSupportedException("This CollectionView does not support Add. If base type is ObservableList, you can use ToWritableSynchronizedView and ToWritableNotifyCollectionChanged."); - } - else - { - var setValue = false; - var newOriginal = converter(item, default!, ref setValue); - - // always add - writableView.AddToSourceCollection(newOriginal); - } - } - - public int Add(object? value) - { - Add((TView)value!); - return -1; // itself does not add in this collection - } - - public void Clear() - { - throw new NotSupportedException(); - } - - public bool Contains(TView item) - { - lock (gate) - { - foreach (var listItem in listView) - { - if (EqualityComparer.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.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(); - } -} - -internal class NonFilteredNotifyCollectionChangedSynchronizedViewList : - NonFilteredSynchronizedViewList, - INotifyCollectionChangedSynchronizedViewList, - IList, IList -{ - static readonly PropertyChangedEventArgs CountPropertyChangedEventArgs = new("Count"); - static readonly Action raiseChangedEventInvoke = RaiseChangedEvent; - - readonly ICollectionEventDispatcher eventDispatcher; - readonly WritableViewChangedEventHandler? converter; // null = readonly - - public event NotifyCollectionChangedEventHandler? CollectionChanged; - public event PropertyChangedEventHandler? PropertyChanged; - - protected override bool IsSupportRangeFeature => false; // WPF, Avalonia etc does not support range notification - - public NonFilteredNotifyCollectionChangedSynchronizedViewList(ISynchronizedView parent, ICollectionEventDispatcher? eventDispatcher) - : base(parent) - { - this.eventDispatcher = eventDispatcher ?? InlineCollectionEventDispatcher.Instance; - } - - public NonFilteredNotifyCollectionChangedSynchronizedViewList(ISynchronizedView parent, ICollectionEventDispatcher? eventDispatcher, WritableViewChangedEventHandler? converter) - : base(parent) - { - this.eventDispatcher = eventDispatcher ?? InlineCollectionEventDispatcher.Instance; - this.converter = converter; - } - - protected override void OnCollectionChanged(in SynchronizedViewChangedEventArgs 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, - Invoker = raiseChangedEventInvoke, - IsInvokeCollectionChanged = true, - IsInvokePropertyChanged = true - }); - } - else - { - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Add, args.NewViews.ToArray(), args.NewStartingIndex) - { - Collection = this, - Invoker = raiseChangedEventInvoke, - IsInvokeCollectionChanged = true, - IsInvokePropertyChanged = true - }); - } - break; - case NotifyCollectionChangedAction.Remove: - if (args.IsSingleItem) - { - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldItem.View, args.OldStartingIndex) - { - Collection = this, - Invoker = raiseChangedEventInvoke, - IsInvokeCollectionChanged = true, - IsInvokePropertyChanged = true - }); - } - else - { - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Remove, args.OldViews.ToArray(), args.OldStartingIndex) - { - Collection = this, - Invoker = raiseChangedEventInvoke, - IsInvokeCollectionChanged = true, - IsInvokePropertyChanged = true - }); - } - break; - case NotifyCollectionChangedAction.Reset: - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Reset) - { - Collection = this, - Invoker = raiseChangedEventInvoke, - IsInvokeCollectionChanged = true, - IsInvokePropertyChanged = true - }); - break; - case NotifyCollectionChangedAction.Replace: - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Replace, args.NewItem.View, args.OldItem.View, args.NewStartingIndex) - { - Collection = this, - Invoker = raiseChangedEventInvoke, - IsInvokeCollectionChanged = true, - IsInvokePropertyChanged = false - }); - break; - case NotifyCollectionChangedAction.Move: - eventDispatcher.Post(new CollectionEventDispatcherEventArgs(NotifyCollectionChangedAction.Move, args.NewItem.View, args.NewStartingIndex, args.OldStartingIndex) - { - Collection = this, - Invoker = raiseChangedEventInvoke, - IsInvokeCollectionChanged = true, - IsInvokePropertyChanged = false - }); - break; - } - } - - static void RaiseChangedEvent(NotifyCollectionChangedEventArgs e) - { - var e2 = (CollectionEventDispatcherEventArgs)e; - var self = (NonFilteredNotifyCollectionChangedSynchronizedViewList)e2.Collection; - - if (e2.IsInvokeCollectionChanged) - { - self.CollectionChanged?.Invoke(self, e); - } - if (e2.IsInvokePropertyChanged) - { - self.PropertyChanged?.Invoke(self, CountPropertyChangedEventArgs); - } - } - - // IList, IList implementation - - TView IList.this[int index] - { - get => ((IReadOnlyList)this)[index]; set { if (converter == null || parent is not IWritableSynchronizedView writableView) @@ -1008,29 +785,29 @@ internal class NonFilteredNotifyCollectionChangedSynchronizedViewList } } - object? IList.this[int index] + public override int Count { get { - return this[index]; + lock (gate) + { + return listView.Count; + } } - set => ((IList)this)[index] = (TView)value!; } - static bool IsCompatibleObject(object? value) + public override IEnumerator GetEnumerator() { - return value is TView || value == null && default(TView) == null; + lock (gate) + { + foreach (var item in listView) + { + yield return item; + } + } } - public bool IsReadOnly => true; - - public bool IsFixedSize => false; - - public bool IsSynchronized => true; - - public object SyncRoot => gate; - - public void Add(TView item) + public override void Add(TView item) { if (converter == null || parent is not IWritableSynchronizedView writableView) { @@ -1046,18 +823,7 @@ internal class NonFilteredNotifyCollectionChangedSynchronizedViewList } } - public int Add(object? value) - { - Add((TView)value!); - return -1; // itself does not add in this collection - } - - public void Clear() - { - throw new NotSupportedException(); - } - - public bool Contains(TView item) + public override bool Contains(TView item) { lock (gate) { @@ -1072,26 +838,7 @@ internal class NonFilteredNotifyCollectionChangedSynchronizedViewList 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) + public override int IndexOf(TView item) { lock (gate) { @@ -1108,37 +855,9 @@ internal class NonFilteredNotifyCollectionChangedSynchronizedViewList return -1; } - public int IndexOf(object? item) + public override void Dispose() { - 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(); + parent.ViewChanged -= Parent_ViewChanged; + parent.Dispose(); // Dispose parent } } \ No newline at end of file