Added new class ObservableDictionary to the collection class library; 100% test coverage for the new ObservableDictionary class; fixed some minor issues with the code of ObservableCollection and ReadOnlyDictionary

git-svn-id: file:///srv/devel/repo-conversion/nusu@117 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
Markus Ewald 2009-01-26 20:05:32 +00:00
parent f0498fbb60
commit b90ff1c78b
7 changed files with 966 additions and 16 deletions

View File

@ -77,6 +77,10 @@
<Compile Include="Source\Collections\ObservableCollection.Test.cs"> <Compile Include="Source\Collections\ObservableCollection.Test.cs">
<DependentUpon>ObservableCollection.cs</DependentUpon> <DependentUpon>ObservableCollection.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Collections\ObservableDictionary.cs" />
<Compile Include="Source\Collections\ObservableDictionary.Test.cs">
<DependentUpon>ObservableDictionary.cs</DependentUpon>
</Compile>
<Compile Include="Source\Collections\PairPriorityQueue.cs" /> <Compile Include="Source\Collections\PairPriorityQueue.cs" />
<Compile Include="Source\Collections\PairPriorityQueue.Test.cs"> <Compile Include="Source\Collections\PairPriorityQueue.Test.cs">
<DependentUpon>PairPriorityQueue.cs</DependentUpon> <DependentUpon>PairPriorityQueue.cs</DependentUpon>

View File

@ -59,6 +59,10 @@
<Compile Include="Source\Collections\ObservableCollection.Test.cs"> <Compile Include="Source\Collections\ObservableCollection.Test.cs">
<DependentUpon>ObservableCollection.cs</DependentUpon> <DependentUpon>ObservableCollection.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Collections\ObservableDictionary.cs" />
<Compile Include="Source\Collections\ObservableDictionary.Test.cs">
<DependentUpon>ObservableDictionary.cs</DependentUpon>
</Compile>
<Compile Include="Source\Collections\PairPriorityQueue.cs" /> <Compile Include="Source\Collections\PairPriorityQueue.cs" />
<Compile Include="Source\Collections\PairPriorityQueue.Test.cs"> <Compile Include="Source\Collections\PairPriorityQueue.Test.cs">
<DependentUpon>PairPriorityQueue.cs</DependentUpon> <DependentUpon>PairPriorityQueue.cs</DependentUpon>

View File

@ -34,23 +34,23 @@ namespace Nuclex.Support.Collections {
#region interface IObservableCollectionSubscriber #region interface IObservableCollectionSubscriber
/// <summary>Interface used to test the observable collection.</summary> /// <summary>Interface used to test the observable collection</summary>
public interface IObservableCollectionSubscriber { public interface IObservableCollectionSubscriber {
/// <summary>Called when the collection is about to clear its contents</summary> /// <summary>Called when the collection is about to clear its contents</summary>
/// <param name="sender">Collection that is clearing its contents</param> /// <param name="sender">Collection that is clearing its contents</param>
/// <param name="e">Not used</param> /// <param name="arguments">Not used</param>
void Clearing(object sender, EventArgs e); void Clearing(object sender, EventArgs arguments);
/// <summary>Called when an item is added to the collection</summary> /// <summary>Called when an item is added to the collection</summary>
/// <param name="sender">Collection to which an item is being added</param> /// <param name="sender">Collection to which an item is being added</param>
/// <param name="e">Contains the item that is being added</param> /// <param name="arguments">Contains the item that is being added</param>
void ItemAdded(object sender, ItemEventArgs<int> e); void ItemAdded(object sender, ItemEventArgs<int> arguments);
/// <summary>Called when an item is removed from the collection</summary> /// <summary>Called when an item is removed from the collection</summary>
/// <param name="sender">Collection from which an item is being removed</param> /// <param name="sender">Collection from which an item is being removed</param>
/// <param name="e">Contains the item that is being removed</param> /// <param name="arguments">Contains the item that is being removed</param>
void ItemRemoved(object sender, ItemEventArgs<int> e); void ItemRemoved(object sender, ItemEventArgs<int> arguments);
} }
@ -73,8 +73,6 @@ namespace Nuclex.Support.Collections {
new EventHandler<ItemEventArgs<int>>( new EventHandler<ItemEventArgs<int>>(
this.mockedSubscriber.ItemRemoved this.mockedSubscriber.ItemRemoved
); );
this.mockery.VerifyAllExpectationsHaveBeenMet();
} }
/// <summary>Tests whether the Clearing event is fired</summary> /// <summary>Tests whether the Clearing event is fired</summary>

View File

@ -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 {
/// <summary>Unit Test for the observable dictionary wrapper</summary>
[TestFixture]
public class ObservableDictionaryTest {
#region interface IObservableDictionarySubscriber
/// <summary>Interface used to test the observable dictionary</summary>
public interface IObservableDictionarySubscriber {
/// <summary>Called when the dictionary is about to clear its contents</summary>
/// <param name="sender">Dictionary that is clearing its contents</param>
/// <param name="arguments">Not used</param>
void Clearing(object sender, EventArgs arguments);
/// <summary>Called when an item is added to the dictionary</summary>
/// <param name="sender">Dictionary to which an item is being added</param>
/// <param name="arguments">Contains the item that is being added</param>
void ItemAdded(object sender, ItemEventArgs<KeyValuePair<int, string>> arguments);
/// <summary>Called when an item is removed from the dictionary</summary>
/// <param name="sender">Dictionary from which an item is being removed</param>
/// <param name="arguments">Contains the item that is being removed</param>
void ItemRemoved(object sender, ItemEventArgs<KeyValuePair<int, string>> arguments);
}
#endregion // interface IObservableDictionarySubscriber
/// <summary>Initialization routine executed before each test is run</summary>
[SetUp]
public void Setup() {
this.mockery = new Mockery();
this.mockedSubscriber = this.mockery.NewMock<IObservableDictionarySubscriber>();
this.observedDictionary = new ObservableDictionary<int, string>();
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<ItemEventArgs<KeyValuePair<int, string>>>(
this.mockedSubscriber.ItemAdded
);
this.observedDictionary.ItemRemoved +=
new EventHandler<ItemEventArgs<KeyValuePair<int, string>>>(
this.mockedSubscriber.ItemRemoved
);
}
/// <summary>
/// Verifies that the default constructor of the observable dictionary works
/// </summary>
[Test]
public void TestDefaultConstructor() {
ObservableDictionary<int, string> testDictionary =
new ObservableDictionary<int, string>();
Assert.AreEqual(0, testDictionary.Count);
}
/// <summary>
/// Verifies that the copy constructor of the observable dictionary works
/// </summary>
[Test]
public void TestCopyConstructor() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
CollectionAssert.AreEqual(numbers, testDictionary);
}
/// <summary>Verifies that the IsReadOnly property is working</summary>
[Test]
public void TestIsReadOnly() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
Assert.IsFalse(testDictionary.IsReadOnly);
}
/// <summary>
/// Checks whether the Contains() method of the observable dictionary is able to
/// determine if the dictionary contains an item
/// </summary>
[Test]
public void TestContains() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
Assert.IsTrue(
testDictionary.Contains(new KeyValuePair<int, string>(42, "forty-two"))
);
Assert.IsFalse(
testDictionary.Contains(new KeyValuePair<int, string>(24, "twenty-four"))
);
}
/// <summary>
/// Checks whether the Contains() method of the observable dictionary is able to
/// determine if the dictionary contains a key
/// </summary>
[Test]
public void TestContainsKey() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
Assert.IsTrue(testDictionary.ContainsKey(42));
Assert.IsFalse(testDictionary.ContainsKey(24));
}
/// <summary>
/// Verifies that the CopyTo() of the observable dictionary works
/// </summary>
[Test]
public void TestCopyToArray() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
KeyValuePair<int, string>[] items = new KeyValuePair<int, string>[numbers.Count];
testDictionary.CopyTo(items, 0);
CollectionAssert.AreEqual(numbers, items);
}
/// <summary>
/// Tests whether the typesafe enumerator of the observable dictionary is working
/// </summary>
[Test]
public void TestTypesafeEnumerator() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
List<KeyValuePair<int, string>> outputItems = new List<KeyValuePair<int, string>>();
foreach(KeyValuePair<int, string> item in testDictionary) {
outputItems.Add(item);
}
CollectionAssert.AreEqual(numbers, outputItems);
}
/// <summary>
/// Tests whether the keys collection of the observable dictionary can be queried
/// </summary>
[Test]
public void TestGetKeysCollection() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
ICollection<int> inputKeys = numbers.Keys;
ICollection<int> keys = testDictionary.Keys;
CollectionAssert.AreEquivalent(inputKeys, keys);
}
/// <summary>
/// Tests whether the values collection of the observable dictionary can be queried
/// </summary>
[Test]
public void TestGetValuesCollection() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
ICollection<string> inputValues = numbers.Values;
ICollection<string> values = testDictionary.Values;
CollectionAssert.AreEquivalent(inputValues, values);
}
/// <summary>
/// Tests whether the TryGetValue() method of the observable dictionary is working
/// </summary>
[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);
}
/// <summary>
/// Tests whether the retrieval of values using the indexer of the observable
/// dictionary is working
/// </summary>
[Test]
public void TestRetrieveValueByIndexer() {
Assert.AreEqual("forty-two", this.observedDictionary[42]);
}
/// <summary>
/// Tests whether an exception is thrown if the indexer of the observable dictionary
/// is used to attempt to retrieve a non-existing value
/// </summary>
[Test, ExpectedException(typeof(KeyNotFoundException))]
public void TestThrowOnRetrieveNonExistingValueByIndexer() {
string numberName = this.observedDictionary[24];
Console.WriteLine(numberName);
}
/// <summary>
/// Checks whether the Add() methods works via the generic
/// IDictionary&lt;&gt; interface
/// </summary>
[Test]
public void TestAddViaGenericIDictionary() {
Expect.Once.On(this.mockedSubscriber).Method("ItemAdded").WithAnyArguments();
(this.observedDictionary as IDictionary<int, string>).Add(10, "ten");
this.mockery.VerifyAllExpectationsHaveBeenMet();
CollectionAssert.Contains(
this.observedDictionary, new KeyValuePair<int, string>(10, "ten")
);
}
/// <summary>
/// Checks whether the Remove() method works via the generic
/// IDictionary&lt;&gt; interface
/// </summary>
[Test]
public void TestRemoveViaGenericIDictionary() {
Expect.Once.On(this.mockedSubscriber).Method("ItemRemoved").WithAnyArguments();
(this.observedDictionary as IDictionary<int, string>).Remove(3);
this.mockery.VerifyAllExpectationsHaveBeenMet();
CollectionAssert.DoesNotContain(this.observedDictionary.Keys, 3);
}
/// <summary>
/// Tests whether the TryGetValue() method of the observable dictionary is working
/// </summary>
[Test]
public void TestRetrieveValueByIndexerViaGenericIDictionary() {
Assert.AreEqual(
"forty-two", (this.observedDictionary as IDictionary<int, string>)[42]
);
}
/// <summary>
/// Verifies that the indexer can be used to insert an item via the generic
/// IDictionar&lt;&gt; interface
/// </summary>
[Test]
public void TestReplaceByIndexerViaGenericIDictionary() {
Expect.Once.On(this.mockedSubscriber).Method("ItemRemoved").WithAnyArguments();
Expect.Once.On(this.mockedSubscriber).Method("ItemAdded").WithAnyArguments();
(this.observedDictionary as IDictionary<int, string>)[42] = "two and fourty";
this.mockery.VerifyAllExpectationsHaveBeenMet();
Assert.AreEqual("two and fourty", this.observedDictionary[42]);
}
/// <summary>
/// Checks whether the Clear() method of observable dictionary is working
/// </summary>
[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);
}
/// <summary>
/// Checks whether the Add() method works via the IDictionary interface
/// </summary>
[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<int, string>(24, "twenty-four")
);
}
/// <summary>
/// Checks whether the Contains() method of the observable dictionary is able to
/// determine if the dictionary contains an item via the IDictionary interface
/// </summary>
[Test]
public void TestContainsViaIDictionary() {
Assert.IsTrue((this.observedDictionary as IDictionary).Contains(42));
Assert.IsFalse((this.observedDictionary as IDictionary).Contains(24));
}
/// <summary>
/// Checks whether the GetEnumerator() method of the observable dictionary
/// returns a working enumerator if accessed via the IDictionary interface
/// </summary>
[Test]
public void TestEnumeratorViaIDictionary() {
Dictionary<int, string> outputNumbers = new Dictionary<int, string>();
foreach(DictionaryEntry entry in (this.observedDictionary as IDictionary)) {
(outputNumbers as IDictionary).Add(entry.Key, entry.Value);
}
CollectionAssert.AreEquivalent(this.observedDictionary, outputNumbers);
}
/// <summary>
/// Checks whether the IsFixedSize property of the observable dictionary returns
/// the expected result for a read only dictionary based on a dynamic dictionary
/// </summary>
[Test]
public void TestIsFixedSizeViaIList() {
Assert.IsFalse((this.observedDictionary as IDictionary).IsFixedSize);
}
/// <summary>
/// Tests whether the keys collection of the observable dictionary can be queried
/// via the IDictionary interface
/// </summary>
[Test]
public void TestGetKeysCollectionViaIDictionary() {
ICollection keys = (this.observedDictionary as IDictionary).Keys;
Assert.AreEqual(this.observedDictionary.Count, keys.Count);
}
/// <summary>
/// Tests whether the values collection of the observable dictionary can be queried
/// via the IDictionary interface
/// </summary>
[Test]
public void TestGetValuesCollectionViaIDictionary() {
ICollection values = (this.observedDictionary as IDictionary).Values;
Assert.AreEqual(this.observedDictionary.Count, values.Count);
}
/// <summary>
/// Checks whether Remove() method works via the IDictionary interface
/// </summary>
[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);
}
/// <summary>
/// Tests whether the retrieval of values using the indexer of the observable
/// dictionary is working via the IDictionary interface
/// </summary>
[Test]
public void TestRetrieveValueByIndexerViaIDictionary() {
Assert.AreEqual("forty-two", (this.observedDictionary as IDictionary)[42]);
}
/// <summary>
/// Verifies the indexer can be used to insert an item via the IDictionary interface
/// </summary>
[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]);
}
/// <summary>
/// Checks whether Add() method is working via the generic
/// ICollection&lt;&gt; interface
/// </summary>
[Test]
public void TestAddViaGenericICollection() {
Expect.Once.On(this.mockedSubscriber).Method("ItemAdded").WithAnyArguments();
(this.observedDictionary as ICollection<KeyValuePair<int, string>>).Add(
new KeyValuePair<int, string>(24, "twenty-four")
);
this.mockery.VerifyAllExpectationsHaveBeenMet();
CollectionAssert.Contains(
this.observedDictionary, new KeyValuePair<int, string>(24, "twenty-four")
);
}
/// <summary>
/// Checks whether the Clear() method is working via the generic
/// ICollection&lt;&gt; interface
/// </summary>
[Test]
public void TestClearViaGenericICollection() {
Expect.Once.On(this.mockedSubscriber).Method("Clearing").WithAnyArguments();
(this.observedDictionary as ICollection<KeyValuePair<int, string>>).Clear();
this.mockery.VerifyAllExpectationsHaveBeenMet();
Assert.AreEqual(0, this.observedDictionary.Count);
}
/// <summary>
/// Checks whether the Remove() method is working via the
/// generic ICollection&lt;&gt; interface
/// </summary>
[Test]
public void TestRemoveViaGenericICollection() {
IEnumerator<KeyValuePair<int, string>> enumerator =
(this.observedDictionary as ICollection<KeyValuePair<int, string>>).GetEnumerator();
enumerator.MoveNext();
Expect.Once.On(this.mockedSubscriber).Method("ItemRemoved").WithAnyArguments();
(this.observedDictionary as ICollection<KeyValuePair<int, string>>).Remove(
enumerator.Current
);
this.mockery.VerifyAllExpectationsHaveBeenMet();
CollectionAssert.DoesNotContain(this.observedDictionary, enumerator.Current);
}
/// <summary>
/// Verifies that the CopyTo() of the observable dictionary works when called
/// via the the ICollection interface
/// </summary>
[Test]
public void TestCopyToArrayViaICollection() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
DictionaryEntry[] entries = new DictionaryEntry[numbers.Count];
(testDictionary as ICollection).CopyTo(entries, 0);
KeyValuePair<int, string>[] items = new KeyValuePair<int, string>[numbers.Count];
for(int index = 0; index < entries.Length; ++index) {
items[index] = new KeyValuePair<int, string>(
(int)entries[index].Key, (string)entries[index].Value
);
}
CollectionAssert.AreEquivalent(numbers, items);
}
/// <summary>
/// Verifies that the IsSynchronized property and the SyncRoot property are working
/// </summary>
[Test]
public void TestSynchronization() {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary = makeObservable(numbers);
if(!(testDictionary as ICollection).IsSynchronized) {
lock((testDictionary as ICollection).SyncRoot) {
Assert.AreEqual(numbers.Count, testDictionary.Count);
}
}
}
/// <summary>
/// Test whether the observable dictionary can be serialized
/// </summary>
[Test]
public void TestSerialization() {
BinaryFormatter formatter = new BinaryFormatter();
using(MemoryStream memory = new MemoryStream()) {
Dictionary<int, string> numbers = createTestDictionary();
ObservableDictionary<int, string> testDictionary1 = makeObservable(numbers);
formatter.Serialize(memory, testDictionary1);
memory.Position = 0;
object testDictionary2 = formatter.Deserialize(memory);
CollectionAssert.AreEquivalent(testDictionary1, (IEnumerable)testDictionary2);
}
}
/// <summary>
/// Creates a new observable dictionary filled with some values for testing
/// </summary>
/// <returns>The newly created observable dictionary</returns>
private static Dictionary<int, string> createTestDictionary() {
Dictionary<int, string> numbers = new Dictionary<int, string>();
numbers.Add(1, "one");
numbers.Add(2, "two");
numbers.Add(3, "three");
numbers.Add(42, "forty-two");
return new Dictionary<int, string>(numbers);
}
/// <summary>
/// Creates a new observable dictionary filled with some values for testing
/// </summary>
/// <returns>The newly created observable dictionary</returns>
private static ObservableDictionary<int, string> makeObservable(
IDictionary<int, string> dictionary
) {
return new ObservableDictionary<int, string>(dictionary);
}
/// <summary>Mock object factory</summary>
private Mockery mockery;
/// <summary>The mocked observable collection subscriber</summary>
private IObservableDictionarySubscriber mockedSubscriber;
/// <summary>An observable dictionary to which a mock will be subscribed</summary>
private ObservableDictionary<int, string> observedDictionary;
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View File

@ -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 {
/// <summary>A dictionary that sneds out change notifications</summary>
/// <typeparam name="KeyType">Type of the keys used in the dictionary</typeparam>
/// <typeparam name="ValueType">Type of the values used in the dictionary</typeparam>
[Serializable]
public class ObservableDictionary<KeyType, ValueType> :
#if !COMPACTFRAMEWORK
ISerializable,
IDeserializationCallback,
#endif
IDictionary<KeyType, ValueType>,
IDictionary {
#if !COMPACTFRAMEWORK
#region class SerializedDictionary
/// <summary>
/// Dictionary wrapped used to reconstruct a serialized read only dictionary
/// </summary>
private class SerializedDictionary : Dictionary<KeyType, ValueType> {
/// <summary>
/// Initializes a new instance of the System.WeakReference class, using deserialized
/// data from the specified serialization and stream objects.
/// </summary>
/// <param name="info">
/// An object that holds all the data needed to serialize or deserialize the
/// current System.WeakReference object.
/// </param>
/// <param name="context">
/// (Reserved) Describes the source and destination of the serialized stream
/// specified by info.
/// </param>
/// <exception cref="System.ArgumentNullException">
/// The info parameter is null.
/// </exception>
public SerializedDictionary(SerializationInfo info, StreamingContext context) :
base(info, context) { }
}
#endregion // class SerializeDictionary
#endif // !COMPACTFRAMEWORK
/// <summary>Raised when an item has been added to the dictionary</summary>
public event EventHandler<ItemEventArgs<KeyValuePair<KeyType, ValueType>>> ItemAdded;
/// <summary>Raised when an item is removed from the dictionary</summary>
public event EventHandler<ItemEventArgs<KeyValuePair<KeyType, ValueType>>> ItemRemoved;
/// <summary>Raised when the dictionary is about to be cleared</summary>
public event EventHandler Clearing;
/// <summary>Initializes a new observable dictionary</summary>
public ObservableDictionary() : this(new Dictionary<KeyType, ValueType>()) { }
/// <summary>Initializes a new observable Dictionary wrapper</summary>
/// <param name="dictionary">Dictionary that will be wrapped</param>
public ObservableDictionary(IDictionary<KeyType, ValueType> dictionary) {
this.typedDictionary = dictionary;
this.objectDictionary = (this.typedDictionary as IDictionary);
}
#if !COMPACTFRAMEWORK
/// <summary>
/// Initializes a new instance of the System.WeakReference class, using deserialized
/// data from the specified serialization and stream objects.
/// </summary>
/// <param name="info">
/// An object that holds all the data needed to serialize or deserialize the
/// current System.WeakReference object.
/// </param>
/// <param name="context">
/// (Reserved) Describes the source and destination of the serialized stream
/// specified by info.
/// </param>
/// <exception cref="System.ArgumentNullException">
/// The info parameter is null.
/// </exception>
protected ObservableDictionary(SerializationInfo info, StreamingContext context) :
this(new SerializedDictionary(info, context)) { }
#endif // !COMPACTFRAMEWORK
/// <summary>Whether the directory is write-protected</summary>
public bool IsReadOnly {
get { return this.typedDictionary.IsReadOnly; }
}
/// <summary>
/// Determines whether the specified KeyValuePair is contained in the Dictionary
/// </summary>
/// <param name="item">KeyValuePair that will be checked for</param>
/// <returns>True if the provided KeyValuePair was contained in the Dictionary</returns>
public bool Contains(KeyValuePair<KeyType, ValueType> item) {
return this.typedDictionary.Contains(item);
}
/// <summary>Determines whether the Dictionary contains the specified key</summary>
/// <param name="key">Key that will be checked for</param>
/// <returns>
/// True if an entry with the specified key was contained in the Dictionary
/// </returns>
public bool ContainsKey(KeyType key) {
return this.typedDictionary.ContainsKey(key);
}
/// <summary>Copies the contents of the Dictionary into an array</summary>
/// <param name="array">Array the Dictionary will be copied into</param>
/// <param name="arrayIndex">
/// Starting index at which to begin filling the destination array
/// </param>
public void CopyTo(KeyValuePair<KeyType, ValueType>[] array, int arrayIndex) {
this.typedDictionary.CopyTo(array, arrayIndex);
}
/// <summary>Number of elements contained in the Dictionary</summary>
public int Count {
get { return this.typedDictionary.Count; }
}
/// <summary>Creates a new enumerator for the Dictionary</summary>
/// <returns>The new Dictionary enumerator</returns>
public IEnumerator<KeyValuePair<KeyType, ValueType>> GetEnumerator() {
return this.typedDictionary.GetEnumerator();
}
/// <summary>Collection of all keys contained in the Dictionary</summary>
public ICollection<KeyType> Keys {
get { return this.typedDictionary.Keys; }
}
/// <summary>Collection of all values contained in the Dictionary</summary>
public ICollection<ValueType> Values {
get { return this.typedDictionary.Values; }
}
/// <summary>
/// Attempts to retrieve the item with the specified key from the Dictionary
/// </summary>
/// <param name="key">Key of the item to attempt to retrieve</param>
/// <param name="value">
/// Output parameter that will receive the key upon successful completion
/// </param>
/// <returns>
/// True if the item was found and has been placed in the output parameter
/// </returns>
public bool TryGetValue(KeyType key, out ValueType value) {
return this.typedDictionary.TryGetValue(key, out value);
}
/// <summary>Accesses an item in the Dictionary by its key</summary>
/// <param name="key">Key of the item that will be accessed</param>
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<KeyType, ValueType>(key, oldValue));
}
OnAdded(new KeyValuePair<KeyType, ValueType>(key, value));
}
}
/// <summary>Inserts an item into the Dictionary</summary>
/// <param name="key">Key under which to add the new item</param>
/// <param name="value">Item that will be added to the Dictionary</param>
public void Add(KeyType key, ValueType value) {
this.typedDictionary.Add(key, value);
OnAdded(new KeyValuePair<KeyType, ValueType>(key, value));
}
/// <summary>Removes the item with the specified key from the Dictionary</summary>
/// <param name="key">Key of the elementes that will be removed</param>
/// <returns>True if an item with the specified key was found and removed</returns>
public bool Remove(KeyType key) {
ValueType oldValue;
this.typedDictionary.TryGetValue(key, out oldValue);
bool removed = this.typedDictionary.Remove(key);
if(removed) {
OnRemoved(new KeyValuePair<KeyType, ValueType>(key, oldValue));
}
return removed;
}
/// <summary>Removes all items from the Dictionary</summary>
public void Clear() {
OnClearing();
this.typedDictionary.Clear();
}
/// <summary>Fires the 'ItemAdded' event</summary>
/// <param name="item">Item that has been added to the collection</param>
protected virtual void OnAdded(KeyValuePair<KeyType, ValueType> item) {
if(ItemAdded != null)
ItemAdded(this, new ItemEventArgs<KeyValuePair<KeyType, ValueType>>(item));
}
/// <summary>Fires the 'ItemRemoved' event</summary>
/// <param name="item">Item that has been removed from the collection</param>
protected virtual void OnRemoved(KeyValuePair<KeyType, ValueType> item) {
if(ItemRemoved != null)
ItemRemoved(this, new ItemEventArgs<KeyValuePair<KeyType, ValueType>>(item));
}
/// <summary>Fires the 'Clearing' event</summary>
protected virtual void OnClearing() {
if(Clearing != null)
Clearing(this, EventArgs.Empty);
}
#region IEnumerable implementation
/// <summary>Returns a new object enumerator for the Dictionary</summary>
/// <returns>The new object enumerator</returns>
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
return (this.typedDictionary as IEnumerable).GetEnumerator();
}
#endregion
#region IDictionary implementation
/// <summary>Adds an item into the Dictionary</summary>
/// <param name="key">Key under which the item will be added</param>
/// <param name="value">Item that will be added</param>
void IDictionary.Add(object key, object value) {
this.objectDictionary.Add(key, value);
OnAdded(new KeyValuePair<KeyType, ValueType>((KeyType)key, (ValueType)value));
}
/// <summary>Determines whether the specified key exists in the Dictionary</summary>
/// <param name="key">Key that will be checked for</param>
/// <returns>True if an item with the specified key exists in the Dictionary</returns>
bool IDictionary.Contains(object key) {
return this.objectDictionary.Contains(key);
}
/// <summary>Returns a new entry enumerator for the dictionary</summary>
/// <returns>The new entry enumerator</returns>
IDictionaryEnumerator IDictionary.GetEnumerator() {
return this.objectDictionary.GetEnumerator();
}
/// <summary>Whether the size of the Dictionary is fixed</summary>
bool IDictionary.IsFixedSize {
get { return this.objectDictionary.IsFixedSize; }
}
/// <summary>Returns a collection of all keys in the Dictionary</summary>
ICollection IDictionary.Keys {
get { return this.objectDictionary.Keys; }
}
/// <summary>Returns a collection of all values stored in the Dictionary</summary>
ICollection IDictionary.Values {
get { return this.objectDictionary.Values; }
}
/// <summary>Removes an item from the Dictionary</summary>
/// <param name="key">Key of the item that will be removed</param>
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, ValueType>((KeyType)key, (ValueType)value));
}
}
/// <summary>Accesses an item in the Dictionary by its key</summary>
/// <param name="key">Key of the item that will be accessed</param>
/// <returns>The item with the specified key</returns>
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, ValueType>((KeyType)key, oldValue));
}
OnAdded(new KeyValuePair<KeyType, ValueType>((KeyType)key, (ValueType)value));
}
}
#endregion
#region ICollection<> implementation
/// <summary>Inserts an already prepared element into the Dictionary</summary>
/// <param name="item">Prepared element that will be added to the Dictionary</param>
void ICollection<KeyValuePair<KeyType, ValueType>>.Add(
KeyValuePair<KeyType, ValueType> item
) {
this.typedDictionary.Add(item);
OnAdded(item);
}
/// <summary>Removes all items from the Dictionary</summary>
void ICollection<KeyValuePair<KeyType, ValueType>>.Clear() {
OnClearing();
this.typedDictionary.Clear();
}
/// <summary>Removes all items from the Dictionary</summary>
/// <param name="itemToRemove">Item that will be removed from the Dictionary</param>
bool ICollection<KeyValuePair<KeyType, ValueType>>.Remove(
KeyValuePair<KeyType, ValueType> itemToRemove
) {
bool removed = this.typedDictionary.Remove(itemToRemove);
if(removed) {
OnRemoved(itemToRemove);
}
return removed;
}
#endregion
#region ICollection implementation
/// <summary>Copies the contents of the Dictionary into an array</summary>
/// <param name="array">Array the Dictionary contents will be copied into</param>
/// <param name="index">
/// Starting index at which to begin filling the destination array
/// </param>
void ICollection.CopyTo(Array array, int index) {
this.objectDictionary.CopyTo(array, index);
}
/// <summary>Whether the Dictionary is synchronized for multi-threaded usage</summary>
bool ICollection.IsSynchronized {
get { return this.objectDictionary.IsSynchronized; }
}
/// <summary>Synchronization root on which the Dictionary locks</summary>
object ICollection.SyncRoot {
get { return this.objectDictionary.SyncRoot; }
}
#endregion
#if !COMPACTFRAMEWORK
#region ISerializable implementation
/// <summary>Serializes the Dictionary</summary>
/// <param name="info">
/// Provides the container into which the Dictionary will serialize itself
/// </param>
/// <param name="context">
/// Contextual informations about the serialization environment
/// </param>
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
(this.typedDictionary as ISerializable).GetObjectData(info, context);
}
/// <summary>Called after all objects have been successfully deserialized</summary>
/// <param name="sender">Nicht unterstützt</param>
void IDeserializationCallback.OnDeserialization(object sender) {
(this.typedDictionary as IDeserializationCallback).OnDeserialization(sender);
}
#endregion
#endif //!COMPACTFRAMEWORK
/// <summary>The wrapped Dictionary under its type-safe interface</summary>
private IDictionary<KeyType, ValueType> typedDictionary;
/// <summary>The wrapped Dictionary under its object interface</summary>
private IDictionary objectDictionary;
}
} // namespace Nuclex.Support.Collections

View File

@ -188,7 +188,7 @@ namespace Nuclex.Support.Collections {
/// Add() method is called via the generic IDictionary&lt;&gt; interface /// Add() method is called via the generic IDictionary&lt;&gt; interface
/// </summary> /// </summary>
[Test, ExpectedException(typeof(NotSupportedException))] [Test, ExpectedException(typeof(NotSupportedException))]
public void TestThrowOnAddAtViaGenericIDictionary() { public void TestThrowOnAddViaGenericIDictionary() {
Dictionary<int, string> numbers = createTestDictionary(); Dictionary<int, string> numbers = createTestDictionary();
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers); ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
@ -388,7 +388,7 @@ namespace Nuclex.Support.Collections {
/// <summary> /// <summary>
/// Checks whether the read only dictionary will throw an exception if its /// Checks whether the read only dictionary will throw an exception if its
/// Clear() method is used via the generic ICollection&lt;&gt; interface /// Remove() method is used via the generic ICollection&lt;&gt; interface
/// </summary> /// </summary>
[Test, ExpectedException(typeof(NotSupportedException))] [Test, ExpectedException(typeof(NotSupportedException))]
public void TestThrowOnRemoveViaGenericICollection() { public void TestThrowOnRemoveViaGenericICollection() {
@ -396,7 +396,7 @@ namespace Nuclex.Support.Collections {
ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers); ReadOnlyDictionary<int, string> testDictionary = makeReadOnly(numbers);
(testDictionary as ICollection<KeyValuePair<int, string>>).Remove( (testDictionary as ICollection<KeyValuePair<int, string>>).Remove(
new KeyValuePair<int, string>(24, "twenty-four") new KeyValuePair<int, string>(42, "fourty-two")
); );
} }

View File

@ -26,9 +26,9 @@ using System.Runtime.Serialization;
namespace Nuclex.Support.Collections { namespace Nuclex.Support.Collections {
/// <summary>Wraps a Dictionary and prevents users from modifying it</summary> /// <summary>Wraps a dictionary and prevents users from modifying it</summary>
/// <typeparam name="KeyType">Type of the keys used in the Dictionary</typeparam> /// <typeparam name="KeyType">Type of the keys used in the dictionary</typeparam>
/// <typeparam name="ValueType">Type of the values used in the Dictionary</typeparam> /// <typeparam name="ValueType">Type of the values used in the dictionary</typeparam>
[Serializable] [Serializable]
public class ReadOnlyDictionary<KeyType, ValueType> : public class ReadOnlyDictionary<KeyType, ValueType> :
#if !COMPACTFRAMEWORK #if !COMPACTFRAMEWORK
@ -89,7 +89,7 @@ namespace Nuclex.Support.Collections {
#endif // !COMPACTFRAMEWORK #endif // !COMPACTFRAMEWORK
/// <summary>Initializes a new read-only Dictionary wrapper</summary> /// <summary>Initializes a new read-only dictionary wrapper</summary>
/// <param name="dictionary">Dictionary that will be wrapped</param> /// <param name="dictionary">Dictionary that will be wrapped</param>
public ReadOnlyDictionary(IDictionary<KeyType, ValueType> dictionary) { public ReadOnlyDictionary(IDictionary<KeyType, ValueType> dictionary) {
this.typedDictionary = dictionary; this.typedDictionary = dictionary;