diff --git a/Runtime/Collections/BindingCollection.cs b/Runtime/Collections/BindingCollection.cs index 0d24e83..d2dac15 100644 --- a/Runtime/Collections/BindingCollection.cs +++ b/Runtime/Collections/BindingCollection.cs @@ -31,7 +31,10 @@ public class BindingCollection : ListBase, IList, IList, IReadOnlyList< #region Constructors - public BindingCollection() => Initialize(); + public BindingCollection() + { + Initialize(); + } public BindingCollection(IEnumerable collection) : base(collection) { @@ -40,6 +43,7 @@ public BindingCollection(IEnumerable collection) : base(collection) public BindingCollection(int capacity) : base(capacity) { + Initialize(); } private void Initialize() @@ -51,7 +55,7 @@ private void Initialize() raiseItemChangedEvents = true; // Loop thru the items already in the collection and hook their change notification. - foreach (T item in items) + foreach (T item in _list) { HookPropertyChanged(item); } @@ -67,7 +71,7 @@ protected override void ClearItems() { if (raiseItemChangedEvents) { - foreach (T item in items) + foreach (T item in _list) { UnhookPropertyChanged(item); } diff --git a/Runtime/Collections/BindingSet.cs b/Runtime/Collections/BindingSet.cs index 6a57c71..6e26183 100644 --- a/Runtime/Collections/BindingSet.cs +++ b/Runtime/Collections/BindingSet.cs @@ -2,40 +2,80 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; +using System.Linq; namespace CiccioSoft.Collections { [DebuggerTypeProxy(typeof(ICollectionDebugView<>))] [DebuggerDisplay("Count = {Count}")] [Serializable] - public class BindingSet : SetBase, ICollection, ISet, IReadOnlyCollection, IReadOnlySet /*, IBindingList, IRaiseItemChangedEvents*/ + public class BindingSet : SetBase, ICollection, ISet, IReadOnlyCollection, IReadOnlySet, IBindingList, IRaiseItemChangedEvents { + private bool raiseItemChangedEvents; // Do not rename (binary serialization) + + [NonSerialized] + private PropertyDescriptorCollection? _itemTypeProperties; + + [NonSerialized] + private PropertyChangedEventHandler? _propertyChangedEventHandler; + + [NonSerialized] + private ListChangedEventHandler? _onListChanged; + + [NonSerialized] + private int _lastChangeIndex = -1; + + #region Constructors public BindingSet() { + Initialize(); } public BindingSet(IEqualityComparer? comparer) : base(comparer) { + Initialize(); } public BindingSet(int capacity) : base(capacity) { + Initialize(); } public BindingSet(IEnumerable collection) : base(collection) { + Initialize(); } public BindingSet(IEnumerable collection, IEqualityComparer? comparer) : base(collection, comparer) { + Initialize(); } public BindingSet(int capacity, IEqualityComparer? comparer) : base(capacity, comparer) { + Initialize(); + } + + private void Initialize() + { + // Check for INotifyPropertyChanged + if (typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(T))) + { + // Supports INotifyPropertyChanged + raiseItemChangedEvents = true; + + // Loop thru the items already in the collection and hook their change notification. + foreach (T item in _set) + { + HookPropertyChanged(item); + } + } } #endregion @@ -45,37 +85,493 @@ public BindingSet(int capacity, IEqualityComparer? comparer) : base(capacity, protected override bool AddItem(T item) { - return base.AddItem(item); + if (_set.Contains(item)) + { + return false; + } + + _set.Add(item); + + int index = _set.ToList().IndexOf(item); + if (raiseItemChangedEvents) + { + HookPropertyChanged(item); + } + FireListChanged(ListChangedType.ItemAdded, index); + + return true; } protected override void ClearItems() { - base.ClearItems(); + if (_set.Count == 0) + { + return; + } + + if (raiseItemChangedEvents) + { + foreach (T item in _set) + { + UnhookPropertyChanged(item); + } + } + + _set.Clear(); + + FireListChanged(ListChangedType.Reset, -1); } protected override void ExceptWithItems(IEnumerable other) { - base.ExceptWithItems(other); + var copy = new HashSet(_set, _set.Comparer); + copy.ExceptWith(other); + if (copy.Count == _set.Count) + { + return; + } + var removed = _set.Where(i => !copy.Contains(i)).ToList(); + + if (raiseItemChangedEvents) + { + foreach (T item in removed) + { + UnhookPropertyChanged(item); + } + } + + _set = copy; + + FireListChanged(ListChangedType.Reset, -1); } protected override void IntersectWithItems(IEnumerable other) { - base.IntersectWithItems(other); + var copy = new HashSet(_set, _set.Comparer); + copy.IntersectWith(other); + if (copy.Count == _set.Count) + { + return; + } + var removed = _set.Where(i => !copy.Contains(i)).ToList(); + + if (raiseItemChangedEvents) + { + foreach (T item in removed) + { + UnhookPropertyChanged(item); + } + } + + _set = copy; + + FireListChanged(ListChangedType.Reset, -1); } protected override bool RemoveItem(T item) { - return base.RemoveItem(item); + if (!_set.Contains(item)) + { + return false; + } + + if (raiseItemChangedEvents) + { + UnhookPropertyChanged(item); + } + int index = _set.ToList().IndexOf(item); + + _set.Remove(item); + + FireListChanged(ListChangedType.ItemDeleted, index); + + return true; } protected override void SymmetricExceptWithItems(IEnumerable other) { - base.SymmetricExceptWithItems(other); + var copy = new HashSet(_set, _set.Comparer); + copy.SymmetricExceptWith(other); + var removed = _set.Where(i => !copy.Contains(i)).ToList(); + var added = copy.Where(i => !_set.Contains(i)).ToList(); + + if (removed.Count == 0 + && added.Count == 0) + { + return; + } + + if (raiseItemChangedEvents) + { + foreach (T item in added) + { + HookPropertyChanged(item); + } + foreach (T item in removed) + { + UnhookPropertyChanged(item); + } + } + + _set = copy; + + FireListChanged(ListChangedType.Reset, -1); } protected override void UnionWithItems(IEnumerable other) { - base.UnionWithItems(other); + var copy = new HashSet(_set, _set.Comparer); + copy.UnionWith(other); + if (copy.Count == _set.Count) + { + return; + } + var added = copy.Where(i => !_set.Contains(i)).ToList(); + + if (raiseItemChangedEvents) + { + foreach (T item in added) + { + HookPropertyChanged(item); + } + } + + _set = copy; + + FireListChanged(ListChangedType.Reset, -1); + } + + #endregion + + + #region ListChanged event + + /// + /// Event that reports changes to the list or to items in the list. + /// + public event ListChangedEventHandler ListChanged + { + add => _onListChanged += value; + remove => _onListChanged -= value; + } + + // Private helper method + private void FireListChanged(ListChangedType type, int index) + { + OnListChanged(new ListChangedEventArgs(type, index)); + } + + /// + /// Raises the ListChanged event. + /// + private void OnListChanged(ListChangedEventArgs e) => _onListChanged?.Invoke(this, e); + + #endregion + + + #region Property Change Support + + private void HookPropertyChanged(T item) + { + // Note: inpc may be null if item is null, so always check. + if (item is INotifyPropertyChanged inpc) + { + _propertyChangedEventHandler ??= new PropertyChangedEventHandler(Child_PropertyChanged); + inpc.PropertyChanged += _propertyChangedEventHandler; + } + } + + private void UnhookPropertyChanged(T item) + { + // Note: inpc may be null if item is null, so always check. + if (item is INotifyPropertyChanged inpc && _propertyChangedEventHandler != null) + { + inpc.PropertyChanged -= _propertyChangedEventHandler; + } + } + + private void Child_PropertyChanged(object? sender, PropertyChangedEventArgs? e) + { + if (sender == null || e == null || string.IsNullOrEmpty(e.PropertyName)) + { + // Fire reset event (per INotifyPropertyChanged spec) + FireListChanged(ListChangedType.Reset, -1); + } + else + { + // The change event is broken should someone pass an item to us that is not + // of type T. Still, if they do so, detect it and ignore. It is an incorrect + // and rare enough occurrence that we do not want to slow the mainline path + // with "is" checks. + T item; + + try + { + item = (T)sender; + } + catch (InvalidCastException) + { + // Fire reset event + FireListChanged(ListChangedType.Reset, -1); + return; + } + + // Find the position of the item. This should never be -1. If it is, + // somehow the item has been removed from our list without our knowledge. + int pos = _lastChangeIndex; + + if (pos < 0 || pos >= Count || !_set.ToList()[pos]!.Equals(item)) + { + pos = _set.ToList().IndexOf(item); + _lastChangeIndex = pos; + } + + if (pos == -1) + { + // The item was removed from the list but we still get change notifications or + // the sender is invalid and was never added to the list. + UnhookPropertyChanged(item); + // Fire reset event + FireListChanged(ListChangedType.Reset, -1); + } + else + { + // Get the property descriptor + if (null == _itemTypeProperties) + { + // Get Shape + _itemTypeProperties = TypeDescriptor.GetProperties(typeof(T)); + Debug.Assert(_itemTypeProperties != null); + } + + PropertyDescriptor? pd = _itemTypeProperties.Find(e.PropertyName, true); + + // Create event args. If there was no matching property descriptor, + // we raise the list changed anyway. + ListChangedEventArgs args = new ListChangedEventArgs(ListChangedType.ItemChanged, pos, pd); + + // Fire the ItemChanged event + OnListChanged(args); + } + } + } + + #endregion + + + #region IBindingList interface + + public T AddNew() => throw new NotSupportedException(); + + object? IBindingList.AddNew() => throw new NotSupportedException(); + + public bool AllowNew => false; + + bool IBindingList.AllowNew => false; + + public bool AllowEdit => true; + + bool IBindingList.AllowEdit => AllowEdit; + + public bool AllowRemove => true; + + bool IBindingList.AllowRemove => AllowRemove; + + bool IBindingList.SupportsChangeNotification => true; + + bool IBindingList.SupportsSearching => false; + + bool IBindingList.SupportsSorting => false; + + bool IBindingList.IsSorted => false; + + PropertyDescriptor? IBindingList.SortProperty => null; + + ListSortDirection IBindingList.SortDirection => ListSortDirection.Ascending; + + void IBindingList.ApplySort(PropertyDescriptor prop, ListSortDirection direction) => throw new NotSupportedException(); + + void IBindingList.RemoveSort() => throw new NotSupportedException(); + + int IBindingList.Find(PropertyDescriptor prop, object key) => throw new NotSupportedException(); + + void IBindingList.AddIndex(PropertyDescriptor prop) + { + // Not supported + } + + void IBindingList.RemoveIndex(PropertyDescriptor prop) + { + // Not supported + } + + #endregion + + + #region IRaiseItemChangedEvents interface + + /// + /// Returns false to indicate that BindingList<T> does NOT raise ListChanged events + /// of type ItemChanged as a result of property changes on individual list items + /// unless those items support INotifyPropertyChanged. + /// + bool IRaiseItemChangedEvents.RaisesItemChangedEvents => raiseItemChangedEvents; + + #endregion + + + #region IList ICollection + + bool ICollection.IsSynchronized => false; + + object ICollection.SyncRoot => this; + + void ICollection.CopyTo(Array array, int index) + { + if (array == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (array.Rank != 1) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); + } + + if (array.GetLowerBound(0) != 0) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound); + } + + if (index < 0) + { + ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); + } + + if (array.Length - index < Count) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + } + + if (array is T[] tArray) + { + _set.CopyTo(tArray, index); + } + else + { + // + // Catch the obvious case assignment will fail. + // We can't find all possible problems by doing the check though. + // For example, if the element type of the Array is derived from T, + // we can't figure out if we can successfully copy the element beforehand. + // + Type targetType = array.GetType().GetElementType()!; + Type sourceType = typeof(T); + if (!(targetType.IsAssignableFrom(sourceType) || sourceType.IsAssignableFrom(targetType))) + { + ThrowHelper.ThrowArgumentException_Argument_IncompatibleArrayType(); + } + + // + // We can't cast array of value type to object[], so we don't support + // widening of primitive types here. + // + object?[]? objects = array as object[]; + if (objects == null) + { + ThrowHelper.ThrowArgumentException_Argument_IncompatibleArrayType(); + } + + //int count = items.Count; + try + { + //for (int i = 0; i < count; i++) + //{ + // objects[index++] = items[i]; + //} + foreach (var item in (IEnumerable)_set) + { + objects[index++] = item; + } + } + catch (ArrayTypeMismatchException) + { + ThrowHelper.ThrowArgumentException_Argument_IncompatibleArrayType(); + } + } + } + + object? IList.this[int index] + { + get => _set.ToList()[index]; + set => throw new NotSupportedException("Mutating a value collection derived from a hashset is not allowed."); + } + + bool IList.IsReadOnly => false; + + bool IList.IsFixedSize => false; + + int IList.Add(object? value) + { + ThrowHelper.IfNullAndNullsAreIllegalThenThrow(value, ExceptionArgument.value); + + T? item = default; + + try + { + item = (T)value!; + } + catch (InvalidCastException) + { + ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(T)); + } + + Add(item); + + return _set.ToList().IndexOf(item); + } + + bool IList.Contains(object? value) + { + if (IsCompatibleObject(value)) + { + return Contains((T)value!); + } + return false; + } + + int IList.IndexOf(object? value) + { + if (IsCompatibleObject(value)) + { + return _set.ToList().IndexOf((T)value!); + } + return -1; + } + + void IList.Insert(int index, object? value) + { + throw new NotSupportedException("Mutating a value collection derived from a hashset is not allowed."); + } + + void IList.Remove(object? value) + { + if (IsCompatibleObject(value)) + { + Remove((T)value!); + } + } + + void IList.RemoveAt(int index) + { + throw new NotSupportedException("Mutating a value collection derived from a hashset is not allowed."); + } + + private static bool IsCompatibleObject(object? value) + { + // Non-null values are fine. Only accept nulls if T is a class or Nullable. + // Note that default(T) is not equal to null for value types except when T is Nullable. + return (value is T) || (value == null && default(T) == null); } #endregion diff --git a/Runtime/Collections/CiccioList.cs b/Runtime/Collections/CiccioList.cs index f4663d8..8a9a574 100644 --- a/Runtime/Collections/CiccioList.cs +++ b/Runtime/Collections/CiccioList.cs @@ -59,7 +59,7 @@ private void Initialize() raiseItemChangedEvents = true; // Loop thru the items already in the collection and hook their change notification. - foreach (T item in items) + foreach (T item in _list) { HookPropertyChanged(item); } @@ -77,7 +77,7 @@ protected override void ClearItems() if (raiseItemChangedEvents) { - foreach (T item in items) + foreach (T item in _list) { UnhookPropertyChanged(item); } diff --git a/Runtime/Collections/CiccioSet.cs b/Runtime/Collections/CiccioSet.cs index 1504e4c..669c5ee 100644 --- a/Runtime/Collections/CiccioSet.cs +++ b/Runtime/Collections/CiccioSet.cs @@ -15,37 +15,75 @@ namespace CiccioSoft.Collections [DebuggerTypeProxy(typeof(ICollectionDebugView<>))] [DebuggerDisplay("Count = {Count}")] [Serializable] - public class CiccioSet : SetBase, ICollection, ISet, IReadOnlyCollection, IReadOnlySet, INotifyCollectionChanged, INotifyPropertyChanged + public class CiccioSet : SetBase, ICollection, ISet, IReadOnlyCollection, IReadOnlySet, INotifyCollectionChanged, INotifyPropertyChanged, IBindingList, IRaiseItemChangedEvents { private SimpleMonitor? _monitor; // Lazily allocated only when a subclass calls BlockReentrancy() or during serialization. Do not rename (binary serialization) [NonSerialized] private int _blockReentrancyCount; + + private bool raiseItemChangedEvents; // Do not rename (binary serialization) + + [NonSerialized] + private PropertyDescriptorCollection? _itemTypeProperties; + + [NonSerialized] + private PropertyChangedEventHandler? _propertyChangedEventHandler; + + [NonSerialized] + private ListChangedEventHandler? _onListChanged; + + [NonSerialized] + private int _lastChangeIndex = -1; + + #region Constructors public CiccioSet() { + Initialize(); } public CiccioSet(IEqualityComparer? comparer) : base(comparer) { + Initialize(); } public CiccioSet(int capacity) : base(capacity) { + Initialize(); } public CiccioSet(IEnumerable collection) : base(collection) { + Initialize(); } public CiccioSet(IEnumerable collection, IEqualityComparer? comparer) : base(collection, comparer) { + Initialize(); } public CiccioSet(int capacity, IEqualityComparer? comparer) : base(capacity, comparer) { + Initialize(); + } + + private void Initialize() + { + // Check for INotifyPropertyChanged + if (typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(T))) + { + // Supports INotifyPropertyChanged + raiseItemChangedEvents = true; + + // Loop thru the items already in the collection and hook their change notification. + foreach (T item in _set) + { + HookPropertyChanged(item); + } + } } #endregion @@ -60,12 +98,18 @@ protected override bool AddItem(T item) return false; } - //OnCountPropertyChanging(); + CheckReentrancy(); _set.Add(item); - OnCollectionChanged(NotifyCollectionChangedAction.Add, item); + int index = _set.ToList().IndexOf(item); + if (raiseItemChangedEvents) + { + HookPropertyChanged(item); + } + FireListChanged(ListChangedType.ItemAdded, index); + OnCollectionChanged(NotifyCollectionChangedAction.Add, item); OnCountPropertyChanged(); return true; @@ -78,58 +122,78 @@ protected override void ClearItems() return; } - //OnCountPropertyChanging(); + if (raiseItemChangedEvents) + { + foreach (T item in _set) + { + UnhookPropertyChanged(item); + } + } + CheckReentrancy(); var removed = this.ToList(); _set.Clear(); - OnCollectionChanged(ObservableHashSetSingletons.NoItems, removed); + FireListChanged(ListChangedType.Reset, -1); + OnCollectionChanged(ObservableHashSetSingletons.NoItems, removed); OnCountPropertyChanged(); } protected override void ExceptWithItems(IEnumerable other) { var copy = new HashSet(_set, _set.Comparer); - copy.ExceptWith(other); - if (copy.Count == _set.Count) { return; } - var removed = _set.Where(i => !copy.Contains(i)).ToList(); - //OnCountPropertyChanging(); + if (raiseItemChangedEvents) + { + foreach (T item in removed) + { + UnhookPropertyChanged(item); + } + } + + CheckReentrancy(); _set = copy; - OnCollectionChanged(ObservableHashSetSingletons.NoItems, removed); + FireListChanged(ListChangedType.Reset, -1); + OnCollectionChanged(ObservableHashSetSingletons.NoItems, removed); OnCountPropertyChanged(); } protected override void IntersectWithItems(IEnumerable other) { var copy = new HashSet(_set, _set.Comparer); - copy.IntersectWith(other); - if (copy.Count == _set.Count) { return; } - var removed = _set.Where(i => !copy.Contains(i)).ToList(); - //OnCountPropertyChanging(); + if (raiseItemChangedEvents) + { + foreach (T item in removed) + { + UnhookPropertyChanged(item); + } + } + + CheckReentrancy(); _set = copy; - OnCollectionChanged(ObservableHashSetSingletons.NoItems, removed); + FireListChanged(ListChangedType.Reset, -1); + OnCollectionChanged(ObservableHashSetSingletons.NoItems, removed); OnCountPropertyChanged(); } @@ -139,13 +203,19 @@ protected override bool RemoveItem(T item) { return false; } + if (raiseItemChangedEvents) + { + UnhookPropertyChanged(item); + } + int index = _set.ToList().IndexOf(item); - //OnCountPropertyChanging(); + CheckReentrancy(); _set.Remove(item); - OnCollectionChanged(NotifyCollectionChangedAction.Remove, item); + FireListChanged(ListChangedType.ItemDeleted, index); + OnCollectionChanged(NotifyCollectionChangedAction.Remove, item); OnCountPropertyChanged(); return true; @@ -154,9 +224,7 @@ protected override bool RemoveItem(T item) protected override void SymmetricExceptWithItems(IEnumerable other) { var copy = new HashSet(_set, _set.Comparer); - copy.SymmetricExceptWith(other); - var removed = _set.Where(i => !copy.Contains(i)).ToList(); var added = copy.Where(i => !_set.Contains(i)).ToList(); @@ -166,34 +234,53 @@ protected override void SymmetricExceptWithItems(IEnumerable other) return; } - //OnCountPropertyChanging(); + if (raiseItemChangedEvents) + { + foreach (T item in added) + { + HookPropertyChanged(item); + } + foreach (T item in removed) + { + UnhookPropertyChanged(item); + } + } + + CheckReentrancy(); _set = copy; - OnCollectionChanged(added, removed); + FireListChanged(ListChangedType.Reset, -1); + OnCollectionChanged(added, removed); OnCountPropertyChanged(); } protected override void UnionWithItems(IEnumerable other) { var copy = new HashSet(_set, _set.Comparer); - copy.UnionWith(other); - if (copy.Count == _set.Count) { return; } - var added = copy.Where(i => !_set.Contains(i)).ToList(); - //OnCountPropertyChanging(); + if (raiseItemChangedEvents) + { + foreach (T item in added) + { + HookPropertyChanged(item); + } + } + + CheckReentrancy(); _set = copy; - OnCollectionChanged(added, ObservableHashSetSingletons.NoItems); + FireListChanged(ListChangedType.Reset, -1); + OnCollectionChanged(added, ObservableHashSetSingletons.NoItems); OnCountPropertyChanged(); } @@ -206,16 +293,7 @@ protected override void UnionWithItems(IEnumerable other) /// PropertyChanged event (per ). /// [field: NonSerialized] - private event PropertyChangedEventHandler? PropertyChanged; - - /// - /// PropertyChanged event (per ). - /// - event PropertyChangedEventHandler? INotifyPropertyChanged.PropertyChanged - { - add => PropertyChanged += value; - remove => PropertyChanged -= value; - } + public event PropertyChangedEventHandler? PropertyChanged; /// /// Raises a PropertyChanged event (per ). @@ -332,5 +410,343 @@ public SimpleMonitor(CiccioSet collection) } #endregion + + + + + + + #region ListChanged event + + /// + /// Event that reports changes to the list or to items in the list. + /// + public event ListChangedEventHandler ListChanged + { + add => _onListChanged += value; + remove => _onListChanged -= value; + } + + // Private helper method + private void FireListChanged(ListChangedType type, int index) + { + OnListChanged(new ListChangedEventArgs(type, index)); + } + + /// + /// Raises the ListChanged event. + /// + private void OnListChanged(ListChangedEventArgs e) => _onListChanged?.Invoke(this, e); + + #endregion + + + #region Property Change Support + + private void HookPropertyChanged(T item) + { + // Note: inpc may be null if item is null, so always check. + if (item is INotifyPropertyChanged inpc) + { + _propertyChangedEventHandler ??= new PropertyChangedEventHandler(Child_PropertyChanged); + inpc.PropertyChanged += _propertyChangedEventHandler; + } + } + + private void UnhookPropertyChanged(T item) + { + // Note: inpc may be null if item is null, so always check. + if (item is INotifyPropertyChanged inpc && _propertyChangedEventHandler != null) + { + inpc.PropertyChanged -= _propertyChangedEventHandler; + } + } + + private void Child_PropertyChanged(object? sender, PropertyChangedEventArgs? e) + { + if (sender == null || e == null || string.IsNullOrEmpty(e.PropertyName)) + { + // Fire reset event (per INotifyPropertyChanged spec) + FireListChanged(ListChangedType.Reset, -1); + } + else + { + // The change event is broken should someone pass an item to us that is not + // of type T. Still, if they do so, detect it and ignore. It is an incorrect + // and rare enough occurrence that we do not want to slow the mainline path + // with "is" checks. + T item; + + try + { + item = (T)sender; + } + catch (InvalidCastException) + { + // Fire reset event + FireListChanged(ListChangedType.Reset, -1); + return; + } + + // Find the position of the item. This should never be -1. If it is, + // somehow the item has been removed from our list without our knowledge. + int pos = _lastChangeIndex; + + if (pos < 0 || pos >= Count || !_set.ToList()[pos]!.Equals(item)) + { + pos = _set.ToList().IndexOf(item); + _lastChangeIndex = pos; + } + + if (pos == -1) + { + // The item was removed from the list but we still get change notifications or + // the sender is invalid and was never added to the list. + UnhookPropertyChanged(item); + // Fire reset event + FireListChanged(ListChangedType.Reset, -1); + } + else + { + // Get the property descriptor + if (null == _itemTypeProperties) + { + // Get Shape + _itemTypeProperties = TypeDescriptor.GetProperties(typeof(T)); + Debug.Assert(_itemTypeProperties != null); + } + + PropertyDescriptor? pd = _itemTypeProperties.Find(e.PropertyName, true); + + // Create event args. If there was no matching property descriptor, + // we raise the list changed anyway. + ListChangedEventArgs args = new ListChangedEventArgs(ListChangedType.ItemChanged, pos, pd); + + // Fire the ItemChanged event + OnListChanged(args); + } + } + } + + #endregion + + + #region IBindingList interface + + public T AddNew() => throw new NotSupportedException(); + + object? IBindingList.AddNew() => throw new NotSupportedException(); + + public bool AllowNew => false; + + bool IBindingList.AllowNew => false; + + public bool AllowEdit => true; + + bool IBindingList.AllowEdit => AllowEdit; + + public bool AllowRemove => true; + + bool IBindingList.AllowRemove => AllowRemove; + + bool IBindingList.SupportsChangeNotification => true; + + bool IBindingList.SupportsSearching => false; + + bool IBindingList.SupportsSorting => false; + + bool IBindingList.IsSorted => false; + + PropertyDescriptor? IBindingList.SortProperty => null; + + ListSortDirection IBindingList.SortDirection => ListSortDirection.Ascending; + + void IBindingList.ApplySort(PropertyDescriptor prop, ListSortDirection direction) => throw new NotSupportedException(); + + void IBindingList.RemoveSort() => throw new NotSupportedException(); + + int IBindingList.Find(PropertyDescriptor prop, object key) => throw new NotSupportedException(); + + void IBindingList.AddIndex(PropertyDescriptor prop) + { + // Not supported + } + + void IBindingList.RemoveIndex(PropertyDescriptor prop) + { + // Not supported + } + + #endregion + + + #region IRaiseItemChangedEvents interface + + /// + /// Returns false to indicate that BindingList<T> does NOT raise ListChanged events + /// of type ItemChanged as a result of property changes on individual list items + /// unless those items support INotifyPropertyChanged. + /// + bool IRaiseItemChangedEvents.RaisesItemChangedEvents => raiseItemChangedEvents; + + #endregion + + + #region IList ICollection + + bool ICollection.IsSynchronized => false; + + object ICollection.SyncRoot => this; + + void ICollection.CopyTo(Array array, int index) + { + if (array == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (array.Rank != 1) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); + } + + if (array.GetLowerBound(0) != 0) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound); + } + + if (index < 0) + { + ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); + } + + if (array.Length - index < Count) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + } + + if (array is T[] tArray) + { + _set.CopyTo(tArray, index); + } + else + { + // + // Catch the obvious case assignment will fail. + // We can't find all possible problems by doing the check though. + // For example, if the element type of the Array is derived from T, + // we can't figure out if we can successfully copy the element beforehand. + // + Type targetType = array.GetType().GetElementType()!; + Type sourceType = typeof(T); + if (!(targetType.IsAssignableFrom(sourceType) || sourceType.IsAssignableFrom(targetType))) + { + ThrowHelper.ThrowArgumentException_Argument_IncompatibleArrayType(); + } + + // + // We can't cast array of value type to object[], so we don't support + // widening of primitive types here. + // + object?[]? objects = array as object[]; + if (objects == null) + { + ThrowHelper.ThrowArgumentException_Argument_IncompatibleArrayType(); + } + + //int count = items.Count; + try + { + //for (int i = 0; i < count; i++) + //{ + // objects[index++] = items[i]; + //} + foreach (var item in (IEnumerable)_set) + { + objects[index++] = item; + } + } + catch (ArrayTypeMismatchException) + { + ThrowHelper.ThrowArgumentException_Argument_IncompatibleArrayType(); + } + } + } + + object? IList.this[int index] + { + get => _set.ToList()[index]; + set => throw new NotSupportedException("Mutating a value collection derived from a hashset is not allowed."); + } + + bool IList.IsReadOnly => false; + + bool IList.IsFixedSize => false; + + int IList.Add(object? value) + { + ThrowHelper.IfNullAndNullsAreIllegalThenThrow(value, ExceptionArgument.value); + + T? item = default; + + try + { + item = (T)value!; + } + catch (InvalidCastException) + { + ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(T)); + } + + Add(item); + + return _set.ToList().IndexOf(item); + } + + bool IList.Contains(object? value) + { + if (IsCompatibleObject(value)) + { + return Contains((T)value!); + } + return false; + } + + int IList.IndexOf(object? value) + { + if (IsCompatibleObject(value)) + { + return _set.ToList().IndexOf((T)value!); + } + return -1; + } + + void IList.Insert(int index, object? value) + { + throw new NotSupportedException("Mutating a value collection derived from a hashset is not allowed."); + } + + void IList.Remove(object? value) + { + if (IsCompatibleObject(value)) + { + Remove((T)value!); + } + } + + void IList.RemoveAt(int index) + { + throw new NotSupportedException("Mutating a value collection derived from a hashset is not allowed."); + } + + private static bool IsCompatibleObject(object? value) + { + // Non-null values are fine. Only accept nulls if T is a class or Nullable. + // Note that default(T) is not equal to null for value types except when T is Nullable. + return (value is T) || (value == null && default(T) == null); + } + + #endregion + } } diff --git a/Runtime/Collections/Collection.cs b/Runtime/Collections/Collection.cs new file mode 100644 index 0000000..179b4f5 --- /dev/null +++ b/Runtime/Collections/Collection.cs @@ -0,0 +1,367 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace CiccioSoft.Collections +{ + [Serializable] + [DebuggerTypeProxy(typeof(ICollectionDebugView<>))] + [DebuggerDisplay("Count = {Count}")] + [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] + public class Collection : IList, IList, IReadOnlyList + { + private readonly IList items; // Do not rename (binary serialization) + + public Collection() + { + items = new List(); + } + + public Collection(IList list) + { + if (list == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.list); + } + items = list; + } + + public int Count => items.Count; + + protected IList Items => items; + + public T this[int index] + { + get => items[index]; + set + { + if (items.IsReadOnly) + { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + if ((uint)index >= (uint)items.Count) + { + ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException(); + } + + SetItem(index, value); + } + } + + public void Add(T item) + { + if (items.IsReadOnly) + { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + int index = items.Count; + InsertItem(index, item); + } + + public void Clear() + { + if (items.IsReadOnly) + { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + ClearItems(); + } + + public void CopyTo(T[] array, int index) + { + items.CopyTo(array, index); + } + + public bool Contains(T item) + { + return items.Contains(item); + } + + public IEnumerator GetEnumerator() + { + return items.GetEnumerator(); + } + + public int IndexOf(T item) + { + return items.IndexOf(item); + } + + public void Insert(int index, T item) + { + if (items.IsReadOnly) + { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + if ((uint)index > (uint)items.Count) + { + ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessOrEqualException(); + } + + InsertItem(index, item); + } + + public bool Remove(T item) + { + if (items.IsReadOnly) + { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + int index = items.IndexOf(item); + if (index < 0) return false; + RemoveItem(index); + return true; + } + + public void RemoveAt(int index) + { + if (items.IsReadOnly) + { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + if ((uint)index >= (uint)items.Count) + { + ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException(); + } + + RemoveItem(index); + } + + protected virtual void ClearItems() + { + items.Clear(); + } + + protected virtual void InsertItem(int index, T item) + { + items.Insert(index, item); + } + + protected virtual void RemoveItem(int index) + { + items.RemoveAt(index); + } + + protected virtual void SetItem(int index, T item) + { + items[index] = item; + } + + bool ICollection.IsReadOnly => items.IsReadOnly; + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)items).GetEnumerator(); + } + + bool ICollection.IsSynchronized => false; + + object ICollection.SyncRoot => items is ICollection coll ? coll.SyncRoot : this; + + void ICollection.CopyTo(Array array, int index) + { + if (array == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (array.Rank != 1) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); + } + + if (array.GetLowerBound(0) != 0) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound); + } + + if (index < 0) + { + ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); + } + + if (array.Length - index < Count) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + } + + if (array is T[] tArray) + { + items.CopyTo(tArray, index); + } + else + { + // + // Catch the obvious case assignment will fail. + // We can't find all possible problems by doing the check though. + // For example, if the element type of the Array is derived from T, + // we can't figure out if we can successfully copy the element beforehand. + // + Type targetType = array.GetType().GetElementType()!; + Type sourceType = typeof(T); + if (!(targetType.IsAssignableFrom(sourceType) || sourceType.IsAssignableFrom(targetType))) + { + ThrowHelper.ThrowArgumentException_Argument_IncompatibleArrayType(); + } + + // + // We can't cast array of value type to object[], so we don't support + // widening of primitive types here. + // + object?[]? objects = array as object[]; + if (objects == null) + { + ThrowHelper.ThrowArgumentException_Argument_IncompatibleArrayType(); + } + + int count = items.Count; + try + { + for (int i = 0; i < count; i++) + { + objects[index++] = items[i]; + } + } + catch (ArrayTypeMismatchException) + { + ThrowHelper.ThrowArgumentException_Argument_IncompatibleArrayType(); + } + } + } + + object? IList.this[int index] + { + get => items[index]; + set + { + ThrowHelper.IfNullAndNullsAreIllegalThenThrow(value, ExceptionArgument.value); + + T? item = default; + + try + { + item = (T)value!; + } + catch (InvalidCastException) + { + ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(T)); + } + + this[index] = item; + } + } + + bool IList.IsReadOnly => items.IsReadOnly; + + bool IList.IsFixedSize + { + get + { + // There is no IList.IsFixedSize, so we must assume that only + // readonly collections are fixed size, if our internal item + // collection does not implement IList. Note that Array implements + // IList, and therefore T[] and U[] will be fixed-size. + if (items is IList list) + { + return list.IsFixedSize; + } + return items.IsReadOnly; + } + } + + int IList.Add(object? value) + { + if (items.IsReadOnly) + { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + ThrowHelper.IfNullAndNullsAreIllegalThenThrow(value, ExceptionArgument.value); + + T? item = default; + + try + { + item = (T)value!; + } + catch (InvalidCastException) + { + ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(T)); + } + + Add(item); + + return this.Count - 1; + } + + bool IList.Contains(object? value) + { + if (IsCompatibleObject(value)) + { + return Contains((T)value!); + } + return false; + } + + int IList.IndexOf(object? value) + { + if (IsCompatibleObject(value)) + { + return IndexOf((T)value!); + } + return -1; + } + + void IList.Insert(int index, object? value) + { + if (items.IsReadOnly) + { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + ThrowHelper.IfNullAndNullsAreIllegalThenThrow(value, ExceptionArgument.value); + + T? item = default; + + try + { + item = (T)value!; + } + catch (InvalidCastException) + { + ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(T)); + } + + Insert(index, item); + } + + void IList.Remove(object? value) + { + if (items.IsReadOnly) + { + ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); + } + + if (IsCompatibleObject(value)) + { + Remove((T)value!); + } + } + + private static bool IsCompatibleObject(object? value) + { + // Non-null values are fine. Only accept nulls if T is a class or Nullable. + // Note that default(T) is not equal to null for value types except when T is Nullable. + return (value is T) || (value == null && default(T) == null); + } + } +} diff --git a/Runtime/Collections/ListBase.cs b/Runtime/Collections/ListBase.cs index 9d266e3..403ac27 100644 --- a/Runtime/Collections/ListBase.cs +++ b/Runtime/Collections/ListBase.cs @@ -13,23 +13,23 @@ namespace CiccioSoft.Collections [Serializable] public class ListBase : IList, IList, IReadOnlyList { - protected List items; + protected List _list; #region Constructors public ListBase() { - items = new List(); + _list = new List(); } public ListBase(IEnumerable collection) { - items = new List(collection); + _list = new List(collection); } public ListBase(int capacity) { - items = new List(capacity); + _list = new List(capacity); } #endregion @@ -39,15 +39,15 @@ public ListBase(int capacity) public T this[int index] { - get => items[index]; + get => _list[index]; set { - if (((ICollection)items).IsReadOnly) + if (((ICollection)_list).IsReadOnly) { ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); } - if ((uint)index >= (uint)items.Count) + if ((uint)index >= (uint)_list.Count) { ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException(); } @@ -58,7 +58,7 @@ public T this[int index] object? IList.this[int index] { - get => items[index]; + get => _list[index]; set { ThrowHelper.IfNullAndNullsAreIllegalThenThrow(value, ExceptionArgument.value); @@ -78,21 +78,21 @@ public T this[int index] } } - T IReadOnlyList.this[int index] => items[index]; + T IReadOnlyList.this[int index] => _list[index]; - public int Count => items.Count; + public int Count => _list.Count; - public bool IsReadOnly => ((ICollection)items).IsReadOnly; + public bool IsReadOnly => false; - bool IList.IsFixedSize => ((IList)items).IsFixedSize; + bool IList.IsFixedSize => ((IList)_list).IsFixedSize; bool ICollection.IsSynchronized => false; - object ICollection.SyncRoot => ((ICollection)items).SyncRoot; + object ICollection.SyncRoot => ((ICollection)_list).SyncRoot; public void Add(T item) { - int index = items.Count; + int index = _list.Count; InsertItem(index, item); } @@ -121,25 +121,25 @@ public void Clear() ClearItems(); } - public bool Contains(T item) => items.Contains(item); + public bool Contains(T item) => _list.Contains(item); - bool IList.Contains(object? value) => ((IList)items).Contains(value); + bool IList.Contains(object? value) => ((IList)_list).Contains(value); - public void CopyTo(T[] array, int arrayIndex) => items.CopyTo(array, arrayIndex); + public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex); - void ICollection.CopyTo(Array array, int index) => ((ICollection)items).CopyTo(array, index); + void ICollection.CopyTo(Array array, int index) => ((ICollection)_list).CopyTo(array, index); - public IEnumerator GetEnumerator() => items.GetEnumerator(); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)items).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_list).GetEnumerator(); - public int IndexOf(T item) => items.IndexOf(item); + public int IndexOf(T item) => _list.IndexOf(item); - int IList.IndexOf(object? value) => ((IList)items).IndexOf(value); + int IList.IndexOf(object? value) => ((IList)_list).IndexOf(value); public void Insert(int index, T item) { - if ((uint)index > (uint)items.Count) + if ((uint)index > (uint)_list.Count) { ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessOrEqualException(); } @@ -167,7 +167,7 @@ void IList.Insert(int index, object? value) public bool Remove(T item) { - int index = items.IndexOf(item); + int index = _list.IndexOf(item); if (index < 0) return false; RemoveItem(index); return true; @@ -183,7 +183,7 @@ void IList.Remove(object? value) public void RemoveAt(int index) { - if ((uint)index >= (uint)items.Count) + if ((uint)index >= (uint)_list.Count) { ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException(); } @@ -198,8 +198,8 @@ public void RemoveAt(int index) public int Capacity { - get => items.Capacity; - set => items.Capacity = value; + get => _list.Capacity; + set => _list.Capacity = value; } #endregion @@ -209,22 +209,22 @@ public int Capacity protected virtual void ClearItems() { - items.Clear(); + _list.Clear(); } protected virtual void InsertItem(int index, T item) { - items.Insert(index, item); + _list.Insert(index, item); } protected virtual void RemoveItem(int index) { - items.RemoveAt(index); + _list.RemoveAt(index); } protected virtual void SetItem(int index, T item) { - items[index] = item; + _list[index] = item; } #endregion diff --git a/Runtime/Collections/ObservableList.cs b/Runtime/Collections/ObservableList.cs index b32d7f2..bd46cef 100644 --- a/Runtime/Collections/ObservableList.cs +++ b/Runtime/Collections/ObservableList.cs @@ -47,7 +47,9 @@ public ObservableList(int capacity) : base(capacity) protected override void ClearItems() { CheckReentrancy(); + base.ClearItems(); + OnCountPropertyChanged(); OnIndexerPropertyChanged(); OnCollectionReset(); @@ -75,7 +77,6 @@ protected override void RemoveItem(int index) { CheckReentrancy(); T removedItem = this[index]; - base.RemoveItem(index); OnCountPropertyChanged(); diff --git a/Runtime/Collections/ObservableSet.cs b/Runtime/Collections/ObservableSet.cs index c554e40..3c76915 100644 --- a/Runtime/Collections/ObservableSet.cs +++ b/Runtime/Collections/ObservableSet.cs @@ -60,12 +60,11 @@ protected override bool AddItem(T item) return false; } - //OnCountPropertyChanging(); + CheckReentrancy(); _set.Add(item); OnCollectionChanged(NotifyCollectionChangedAction.Add, item); - OnCountPropertyChanged(); return true; @@ -78,58 +77,48 @@ protected override void ClearItems() return; } - //OnCountPropertyChanging(); - + CheckReentrancy(); var removed = this.ToList(); _set.Clear(); OnCollectionChanged(ObservableHashSetSingletons.NoItems, removed); - OnCountPropertyChanged(); } protected override void ExceptWithItems(IEnumerable other) { var copy = new HashSet(_set, _set.Comparer); - copy.ExceptWith(other); - if (copy.Count == _set.Count) { return; } - var removed = _set.Where(i => !copy.Contains(i)).ToList(); - //OnCountPropertyChanging(); + CheckReentrancy(); _set = copy; OnCollectionChanged(ObservableHashSetSingletons.NoItems, removed); - OnCountPropertyChanged(); } protected override void IntersectWithItems(IEnumerable other) { var copy = new HashSet(_set, _set.Comparer); - copy.IntersectWith(other); - if (copy.Count == _set.Count) { return; } - var removed = _set.Where(i => !copy.Contains(i)).ToList(); - //OnCountPropertyChanging(); + CheckReentrancy(); _set = copy; OnCollectionChanged(ObservableHashSetSingletons.NoItems, removed); - OnCountPropertyChanged(); } @@ -140,12 +129,11 @@ protected override bool RemoveItem(T item) return false; } - //OnCountPropertyChanging(); + CheckReentrancy(); _set.Remove(item); OnCollectionChanged(NotifyCollectionChangedAction.Remove, item); - OnCountPropertyChanged(); return true; @@ -154,9 +142,7 @@ protected override bool RemoveItem(T item) protected override void SymmetricExceptWithItems(IEnumerable other) { var copy = new HashSet(_set, _set.Comparer); - copy.SymmetricExceptWith(other); - var removed = _set.Where(i => !copy.Contains(i)).ToList(); var added = copy.Where(i => !_set.Contains(i)).ToList(); @@ -166,34 +152,29 @@ protected override void SymmetricExceptWithItems(IEnumerable other) return; } - //OnCountPropertyChanging(); + CheckReentrancy(); _set = copy; OnCollectionChanged(added, removed); - OnCountPropertyChanged(); } protected override void UnionWithItems(IEnumerable other) { var copy = new HashSet(_set, _set.Comparer); - copy.UnionWith(other); - if (copy.Count == _set.Count) { return; } - var added = copy.Where(i => !_set.Contains(i)).ToList(); - //OnCountPropertyChanging(); + CheckReentrancy(); _set = copy; OnCollectionChanged(added, ObservableHashSetSingletons.NoItems); - OnCountPropertyChanged(); } @@ -206,16 +187,16 @@ protected override void UnionWithItems(IEnumerable other) /// PropertyChanged event (per ). /// [field: NonSerialized] - private event PropertyChangedEventHandler? PropertyChanged; - - /// - /// PropertyChanged event (per ). - /// - event PropertyChangedEventHandler? INotifyPropertyChanged.PropertyChanged - { - add => PropertyChanged += value; - remove => PropertyChanged -= value; - } + public event PropertyChangedEventHandler? PropertyChanged; + + ///// + ///// PropertyChanged event (per ). + ///// + //event PropertyChangedEventHandler? INotifyPropertyChanged.PropertyChanged + //{ + // add => PropertyChanged += value; + // remove => PropertyChanged -= value; + //} /// /// Raises a PropertyChanged event (per ). diff --git a/Runtime/Collections/SetBase.cs b/Runtime/Collections/SetBase.cs index 9438ac9..ec6dcf9 100644 --- a/Runtime/Collections/SetBase.cs +++ b/Runtime/Collections/SetBase.cs @@ -42,7 +42,7 @@ public SetBase(int capacity, IEqualityComparer? comparer) public int Count => _set.Count; - bool ICollection.IsReadOnly => ((ICollection)_set).IsReadOnly; + bool ICollection.IsReadOnly => false; public bool Add(T item) => AddItem(item); diff --git a/TestProject/BindingCollection/BindingCollection.Tests.cs b/TestProject/BindingCollection/BindingCollection.Tests.cs index 9d1d651..514af47 100644 --- a/TestProject/BindingCollection/BindingCollection.Tests.cs +++ b/TestProject/BindingCollection/BindingCollection.Tests.cs @@ -8,7 +8,7 @@ namespace CiccioSoft.Collections.Tests.BindingCollection { - public partial class BindingCollection_Test + public class BindingCollection_Test { [Fact] public void Ctor_Default() @@ -396,7 +396,7 @@ public void Insert_Null_Success() [Fact] - public void AddNew_Invoke__ThrowsNotSupportedException() + public void AddNew_Invoke_ThrowsNotSupportedException() { var bindingList = new BindingCollection(); Assert.Throws(() => diff --git a/TestProject/BindingHashSet/BindingHashSetTest.cs b/TestProject/BindingHashSet/BindingHashSetTest.cs deleted file mode 100644 index 7581877..0000000 --- a/TestProject/BindingHashSet/BindingHashSetTest.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Xunit; - -namespace CiccioSoft.Collections.Tests.BindingHashSet -{ - public class BindingHashSetTest - { - [Fact] - public void FakeTest() - { - Assert.True(true); - } - } -} diff --git a/TestProject/BindingList/BindingList.Test.AddNew.cs b/TestProject/BindingList/BindingList.Test.AddNew.cs index 71cf8f9..39683b2 100644 --- a/TestProject/BindingList/BindingList.Test.AddNew.cs +++ b/TestProject/BindingList/BindingList.Test.AddNew.cs @@ -6,7 +6,7 @@ namespace CiccioSoft.Collections.Tests.BindingList public partial class BindingList_Test { [Fact] - public void AddNew_Invoke__ThrowsNotSupportedException() + public void AddNew_Invoke_ThrowsNotSupportedException() { var bindingList = new BindingList(); Assert.Throws(() => diff --git a/TestProject/BindingSet/BindingSet.Tests.cs b/TestProject/BindingSet/BindingSet.Tests.cs new file mode 100644 index 0000000..4213f5d --- /dev/null +++ b/TestProject/BindingSet/BindingSet.Tests.cs @@ -0,0 +1,408 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using Xunit; + +namespace CiccioSoft.Collections.Tests.BindingSet +{ + public class BindingSet_Test + { + [Fact] + public void Ctor_Default() + { + var list = new BindingSet(); + IBindingList iBindingList = list; + + Assert.True(list.AllowEdit); + Assert.False(list.AllowNew); + Assert.True(list.AllowRemove); + //Assert.True(list.RaiseListChangedEvents); + + Assert.True(iBindingList.AllowEdit); + Assert.False(iBindingList.AllowNew); + Assert.True(iBindingList.AllowRemove); + Assert.Equal(ListSortDirection.Ascending, iBindingList.SortDirection); + Assert.True(iBindingList.SupportsChangeNotification); + Assert.False(iBindingList.SupportsSearching); + Assert.False(iBindingList.SupportsSorting); + Assert.False(((IRaiseItemChangedEvents)list).RaisesItemChangedEvents); + } + + [Fact] + public void Ctor_FixedSizeIList() + { + var array = new string[10]; + var bindingList = new BindingSet(array); + IBindingList iBindingList = bindingList; + + Assert.True(bindingList.AllowEdit); + Assert.False(bindingList.AllowNew); + Assert.True(bindingList.AllowRemove); + //Assert.True(bindingList.RaiseListChangedEvents); + + Assert.True(iBindingList.AllowEdit); + Assert.False(iBindingList.AllowNew); + Assert.True(iBindingList.AllowRemove); + Assert.False(iBindingList.IsSorted); + Assert.Equal(ListSortDirection.Ascending, iBindingList.SortDirection); + Assert.True(iBindingList.SupportsChangeNotification); + Assert.False(iBindingList.SupportsSearching); + Assert.False(iBindingList.SupportsSorting); + Assert.False(((IRaiseItemChangedEvents)bindingList).RaisesItemChangedEvents); + } + + [Fact] + public void Ctor_NonFixedSizeIList() + { + var list = new List(); + var bindingList = new BindingSet(list); + IBindingList iBindingList = bindingList; + + Assert.True(bindingList.AllowEdit); + Assert.False(bindingList.AllowNew); + Assert.True(bindingList.AllowRemove); + //Assert.True(bindingList.RaiseListChangedEvents); + + Assert.True(iBindingList.AllowEdit); + Assert.False(iBindingList.AllowNew); + Assert.True(iBindingList.AllowRemove); + Assert.False(iBindingList.IsSorted); + Assert.Equal(ListSortDirection.Ascending, iBindingList.SortDirection); + Assert.True(iBindingList.SupportsChangeNotification); + Assert.False(iBindingList.SupportsSearching); + Assert.False(iBindingList.SupportsSorting); + Assert.False(((IRaiseItemChangedEvents)bindingList).RaisesItemChangedEvents); + } + + [Fact] + public void Ctor_IReadOnlyList() + { + var list = new List(); + var bindingList = new BindingSet(list); + IBindingList iBindingList = bindingList; + + Assert.True(bindingList.AllowEdit); + Assert.False(bindingList.AllowNew); + Assert.True(bindingList.AllowRemove); + //Assert.True(bindingList.RaiseListChangedEvents); + + Assert.True(iBindingList.AllowEdit); + Assert.False(iBindingList.AllowNew); + Assert.True(iBindingList.AllowRemove); + Assert.False(iBindingList.IsSorted); + Assert.Equal(ListSortDirection.Ascending, iBindingList.SortDirection); + Assert.True(iBindingList.SupportsChangeNotification); + Assert.False(iBindingList.SupportsSearching); + Assert.False(iBindingList.SupportsSorting); + Assert.False(((IRaiseItemChangedEvents)bindingList).RaisesItemChangedEvents); + } + + //[Fact] + //public void RemoveAt_Invoke_CallsListChanged() + //{ + // var list = new List { new object() }; + // var bindingList = new BindingSet(list); + + // bool calledListChanged = false; + // bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + // { + // calledListChanged = true; + // Assert.Equal(0, e.NewIndex); + // Assert.Equal(ListChangedType.ItemDeleted, e.ListChangedType); + + // // The event is raised after the removal. + // Assert.Equal(0, bindingList.Count); + // }; + // bindingList.RemoveAt(0); + + // Assert.True(calledListChanged); + //} + + [Fact] + public void Clear_Invoke_Success() + { + var bindingList = new BindingSet { new object(), new object() }; + + bool calledListChanged = false; + bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + { + calledListChanged = true; + Assert.Equal(ListChangedType.Reset, e.ListChangedType); + Assert.Equal(-1, e.NewIndex); + }; + + bindingList.Clear(); + Assert.True(calledListChanged); + Assert.Empty(bindingList); + } + + [Fact] + public void Clear_INotifyPropertyChangedItems_RemovesPropertyChangedEventHandlers() + { + var item1 = new Item(); + var item2 = new Item(); + var list = new List { item1, item2, null }; + var bindingList = new BindingSet(list); + Assert.Equal(1, item1.InvocationList.Length); + Assert.Equal(1, item2.InvocationList.Length); + + bool calledListChanged = false; + bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + { + calledListChanged = true; + Assert.Equal(ListChangedType.Reset, e.ListChangedType); + Assert.Equal(-1, e.NewIndex); + }; + + bindingList.Clear(); + Assert.True(calledListChanged); + Assert.Empty(bindingList); + + Assert.Null(item1.InvocationList); + Assert.Null(item2.InvocationList); + } + + //[Fact] + //public void RemoveAt_INotifyPropertyChangedItems_RemovesPropertyChangedEventHandlers() + //{ + // var item = new Item(); + // var bindingList = new BindingSet { item }; + // Assert.Equal(1, item.InvocationList.Length); + + // bool calledListChanged = false; + // bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + // { + // calledListChanged = true; + // Assert.Equal(ListChangedType.ItemDeleted, e.ListChangedType); + // Assert.Equal(0, e.NewIndex); + // }; + + // bindingList.RemoveAt(0); + // Assert.True(calledListChanged); + // Assert.Empty(bindingList); + // Assert.Null(item.InvocationList); + //} + + //[Fact] + //public void ItemSet_Invoke_CallsListChanged() + //{ + // var bindingList = new BindingSet { 1 }; + + // bool calledListChanged = false; + // bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + // { + // calledListChanged = true; + // Assert.Equal(ListChangedType.ItemChanged, e.ListChangedType); + // Assert.Equal(0, e.NewIndex); + // }; + + // bindingList[0] = 2; + // Assert.True(calledListChanged); + // Assert.Equal(2, bindingList[0]); + //} + + //[Fact] + //public void ItemSet_INotifyPropertyChangedItem_RemovesPropertyChangedEventHandlers() + //{ + // var item1 = new Item(); + // var item2 = new Item(); + // var bindingList = new BindingSet { item1 }; + // Assert.Equal(1, item1.InvocationList.Length); + + // bool calledListChanged = false; + // bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + // { + // calledListChanged = true; + // Assert.Equal(ListChangedType.ItemChanged, e.ListChangedType); + // Assert.Equal(0, e.NewIndex); + // }; + + // bindingList[0] = item2; + // Assert.True(calledListChanged); + // Assert.Equal(item2, bindingList[0]); + // Assert.Null(item1.InvocationList); + // Assert.Equal(1, item2.InvocationList.Length); + //} + + [Fact] + public void SortProperty_Get_ReturnsNull() + { + IBindingList bindingList = new BindingSet(); + Assert.Null(bindingList.SortProperty); + } + + [Fact] + public void ApplySort_Invoke_ThrowsNotSupportedException() + { + IBindingList bindingList = new BindingSet(); + Assert.Throws(() => bindingList.ApplySort(null, ListSortDirection.Descending)); + } + + [Fact] + public void RemoveSort_Invoke_ThrowsNotSupportedException() + { + IBindingList bindingList = new BindingSet(); + Assert.Throws(() => bindingList.RemoveSort()); + } + + [Fact] + public void Find_Invoke_ThrowsNotSupportedException() + { + IBindingList bindingList = new BindingSet(); + Assert.Throws(() => bindingList.Find(null, null)); + } + + [Fact] + public void AddIndex_RemoveIndex_Nop() + { + IBindingList bindingList = new BindingSet(); + bindingList.AddIndex(null); + bindingList.RemoveIndex(null); + } + + [Fact] + public void ItemPropertyChanged_RaiseListChangedEventsFalse_InvokesItemChanged() + { + var item = new Item(); + var bindingList = new BindingSet { item }; + + bool calledListChanged = false; + bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + { + calledListChanged = true; + Assert.Equal(ListChangedType.ItemChanged, e.ListChangedType); + Assert.Equal(0, e.NewIndex); + Assert.Equal("Name", e.PropertyDescriptor.Name); + Assert.Equal(typeof(string), e.PropertyDescriptor.PropertyType); + }; + + // Invoke once + item.Name = "name"; + Assert.True(calledListChanged); + + // Invoke twice. + calledListChanged = false; + item.Name = "name2"; + Assert.True(calledListChanged); + } + + [Theory] + [InlineData(null)] + [InlineData("sender")] + public void ItemPropertyChanged_InvalidSender_InvokesReset(object invokeSender) + { + var item = new Item(); + var bindingList = new BindingSet { item }; + + bool calledListChanged = false; + bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + { + calledListChanged = true; + Assert.Equal(ListChangedType.Reset, e.ListChangedType); + Assert.Equal(-1, e.NewIndex); + }; + + item.InvokePropertyChanged(invokeSender, new PropertyChangedEventArgs("Name")); + Assert.True(calledListChanged); + } + + public static IEnumerable InvalidEventArgs_TestData() + { + yield return new object[] { null }; + yield return new object[] { new PropertyChangedEventArgs(null) }; + yield return new object[] { new PropertyChangedEventArgs(string.Empty) }; + } + + [Theory] + [MemberData(nameof(InvalidEventArgs_TestData))] + public void ItemPropertyChanged_InvalidEventArgs_InvokesReset(PropertyChangedEventArgs eventArgs) + { + var item = new Item(); + var bindingList = new BindingSet { item }; + + bool calledListChanged = false; + bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + { + calledListChanged = true; + Assert.Equal(ListChangedType.Reset, e.ListChangedType); + Assert.Equal(-1, e.NewIndex); + }; + + item.InvokePropertyChanged(item, eventArgs); + Assert.True(calledListChanged); + } + + [Fact] + public void InvokePropertyChanged_NoSuchObjectAnymore_InvokesReset() + { + var item1 = new Item(); + var item2 = new Item(); + var bindingList = new BindingSet { item1 }; + + bool calledListChanged = false; + bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + { + calledListChanged = true; + Assert.Equal(ListChangedType.Reset, e.ListChangedType); + Assert.Equal(-1, e.NewIndex); + }; + + item1.InvokePropertyChanged(item2, new PropertyChangedEventArgs("Name")); + Assert.True(calledListChanged); + } + + private class Item : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + private string _name; + public string Name + { + get => _name; + set + { + if (_name != value) + { + _name = value; + OnPropertyChanged(); + } + } + } + + public Delegate[] InvocationList => PropertyChanged?.GetInvocationList(); + + public void InvokePropertyChanged(object sender, PropertyChangedEventArgs e) + { + PropertyChanged?.Invoke(sender, e); + } + + private void OnPropertyChanged() + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name))); + } + } + + //[Fact] + //public void Insert_Null_Success() + //{ + // var list = new BindingSet(); + // list.Insert(0, null); + + // Assert.Equal(1, list.Count); + //} + + + [Fact] + public void AddNew_Invoke_ThrowsNotSupportedException() + { + var bindingList = new BindingSet(); + Assert.Throws(() => + { + bindingList.AddNew(); + }); + } + } +} diff --git a/TestProject/CiccioList/CiccioList.Tests.AsObservableCollection.ConstructorAndProperty.cs b/TestProject/CiccioList/CiccioList.Tests.AsObservableCollection.ConstructorAndProperty.cs index 698d08c..2d9e124 100644 --- a/TestProject/CiccioList/CiccioList.Tests.AsObservableCollection.ConstructorAndProperty.cs +++ b/TestProject/CiccioList/CiccioList.Tests.AsObservableCollection.ConstructorAndProperty.cs @@ -142,7 +142,7 @@ private partial class ObservableCollectionSubclass : CiccioList { public ObservableCollectionSubclass(IEnumerable collection) : base(collection) { } - public List InnerList => (List)base.items; + public List InnerList => (List)base._list; } /// diff --git a/TestProject/CiccioSet/CiccioHashSetTest.cs b/TestProject/CiccioSet/CiccioHashSetTest.cs deleted file mode 100644 index 08e8ee5..0000000 --- a/TestProject/CiccioSet/CiccioHashSetTest.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Xunit; - -namespace CiccioSoft.Collections.Tests.CiccioHashSet -{ - public class CiccioHashSetTest - { - [Fact] - public void FakeTest() - { - Assert.True(true); - } - } -} diff --git a/TestProject/CiccioSet/CiccioSet.Test.AsBindingList.cs b/TestProject/CiccioSet/CiccioSet.Test.AsBindingList.cs new file mode 100644 index 0000000..592b8aa --- /dev/null +++ b/TestProject/CiccioSet/CiccioSet.Test.AsBindingList.cs @@ -0,0 +1,408 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using Xunit; + +namespace CiccioSoft.Collections.Tests.CiccioSet +{ + public class CiccioSet_Test_AsBindingList + { + [Fact] + public void Ctor_Default() + { + var list = new CiccioSet(); + IBindingList iBindingList = list; + + Assert.True(list.AllowEdit); + Assert.False(list.AllowNew); + Assert.True(list.AllowRemove); + //Assert.True(list.RaiseListChangedEvents); + + Assert.True(iBindingList.AllowEdit); + Assert.False(iBindingList.AllowNew); + Assert.True(iBindingList.AllowRemove); + Assert.Equal(ListSortDirection.Ascending, iBindingList.SortDirection); + Assert.True(iBindingList.SupportsChangeNotification); + Assert.False(iBindingList.SupportsSearching); + Assert.False(iBindingList.SupportsSorting); + Assert.False(((IRaiseItemChangedEvents)list).RaisesItemChangedEvents); + } + + [Fact] + public void Ctor_FixedSizeIList() + { + var array = new string[10]; + var bindingList = new CiccioSet(array); + IBindingList iBindingList = bindingList; + + Assert.True(bindingList.AllowEdit); + Assert.False(bindingList.AllowNew); + Assert.True(bindingList.AllowRemove); + //Assert.True(bindingList.RaiseListChangedEvents); + + Assert.True(iBindingList.AllowEdit); + Assert.False(iBindingList.AllowNew); + Assert.True(iBindingList.AllowRemove); + Assert.False(iBindingList.IsSorted); + Assert.Equal(ListSortDirection.Ascending, iBindingList.SortDirection); + Assert.True(iBindingList.SupportsChangeNotification); + Assert.False(iBindingList.SupportsSearching); + Assert.False(iBindingList.SupportsSorting); + Assert.False(((IRaiseItemChangedEvents)bindingList).RaisesItemChangedEvents); + } + + [Fact] + public void Ctor_NonFixedSizeIList() + { + var list = new List(); + var bindingList = new CiccioSet(list); + IBindingList iBindingList = bindingList; + + Assert.True(bindingList.AllowEdit); + Assert.False(bindingList.AllowNew); + Assert.True(bindingList.AllowRemove); + //Assert.True(bindingList.RaiseListChangedEvents); + + Assert.True(iBindingList.AllowEdit); + Assert.False(iBindingList.AllowNew); + Assert.True(iBindingList.AllowRemove); + Assert.False(iBindingList.IsSorted); + Assert.Equal(ListSortDirection.Ascending, iBindingList.SortDirection); + Assert.True(iBindingList.SupportsChangeNotification); + Assert.False(iBindingList.SupportsSearching); + Assert.False(iBindingList.SupportsSorting); + Assert.False(((IRaiseItemChangedEvents)bindingList).RaisesItemChangedEvents); + } + + [Fact] + public void Ctor_IReadOnlyList() + { + var list = new List(); + var bindingList = new CiccioSet(list); + IBindingList iBindingList = bindingList; + + Assert.True(bindingList.AllowEdit); + Assert.False(bindingList.AllowNew); + Assert.True(bindingList.AllowRemove); + //Assert.True(bindingList.RaiseListChangedEvents); + + Assert.True(iBindingList.AllowEdit); + Assert.False(iBindingList.AllowNew); + Assert.True(iBindingList.AllowRemove); + Assert.False(iBindingList.IsSorted); + Assert.Equal(ListSortDirection.Ascending, iBindingList.SortDirection); + Assert.True(iBindingList.SupportsChangeNotification); + Assert.False(iBindingList.SupportsSearching); + Assert.False(iBindingList.SupportsSorting); + Assert.False(((IRaiseItemChangedEvents)bindingList).RaisesItemChangedEvents); + } + + //[Fact] + //public void RemoveAt_Invoke_CallsListChanged() + //{ + // var list = new List { new object() }; + // var bindingList = new BindingSet(list); + + // bool calledListChanged = false; + // bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + // { + // calledListChanged = true; + // Assert.Equal(0, e.NewIndex); + // Assert.Equal(ListChangedType.ItemDeleted, e.ListChangedType); + + // // The event is raised after the removal. + // Assert.Equal(0, bindingList.Count); + // }; + // bindingList.RemoveAt(0); + + // Assert.True(calledListChanged); + //} + + [Fact] + public void Clear_Invoke_Success() + { + var bindingList = new CiccioSet { new object(), new object() }; + + bool calledListChanged = false; + bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + { + calledListChanged = true; + Assert.Equal(ListChangedType.Reset, e.ListChangedType); + Assert.Equal(-1, e.NewIndex); + }; + + bindingList.Clear(); + Assert.True(calledListChanged); + Assert.Empty(bindingList); + } + + [Fact] + public void Clear_INotifyPropertyChangedItems_RemovesPropertyChangedEventHandlers() + { + var item1 = new Item(); + var item2 = new Item(); + var list = new List { item1, item2, null }; + var bindingList = new CiccioSet(list); + Assert.Equal(1, item1.InvocationList.Length); + Assert.Equal(1, item2.InvocationList.Length); + + bool calledListChanged = false; + bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + { + calledListChanged = true; + Assert.Equal(ListChangedType.Reset, e.ListChangedType); + Assert.Equal(-1, e.NewIndex); + }; + + bindingList.Clear(); + Assert.True(calledListChanged); + Assert.Empty(bindingList); + + Assert.Null(item1.InvocationList); + Assert.Null(item2.InvocationList); + } + + //[Fact] + //public void RemoveAt_INotifyPropertyChangedItems_RemovesPropertyChangedEventHandlers() + //{ + // var item = new Item(); + // var bindingList = new BindingSet { item }; + // Assert.Equal(1, item.InvocationList.Length); + + // bool calledListChanged = false; + // bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + // { + // calledListChanged = true; + // Assert.Equal(ListChangedType.ItemDeleted, e.ListChangedType); + // Assert.Equal(0, e.NewIndex); + // }; + + // bindingList.RemoveAt(0); + // Assert.True(calledListChanged); + // Assert.Empty(bindingList); + // Assert.Null(item.InvocationList); + //} + + //[Fact] + //public void ItemSet_Invoke_CallsListChanged() + //{ + // var bindingList = new BindingSet { 1 }; + + // bool calledListChanged = false; + // bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + // { + // calledListChanged = true; + // Assert.Equal(ListChangedType.ItemChanged, e.ListChangedType); + // Assert.Equal(0, e.NewIndex); + // }; + + // bindingList[0] = 2; + // Assert.True(calledListChanged); + // Assert.Equal(2, bindingList[0]); + //} + + //[Fact] + //public void ItemSet_INotifyPropertyChangedItem_RemovesPropertyChangedEventHandlers() + //{ + // var item1 = new Item(); + // var item2 = new Item(); + // var bindingList = new BindingSet { item1 }; + // Assert.Equal(1, item1.InvocationList.Length); + + // bool calledListChanged = false; + // bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + // { + // calledListChanged = true; + // Assert.Equal(ListChangedType.ItemChanged, e.ListChangedType); + // Assert.Equal(0, e.NewIndex); + // }; + + // bindingList[0] = item2; + // Assert.True(calledListChanged); + // Assert.Equal(item2, bindingList[0]); + // Assert.Null(item1.InvocationList); + // Assert.Equal(1, item2.InvocationList.Length); + //} + + [Fact] + public void SortProperty_Get_ReturnsNull() + { + IBindingList bindingList = new CiccioSet(); + Assert.Null(bindingList.SortProperty); + } + + [Fact] + public void ApplySort_Invoke_ThrowsNotSupportedException() + { + IBindingList bindingList = new CiccioSet(); + Assert.Throws(() => bindingList.ApplySort(null, ListSortDirection.Descending)); + } + + [Fact] + public void RemoveSort_Invoke_ThrowsNotSupportedException() + { + IBindingList bindingList = new CiccioSet(); + Assert.Throws(() => bindingList.RemoveSort()); + } + + [Fact] + public void Find_Invoke_ThrowsNotSupportedException() + { + IBindingList bindingList = new CiccioSet(); + Assert.Throws(() => bindingList.Find(null, null)); + } + + [Fact] + public void AddIndex_RemoveIndex_Nop() + { + IBindingList bindingList = new CiccioSet(); + bindingList.AddIndex(null); + bindingList.RemoveIndex(null); + } + + [Fact] + public void ItemPropertyChanged_RaiseListChangedEventsFalse_InvokesItemChanged() + { + var item = new Item(); + var bindingList = new CiccioSet { item }; + + bool calledListChanged = false; + bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + { + calledListChanged = true; + Assert.Equal(ListChangedType.ItemChanged, e.ListChangedType); + Assert.Equal(0, e.NewIndex); + Assert.Equal("Name", e.PropertyDescriptor.Name); + Assert.Equal(typeof(string), e.PropertyDescriptor.PropertyType); + }; + + // Invoke once + item.Name = "name"; + Assert.True(calledListChanged); + + // Invoke twice. + calledListChanged = false; + item.Name = "name2"; + Assert.True(calledListChanged); + } + + [Theory] + [InlineData(null)] + [InlineData("sender")] + public void ItemPropertyChanged_InvalidSender_InvokesReset(object invokeSender) + { + var item = new Item(); + var bindingList = new CiccioSet { item }; + + bool calledListChanged = false; + bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + { + calledListChanged = true; + Assert.Equal(ListChangedType.Reset, e.ListChangedType); + Assert.Equal(-1, e.NewIndex); + }; + + item.InvokePropertyChanged(invokeSender, new PropertyChangedEventArgs("Name")); + Assert.True(calledListChanged); + } + + public static IEnumerable InvalidEventArgs_TestData() + { + yield return new object[] { null }; + yield return new object[] { new PropertyChangedEventArgs(null) }; + yield return new object[] { new PropertyChangedEventArgs(string.Empty) }; + } + + [Theory] + [MemberData(nameof(InvalidEventArgs_TestData))] + public void ItemPropertyChanged_InvalidEventArgs_InvokesReset(PropertyChangedEventArgs eventArgs) + { + var item = new Item(); + var bindingList = new CiccioSet { item }; + + bool calledListChanged = false; + bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + { + calledListChanged = true; + Assert.Equal(ListChangedType.Reset, e.ListChangedType); + Assert.Equal(-1, e.NewIndex); + }; + + item.InvokePropertyChanged(item, eventArgs); + Assert.True(calledListChanged); + } + + [Fact] + public void InvokePropertyChanged_NoSuchObjectAnymore_InvokesReset() + { + var item1 = new Item(); + var item2 = new Item(); + var bindingList = new CiccioSet { item1 }; + + bool calledListChanged = false; + bindingList.ListChanged += (object sender, ListChangedEventArgs e) => + { + calledListChanged = true; + Assert.Equal(ListChangedType.Reset, e.ListChangedType); + Assert.Equal(-1, e.NewIndex); + }; + + item1.InvokePropertyChanged(item2, new PropertyChangedEventArgs("Name")); + Assert.True(calledListChanged); + } + + private class Item : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + private string _name; + public string Name + { + get => _name; + set + { + if (_name != value) + { + _name = value; + OnPropertyChanged(); + } + } + } + + public Delegate[] InvocationList => PropertyChanged?.GetInvocationList(); + + public void InvokePropertyChanged(object sender, PropertyChangedEventArgs e) + { + PropertyChanged?.Invoke(sender, e); + } + + private void OnPropertyChanged() + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name))); + } + } + + //[Fact] + //public void Insert_Null_Success() + //{ + // var list = new BindingSet(); + // list.Insert(0, null); + + // Assert.Equal(1, list.Count); + //} + + + [Fact] + public void AddNew_Invoke_ThrowsNotSupportedException() + { + var bindingList = new CiccioSet(); + Assert.Throws(() => + { + bindingList.AddNew(); + }); + } + } +} diff --git a/TestProject/CiccioSet/CiccioSet.Test.AsObservableCollection.cs b/TestProject/CiccioSet/CiccioSet.Test.AsObservableCollection.cs new file mode 100644 index 0000000..be06d84 --- /dev/null +++ b/TestProject/CiccioSet/CiccioSet.Test.AsObservableCollection.cs @@ -0,0 +1,560 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using Xunit; + +namespace CiccioSoft.Collections.Tests.CiccioSet +{ + public class CiccioSet_Test_AsObservableCollection + { + private static readonly Random _random = new(); + + [ConditionalFact] + public void Can_construct() + { + Assert.Same( + new HashSet().Comparer, + new CiccioSet().Comparer); + + Assert.Same( + ReferenceEqualityComparer.Instance, + new CiccioSet(ReferenceEqualityComparer.Instance).Comparer); + + var testData1 = CreateTestData(); + + var rh1 = new HashSet(testData1); + var ohs1 = new CiccioSet(testData1); + Assert.Equal(rh1.OrderBy(i => i), ohs1.OrderBy(i => i)); + Assert.Same(rh1.Comparer, ohs1.Comparer); + + var testData2 = CreateTestData().Cast(); + + var rh2 = new HashSet(testData2, ReferenceEqualityComparer.Instance); + var ohs2 = new CiccioSet(testData2, ReferenceEqualityComparer.Instance); + Assert.Equal(rh2.OrderBy(i => i), ohs2.OrderBy(i => i)); + Assert.Same(rh2.Comparer, ohs2.Comparer); + } + + [ConditionalFact] + public void Can_add() + { + var hashSet = new CiccioSet(); + //var countChanging = 0; + var countChanged = 0; + var collectionChanged = 0; + var currentCount = 0; + var countChange = 1; + var adding = Array.Empty(); + + //hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); + hashSet.PropertyChanged += (s, a) => AssertCountChanged(hashSet, s, a, ref currentCount, countChange, ref countChanged); + hashSet.CollectionChanged += (s, a) => + { + Assert.Equal(NotifyCollectionChangedAction.Add, a.Action); + Assert.Null(a.OldItems); + Assert.Equal(adding, a.NewItems.OfType()); + collectionChanged++; + }; + + adding = new[] { "Palmer" }; + Assert.True(hashSet.Add("Palmer")); + + //Assert.Equal(1, countChanging); + Assert.Equal(1, countChanged); + Assert.Equal(1, collectionChanged); + Assert.Equal(new[] { "Palmer" }, hashSet); + + adding = new[] { "Carmack" }; + Assert.True(hashSet.Add("Carmack")); + + //Assert.Equal(2, countChanging); + Assert.Equal(2, countChanged); + Assert.Equal(2, collectionChanged); + Assert.Equal(new[] { "Carmack", "Palmer" }, hashSet.OrderBy(i => i)); + + Assert.False(hashSet.Add("Palmer")); + + //Assert.Equal(2, countChanging); + Assert.Equal(2, countChanged); + Assert.Equal(2, collectionChanged); + Assert.Equal(new[] { "Carmack", "Palmer" }, hashSet.OrderBy(i => i)); + } + + [ConditionalFact] + public void Can_clear() + { + var testData = new HashSet(CreateTestData()); + + var hashSet = new CiccioSet(testData); + //var countChanging = 0; + var countChanged = 0; + var collectionChanged = 0; + var currentCount = testData.Count; + var countChange = -testData.Count; + + //hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); + hashSet.PropertyChanged += (s, a) => AssertCountChanged(hashSet, s, a, ref currentCount, countChange, ref countChanged); + hashSet.CollectionChanged += (s, a) => + { + Assert.Equal(NotifyCollectionChangedAction.Replace, a.Action); + Assert.Equal(testData.OrderBy(i => i), a.OldItems.OfType().OrderBy(i => i)); + Assert.Empty(a.NewItems); + collectionChanged++; + }; + + hashSet.Clear(); + + //Assert.Equal(testData.Count == 0 ? 0 : 1, countChanging); + Assert.Equal(testData.Count == 0 ? 0 : 1, countChanged); + Assert.Equal(testData.Count == 0 ? 0 : 1, collectionChanged); + Assert.Empty(hashSet); + + hashSet.Clear(); + + //Assert.Equal(testData.Count == 0 ? 0 : 1, countChanging); + Assert.Equal(testData.Count == 0 ? 0 : 1, countChanged); + Assert.Equal(testData.Count == 0 ? 0 : 1, collectionChanged); + Assert.Empty(hashSet); + } + + [ConditionalFact] + public void Contains_works() + { + var testData = CreateTestData(); + var hashSet = new CiccioSet(testData); + + foreach (var item in testData) + { + Assert.Contains(item, (ISet)hashSet); + } + + foreach (var item in CreateTestData(1000, 10000).Except(testData)) + { + Assert.DoesNotContain(item, (ISet)hashSet); + } + } + + [ConditionalFact] + public void Can_copy_to_array() + { + var testData = CreateTestData(); + var orderedDistinct = testData.Distinct().OrderBy(i => i).ToList(); + + var hashSet = new CiccioSet(testData); + + Assert.Equal(orderedDistinct.Count, hashSet.Count); + + //var array = new int[hashSet.Count]; + //hashSet.CopyTo(array); + + //Assert.Equal(orderedDistinct, array.OrderBy(i => i)); + + var array = new int[hashSet.Count + 100]; + hashSet.CopyTo(array, 100); + + Assert.Equal(orderedDistinct, array.Skip(100).OrderBy(i => i)); + + //var toTake = Math.Min(10, hashSet.Count); + //array = new int[100 + toTake]; + //hashSet.CopyTo(array, 100, toTake); + + //foreach (var value in array.Skip(100).Take(toTake)) + //{ + // Assert.Contains(value, hashSet); + //} + } + + [ConditionalFact] + public void Can_remove() + { + var hashSet = new CiccioSet { "Palmer", "Carmack" }; + //var countChanging = 0; + var countChanged = 0; + var collectionChanged = 0; + var currentCount = 2; + var countChange = -1; + var removing = Array.Empty(); + + //hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); + hashSet.PropertyChanged += (s, a) => AssertCountChanged(hashSet, s, a, ref currentCount, countChange, ref countChanged); + hashSet.CollectionChanged += (s, a) => + { + Assert.Equal(NotifyCollectionChangedAction.Remove, a.Action); + Assert.Equal(removing, a.OldItems.OfType()); + Assert.Null(a.NewItems); + collectionChanged++; + }; + + removing = new[] { "Palmer" }; + Assert.True(hashSet.Remove("Palmer")); + + //Assert.Equal(1, countChanging); + Assert.Equal(1, countChanged); + Assert.Equal(1, collectionChanged); + Assert.Equal(new[] { "Carmack" }, hashSet); + + removing = new[] { "Carmack" }; + Assert.True(hashSet.Remove("Carmack")); + + //Assert.Equal(2, countChanging); + Assert.Equal(2, countChanged); + Assert.Equal(2, collectionChanged); + Assert.Empty(hashSet); + + Assert.False(hashSet.Remove("Palmer")); + + //Assert.Equal(2, countChanging); + Assert.Equal(2, countChanged); + Assert.Equal(2, collectionChanged); + Assert.Empty(hashSet); + } + + [ConditionalFact] + public void Not_read_only() + => Assert.False(((ICollection)new CiccioSet()).IsReadOnly); + + [ConditionalFact] + public void Can_union_with() + { + var hashSet = new CiccioSet { "Palmer", "Carmack" }; + //var countChanging = 0; + var countChanged = 0; + var collectionChanged = 0; + var currentCount = 2; + var countChange = 2; + var adding = new[] { "Brendan", "Nate" }; + + //hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); + hashSet.PropertyChanged += (s, a) => AssertCountChanged(hashSet, s, a, ref currentCount, countChange, ref countChanged); + hashSet.CollectionChanged += (s, a) => + { + Assert.Equal(NotifyCollectionChangedAction.Replace, a.Action); + Assert.Empty(a.OldItems); + Assert.Equal(adding, a.NewItems.OfType().OrderBy(i => i)); + collectionChanged++; + }; + + hashSet.UnionWith(new[] { "Carmack", "Nate", "Brendan" }); + + //Assert.Equal(1, countChanging); + Assert.Equal(1, countChanged); + Assert.Equal(1, collectionChanged); + Assert.Equal(new[] { "Brendan", "Carmack", "Nate", "Palmer" }, hashSet.OrderBy(i => i)); + + hashSet.UnionWith(new[] { "Brendan" }); + + //Assert.Equal(1, countChanging); + Assert.Equal(1, countChanged); + Assert.Equal(1, collectionChanged); + Assert.Equal(new[] { "Brendan", "Carmack", "Nate", "Palmer" }, hashSet.OrderBy(i => i)); + } + + [ConditionalFact] + public void Can_intersect_with() + { + var hashSet = new CiccioSet + { + "Brendan", + "Carmack", + "Nate", + "Palmer" + }; + //var countChanging = 0; + var countChanged = 0; + var collectionChanged = 0; + var currentCount = 4; + var countChange = -2; + var removing = new[] { "Brendan", "Nate" }; + + //hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); + hashSet.PropertyChanged += (s, a) => AssertCountChanged(hashSet, s, a, ref currentCount, countChange, ref countChanged); + hashSet.CollectionChanged += (s, a) => + { + Assert.Equal(NotifyCollectionChangedAction.Replace, a.Action); + Assert.Equal(removing, a.OldItems.OfType().OrderBy(i => i)); + Assert.Empty(a.NewItems); + collectionChanged++; + }; + + hashSet.IntersectWith(new[] { "Carmack", "Palmer", "Abrash" }); + + //Assert.Equal(1, countChanging); + Assert.Equal(1, countChanged); + Assert.Equal(1, collectionChanged); + Assert.Equal(new[] { "Carmack", "Palmer" }, hashSet.OrderBy(i => i)); + + hashSet.IntersectWith(new[] { "Carmack", "Palmer", "Abrash" }); + + //Assert.Equal(1, countChanging); + Assert.Equal(1, countChanged); + Assert.Equal(1, collectionChanged); + Assert.Equal(new[] { "Carmack", "Palmer" }, hashSet.OrderBy(i => i)); + } + + [ConditionalFact] + public void Can_except_with() + { + var hashSet = new CiccioSet + { + "Brendan", + "Carmack", + "Nate", + "Palmer" + }; + //var countChanging = 0; + var countChanged = 0; + var collectionChanged = 0; + var currentCount = 4; + var countChange = -2; + var removing = new[] { "Carmack", "Palmer" }; + + //hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); + hashSet.PropertyChanged += (s, a) => AssertCountChanged(hashSet, s, a, ref currentCount, countChange, ref countChanged); + hashSet.CollectionChanged += (s, a) => + { + Assert.Equal(NotifyCollectionChangedAction.Replace, a.Action); + Assert.Equal(removing, a.OldItems.OfType().OrderBy(i => i)); + Assert.Empty(a.NewItems); + collectionChanged++; + }; + + hashSet.ExceptWith(new[] { "Carmack", "Palmer", "Abrash" }); + + //Assert.Equal(1, countChanging); + Assert.Equal(1, countChanged); + Assert.Equal(1, collectionChanged); + Assert.Equal(new[] { "Brendan", "Nate" }, hashSet.OrderBy(i => i)); + + hashSet.ExceptWith(new[] { "Abrash", "Carmack", "Palmer" }); + + //Assert.Equal(1, countChanging); + Assert.Equal(1, countChanged); + Assert.Equal(1, collectionChanged); + Assert.Equal(new[] { "Brendan", "Nate" }, hashSet.OrderBy(i => i)); + } + + [ConditionalFact] + public void Can_symmetrical_except_with() + { + var hashSet = new CiccioSet + { + "Brendan", + "Carmack", + "Nate", + "Palmer" + }; + //var countChanging = 0; + var countChanged = 0; + var collectionChanged = 0; + var currentCount = 4; + var countChange = -1; + var removing = new[] { "Carmack", "Palmer" }; + var adding = new[] { "Abrash" }; + + //hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); + hashSet.PropertyChanged += (s, a) => AssertCountChanged(hashSet, s, a, ref currentCount, countChange, ref countChanged); + hashSet.CollectionChanged += (s, a) => + { + Assert.Equal(NotifyCollectionChangedAction.Replace, a.Action); + Assert.Equal(removing, a.OldItems.OfType().OrderBy(i => i)); + Assert.Equal(adding, a.NewItems.OfType().OrderBy(i => i)); + collectionChanged++; + }; + + hashSet.SymmetricExceptWith(new[] { "Carmack", "Palmer", "Abrash" }); + + //Assert.Equal(1, countChanging); + Assert.Equal(1, countChanged); + Assert.Equal(1, collectionChanged); + Assert.Equal(new[] { "Abrash", "Brendan", "Nate" }, hashSet.OrderBy(i => i)); + + hashSet.SymmetricExceptWith(Array.Empty()); + + //Assert.Equal(1, countChanging); + Assert.Equal(1, countChanged); + Assert.Equal(1, collectionChanged); + Assert.Equal(new[] { "Abrash", "Brendan", "Nate" }, hashSet.OrderBy(i => i)); + } + + [ConditionalFact] + public void IsSubsetOf_works_like_normal_hashset() + { + var bigData = CreateTestData(); + var smallData = CreateTestData(10); + + Assert.Equal( + new HashSet(smallData).IsSubsetOf(bigData), + new CiccioSet(smallData).IsSubsetOf(bigData)); + } + + [ConditionalFact] + public void IsProperSubsetOf_works_like_normal_hashset() + { + var bigData = CreateTestData(); + var smallData = CreateTestData(10); + + Assert.Equal( + new HashSet(smallData).IsProperSubsetOf(bigData), + new CiccioSet(smallData).IsProperSubsetOf(bigData)); + } + + [ConditionalFact] + public void IsSupersetOf_works_like_normal_hashset() + { + var bigData = CreateTestData(); + var smallData = CreateTestData(10); + + Assert.Equal( + new HashSet(bigData).IsSupersetOf(smallData), + new CiccioSet(bigData).IsSupersetOf(smallData)); + } + + [ConditionalFact] + public void IsProperSupersetOf_works_like_normal_hashset() + { + var bigData = CreateTestData(); + var smallData = CreateTestData(10); + + Assert.Equal( + new HashSet(bigData).IsProperSupersetOf(smallData), + new CiccioSet(bigData).IsProperSupersetOf(smallData)); + } + + [ConditionalFact] + public void Overlaps_works_like_normal_hashset() + { + var bigData = CreateTestData(); + var smallData = CreateTestData(10); + + Assert.Equal( + new HashSet(bigData).Overlaps(smallData), + new CiccioSet(bigData).Overlaps(smallData)); + } + + [ConditionalFact] + public void SetEquals_works_like_normal_hashset() + { + var data1 = CreateTestData(5); + var data2 = CreateTestData(5); + + Assert.Equal( + new HashSet(data1).SetEquals(data2), + new CiccioSet(data1).SetEquals(data2)); + } + + //[ConditionalFact] + //public void TrimExcess_doesnt_throw() + //{ + // var bigData = CreateTestData(); + // var smallData = CreateTestData(10); + + // var hashSet = new ObservableHashSet(bigData.Concat(smallData)); + // foreach (var item in bigData) + // { + // hashSet.Remove(item); + // } + + // hashSet.TrimExcess(); + //} + + //[ConditionalFact] + //public void Can_remove_with_predicate() + //{ + // var hashSet = new ObservableSet + // { + // "Brendan", + // "Carmack", + // "Nate", + // "Palmer" + // }; + // var countChanging = 0; + // var countChanged = 0; + // var collectionChanged = 0; + // var currentCount = 4; + // var countChange = -2; + // var removing = new[] { "Carmack", "Palmer" }; + + // //hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); + // hashSet.PropertyChanged += (s, a) => AssertCountChanged(hashSet, s, a, ref currentCount, countChange, ref countChanged); + // hashSet.CollectionChanged += (s, a) => + // { + // Assert.Equal(NotifyCollectionChangedAction.Replace, a.Action); + // Assert.Equal(removing, a.OldItems.OfType().OrderBy(i => i)); + // Assert.Empty(a.NewItems); + // collectionChanged++; + // }; + + // Assert.Equal(2, hashSet.RemoveWhere(i => i.Contains("m"))); + + // Assert.Equal(1, countChanging); + // Assert.Equal(1, countChanged); + // Assert.Equal(1, collectionChanged); + // Assert.Equal(new[] { "Brendan", "Nate" }, hashSet.OrderBy(i => i)); + + // Assert.Equal(0, hashSet.RemoveWhere(i => i.Contains("m"))); + + // Assert.Equal(1, countChanging); + // Assert.Equal(1, countChanged); + // Assert.Equal(1, collectionChanged); + // Assert.Equal(new[] { "Brendan", "Nate" }, hashSet.OrderBy(i => i)); + //} + + //[ConditionalFact] + //public void ToBindingList_returns_a_new_binding_list_each_time_when_called_on_non_DbLocalView_ObservableCollections() + //{ + // var oc = new ObservableCollection(); + + // var bindingList = oc.ToBindingList(); + // Assert.NotNull(bindingList); + + // var bindingListAgain = oc.ToBindingList(); + // Assert.NotNull(bindingListAgain); + // Assert.NotSame(bindingList, bindingListAgain); + //} + + //private static void AssertCountChanging( + // ObservableHashSet hashSet, + // object sender, + // PropertyChangingEventArgs eventArgs, + // int expectedCount, + // ref int changingCount) + //{ + // Assert.Same(hashSet, sender); + // Assert.Equal("Count", eventArgs.PropertyName); + // Assert.Equal(expectedCount, hashSet.Count); + // changingCount++; + //} + + private static void AssertCountChanged( + CiccioSet hashSet, + object sender, + PropertyChangedEventArgs eventArgs, + ref int expectedCount, + int countDelta, + ref int changedCount) + { + Assert.Same(hashSet, sender); + Assert.Equal("Count", eventArgs.PropertyName); + Assert.Equal(expectedCount + countDelta, hashSet.Count); + changedCount++; + expectedCount += countDelta; + } + + private static List CreateTestData(int minSize = 0, int maxLength = 1000) + { + var length = _random.Next(minSize, maxLength); + var data = new List(); + for (var i = 0; i < length; i++) + { + data.Add(_random.Next(int.MinValue, int.MaxValue)); + } + + return data; + } + } +} diff --git a/TestProject/ObservableList/ObservableList_ConstructorAndPropertyTests.cs b/TestProject/ObservableList/ObservableList_ConstructorAndPropertyTests.cs index c2e5a24..9ae459e 100644 --- a/TestProject/ObservableList/ObservableList_ConstructorAndPropertyTests.cs +++ b/TestProject/ObservableList/ObservableList_ConstructorAndPropertyTests.cs @@ -142,7 +142,7 @@ private partial class ObservableListSubclass : ObservableList { public ObservableListSubclass(IEnumerable collection) : base(collection) { } - public List InnerList => (List)base.items; + public List InnerList => (List)base._list; } /// diff --git a/TestProject/ObservableSet/ObservableSetTest.cs b/TestProject/ObservableSet/ObservableSetTest.cs index 6789c05..f361799 100644 --- a/TestProject/ObservableSet/ObservableSetTest.cs +++ b/TestProject/ObservableSet/ObservableSetTest.cs @@ -43,15 +43,15 @@ public void Can_construct() [ConditionalFact] public void Can_add() { - var hashSet = new ObservableHashSet(); - var countChanging = 0; + var hashSet = new ObservableSet(); + //var countChanging = 0; var countChanged = 0; var collectionChanged = 0; var currentCount = 0; var countChange = 1; var adding = Array.Empty(); - hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); + //hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); hashSet.PropertyChanged += (s, a) => AssertCountChanged(hashSet, s, a, ref currentCount, countChange, ref countChanged); hashSet.CollectionChanged += (s, a) => { @@ -64,7 +64,7 @@ public void Can_add() adding = new[] { "Palmer" }; Assert.True(hashSet.Add("Palmer")); - Assert.Equal(1, countChanging); + //Assert.Equal(1, countChanging); Assert.Equal(1, countChanged); Assert.Equal(1, collectionChanged); Assert.Equal(new[] { "Palmer" }, hashSet); @@ -72,14 +72,14 @@ public void Can_add() adding = new[] { "Carmack" }; Assert.True(hashSet.Add("Carmack")); - Assert.Equal(2, countChanging); + //Assert.Equal(2, countChanging); Assert.Equal(2, countChanged); Assert.Equal(2, collectionChanged); Assert.Equal(new[] { "Carmack", "Palmer" }, hashSet.OrderBy(i => i)); Assert.False(hashSet.Add("Palmer")); - Assert.Equal(2, countChanging); + //Assert.Equal(2, countChanging); Assert.Equal(2, countChanged); Assert.Equal(2, collectionChanged); Assert.Equal(new[] { "Carmack", "Palmer" }, hashSet.OrderBy(i => i)); @@ -90,14 +90,14 @@ public void Can_clear() { var testData = new HashSet(CreateTestData()); - var hashSet = new ObservableHashSet(testData); - var countChanging = 0; + var hashSet = new ObservableSet(testData); + //var countChanging = 0; var countChanged = 0; var collectionChanged = 0; var currentCount = testData.Count; var countChange = -testData.Count; - hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); + //hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); hashSet.PropertyChanged += (s, a) => AssertCountChanged(hashSet, s, a, ref currentCount, countChange, ref countChanged); hashSet.CollectionChanged += (s, a) => { @@ -109,14 +109,14 @@ public void Can_clear() hashSet.Clear(); - Assert.Equal(testData.Count == 0 ? 0 : 1, countChanging); + //Assert.Equal(testData.Count == 0 ? 0 : 1, countChanging); Assert.Equal(testData.Count == 0 ? 0 : 1, countChanged); Assert.Equal(testData.Count == 0 ? 0 : 1, collectionChanged); Assert.Empty(hashSet); hashSet.Clear(); - Assert.Equal(testData.Count == 0 ? 0 : 1, countChanging); + //Assert.Equal(testData.Count == 0 ? 0 : 1, countChanging); Assert.Equal(testData.Count == 0 ? 0 : 1, countChanged); Assert.Equal(testData.Count == 0 ? 0 : 1, collectionChanged); Assert.Empty(hashSet); @@ -126,16 +126,16 @@ public void Can_clear() public void Contains_works() { var testData = CreateTestData(); - var hashSet = new ObservableHashSet(testData); + var hashSet = new ObservableSet(testData); foreach (var item in testData) { - Assert.Contains(item, hashSet); + Assert.Contains(item, (ISet)hashSet); } foreach (var item in CreateTestData(1000, 10000).Except(testData)) { - Assert.DoesNotContain(item, hashSet); + Assert.DoesNotContain(item, (ISet)hashSet); } } @@ -145,42 +145,42 @@ public void Can_copy_to_array() var testData = CreateTestData(); var orderedDistinct = testData.Distinct().OrderBy(i => i).ToList(); - var hashSet = new ObservableHashSet(testData); + var hashSet = new ObservableSet(testData); Assert.Equal(orderedDistinct.Count, hashSet.Count); - var array = new int[hashSet.Count]; - hashSet.CopyTo(array); + //var array = new int[hashSet.Count]; + //hashSet.CopyTo(array); - Assert.Equal(orderedDistinct, array.OrderBy(i => i)); + //Assert.Equal(orderedDistinct, array.OrderBy(i => i)); - array = new int[hashSet.Count + 100]; + var array = new int[hashSet.Count + 100]; hashSet.CopyTo(array, 100); Assert.Equal(orderedDistinct, array.Skip(100).OrderBy(i => i)); - var toTake = Math.Min(10, hashSet.Count); - array = new int[100 + toTake]; - hashSet.CopyTo(array, 100, toTake); + //var toTake = Math.Min(10, hashSet.Count); + //array = new int[100 + toTake]; + //hashSet.CopyTo(array, 100, toTake); - foreach (var value in array.Skip(100).Take(toTake)) - { - Assert.Contains(value, hashSet); - } + //foreach (var value in array.Skip(100).Take(toTake)) + //{ + // Assert.Contains(value, hashSet); + //} } [ConditionalFact] public void Can_remove() { - var hashSet = new ObservableHashSet { "Palmer", "Carmack" }; - var countChanging = 0; + var hashSet = new ObservableSet { "Palmer", "Carmack" }; + //var countChanging = 0; var countChanged = 0; var collectionChanged = 0; var currentCount = 2; var countChange = -1; var removing = Array.Empty(); - hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); + //hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); hashSet.PropertyChanged += (s, a) => AssertCountChanged(hashSet, s, a, ref currentCount, countChange, ref countChanged); hashSet.CollectionChanged += (s, a) => { @@ -193,7 +193,7 @@ public void Can_remove() removing = new[] { "Palmer" }; Assert.True(hashSet.Remove("Palmer")); - Assert.Equal(1, countChanging); + //Assert.Equal(1, countChanging); Assert.Equal(1, countChanged); Assert.Equal(1, collectionChanged); Assert.Equal(new[] { "Carmack" }, hashSet); @@ -201,14 +201,14 @@ public void Can_remove() removing = new[] { "Carmack" }; Assert.True(hashSet.Remove("Carmack")); - Assert.Equal(2, countChanging); + //Assert.Equal(2, countChanging); Assert.Equal(2, countChanged); Assert.Equal(2, collectionChanged); Assert.Empty(hashSet); Assert.False(hashSet.Remove("Palmer")); - Assert.Equal(2, countChanging); + //Assert.Equal(2, countChanging); Assert.Equal(2, countChanged); Assert.Equal(2, collectionChanged); Assert.Empty(hashSet); @@ -216,20 +216,20 @@ public void Can_remove() [ConditionalFact] public void Not_read_only() - => Assert.False(new ObservableHashSet().IsReadOnly); + => Assert.False(((ICollection)new ObservableSet()).IsReadOnly); [ConditionalFact] public void Can_union_with() { - var hashSet = new ObservableHashSet { "Palmer", "Carmack" }; - var countChanging = 0; + var hashSet = new ObservableSet { "Palmer", "Carmack" }; + //var countChanging = 0; var countChanged = 0; var collectionChanged = 0; var currentCount = 2; var countChange = 2; var adding = new[] { "Brendan", "Nate" }; - hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); + //hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); hashSet.PropertyChanged += (s, a) => AssertCountChanged(hashSet, s, a, ref currentCount, countChange, ref countChanged); hashSet.CollectionChanged += (s, a) => { @@ -241,14 +241,14 @@ public void Can_union_with() hashSet.UnionWith(new[] { "Carmack", "Nate", "Brendan" }); - Assert.Equal(1, countChanging); + //Assert.Equal(1, countChanging); Assert.Equal(1, countChanged); Assert.Equal(1, collectionChanged); Assert.Equal(new[] { "Brendan", "Carmack", "Nate", "Palmer" }, hashSet.OrderBy(i => i)); hashSet.UnionWith(new[] { "Brendan" }); - Assert.Equal(1, countChanging); + //Assert.Equal(1, countChanging); Assert.Equal(1, countChanged); Assert.Equal(1, collectionChanged); Assert.Equal(new[] { "Brendan", "Carmack", "Nate", "Palmer" }, hashSet.OrderBy(i => i)); @@ -257,21 +257,21 @@ public void Can_union_with() [ConditionalFact] public void Can_intersect_with() { - var hashSet = new ObservableHashSet + var hashSet = new ObservableSet { "Brendan", "Carmack", "Nate", "Palmer" }; - var countChanging = 0; + //var countChanging = 0; var countChanged = 0; var collectionChanged = 0; var currentCount = 4; var countChange = -2; var removing = new[] { "Brendan", "Nate" }; - hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); + //hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); hashSet.PropertyChanged += (s, a) => AssertCountChanged(hashSet, s, a, ref currentCount, countChange, ref countChanged); hashSet.CollectionChanged += (s, a) => { @@ -283,14 +283,14 @@ public void Can_intersect_with() hashSet.IntersectWith(new[] { "Carmack", "Palmer", "Abrash" }); - Assert.Equal(1, countChanging); + //Assert.Equal(1, countChanging); Assert.Equal(1, countChanged); Assert.Equal(1, collectionChanged); Assert.Equal(new[] { "Carmack", "Palmer" }, hashSet.OrderBy(i => i)); hashSet.IntersectWith(new[] { "Carmack", "Palmer", "Abrash" }); - Assert.Equal(1, countChanging); + //Assert.Equal(1, countChanging); Assert.Equal(1, countChanged); Assert.Equal(1, collectionChanged); Assert.Equal(new[] { "Carmack", "Palmer" }, hashSet.OrderBy(i => i)); @@ -299,21 +299,21 @@ public void Can_intersect_with() [ConditionalFact] public void Can_except_with() { - var hashSet = new ObservableHashSet + var hashSet = new ObservableSet { "Brendan", "Carmack", "Nate", "Palmer" }; - var countChanging = 0; + //var countChanging = 0; var countChanged = 0; var collectionChanged = 0; var currentCount = 4; var countChange = -2; var removing = new[] { "Carmack", "Palmer" }; - hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); + //hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); hashSet.PropertyChanged += (s, a) => AssertCountChanged(hashSet, s, a, ref currentCount, countChange, ref countChanged); hashSet.CollectionChanged += (s, a) => { @@ -325,14 +325,14 @@ public void Can_except_with() hashSet.ExceptWith(new[] { "Carmack", "Palmer", "Abrash" }); - Assert.Equal(1, countChanging); + //Assert.Equal(1, countChanging); Assert.Equal(1, countChanged); Assert.Equal(1, collectionChanged); Assert.Equal(new[] { "Brendan", "Nate" }, hashSet.OrderBy(i => i)); hashSet.ExceptWith(new[] { "Abrash", "Carmack", "Palmer" }); - Assert.Equal(1, countChanging); + //Assert.Equal(1, countChanging); Assert.Equal(1, countChanged); Assert.Equal(1, collectionChanged); Assert.Equal(new[] { "Brendan", "Nate" }, hashSet.OrderBy(i => i)); @@ -341,14 +341,14 @@ public void Can_except_with() [ConditionalFact] public void Can_symmetrical_except_with() { - var hashSet = new ObservableHashSet + var hashSet = new ObservableSet { "Brendan", "Carmack", "Nate", "Palmer" }; - var countChanging = 0; + //var countChanging = 0; var countChanged = 0; var collectionChanged = 0; var currentCount = 4; @@ -356,7 +356,7 @@ public void Can_symmetrical_except_with() var removing = new[] { "Carmack", "Palmer" }; var adding = new[] { "Abrash" }; - hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); + //hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); hashSet.PropertyChanged += (s, a) => AssertCountChanged(hashSet, s, a, ref currentCount, countChange, ref countChanged); hashSet.CollectionChanged += (s, a) => { @@ -368,14 +368,14 @@ public void Can_symmetrical_except_with() hashSet.SymmetricExceptWith(new[] { "Carmack", "Palmer", "Abrash" }); - Assert.Equal(1, countChanging); + //Assert.Equal(1, countChanging); Assert.Equal(1, countChanged); Assert.Equal(1, collectionChanged); Assert.Equal(new[] { "Abrash", "Brendan", "Nate" }, hashSet.OrderBy(i => i)); hashSet.SymmetricExceptWith(Array.Empty()); - Assert.Equal(1, countChanging); + //Assert.Equal(1, countChanging); Assert.Equal(1, countChanged); Assert.Equal(1, collectionChanged); Assert.Equal(new[] { "Abrash", "Brendan", "Nate" }, hashSet.OrderBy(i => i)); @@ -447,62 +447,62 @@ public void SetEquals_works_like_normal_hashset() new ObservableSet(data1).SetEquals(data2)); } - [ConditionalFact] - public void TrimExcess_doesnt_throw() - { - var bigData = CreateTestData(); - var smallData = CreateTestData(10); - - var hashSet = new ObservableHashSet(bigData.Concat(smallData)); - foreach (var item in bigData) - { - hashSet.Remove(item); - } - - hashSet.TrimExcess(); - } - - [ConditionalFact] - public void Can_remove_with_predicate() - { - var hashSet = new ObservableHashSet - { - "Brendan", - "Carmack", - "Nate", - "Palmer" - }; - var countChanging = 0; - var countChanged = 0; - var collectionChanged = 0; - var currentCount = 4; - var countChange = -2; - var removing = new[] { "Carmack", "Palmer" }; - - hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); - hashSet.PropertyChanged += (s, a) => AssertCountChanged(hashSet, s, a, ref currentCount, countChange, ref countChanged); - hashSet.CollectionChanged += (s, a) => - { - Assert.Equal(NotifyCollectionChangedAction.Replace, a.Action); - Assert.Equal(removing, a.OldItems.OfType().OrderBy(i => i)); - Assert.Empty(a.NewItems); - collectionChanged++; - }; - - Assert.Equal(2, hashSet.RemoveWhere(i => i.Contains("m"))); + //[ConditionalFact] + //public void TrimExcess_doesnt_throw() + //{ + // var bigData = CreateTestData(); + // var smallData = CreateTestData(10); - Assert.Equal(1, countChanging); - Assert.Equal(1, countChanged); - Assert.Equal(1, collectionChanged); - Assert.Equal(new[] { "Brendan", "Nate" }, hashSet.OrderBy(i => i)); + // var hashSet = new ObservableHashSet(bigData.Concat(smallData)); + // foreach (var item in bigData) + // { + // hashSet.Remove(item); + // } - Assert.Equal(0, hashSet.RemoveWhere(i => i.Contains("m"))); + // hashSet.TrimExcess(); + //} - Assert.Equal(1, countChanging); - Assert.Equal(1, countChanged); - Assert.Equal(1, collectionChanged); - Assert.Equal(new[] { "Brendan", "Nate" }, hashSet.OrderBy(i => i)); - } + //[ConditionalFact] + //public void Can_remove_with_predicate() + //{ + // var hashSet = new ObservableSet + // { + // "Brendan", + // "Carmack", + // "Nate", + // "Palmer" + // }; + // var countChanging = 0; + // var countChanged = 0; + // var collectionChanged = 0; + // var currentCount = 4; + // var countChange = -2; + // var removing = new[] { "Carmack", "Palmer" }; + + // //hashSet.PropertyChanging += (s, a) => AssertCountChanging(hashSet, s, a, currentCount, ref countChanging); + // hashSet.PropertyChanged += (s, a) => AssertCountChanged(hashSet, s, a, ref currentCount, countChange, ref countChanged); + // hashSet.CollectionChanged += (s, a) => + // { + // Assert.Equal(NotifyCollectionChangedAction.Replace, a.Action); + // Assert.Equal(removing, a.OldItems.OfType().OrderBy(i => i)); + // Assert.Empty(a.NewItems); + // collectionChanged++; + // }; + + // Assert.Equal(2, hashSet.RemoveWhere(i => i.Contains("m"))); + + // Assert.Equal(1, countChanging); + // Assert.Equal(1, countChanged); + // Assert.Equal(1, collectionChanged); + // Assert.Equal(new[] { "Brendan", "Nate" }, hashSet.OrderBy(i => i)); + + // Assert.Equal(0, hashSet.RemoveWhere(i => i.Contains("m"))); + + // Assert.Equal(1, countChanging); + // Assert.Equal(1, countChanged); + // Assert.Equal(1, collectionChanged); + // Assert.Equal(new[] { "Brendan", "Nate" }, hashSet.OrderBy(i => i)); + //} //[ConditionalFact] //public void ToBindingList_returns_a_new_binding_list_each_time_when_called_on_non_DbLocalView_ObservableCollections() @@ -517,21 +517,21 @@ public void Can_remove_with_predicate() // Assert.NotSame(bindingList, bindingListAgain); //} - private static void AssertCountChanging( - ObservableHashSet hashSet, - object sender, - PropertyChangingEventArgs eventArgs, - int expectedCount, - ref int changingCount) - { - Assert.Same(hashSet, sender); - Assert.Equal("Count", eventArgs.PropertyName); - Assert.Equal(expectedCount, hashSet.Count); - changingCount++; - } + //private static void AssertCountChanging( + // ObservableHashSet hashSet, + // object sender, + // PropertyChangingEventArgs eventArgs, + // int expectedCount, + // ref int changingCount) + //{ + // Assert.Same(hashSet, sender); + // Assert.Equal("Count", eventArgs.PropertyName); + // Assert.Equal(expectedCount, hashSet.Count); + // changingCount++; + //} private static void AssertCountChanged( - ObservableHashSet hashSet, + ObservableSet hashSet, object sender, PropertyChangedEventArgs eventArgs, ref int expectedCount,