Skip to content

Commit

Permalink
feat: add collections
Browse files Browse the repository at this point in the history
  • Loading branch information
sandre58 committed Jun 13, 2024
1 parent 0c7a263 commit f6c9d74
Show file tree
Hide file tree
Showing 5 changed files with 526 additions and 0 deletions.
174 changes: 174 additions & 0 deletions src/MyNet.Utilities/Collections/ExtendedObservableCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using MyNet.Utilities.Deferring;

namespace MyNet.Utilities.Collections;

/// <summary>
/// An override of observable collection which allows the suspension of notifications.
/// </summary>
/// <typeparam name="T">The type of the item.</typeparam>
public class ExtendedObservableCollection<T> : ObservableCollection<T>
{
private bool _suspendCount;

private bool _suspendNotifications;

/// <summary>
/// Initializes a new instance of the <see cref="ObservableCollectionExtended{T}"/> class.
/// </summary>
public ExtendedObservableCollection()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="ObservableCollectionExtended{T}"/> class that contains elements copied from the specified list.
/// </summary>
/// <param name="list">The list from which the elements are copied.</param><exception cref="ArgumentNullException">The <paramref name="list"/> parameter cannot be null.</exception>
public ExtendedObservableCollection(List<T> list)
: base(list)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="ObservableCollectionExtended{T}"/> class that contains elements copied from the specified collection.
/// </summary>
/// <param name="collection">The collection from which the elements are copied.</param><exception cref="ArgumentNullException">The <paramref name="collection"/> parameter cannot be null.</exception>
public ExtendedObservableCollection(IEnumerable<T> collection)
: base(collection)
{
}

/// <summary>
/// Adds the elements of the specified collection to the end of the collection.
/// </summary>
/// <param name="collection">The collection whose elements should be added to the end of the List. The collection itself cannot be null, but it can contain elements that are null.</param>
/// <exception cref="ArgumentNullException"><paramref name="collection" /> is null.</exception>
public void AddRange(IEnumerable<T> collection)
{
ArgumentNullException.ThrowIfNull(collection);

foreach (var item in collection)
{
Add(item);
}
}

/// <summary>
/// Inserts the elements of a collection into the <see cref="ObservableCollectionExtended{T}" /> at the specified index.
/// </summary>
/// <param name="collection">Inserts the items at the specified index.</param>
/// <param name="index">The zero-based index at which the new elements should be inserted.</param>
/// <exception cref="ArgumentNullException"><paramref name="collection" /> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index" /> is less than 0.-or-<paramref name="index" /> is greater than Count.</exception>
public void InsertRange(IEnumerable<T> collection, int index)
{
ArgumentNullException.ThrowIfNull(collection);

foreach (var item in collection)
{
InsertItem(index++, item);
}
}

/// <summary>
/// Clears the list and Loads the specified items.
/// </summary>
/// <param name="items">The items.</param>
public void Load(IEnumerable<T> items)
{
ArgumentNullException.ThrowIfNull(items);

CheckReentrancy();
Clear();

foreach (var item in items)
{
Add(item);
}
}

/// <summary>
/// Removes a range of elements from the <see cref="ObservableCollectionExtended{T}"/>.
/// </summary>
/// <param name="index">The zero-based starting index of the range of elements to remove.</param><param name="count">The number of elements to remove.</param><exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than 0.-or-<paramref name="count"/> is less than 0.</exception><exception cref="ArgumentException"><paramref name="index"/> and <paramref name="count"/> do not denote a valid range of elements in the <see cref="List{T}"/>.</exception>
public void RemoveRange(int index, int count)
{
for (var i = 0; i < count; i++)
{
RemoveAt(index);
}
}

/// <summary>
/// Suspends count notifications.
/// </summary>
/// <returns>A disposable when disposed will reset the count.</returns>
public IDisposable SuspendCount()
{
var count = Count;
_suspendCount = true;
return new Deferrer(
() =>
{
_suspendCount = false;
if (Count != count)
{
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
}
}).Defer();
}

/// <summary>
/// Suspends notifications. When disposed, a reset notification is fired.
/// </summary>
/// <returns>A disposable when disposed will reset notifications.</returns>
public IDisposable SuspendNotifications()
{
_suspendCount = true;
_suspendNotifications = true;

return new Deferrer(
() =>
{
_suspendCount = false;
_suspendNotifications = false;
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}).Defer();
}

/// <summary>
/// Raises the <see cref="INotifyCollectionChanged.CollectionChanged" /> event.
/// </summary>
/// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (_suspendNotifications)
{
return;
}

base.OnCollectionChanged(e);
}

/// <summary>
/// Raises the <see cref="INotifyPropertyChanged.PropertyChanged" /> event.
/// </summary>
/// <param name="e">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
ArgumentNullException.ThrowIfNull(e);

if (_suspendCount && e.PropertyName == "Count")
{
return;
}

base.OnPropertyChanged(e);
}
}
209 changes: 209 additions & 0 deletions src/MyNet.Utilities/Collections/ObservableKeyedCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// Copyright (c) Stéphane ANDRE. All Right Reserved.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace MyNet.Utilities.Collections
{
public abstract class ObservableKeyedCollection<TKey, T> : SortableObservableCollection<T>
where TKey : notnull
{
private Dictionary<TKey, T>? _dict;

protected ObservableKeyedCollection(IEnumerable<T> list) : this(list, null) { }

protected ObservableKeyedCollection() : this([], null) { }

protected ObservableKeyedCollection(IEqualityComparer<TKey> comparer) : this([], comparer) { }

protected ObservableKeyedCollection(Func<T, object> sortSelector, ListSortDirection direction = ListSortDirection.Ascending) : base(sortSelector, direction) => Comparer = EqualityComparer<TKey>.Default;

protected ObservableKeyedCollection(IEnumerable<T> list, IEqualityComparer<TKey>? comparer) : base(list)
{
comparer ??= EqualityComparer<TKey>.Default;

Comparer = comparer;
}

public IEqualityComparer<TKey> Comparer { get; }

public T? this[TKey key]
=> key switch
{
null => throw new ArgumentNullException(nameof(key)),
_ => _dict is not null && _dict.TryGetValue(key, out var value) ? value : Items.FirstOrDefault(x => Comparer.Equals(GetKeyForItem(x), key))
};

public bool Contains(TKey key)
=> key switch
{
null => throw new ArgumentNullException(nameof(key)),
_ => _dict is not null ? _dict.ContainsKey(key) : Items.Any(x => Comparer.Equals(GetKeyForItem(x), key))
};

private bool ContainsItem(T item)
{
TKey key;
if (_dict is null || (key = GetKeyForItem(item)) is null)
{
return Items.Contains(item);
}

var exist = _dict.TryGetValue(key, out var itemInDict);
return exist && EqualityComparer<T>.Default.Equals(itemInDict, item);
}

public bool TryAdd(T item)
{
var key = GetKeyForItem(item);
if (key is null || _dict is null || _dict.ContainsKey(key)) return false;

Add(item);

return true;
}

public bool Remove(TKey key)
{
if (key is null)
{
throw new ArgumentNullException(nameof(key));
}

if (_dict is not null)
{
return _dict.ContainsKey(key) && Remove(_dict[key]);
}

for (var i = 0; i < Items.Count; i++)
{
if (Comparer.Equals(GetKeyForItem(Items[i]), key))
{
RemoveItem(i);
return true;
}
}
return false;
}

protected IDictionary<TKey, T>? Dictionary => _dict;

protected void ChangeItemKey(T item, TKey newKey)
{
// check if the item exists in the collection
if (!ContainsItem(item))
{
return;
}

var oldKey = GetKeyForItem(item);
if (!Comparer.Equals(oldKey, newKey))
{
if (newKey is not null)
{
AddKey(newKey, item);
}

if (oldKey is not null)
{
RemoveKey(oldKey);
}
}
}

protected override void ClearItems()
{
_dict?.Clear();

base.ClearItems();
}

protected abstract TKey GetKeyForItem(T item);

protected override void InsertItem(int index, T item)
{
var key = GetKeyForItem(item);
if (key is not null)
{
AddKey(key, item);
}
base.InsertItem(index, item);
}

protected void InsertItemInItems(int index, T item) => base.InsertItem(index, item);

protected override void RemoveItem(int index)
{
var key = GetKeyForItem(Items[index]);
if (key is not null)
{
RemoveKey(key);
}
base.RemoveItem(index);
}

protected override void SetItem(int index, T item)
=> ExecuteThreadSafe(() =>
{
var newKey = GetKeyForItem(item);
var oldKey = GetKeyForItem(Items[index]);
if (Comparer.Equals(oldKey, newKey))
{
if (newKey is not null && _dict is not null)
{
_dict[newKey] = item;
}
}
else
{
if (newKey is not null)
{
AddKey(newKey, item);
}
if (oldKey is not null)
{
RemoveKey(oldKey);
}
}
base.SetItem(index, item);
});

private void AddKey(TKey key, T item)
=> ExecuteThreadSafe(() =>
{
if (_dict is null)
{
CreateDictionary();
}
_dict?.Add(key, item);
});

private void CreateDictionary()
{
_dict = new Dictionary<TKey, T>(Comparer);
foreach (var item in Items)
{
var key = GetKeyForItem(item);
if (key is not null)
{
_dict.Add(key, item);
}
}
}

private void RemoveKey(TKey key)
=> ExecuteThreadSafe(() =>
{
if (_dict is not null)
{
_ = _dict.Remove(key);
}
});

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) Stéphane ANDRE. All Right Reserved.
// See the LICENSE file in the project root for more information.

using System.Collections.ObjectModel;

namespace MyNet.Utilities.Collections
{
public class ReadOnlyObservableKeyedCollection<TKey, T>(ObservableKeyedCollection<TKey, T> list) : ReadOnlyObservableCollection<T>(list)
where TKey : notnull
{
public T? this[TKey key] => ((ObservableKeyedCollection<TKey, T>)Items)[key];
}
}

0 comments on commit f6c9d74

Please sign in to comment.