#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