using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Runtime.Serialization; namespace Umbraco.Core.Collections { /// /// An ObservableDictionary /// /// /// Assumes that the key will not change and is unique for each element in the collection. /// Collection is not thread-safe, so calls should be made single-threaded. /// /// The type of elements contained in the BindableCollection /// The type of the indexing key public class ObservableDictionary : ObservableCollection, IReadOnlyDictionary, IDictionary { protected Dictionary Indecies { get; } protected Func KeySelector { get; } /// /// Create new ObservableDictionary /// /// Selector function to create key from value /// The equality comparer to use when comparing keys, or null to use the default comparer. public ObservableDictionary(Func keySelector, IEqualityComparer equalityComparer = null) { KeySelector = keySelector ?? throw new ArgumentException(nameof(keySelector)); Indecies = new Dictionary(equalityComparer); } #region Protected Methods protected override void InsertItem(int index, TValue item) { var key = KeySelector(item); if (Indecies.ContainsKey(key)) throw new ArgumentException($"An element with the same key '{key}' already exists in the dictionary.", nameof(item)); if (index != Count) { foreach (var k in Indecies.Keys.Where(k => Indecies[k] >= index).ToList()) { Indecies[k]++; } } base.InsertItem(index, item); Indecies[key] = index; } protected override void ClearItems() { base.ClearItems(); Indecies.Clear(); } protected override void RemoveItem(int index) { var item = this[index]; var key = KeySelector(item); base.RemoveItem(index); Indecies.Remove(key); foreach (var k in Indecies.Keys.Where(k => Indecies[k] > index).ToList()) { Indecies[k]--; } } #endregion public bool ContainsKey(TKey key) { return Indecies.ContainsKey(key); } /// /// Gets or sets the element with the specified key. If setting a new value, new value must have same key. /// /// Key of element to replace /// public TValue this[TKey key] { get => this[Indecies[key]]; set { //confirm key matches if (!KeySelector(value).Equals(key)) throw new InvalidOperationException("Key of new value does not match."); if (!Indecies.ContainsKey(key)) { Add(value); } else { this[Indecies[key]] = value; } } } /// /// Replaces element at given key with new value. New value must have same key. /// /// Key of element to replace /// New value /// /// /// False if key not found public bool Replace(TKey key, TValue value) { if (!Indecies.ContainsKey(key)) return false; //confirm key matches if (!KeySelector(value).Equals(key)) throw new InvalidOperationException("Key of new value does not match."); this[Indecies[key]] = value; return true; } public void ReplaceAll(IEnumerable values) { if (values == null) throw new ArgumentNullException(nameof(values)); Clear(); foreach (var value in values) { Add(value); } } public bool Remove(TKey key) { if (!Indecies.ContainsKey(key)) return false; RemoveAt(Indecies[key]); return true; } /// /// Allows us to change the key of an item /// /// /// public void ChangeKey(TKey currentKey, TKey newKey) { if (!Indecies.ContainsKey(currentKey)) { throw new InvalidOperationException($"No item with the key '{currentKey}' was found in the dictionary."); } if (ContainsKey(newKey)) { throw new ArgumentException($"An element with the same key '{newKey}' already exists in the dictionary.", nameof(newKey)); } var currentIndex = Indecies[currentKey]; Indecies.Remove(currentKey); Indecies.Add(newKey, currentIndex); } #region IDictionary and IReadOnlyDictionary implementation public bool TryGetValue(TKey key, out TValue val) { if (Indecies.TryGetValue(key, out var index)) { val = this[index]; return true; } val = default; return false; } /// /// Returns all keys /// public IEnumerable Keys => Indecies.Keys; /// /// Returns all values /// public IEnumerable Values => base.Items; ICollection IDictionary.Keys => Indecies.Keys; //this will never be used ICollection IDictionary.Values => Values.ToList(); bool ICollection>.IsReadOnly => false; IEnumerator> IEnumerable>.GetEnumerator() { foreach (var i in Values) { var key = KeySelector(i); yield return new KeyValuePair(key, i); } } void IDictionary.Add(TKey key, TValue value) { Add(value); } void ICollection>.Add(KeyValuePair item) { Add(item.Value); } bool ICollection>.Contains(KeyValuePair item) { return ContainsKey(item.Key); } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { throw new NotImplementedException(); } bool ICollection>.Remove(KeyValuePair item) { return Remove(item.Key); } #endregion } }