diff --git a/Nuclex.Support (Xbox 360).csproj b/Nuclex.Support (Xbox 360).csproj index 5a01525..d71fd52 100644 --- a/Nuclex.Support (Xbox 360).csproj +++ b/Nuclex.Support (Xbox 360).csproj @@ -77,6 +77,10 @@ ObservableCollection.cs + + + ObservableDictionary.cs + PairPriorityQueue.cs diff --git a/Nuclex.Support.csproj b/Nuclex.Support.csproj index 0507dd1..704f9b3 100644 --- a/Nuclex.Support.csproj +++ b/Nuclex.Support.csproj @@ -59,6 +59,10 @@ ObservableCollection.cs + + + ObservableDictionary.cs + PairPriorityQueue.cs diff --git a/Source/Collections/ObservableCollection.Test.cs b/Source/Collections/ObservableCollection.Test.cs index a008f47..cd539cd 100644 --- a/Source/Collections/ObservableCollection.Test.cs +++ b/Source/Collections/ObservableCollection.Test.cs @@ -34,23 +34,23 @@ namespace Nuclex.Support.Collections { #region interface IObservableCollectionSubscriber - /// Interface used to test the observable collection. + /// Interface used to test the observable collection public interface IObservableCollectionSubscriber { /// Called when the collection is about to clear its contents /// Collection that is clearing its contents - /// Not used - void Clearing(object sender, EventArgs e); + /// Not used + void Clearing(object sender, EventArgs arguments); /// Called when an item is added to the collection /// Collection to which an item is being added - /// Contains the item that is being added - void ItemAdded(object sender, ItemEventArgs e); + /// Contains the item that is being added + void ItemAdded(object sender, ItemEventArgs arguments); /// Called when an item is removed from the collection /// Collection from which an item is being removed - /// Contains the item that is being removed - void ItemRemoved(object sender, ItemEventArgs e); + /// Contains the item that is being removed + void ItemRemoved(object sender, ItemEventArgs arguments); } @@ -73,8 +73,6 @@ namespace Nuclex.Support.Collections { new EventHandler>( this.mockedSubscriber.ItemRemoved ); - - this.mockery.VerifyAllExpectationsHaveBeenMet(); } /// Tests whether the Clearing event is fired diff --git a/Source/Collections/ObservableDictionary.Test.cs b/Source/Collections/ObservableDictionary.Test.cs new file mode 100644 index 0000000..6b932f6 --- /dev/null +++ b/Source/Collections/ObservableDictionary.Test.cs @@ -0,0 +1,539 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2009 Nuclex Development Labs + +This library is free software; you can redistribute it and/or +modify it under the terms of the IBM Common Public License as +published by the IBM Corporation; either version 1.0 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +IBM Common Public License for more details. + +You should have received a copy of the IBM Common Public +License along with this library +*/ +#endregion + +#if UNITTEST + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; + +using NUnit.Framework; +using NMock2; + +namespace Nuclex.Support.Collections { + + /// Unit Test for the observable dictionary wrapper + [TestFixture] + public class ObservableDictionaryTest { + + #region interface IObservableDictionarySubscriber + + /// Interface used to test the observable dictionary + public interface IObservableDictionarySubscriber { + + /// Called when the dictionary is about to clear its contents + /// Dictionary that is clearing its contents + /// Not used + void Clearing(object sender, EventArgs arguments); + + /// Called when an item is added to the dictionary + /// Dictionary to which an item is being added + /// Contains the item that is being added + void ItemAdded(object sender, ItemEventArgs> arguments); + + /// Called when an item is removed from the dictionary + /// Dictionary from which an item is being removed + /// Contains the item that is being removed + void ItemRemoved(object sender, ItemEventArgs> arguments); + + } + + #endregion // interface IObservableDictionarySubscriber + + /// Initialization routine executed before each test is run + [SetUp] + public void Setup() { + this.mockery = new Mockery(); + + this.mockedSubscriber = this.mockery.NewMock(); + + this.observedDictionary = new ObservableDictionary(); + this.observedDictionary.Add(1, "one"); + this.observedDictionary.Add(2, "two"); + this.observedDictionary.Add(3, "three"); + this.observedDictionary.Add(42, "forty-two"); + + this.observedDictionary.Clearing += new EventHandler(this.mockedSubscriber.Clearing); + this.observedDictionary.ItemAdded += + new EventHandler>>( + this.mockedSubscriber.ItemAdded + ); + this.observedDictionary.ItemRemoved += + new EventHandler>>( + this.mockedSubscriber.ItemRemoved + ); + } + + /// + /// Verifies that the default constructor of the observable dictionary works + /// + [Test] + public void TestDefaultConstructor() { + ObservableDictionary testDictionary = + new ObservableDictionary(); + + Assert.AreEqual(0, testDictionary.Count); + } + + /// + /// Verifies that the copy constructor of the observable dictionary works + /// + [Test] + public void TestCopyConstructor() { + Dictionary numbers = createTestDictionary(); + ObservableDictionary testDictionary = makeObservable(numbers); + + CollectionAssert.AreEqual(numbers, testDictionary); + } + + /// Verifies that the IsReadOnly property is working + [Test] + public void TestIsReadOnly() { + Dictionary numbers = createTestDictionary(); + ObservableDictionary testDictionary = makeObservable(numbers); + + Assert.IsFalse(testDictionary.IsReadOnly); + } + + /// + /// Checks whether the Contains() method of the observable dictionary is able to + /// determine if the dictionary contains an item + /// + [Test] + public void TestContains() { + Dictionary numbers = createTestDictionary(); + ObservableDictionary testDictionary = makeObservable(numbers); + + Assert.IsTrue( + testDictionary.Contains(new KeyValuePair(42, "forty-two")) + ); + Assert.IsFalse( + testDictionary.Contains(new KeyValuePair(24, "twenty-four")) + ); + } + + /// + /// Checks whether the Contains() method of the observable dictionary is able to + /// determine if the dictionary contains a key + /// + [Test] + public void TestContainsKey() { + Dictionary numbers = createTestDictionary(); + ObservableDictionary testDictionary = makeObservable(numbers); + + Assert.IsTrue(testDictionary.ContainsKey(42)); + Assert.IsFalse(testDictionary.ContainsKey(24)); + } + + /// + /// Verifies that the CopyTo() of the observable dictionary works + /// + [Test] + public void TestCopyToArray() { + Dictionary numbers = createTestDictionary(); + ObservableDictionary testDictionary = makeObservable(numbers); + + KeyValuePair[] items = new KeyValuePair[numbers.Count]; + + testDictionary.CopyTo(items, 0); + + CollectionAssert.AreEqual(numbers, items); + } + + /// + /// Tests whether the typesafe enumerator of the observable dictionary is working + /// + [Test] + public void TestTypesafeEnumerator() { + Dictionary numbers = createTestDictionary(); + ObservableDictionary testDictionary = makeObservable(numbers); + + List> outputItems = new List>(); + foreach(KeyValuePair item in testDictionary) { + outputItems.Add(item); + } + + CollectionAssert.AreEqual(numbers, outputItems); + } + + /// + /// Tests whether the keys collection of the observable dictionary can be queried + /// + [Test] + public void TestGetKeysCollection() { + Dictionary numbers = createTestDictionary(); + ObservableDictionary testDictionary = makeObservable(numbers); + + ICollection inputKeys = numbers.Keys; + ICollection keys = testDictionary.Keys; + CollectionAssert.AreEquivalent(inputKeys, keys); + } + + /// + /// Tests whether the values collection of the observable dictionary can be queried + /// + [Test] + public void TestGetValuesCollection() { + Dictionary numbers = createTestDictionary(); + ObservableDictionary testDictionary = makeObservable(numbers); + + ICollection inputValues = numbers.Values; + ICollection values = testDictionary.Values; + CollectionAssert.AreEquivalent(inputValues, values); + } + + /// + /// Tests whether the TryGetValue() method of the observable dictionary is working + /// + [Test] + public void TestTryGetValue() { + string value; + + Assert.IsTrue(this.observedDictionary.TryGetValue(42, out value)); + Assert.AreEqual("forty-two", value); + + Assert.IsFalse(this.observedDictionary.TryGetValue(24, out value)); + Assert.AreEqual(null, value); + } + + /// + /// Tests whether the retrieval of values using the indexer of the observable + /// dictionary is working + /// + [Test] + public void TestRetrieveValueByIndexer() { + Assert.AreEqual("forty-two", this.observedDictionary[42]); + } + + /// + /// Tests whether an exception is thrown if the indexer of the observable dictionary + /// is used to attempt to retrieve a non-existing value + /// + [Test, ExpectedException(typeof(KeyNotFoundException))] + public void TestThrowOnRetrieveNonExistingValueByIndexer() { + string numberName = this.observedDictionary[24]; + Console.WriteLine(numberName); + } + + /// + /// Checks whether the Add() methods works via the generic + /// IDictionary<> interface + /// + [Test] + public void TestAddViaGenericIDictionary() { + Expect.Once.On(this.mockedSubscriber).Method("ItemAdded").WithAnyArguments(); + (this.observedDictionary as IDictionary).Add(10, "ten"); + this.mockery.VerifyAllExpectationsHaveBeenMet(); + + CollectionAssert.Contains( + this.observedDictionary, new KeyValuePair(10, "ten") + ); + } + + /// + /// Checks whether the Remove() method works via the generic + /// IDictionary<> interface + /// + [Test] + public void TestRemoveViaGenericIDictionary() { + Expect.Once.On(this.mockedSubscriber).Method("ItemRemoved").WithAnyArguments(); + (this.observedDictionary as IDictionary).Remove(3); + this.mockery.VerifyAllExpectationsHaveBeenMet(); + + CollectionAssert.DoesNotContain(this.observedDictionary.Keys, 3); + } + + /// + /// Tests whether the TryGetValue() method of the observable dictionary is working + /// + [Test] + public void TestRetrieveValueByIndexerViaGenericIDictionary() { + Assert.AreEqual( + "forty-two", (this.observedDictionary as IDictionary)[42] + ); + } + + /// + /// Verifies that the indexer can be used to insert an item via the generic + /// IDictionar<> interface + /// + [Test] + public void TestReplaceByIndexerViaGenericIDictionary() { + Expect.Once.On(this.mockedSubscriber).Method("ItemRemoved").WithAnyArguments(); + Expect.Once.On(this.mockedSubscriber).Method("ItemAdded").WithAnyArguments(); + (this.observedDictionary as IDictionary)[42] = "two and fourty"; + this.mockery.VerifyAllExpectationsHaveBeenMet(); + + Assert.AreEqual("two and fourty", this.observedDictionary[42]); + } + + /// + /// Checks whether the Clear() method of observable dictionary is working + /// + [Test] + public void TestClearViaIDictionary() { + Expect.Once.On(this.mockedSubscriber).Method("Clearing").WithAnyArguments(); + (this.observedDictionary as IDictionary).Clear(); + this.mockery.VerifyAllExpectationsHaveBeenMet(); + + Assert.AreEqual(0, this.observedDictionary.Count); + } + + /// + /// Checks whether the Add() method works via the IDictionary interface + /// + [Test] + public void TestAddViaIDictionary() { + Expect.Once.On(this.mockedSubscriber).Method("ItemAdded").WithAnyArguments(); + (this.observedDictionary as IDictionary).Add(24, "twenty-four"); + this.mockery.VerifyAllExpectationsHaveBeenMet(); + + CollectionAssert.Contains( + this.observedDictionary, new KeyValuePair(24, "twenty-four") + ); + } + + /// + /// Checks whether the Contains() method of the observable dictionary is able to + /// determine if the dictionary contains an item via the IDictionary interface + /// + [Test] + public void TestContainsViaIDictionary() { + Assert.IsTrue((this.observedDictionary as IDictionary).Contains(42)); + Assert.IsFalse((this.observedDictionary as IDictionary).Contains(24)); + } + + /// + /// Checks whether the GetEnumerator() method of the observable dictionary + /// returns a working enumerator if accessed via the IDictionary interface + /// + [Test] + public void TestEnumeratorViaIDictionary() { + Dictionary outputNumbers = new Dictionary(); + foreach(DictionaryEntry entry in (this.observedDictionary as IDictionary)) { + (outputNumbers as IDictionary).Add(entry.Key, entry.Value); + } + + CollectionAssert.AreEquivalent(this.observedDictionary, outputNumbers); + } + + /// + /// Checks whether the IsFixedSize property of the observable dictionary returns + /// the expected result for a read only dictionary based on a dynamic dictionary + /// + [Test] + public void TestIsFixedSizeViaIList() { + Assert.IsFalse((this.observedDictionary as IDictionary).IsFixedSize); + } + + /// + /// Tests whether the keys collection of the observable dictionary can be queried + /// via the IDictionary interface + /// + [Test] + public void TestGetKeysCollectionViaIDictionary() { + ICollection keys = (this.observedDictionary as IDictionary).Keys; + Assert.AreEqual(this.observedDictionary.Count, keys.Count); + } + + /// + /// Tests whether the values collection of the observable dictionary can be queried + /// via the IDictionary interface + /// + [Test] + public void TestGetValuesCollectionViaIDictionary() { + ICollection values = (this.observedDictionary as IDictionary).Values; + Assert.AreEqual(this.observedDictionary.Count, values.Count); + } + + /// + /// Checks whether Remove() method works via the IDictionary interface + /// + [Test] + public void TestRemoveViaIDictionary() { + Expect.Once.On(this.mockedSubscriber).Method("ItemRemoved").WithAnyArguments(); + (this.observedDictionary as IDictionary).Remove(3); + this.mockery.VerifyAllExpectationsHaveBeenMet(); + + CollectionAssert.DoesNotContain(this.observedDictionary.Keys, 3); + } + + /// + /// Tests whether the retrieval of values using the indexer of the observable + /// dictionary is working via the IDictionary interface + /// + [Test] + public void TestRetrieveValueByIndexerViaIDictionary() { + Assert.AreEqual("forty-two", (this.observedDictionary as IDictionary)[42]); + } + + /// + /// Verifies the indexer can be used to insert an item via the IDictionary interface + /// + [Test] + public void TestReplaceByIndexerViaIDictionary() { + Expect.Once.On(this.mockedSubscriber).Method("ItemRemoved").WithAnyArguments(); + Expect.Once.On(this.mockedSubscriber).Method("ItemAdded").WithAnyArguments(); + (this.observedDictionary as IDictionary)[42] = "two and fourty"; + this.mockery.VerifyAllExpectationsHaveBeenMet(); + + Assert.AreEqual("two and fourty", this.observedDictionary[42]); + } + + /// + /// Checks whether Add() method is working via the generic + /// ICollection<> interface + /// + [Test] + public void TestAddViaGenericICollection() { + Expect.Once.On(this.mockedSubscriber).Method("ItemAdded").WithAnyArguments(); + (this.observedDictionary as ICollection>).Add( + new KeyValuePair(24, "twenty-four") + ); + this.mockery.VerifyAllExpectationsHaveBeenMet(); + + CollectionAssert.Contains( + this.observedDictionary, new KeyValuePair(24, "twenty-four") + ); + } + + /// + /// Checks whether the Clear() method is working via the generic + /// ICollection<> interface + /// + [Test] + public void TestClearViaGenericICollection() { + Expect.Once.On(this.mockedSubscriber).Method("Clearing").WithAnyArguments(); + (this.observedDictionary as ICollection>).Clear(); + this.mockery.VerifyAllExpectationsHaveBeenMet(); + + Assert.AreEqual(0, this.observedDictionary.Count); + } + + /// + /// Checks whether the Remove() method is working via the + /// generic ICollection<> interface + /// + [Test] + public void TestRemoveViaGenericICollection() { + IEnumerator> enumerator = + (this.observedDictionary as ICollection>).GetEnumerator(); + enumerator.MoveNext(); + Expect.Once.On(this.mockedSubscriber).Method("ItemRemoved").WithAnyArguments(); + (this.observedDictionary as ICollection>).Remove( + enumerator.Current + ); + this.mockery.VerifyAllExpectationsHaveBeenMet(); + + CollectionAssert.DoesNotContain(this.observedDictionary, enumerator.Current); + } + + /// + /// Verifies that the CopyTo() of the observable dictionary works when called + /// via the the ICollection interface + /// + [Test] + public void TestCopyToArrayViaICollection() { + Dictionary numbers = createTestDictionary(); + ObservableDictionary testDictionary = makeObservable(numbers); + + DictionaryEntry[] entries = new DictionaryEntry[numbers.Count]; + (testDictionary as ICollection).CopyTo(entries, 0); + + KeyValuePair[] items = new KeyValuePair[numbers.Count]; + for(int index = 0; index < entries.Length; ++index) { + items[index] = new KeyValuePair( + (int)entries[index].Key, (string)entries[index].Value + ); + } + CollectionAssert.AreEquivalent(numbers, items); + } + + /// + /// Verifies that the IsSynchronized property and the SyncRoot property are working + /// + [Test] + public void TestSynchronization() { + Dictionary numbers = createTestDictionary(); + ObservableDictionary testDictionary = makeObservable(numbers); + + if(!(testDictionary as ICollection).IsSynchronized) { + lock((testDictionary as ICollection).SyncRoot) { + Assert.AreEqual(numbers.Count, testDictionary.Count); + } + } + } + + /// + /// Test whether the observable dictionary can be serialized + /// + [Test] + public void TestSerialization() { + BinaryFormatter formatter = new BinaryFormatter(); + + using(MemoryStream memory = new MemoryStream()) { + Dictionary numbers = createTestDictionary(); + ObservableDictionary testDictionary1 = makeObservable(numbers); + + formatter.Serialize(memory, testDictionary1); + memory.Position = 0; + object testDictionary2 = formatter.Deserialize(memory); + + CollectionAssert.AreEquivalent(testDictionary1, (IEnumerable)testDictionary2); + } + } + + /// + /// Creates a new observable dictionary filled with some values for testing + /// + /// The newly created observable dictionary + private static Dictionary createTestDictionary() { + Dictionary numbers = new Dictionary(); + numbers.Add(1, "one"); + numbers.Add(2, "two"); + numbers.Add(3, "three"); + numbers.Add(42, "forty-two"); + return new Dictionary(numbers); + } + + /// + /// Creates a new observable dictionary filled with some values for testing + /// + /// The newly created observable dictionary + private static ObservableDictionary makeObservable( + IDictionary dictionary + ) { + return new ObservableDictionary(dictionary); + } + + /// Mock object factory + private Mockery mockery; + /// The mocked observable collection subscriber + private IObservableDictionarySubscriber mockedSubscriber; + /// An observable dictionary to which a mock will be subscribed + private ObservableDictionary observedDictionary; + + } + +} // namespace Nuclex.Support.Collections + +#endif // UNITTEST diff --git a/Source/Collections/ObservableDictionary.cs b/Source/Collections/ObservableDictionary.cs new file mode 100644 index 0000000..7c01243 --- /dev/null +++ b/Source/Collections/ObservableDictionary.cs @@ -0,0 +1,405 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2009 Nuclex Development Labs + +This library is free software; you can redistribute it and/or +modify it under the terms of the IBM Common Public License as +published by the IBM Corporation; either version 1.0 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +IBM Common Public License for more details. + +You should have received a copy of the IBM Common Public +License along with this library +*/ +#endregion + +using System; +using System.Collections.Generic; +using System.Collections; +using System.Collections.ObjectModel; +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 + [Serializable] + public class ObservableDictionary : +#if !COMPACTFRAMEWORK + ISerializable, + IDeserializationCallback, +#endif + IDictionary, + IDictionary { + +#if !COMPACTFRAMEWORK + #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 SerializeDictionary +#endif // !COMPACTFRAMEWORK + + /// 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 the dictionary is about to be cleared + public event EventHandler Clearing; + + /// 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 !COMPACTFRAMEWORK + /// + /// 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 // !COMPACTFRAMEWORK + + /// 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(KeyType 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(KeyType key, out ValueType 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 ValueType this[KeyType key] { + get { return this.typedDictionary[key]; } + set { + bool removed; + ValueType oldValue; + removed = this.typedDictionary.TryGetValue(key, out oldValue); + + this.typedDictionary[key] = value; + + if(removed) { + OnRemoved(new KeyValuePair(key, oldValue)); + } + 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(KeyType key, ValueType 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(KeyType key) { + ValueType 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(); + } + + /// 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)); + } + + /// 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)); + } + + /// Fires the 'Clearing' event + protected virtual void OnClearing() { + if(Clearing != null) + Clearing(this, EventArgs.Empty); + } + + #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((KeyType)key, (ValueType)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); + } + + /// Returns a new entry enumerator for the dictionary + /// The new entry enumerator + IDictionaryEnumerator IDictionary.GetEnumerator() { + return this.objectDictionary.GetEnumerator(); + } + + /// 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) { + ValueType value; + bool removed = this.typedDictionary.TryGetValue((KeyType)key, out value); + this.objectDictionary.Remove(key); + if(removed) { + OnRemoved(new KeyValuePair((KeyType)key, (ValueType)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; + ValueType oldValue; + removed = this.typedDictionary.TryGetValue((KeyType)key, out oldValue); + + this.objectDictionary[key] = value; + + if(removed) { + OnRemoved(new KeyValuePair((KeyType)key, oldValue)); + } + OnAdded(new KeyValuePair((KeyType)key, (ValueType)value)); + } + } + + #endregion + + #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(); + } + + /// 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 !COMPACTFRAMEWORK + #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 //!COMPACTFRAMEWORK + + /// 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 diff --git a/Source/Collections/ReadOnlyDictionary.Test.cs b/Source/Collections/ReadOnlyDictionary.Test.cs index befbf2e..4f3d363 100644 --- a/Source/Collections/ReadOnlyDictionary.Test.cs +++ b/Source/Collections/ReadOnlyDictionary.Test.cs @@ -188,7 +188,7 @@ namespace Nuclex.Support.Collections { /// Add() method is called via the generic IDictionary<> interface /// [Test, ExpectedException(typeof(NotSupportedException))] - public void TestThrowOnAddAtViaGenericIDictionary() { + public void TestThrowOnAddViaGenericIDictionary() { Dictionary numbers = createTestDictionary(); ReadOnlyDictionary testDictionary = makeReadOnly(numbers); @@ -388,7 +388,7 @@ namespace Nuclex.Support.Collections { /// /// Checks whether the read only dictionary will throw an exception if its - /// Clear() method is used via the generic ICollection<> interface + /// Remove() method is used via the generic ICollection<> interface /// [Test, ExpectedException(typeof(NotSupportedException))] public void TestThrowOnRemoveViaGenericICollection() { @@ -396,7 +396,7 @@ namespace Nuclex.Support.Collections { ReadOnlyDictionary testDictionary = makeReadOnly(numbers); (testDictionary as ICollection>).Remove( - new KeyValuePair(24, "twenty-four") + new KeyValuePair(42, "fourty-two") ); } diff --git a/Source/Collections/ReadOnlyDictionary.cs b/Source/Collections/ReadOnlyDictionary.cs index 7d0e52d..b0f1c64 100644 --- a/Source/Collections/ReadOnlyDictionary.cs +++ b/Source/Collections/ReadOnlyDictionary.cs @@ -26,9 +26,9 @@ using System.Runtime.Serialization; namespace Nuclex.Support.Collections { - /// Wraps a Dictionary and prevents users from modifying it - /// Type of the keys used in the Dictionary - /// Type of the values used in the Dictionary + /// Wraps a dictionary and prevents users from modifying it + /// Type of the keys used in the dictionary + /// Type of the values used in the dictionary [Serializable] public class ReadOnlyDictionary : #if !COMPACTFRAMEWORK @@ -89,7 +89,7 @@ namespace Nuclex.Support.Collections { #endif // !COMPACTFRAMEWORK - /// Initializes a new read-only Dictionary wrapper + /// Initializes a new read-only dictionary wrapper /// Dictionary that will be wrapped public ReadOnlyDictionary(IDictionary dictionary) { this.typedDictionary = dictionary;