#region Apache License 2.0 /* Nuclex .NET Framework Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #endregion // Apache License 2.0 using System; using System.Collections; using System.Collections.Generic; #if !NO_SPECIALIZED_COLLECTIONS using System.Collections.Specialized; #endif using System.Runtime.Serialization; namespace Nuclex.Support.Collections { /// A dictionary that sneds out change notifications /// Type of the keys used in the dictionary /// Type of the values used in the dictionary #if !NO_SERIALIZATION [Serializable] #endif public class ObservableDictionary : #if !NO_SERIALIZATION ISerializable, IDeserializationCallback, #endif IDictionary, IDictionary, #if !NO_SPECIALIZED_COLLECTIONS INotifyCollectionChanged, #endif IObservableCollection> { #if !NO_SERIALIZATION #region class SerializedDictionary /// /// Dictionary wrapped used to reconstruct a serialized read only dictionary /// private class SerializedDictionary : Dictionary { /// /// Initializes a new instance of the System.WeakReference class, using deserialized /// data from the specified serialization and stream objects. /// /// /// An object that holds all the data needed to serialize or deserialize the /// current System.WeakReference object. /// /// /// (Reserved) Describes the source and destination of the serialized stream /// specified by info. /// /// /// The info parameter is null. /// public SerializedDictionary(SerializationInfo info, StreamingContext context) : base(info, context) { } } #endregion // class SerializedDictionary #endif // !NO_SERIALIZATION /// Raised when an item has been added to the dictionary public event EventHandler>> ItemAdded; /// Raised when an item is removed from the dictionary public event EventHandler>> ItemRemoved; /// Raised when an item is replaced in the collection public event EventHandler>> ItemReplaced; /// Raised when the dictionary is about to be cleared public event EventHandler Clearing; /// Raised when the dictionary has been cleared public event EventHandler Cleared; #if !NO_SPECIALIZED_COLLECTIONS /// Called when the collection has changed public event NotifyCollectionChangedEventHandler CollectionChanged; #endif /// Initializes a new observable dictionary public ObservableDictionary() : this(new Dictionary()) { } /// Initializes a new observable Dictionary wrapper /// Dictionary that will be wrapped public ObservableDictionary(IDictionary dictionary) { this.typedDictionary = dictionary; this.objectDictionary = (this.typedDictionary as IDictionary); } #if !NO_SERIALIZATION /// /// Initializes a new instance of the System.WeakReference class, using deserialized /// data from the specified serialization and stream objects. /// /// /// An object that holds all the data needed to serialize or deserialize the /// current System.WeakReference object. /// /// /// (Reserved) Describes the source and destination of the serialized stream /// specified by info. /// /// /// The info parameter is null. /// protected ObservableDictionary(SerializationInfo info, StreamingContext context) : this(new SerializedDictionary(info, context)) { } #endif // !NO_SERIALIZATION /// Whether the directory is write-protected public bool IsReadOnly { get { return this.typedDictionary.IsReadOnly; } } /// /// Determines whether the specified KeyValuePair is contained in the Dictionary /// /// KeyValuePair that will be checked for /// True if the provided KeyValuePair was contained in the Dictionary public bool Contains(KeyValuePair item) { return this.typedDictionary.Contains(item); } /// Determines whether the Dictionary contains the specified key /// Key that will be checked for /// /// True if an entry with the specified key was contained in the Dictionary /// public bool ContainsKey(TKey key) { return this.typedDictionary.ContainsKey(key); } /// Copies the contents of the Dictionary into an array /// Array the Dictionary will be copied into /// /// Starting index at which to begin filling the destination array /// public void CopyTo(KeyValuePair[] array, int arrayIndex) { this.typedDictionary.CopyTo(array, arrayIndex); } /// Number of elements contained in the Dictionary public int Count { get { return this.typedDictionary.Count; } } /// Creates a new enumerator for the Dictionary /// The new Dictionary enumerator public IEnumerator> GetEnumerator() { return this.typedDictionary.GetEnumerator(); } /// Collection of all keys contained in the Dictionary public ICollection Keys { get { return this.typedDictionary.Keys; } } /// Collection of all values contained in the Dictionary public ICollection Values { get { return this.typedDictionary.Values; } } /// /// Attempts to retrieve the item with the specified key from the Dictionary /// /// Key of the item to attempt to retrieve /// /// Output parameter that will receive the key upon successful completion /// /// /// True if the item was found and has been placed in the output parameter /// public bool TryGetValue(TKey key, out TValue value) { return this.typedDictionary.TryGetValue(key, out value); } /// Accesses an item in the Dictionary by its key /// Key of the item that will be accessed public TValue this[TKey key] { get { return this.typedDictionary[key]; } set { bool removed; TValue oldValue; removed = this.typedDictionary.TryGetValue(key, out oldValue); this.typedDictionary[key] = value; if(removed) { OnReplaced( new KeyValuePair(key, oldValue), new KeyValuePair(key, value) ); } else { OnAdded(new KeyValuePair(key, value)); } } } /// Inserts an item into the Dictionary /// Key under which to add the new item /// Item that will be added to the Dictionary public void Add(TKey key, TValue value) { this.typedDictionary.Add(key, value); OnAdded(new KeyValuePair(key, value)); } /// Removes the item with the specified key from the Dictionary /// Key of the elementes that will be removed /// True if an item with the specified key was found and removed public bool Remove(TKey key) { TValue oldValue; this.typedDictionary.TryGetValue(key, out oldValue); bool removed = this.typedDictionary.Remove(key); if(removed) { OnRemoved(new KeyValuePair(key, oldValue)); } return removed; } /// Removes all items from the Dictionary public void Clear() { OnClearing(); this.typedDictionary.Clear(); OnCleared(); } /// Fires the 'ItemAdded' event /// Item that has been added to the collection protected virtual void OnAdded(KeyValuePair item) { if(ItemAdded != null) ItemAdded(this, new ItemEventArgs>(item)); #if !NO_SPECIALIZED_COLLECTIONS if(CollectionChanged != null) { CollectionChanged( this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item) ); } #endif } /// Fires the 'ItemRemoved' event /// Item that has been removed from the collection protected virtual void OnRemoved(KeyValuePair item) { if(ItemRemoved != null) { ItemRemoved(this, new ItemEventArgs>(item)); } #if !NO_SPECIALIZED_COLLECTIONS if(CollectionChanged != null) { CollectionChanged( this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item) ); } #endif } /// Fires the 'ItemReplaced' event /// Item that has been replaced in the collection /// Item with which the original item was replaced protected virtual void OnReplaced( KeyValuePair oldItem, KeyValuePair newItem ) { if(ItemReplaced != null) { ItemReplaced( this, new ItemReplaceEventArgs>(oldItem, newItem) ); } #if !NO_SPECIALIZED_COLLECTIONS if(CollectionChanged != null) { CollectionChanged( this, new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Replace, newItem, oldItem ) ); } #endif } /// Fires the 'Clearing' event protected virtual void OnClearing() { if(Clearing != null) { Clearing(this, EventArgs.Empty); } } /// Fires the 'Cleared' event protected virtual void OnCleared() { if(Cleared != null) { Cleared(this, EventArgs.Empty); } #if !NO_SPECIALIZED_COLLECTIONS if(CollectionChanged != null) { CollectionChanged(this, Constants.NotifyCollectionResetEventArgs); } #endif } #region IEnumerable implementation /// Returns a new object enumerator for the Dictionary /// The new object enumerator System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return (this.typedDictionary as IEnumerable).GetEnumerator(); } #endregion #region IDictionary implementation /// Adds an item into the Dictionary /// Key under which the item will be added /// Item that will be added void IDictionary.Add(object key, object value) { this.objectDictionary.Add(key, value); OnAdded(new KeyValuePair((TKey)key, (TValue)value)); } /// Determines whether the specified key exists in the Dictionary /// Key that will be checked for /// True if an item with the specified key exists in the Dictionary bool IDictionary.Contains(object key) { return this.objectDictionary.Contains(key); } /// Whether the size of the Dictionary is fixed bool IDictionary.IsFixedSize { get { return this.objectDictionary.IsFixedSize; } } /// Returns a collection of all keys in the Dictionary ICollection IDictionary.Keys { get { return this.objectDictionary.Keys; } } /// Returns a collection of all values stored in the Dictionary ICollection IDictionary.Values { get { return this.objectDictionary.Values; } } /// Removes an item from the Dictionary /// Key of the item that will be removed void IDictionary.Remove(object key) { TValue value; bool removed = this.typedDictionary.TryGetValue((TKey)key, out value); this.objectDictionary.Remove(key); if(removed) { OnRemoved(new KeyValuePair((TKey)key, (TValue)value)); } } /// Accesses an item in the Dictionary by its key /// Key of the item that will be accessed /// The item with the specified key object IDictionary.this[object key] { get { return this.objectDictionary[key]; } set { bool removed; TValue oldValue; removed = this.typedDictionary.TryGetValue((TKey)key, out oldValue); this.objectDictionary[key] = value; if(removed) { OnRemoved(new KeyValuePair((TKey)key, oldValue)); } OnAdded(new KeyValuePair((TKey)key, (TValue)value)); } } #endregion // IDictionary implementation #region IDictionaryEnumerator implementation /// Returns a new entry enumerator for the dictionary /// The new entry enumerator IDictionaryEnumerator IDictionary.GetEnumerator() { return this.objectDictionary.GetEnumerator(); } #endregion // IDictionaryEnumerator implementation #region ICollection<> implementation /// Inserts an already prepared element into the Dictionary /// Prepared element that will be added to the Dictionary void ICollection>.Add( KeyValuePair item ) { this.typedDictionary.Add(item); OnAdded(item); } /// Removes all items from the Dictionary void ICollection>.Clear() { OnClearing(); this.typedDictionary.Clear(); OnCleared(); } /// Removes all items from the Dictionary /// Item that will be removed from the Dictionary bool ICollection>.Remove( KeyValuePair itemToRemove ) { bool removed = this.typedDictionary.Remove(itemToRemove); if(removed) { OnRemoved(itemToRemove); } return removed; } #endregion #region ICollection implementation /// Copies the contents of the Dictionary into an array /// Array the Dictionary contents will be copied into /// /// Starting index at which to begin filling the destination array /// void ICollection.CopyTo(Array array, int index) { this.objectDictionary.CopyTo(array, index); } /// Whether the Dictionary is synchronized for multi-threaded usage bool ICollection.IsSynchronized { get { return this.objectDictionary.IsSynchronized; } } /// Synchronization root on which the Dictionary locks object ICollection.SyncRoot { get { return this.objectDictionary.SyncRoot; } } #endregion #if !NO_SERIALIZATION #region ISerializable implementation /// Serializes the Dictionary /// /// Provides the container into which the Dictionary will serialize itself /// /// /// Contextual informations about the serialization environment /// void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { (this.typedDictionary as ISerializable).GetObjectData(info, context); } /// Called after all objects have been successfully deserialized /// Nicht unterstützt void IDeserializationCallback.OnDeserialization(object sender) { (this.typedDictionary as IDeserializationCallback).OnDeserialization(sender); } #endregion #endif //!NO_SERIALIZATION /// The wrapped Dictionary under its type-safe interface private IDictionary typedDictionary; /// The wrapped Dictionary under its object interface private IDictionary objectDictionary; } } // namespace Nuclex.Support.Collections