diff --git a/src/Spring/Spring.Core/Collections/Generic/ReadOnlyDictionary.cs b/src/Spring/Spring.Core/Collections/Generic/ReadOnlyDictionary.cs
new file mode 100644
index 00000000..359aaec5
--- /dev/null
+++ b/src/Spring/Spring.Core/Collections/Generic/ReadOnlyDictionary.cs
@@ -0,0 +1,431 @@
+#if NET_2_0
+
+#region License
+
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Runtime.Serialization;
+using Spring.Util;
+
+namespace Spring.Collections.Generic
+{
+ ///
+ /// Represents a read only wrapper around a generic IDictionary. The design pattern
+ /// mirrors ReadOnlyCollection, and follows the apparent pattern that write operations
+ /// do not throw an exception, but simply make no change to the underlying collection.
+ ///
+ /// Originally put into the public domain.
+ /// http://www.simple-talk.com/community/forums/thread/2263.aspx
+ ///
+ ///
+ ///
+ /// Original from Public Domain
+ /// Mark Pollack (.NET)
+ [Serializable]
+ public class ReadOnlyDictionary : IDictionary, IDictionary, ISerializable, IDeserializationCallback
+ {
+ ///
+ /// Inner storage for ReadOnlyDictionary
+ ///
+ private readonly IDictionary dict;
+
+ ///
+ /// Easy access to non-generic dictionary API
+ ///
+ private readonly IDictionary idict;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The dictionary to wrap.
+ public ReadOnlyDictionary(IDictionary dictionaryToWrap)
+ {
+ AssertUtils.ArgumentNotNull(dictionaryToWrap, "dictionaryToWrap");
+ dict = dictionaryToWrap;
+ idict = (IDictionary)dict;
+ }
+
+ ///
+ /// Add does not change a read only Dictionary
+ ///
+ /// The object to use as the key of the element to add.
+ /// The object to use as the value of the element to add.
+ /// The is read-only.
+ public void Add(TKey key, TValue value)
+ {
+ throw new NotSupportedException("Dictionary Is ReadOnly");
+ }
+
+ ///
+ /// Determines whether the contains an element with the specified key.
+ ///
+ /// The key to locate in the .
+ ///
+ /// true if the contains an element with the key; otherwise, false.
+ ///
+ /// key is null.
+ public bool ContainsKey(TKey key)
+ {
+ return dict.ContainsKey(key);
+ }
+
+ ///
+ /// Gets a read only containing the keys of the .
+ ///
+ ///
+ /// An containing the keys of the object that implements .
+ public ICollection Keys
+ {
+ get
+ {
+ return new ReadOnlyCollection(new List(dict.Keys));
+ }
+ }
+
+ ///
+ /// Remove does not change a read only Dictionary, will throw an exception.
+ ///
+ /// The is read-only.
+ public bool Remove(TKey key)
+ {
+ throw new NotSupportedException("Dictionary Is ReadOnly");
+ }
+
+ ///
+ /// Tries the get value.
+ ///
+ /// The key.
+ /// The value.
+ ///
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ return dict.TryGetValue(key, out value);
+ }
+
+ ///
+ /// Gets an containing the values in the .
+ ///
+ ///
+ /// An containing the values in the object that implements .
+ public ICollection Values
+ {
+ get
+ {
+ return new ReadOnlyCollection(new List(dict.Values));
+ }
+ }
+
+ ///
+ /// Gets the with the specified key. Set
+ /// will throw an exception
+ ///
+ public TValue this[TKey key]
+ {
+ get
+ {
+ return dict[key];
+ }
+ set
+ {
+ throw new NotSupportedException("Dictionary Is ReadOnly");
+ }
+ }
+
+ ///
+ /// Add does not change a read only Dictionary
+ ///
+ /// The object to add to the .
+ /// The is read-only.
+ public void Add(KeyValuePair item)
+ {
+ throw new NotSupportedException("Dictionary Is ReadOnly");
+ }
+
+ ///
+ /// Clear does not change a read only Dictionary.
+ ///
+ /// The is read-only.
+ public void Clear()
+ {
+ throw new NotSupportedException("Dictionary Is ReadOnly");
+ }
+
+ ///
+ /// Determines whether the contains a specific value.
+ ///
+ /// The object to locate in the .
+ ///
+ /// true if item is found in the ; otherwise, false.
+ ///
+ public bool Contains(KeyValuePair item)
+ {
+ return dict.Contains(item);
+ }
+
+ ///
+ /// Copies the elements of the to an , starting at a particular index.
+ ///
+ /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing.
+ /// The zero-based index in array at which copying begins.
+ /// arrayIndex is less than 0.
+ /// array is null.
+ /// array is multidimensional.-or-arrayIndex is equal to or greater than the length of array.-or-The number of elements in the source is greater than the available space from arrayIndex to the end of the destination array.-or-Type T cannot be cast automatically to the type of the destination array.
+ public void CopyTo(KeyValuePair[] array, int arrayIndex)
+ {
+ dict.CopyTo(array, arrayIndex);
+ }
+
+ ///
+ /// Gets the number of elements contained in the .
+ ///
+ ///
+ /// The number of elements contained in the .
+ public int Count
+ {
+ get
+ {
+ return dict.Count;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the is read-only.
+ ///
+ ///
+ /// true if the is read-only; otherwise, false.
+ public bool IsReadOnly
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ ///
+ /// Remove does not change a read only Dictionary. Throws an exception
+ ///
+ /// The object to remove from the .
+ ///
+ /// true if item was successfully removed from the ; otherwise, false. This method also returns false if item is not found in the original .
+ ///
+ /// The is read-only.
+ public bool Remove(KeyValuePair item)
+ {
+ throw new NotSupportedException("Dictionary Is ReadOnly");
+
+ }
+
+ ///
+ /// Returns an enumerator that iterates through the collection.
+ ///
+ ///
+ /// A that can be used to iterate through the collection.
+ ///
+ public IEnumerator> GetEnumerator()
+ {
+ return dict.GetEnumerator();
+ }
+
+ ///
+ /// Returns an enumerator that iterates through a collection.
+ ///
+ ///
+ /// An object that can be used to iterate through the collection.
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return idict.GetEnumerator();
+ }
+
+ ///
+ /// Add does not change a read only Dictionary. Throws an exception.
+ ///
+ /// The to use as the key of the element to add.
+ /// The to use as the value of the element to add.
+ /// The is read-only.-or- The has a fixed size.
+ public void Add(object key, object value)
+ {
+ throw new NotSupportedException("Dictionary Is ReadOnly");
+ }
+
+ ///
+ /// Determines whether the object contains an element with the specified key.
+ ///
+ /// The key to locate in the object.
+ ///
+ /// true if the contains an element with the key; otherwise, false.
+ ///
+ /// key is null.
+ public bool Contains(object key)
+ {
+ return idict.Contains(key);
+ }
+
+ ///
+ /// Returns an object for the object.
+ ///
+ ///
+ /// An object for the object.
+ ///
+ IDictionaryEnumerator IDictionary.GetEnumerator()
+ {
+ return idict.GetEnumerator();
+ }
+
+ ///
+ /// Gets a value indicating whether the object has a fixed size.
+ ///
+ ///
+ /// true if the object has a fixed size; otherwise, false.
+ public bool IsFixedSize
+ {
+ get
+ {
+ return idict.IsFixedSize;
+ }
+ }
+
+ ///
+ /// Gets an containing the keys of the .
+ ///
+ ///
+ /// An containing the keys of the object that implements .
+ ICollection IDictionary.Keys
+ {
+ get
+ {
+ return idict.Keys;
+ }
+ }
+
+ ///
+ /// Remove does not change a read only Dictionary. Throws an exception.
+ ///
+ /// The key of the element to remove.
+ /// The object is read-only.-or- The has a fixed size.
+ public void Remove(object key)
+ {
+ throw new NotSupportedException("Dictionary Is ReadOnly");
+ }
+
+ ///
+ /// Gets an containing the values in the .
+ ///
+ ///
+ /// An containing the values in the object that implements .
+ ICollection IDictionary.Values
+ {
+ get
+ {
+ return idict.Values;
+ }
+ }
+
+ ///
+ /// Gets the with the specified key. Set throws an exception.
+ ///
+ ///
+ /// The is read-only.
+ /// if try to set value
+ public object this[object key]
+ {
+ get
+ {
+ return idict[key];
+ }
+ set
+ {
+ throw new NotSupportedException("Dictionary Is ReadOnly");
+ }
+ }
+
+ ///
+ /// Copies the elements of the to an , starting at a particular index.
+ ///
+ /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing.
+ /// The zero-based index in array at which copying begins.
+ /// array is null.
+ /// index is less than zero.
+ /// array is multidimensional.-or- index is equal to or greater than the length of array.-or- The number of elements in the source is greater than the available space from index to the end of the destination array.
+ /// The type of the source cannot be cast automatically to the type of the destination array.
+ public void CopyTo(Array array, int index)
+ {
+ idict.CopyTo(array, index);
+ }
+
+ ///
+ /// Gets a value indicating whether access to the is synchronized (thread safe).
+ ///
+ ///
+ /// true if access to the is synchronized (thread safe); otherwise, false.
+ public bool IsSynchronized
+ {
+ get
+ {
+ return idict.IsSynchronized;
+ }
+ }
+
+ ///
+ /// Gets an object that can be used to synchronize access to the .
+ ///
+ ///
+ /// An object that can be used to synchronize access to the .
+ public object SyncRoot
+ {
+ get
+ {
+ return idict.SyncRoot;
+ }
+ }
+
+ ///
+ /// Runs when the entire object graph has been deserialized.
+ ///
+ /// The object that initiated the callback. The functionality for this parameter is not currently implemented.
+ public void OnDeserialization(object sender)
+ {
+ IDeserializationCallback callback = dict as IDeserializationCallback;
+ if (callback != null)
+ {
+ callback.OnDeserialization(sender);
+ }
+ }
+
+ ///
+ /// Populates a with the data needed to serialize the target object.
+ ///
+ /// The to populate with data.
+ /// The destination (see ) for this serialization.
+ /// The caller does not have the required permission.
+ public void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ ISerializable serializable = dict as ISerializable;
+ if (serializable != null)
+ {
+ serializable.GetObjectData(info, context);
+ }
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Spring/Spring.Core/Spring.Core.2008.csproj b/src/Spring/Spring.Core/Spring.Core.2008.csproj
index 42fbc7f5..8d34df10 100644
--- a/src/Spring/Spring.Core/Spring.Core.2008.csproj
+++ b/src/Spring/Spring.Core/Spring.Core.2008.csproj
@@ -1,7 +1,7 @@

Local
- 9.0.30729
+ 9.0.21022
2.0
{710961A3-0DF4-49E4-A26E-F5B9C044AC84}
Debug
@@ -124,6 +124,7 @@
Code
+
Code
diff --git a/test/Spring/Spring.Core.Tests/Collections/Generic/ReadOnlyDictionaryTests.cs b/test/Spring/Spring.Core.Tests/Collections/Generic/ReadOnlyDictionaryTests.cs
new file mode 100644
index 00000000..4bdba5e4
--- /dev/null
+++ b/test/Spring/Spring.Core.Tests/Collections/Generic/ReadOnlyDictionaryTests.cs
@@ -0,0 +1,94 @@
+#if NET_2_0
+#region License
+
+/*
+ * Copyright © 2002-2007 the original author or authors.
+ *
+ * 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
+
+#region Imports
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using NUnit.Framework.SyntaxHelpers;
+
+#endregion
+
+namespace Spring.Collections.Generic
+{
+ ///
+ /// This class contains tests for ReadOnlyDictionary
+ ///
+ /// Mark Pollack
+ [TestFixture]
+ public class ReadOnlyDictionaryTests
+ {
+ private IDictionary personDictionary;
+ private IDictionary readonlyPersonDictionary;
+
+ [SetUp]
+ public void Setup()
+ {
+ personDictionary = new Dictionary();
+ personDictionary.Add("Mark", 38);
+ personDictionary.Add("Daniela", 38);
+ readonlyPersonDictionary = new ReadOnlyDictionary(personDictionary);
+
+ }
+
+ [Test]
+ public void TestSunnyDay()
+ {
+
+ Assert.That(readonlyPersonDictionary.IsReadOnly, Is.True);
+ Assert.That(readonlyPersonDictionary.Contains(new KeyValuePair("Mark", 38)));
+ Assert.That(readonlyPersonDictionary.ContainsKey("Mark"), Is.True);
+ Assert.That(readonlyPersonDictionary.Count, Is.EqualTo(2));
+ }
+
+ [Test]
+ public void Exceptions()
+ {
+ //updating to nunit 2.5 would be nice to use Assert.That( SomeMethod, Throws.Exception());
+ // Execute(delegate { });
+
+ Execute(delegate { readonlyPersonDictionary["Mark"] = 4; });
+ Execute(delegate { readonlyPersonDictionary.Add("Gabriel", 3); });
+ Execute(delegate { readonlyPersonDictionary.Add(new KeyValuePair("Mark", 38)); });
+ Execute(delegate { readonlyPersonDictionary.Clear();});
+ Execute(delegate { readonlyPersonDictionary.Remove("Mark"); });
+ Execute(delegate { readonlyPersonDictionary.Remove(new KeyValuePair("Mark", 38)); });
+ }
+
+ public void Execute(DoInTryCatch exceptionDelegate)
+ {
+ try
+ {
+ exceptionDelegate.Invoke();
+ Assert.Fail("Should throw NotSupportedException");
+ } catch (NotSupportedException)
+ {
+
+ }
+ }
+
+ public delegate void DoInTryCatch();
+
+
+ }
+}
+#endif
\ No newline at end of file
diff --git a/test/Spring/Spring.Core.Tests/Spring.Core.Tests.2008.csproj b/test/Spring/Spring.Core.Tests/Spring.Core.Tests.2008.csproj
index 3cd88f9e..c0db813a 100644
--- a/test/Spring/Spring.Core.Tests/Spring.Core.Tests.2008.csproj
+++ b/test/Spring/Spring.Core.Tests/Spring.Core.Tests.2008.csproj
@@ -1,7 +1,7 @@

Local
- 9.0.30729
+ 9.0.21022
2.0
{44B16BAA-6DF8-447C-9D7F-3AD3D854D904}
Debug
@@ -120,6 +120,7 @@
Code
+
Code