using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; 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 { protected Dictionary Indecies = new Dictionary(); protected Func KeySelector; /// /// Create new ObservableDictionary /// /// Selector function to create key from value public ObservableDictionary(Func keySelector) : base() { if (keySelector == null) throw new ArgumentException("keySelector"); KeySelector = keySelector; } #region Protected Methods protected override void InsertItem(int index, TValue item) { var key = KeySelector(item); if (Indecies.ContainsKey(key)) throw new DuplicateKeyException(key.ToString()); if (index != this.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 virtual 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 virtual TValue this[TKey key] { get { return 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)) { this.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 virtual 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 virtual bool Remove(TKey key) { if (!Indecies.ContainsKey(key)) return false; this.RemoveAt(Indecies[key]); return true; } /// /// Allows us to change the key of an item /// /// /// public virtual void ChangeKey(TKey currentKey, TKey newKey) { if (!Indecies.ContainsKey(currentKey)) { throw new InvalidOperationException("No item with the key " + currentKey + "was found in the collection"); } if (ContainsKey(newKey)) { throw new DuplicateKeyException(newKey.ToString()); } var currentIndex = Indecies[currentKey]; Indecies.Remove(currentKey); Indecies.Add(newKey, currentIndex); } internal class DuplicateKeyException : Exception { public string Key { get; private set; } public DuplicateKeyException(string key) : base("Attempted to insert duplicate key " + key + " in collection") { Key = key; } } } }