diff --git a/Source/Collections/ReadOnlyDictionary.Test.cs b/Source/Collections/ReadOnlyDictionary.Test.cs index 0c0894a..f109d01 100644 --- a/Source/Collections/ReadOnlyDictionary.Test.cs +++ b/Source/Collections/ReadOnlyDictionary.Test.cs @@ -21,6 +21,8 @@ License along with this library using System; using System.Collections; using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; #if UNITTEST @@ -32,6 +34,442 @@ namespace Nuclex.Support.Collections { [TestFixture] public class ReadOnlyDictionaryTest { + /// + /// Verifies that the copy constructor of the read only dictionary works + /// + [Test] + public void TestCopyConstructor() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + CollectionAssert.AreEqual(numbers, testDictionary); + } + + /// Verifies that the IsReadOnly property returns true + [Test] + public void TestIsReadOnly() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + Assert.IsTrue(testDictionary.IsReadOnly); + } + + /// + /// Checks whether the Contains() method of the read only dictionary is able to + /// determine if the dictionary contains an item + /// + [Test] + public void TestContains() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(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 read only dictionary is able to + /// determine if the dictionary contains a key + /// + [Test] + public void TestContainsKey() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + Assert.IsTrue(testDictionary.ContainsKey(42)); + Assert.IsFalse(testDictionary.ContainsKey(24)); + } + + /// + /// Verifies that the CopyTo() of the read only dictionary works + /// + [Test] + public void TestCopyToArray() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + KeyValuePair[] items = new KeyValuePair[numbers.Count]; + + testDictionary.CopyTo(items, 0); + + CollectionAssert.AreEqual(numbers, items); + } + + /// + /// Tests whether the typesafe enumerator of the read only dictionary is working + /// + [Test] + public void TestTypesafeEnumerator() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + List> outputItems = new List>(); + foreach(KeyValuePair item in testDictionary) { + outputItems.Add(item); + } + + CollectionAssert.AreEqual(numbers, outputItems); + } + + /// + /// Tests whether the keys collection of the read only dictionary can be queried + /// + [Test] + public void TestGetKeysCollection() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + ICollection inputKeys = numbers.Keys; + ICollection keys = testDictionary.Keys; + CollectionAssert.AreEqual(inputKeys, keys); + } + + /// + /// Tests whether the values collection of the read only dictionary can be queried + /// + [Test] + public void TestGetValuesCollection() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + ICollection inputValues = numbers.Values; + ICollection values = testDictionary.Values; + CollectionAssert.AreEqual(inputValues, values); + } + + /// + /// Tests whether the TryGetValue() method of the read only dictionary is working + /// + [Test] + public void TestTryGetValue() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + string value; + + Assert.IsTrue(testDictionary.TryGetValue(42, out value)); + Assert.AreEqual("forty-two", value); + + Assert.IsFalse(testDictionary.TryGetValue(24, out value)); + Assert.AreEqual(null, value); + } + + /// + /// Tests whether the retrieval of values using the indexer of the read only + /// dictionary is working + /// + [Test] + public void TestRetrieveValueByIndexer() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + Assert.AreEqual("forty-two", testDictionary[42]); + } + + /// + /// Tests whether an exception is thrown if the indexer of the read only dictionary + /// is used to attempt to retrieve a non-existing value + /// + [Test, ExpectedException(typeof(KeyNotFoundException))] + public void TestThrowOnRetrieveNonExistingValueByIndexer() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + string numberName = testDictionary[24]; + } + + /// + /// Checks whether the read only dictionary will throw an exception if its + /// Add() method is called via the generic IDictionary<> interface + /// + [Test, ExpectedException(typeof(NotSupportedException))] + public void TestThrowOnAddAtViaGenericIDictionary() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + (testDictionary as IDictionary).Add(10, "ten"); + } + + /// + /// Checks whether the read only dictionary will throw an exception if its + /// Remove() method is called via the generic IDictionary<> interface + /// + [Test, ExpectedException(typeof(NotSupportedException))] + public void TestThrowOnRemoveViaGenericIDictionary() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + (testDictionary as IDictionary).Remove(3); + } + + /// + /// Tests whether the TryGetValue() method of the read only dictionary is working + /// + [Test] + public void TestRetrieveValueByIndexerViaGenericIDictionary() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + Assert.AreEqual("forty-two", (testDictionary as IDictionary)[42]); + } + + /// + /// Checks whether the read only dictionary will throw an exception if its + /// indexer is used to insert an item via the generic IDictionar<> interface + /// + [Test, ExpectedException(typeof(NotSupportedException))] + public void TestThrowOnReplaceByIndexerViaGenericIDictionary() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + (testDictionary as IDictionary)[24] = "twenty-four"; + } + + /// + /// Checks whether the read only dictionary will throw an exception if its + /// Clear() method is called via the IDictionary interface + /// + [Test, ExpectedException(typeof(NotSupportedException))] + public void TestThrowOnClearViaIDictionary() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + (testDictionary as IDictionary).Clear(); + } + + /// + /// Checks whether the read only dictionary will throw an exception if its + /// Add() method is called via the IDictionary interface + /// + [Test, ExpectedException(typeof(NotSupportedException))] + public void TestThrowOnAddViaIDictionary() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + (testDictionary as IDictionary).Add(24, "twenty-four"); + } + + /// + /// Checks whether the Contains() method of the read only dictionary is able to + /// determine if the dictionary contains an item via the IDictionary interface + /// + [Test] + public void TestContainsViaIDictionary() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + Assert.IsTrue((testDictionary as IDictionary).Contains(42)); + Assert.IsFalse((testDictionary as IDictionary).Contains(24)); + } + + /// + /// Checks whether the GetEnumerator() method of the read only dictionary returns + /// a working enumerator if accessed via the IDictionary interface + /// + [Test] + public void TestEnumeratorViaIDictionary() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + Dictionary outputNumbers = new Dictionary(); + foreach(DictionaryEntry entry in (testDictionary as IDictionary)) { + (outputNumbers as IDictionary).Add(entry.Key, entry.Value); + } + + CollectionAssert.AreEqual(numbers, outputNumbers); + } + + /// + /// Checks whether the IsFixedSize property of the read only dictionary returns + /// the expected result for a read only dictionary based on a dynamic dictionary + /// + [Test] + public void TestIsFixedSizeViaIList() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + Assert.IsFalse((testDictionary as IDictionary).IsFixedSize); + } + + /// + /// Tests whether the keys collection of the read only dictionary can be queried + /// via the IDictionary interface + /// + [Test] + public void TestGetKeysCollectionViaIDictionary() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + ICollection inputKeys = (numbers as IDictionary).Keys; + ICollection keys = (testDictionary as IDictionary).Keys; + CollectionAssert.AreEqual(inputKeys, keys); + } + + /// + /// Tests whether the values collection of the read only dictionary can be queried + /// via the IDictionary interface + /// + [Test] + public void TestGetValuesCollectionViaIDictionary() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + ICollection inputValues = (numbers as IDictionary).Values; + ICollection values = (testDictionary as IDictionary).Values; + CollectionAssert.AreEqual(inputValues, values); + } + + /// + /// Checks whether the read only dictionary will throw an exception if its + /// Remove() method is called via the IDictionary interface + /// + [Test, ExpectedException(typeof(NotSupportedException))] + public void TestThrowOnRemoveViaIDictionary() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + (testDictionary as IDictionary).Remove(3); + } + + /// + /// Tests whether the retrieval of values using the indexer of the read only + /// dictionary is working via the IDictionary interface + /// + [Test] + public void TestRetrieveValueByIndexerViaIDictionary() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + Assert.AreEqual("forty-two", (testDictionary as IDictionary)[42]); + } + + /// + /// Checks whether the read only dictionary will throw an exception if its + /// indexer is used to insert an item via the IDictionary interface + /// + [Test, ExpectedException(typeof(NotSupportedException))] + public void TestThrowOnReplaceByIndexerViaIDictionary() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + (testDictionary as IDictionary)[24] = "twenty-four"; + } + + /// + /// Checks whether the read only dictionary will throw an exception if its + /// Add() method is used via the generic ICollection<> interface + /// + [Test, ExpectedException(typeof(NotSupportedException))] + public void TestThrowOnAddViaGenericICollection() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + (testDictionary as ICollection>).Add( + new KeyValuePair(24, "twenty-four") + ); + } + + /// + /// Checks whether the read only dictionary will throw an exception if its + /// Clear() method is used via the generic ICollection<> interface + /// + [Test, ExpectedException(typeof(NotSupportedException))] + public void TestThrowOnClearViaGenericICollection() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + (testDictionary as ICollection>).Clear(); + } + + /// + /// Checks whether the read only dictionary will throw an exception if its + /// Clear() method is used via the generic ICollection<> interface + /// + [Test, ExpectedException(typeof(NotSupportedException))] + public void TestThrowOnRemoveViaGenericICollection() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + (testDictionary as ICollection>).Remove( + new KeyValuePair(24, "twenty-four") + ); + } + + /// + /// Verifies that the CopyTo() of the read only dictionary works when called + /// via the the ICollection interface + /// + [Test] + public void TestCopyToArrayViaICollection() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + KeyValuePair[] items = new KeyValuePair[numbers.Count]; + (testDictionary as ICollection).CopyTo(items, 0); + CollectionAssert.AreEqual(numbers, items); + } + + /// + /// Verifies that the IsSynchronized property and the SyncRoot property are working + /// + [Test] + public void TestSynchronization() { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary = makeReadOnly(numbers); + + if(!(testDictionary as ICollection).IsSynchronized) { + lock((testDictionary as ICollection).SyncRoot) { + int count = testDictionary.Count; + } + } + } + + /// + /// Test whether the read only dictionary can be serialized + /// + [Test] + public void TestSerialization() { + BinaryFormatter formatter = new BinaryFormatter(); + + using(MemoryStream memory = new MemoryStream()) { + Dictionary numbers = createTestDictionary(); + ReadOnlyDictionary testDictionary1 = makeReadOnly(numbers); + + formatter.Serialize(memory, testDictionary1); + memory.Position = 0; + object testDictionary2 = formatter.Deserialize(memory); + + CollectionAssert.AreEqual(testDictionary1, (IEnumerable)testDictionary2); + } + } + + /// + /// Creates a new read-only dictionary filled with some values for testing + /// + /// The newly created read-only 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 read-only dictionary filled with some values for testing + /// + /// The newly created read-only dictionary + private static ReadOnlyDictionary makeReadOnly( + IDictionary dictionary + ) { + return new ReadOnlyDictionary(dictionary); + } + } } // namespace Nuclex.Support.Collections diff --git a/Source/Collections/ReadOnlyDictionary.cs b/Source/Collections/ReadOnlyDictionary.cs index fbbfc76..7310df7 100644 --- a/Source/Collections/ReadOnlyDictionary.cs +++ b/Source/Collections/ReadOnlyDictionary.cs @@ -29,12 +29,42 @@ 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 + [Serializable] public class ReadOnlyDictionary : IDictionary, IDictionary, ISerializable, IDeserializationCallback { + #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 + /// Initializes a new read-only Dictionary wrapper /// Dictionary that will be wrapped public ReadOnlyDictionary(IDictionary dictionary) { @@ -42,6 +72,24 @@ namespace Nuclex.Support.Collections { this.objectDictionary = (this.typedDictionary as IDictionary); } + /// + /// 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 ReadOnlyDictionary(SerializationInfo info, StreamingContext context) : + this(new SerializedDictionary(info, context)) { } + /// Whether the directory is write-protected public bool IsReadOnly { get { return true; } diff --git a/Source/Collections/ReadOnlyList.Test.cs b/Source/Collections/ReadOnlyList.Test.cs index ed623e9..6b4b84f 100644 --- a/Source/Collections/ReadOnlyList.Test.cs +++ b/Source/Collections/ReadOnlyList.Test.cs @@ -187,7 +187,7 @@ namespace Nuclex.Support.Collections { } /// - /// Tests whether the typesafe enumerator of the read only collection is working + /// Tests whether the typesafe enumerator of the read only list is working /// [Test] public void TestTypesafeEnumerator() {