This article describes an implementation of a ReadOnlyDictionary <TKey, TValue> that's missing from the .NET framework.

UPDATE 2012-06-05: .NET 4.5 will (finally finally!!) contain a ReadOnlyDictionary<TKey, TValue>, which will make this post (that has long be my top most googled article) finally redundant. If you're still developing under .NET 4.0 or below, please read on.

UPDATE 2013-04-11:Software license notice: I previously released this under the MIT license, but decided to change this. The source code presented in this post is released as 'public domain'. This means that you can do whatever you want with it, no need to tell anyone about it, but don't blame me if shit hits the fan.

I always wondered why Microsoft didn't add a ReadOnlyDictionary class in the System.Collections.Generic or System.Collections.ObjectModel namespace. I'm notalone. This feature request has been posted at least twiceon the Microsoft Connect feedback website and many people decided to write theirownimplementation.

None of the implementations I found on the internet appealed to me. The two biggest problems I had with them where that they weren't actually read-only and didn't implement interface members explicitly. The latter complicates the use of the dictionary unnecessarily while using it through IntelliSense.

While I could try to fix one of those implementations, I decided to write my own and looked closely at the Dictionary<TKey, TValue> and ReadOnlyCollection<T> implementations that already were in the framework. In the code below you'll see that the implementation is rather straight forward. I've implemented the correct interfaces, with most methods explicitly and I wrapped a Dictionary<TKey, TValue> internally. The internal dictionary is a copy of the provided constructor argument. This last point is rather important, because the other implementations I saw didn't make such a copy, allowing you to change the read-only dictionary after creation by using a reference to the original dictionary. Last but not least is the implementation of a ReadOnlyDictionaryDebugView class that helps you display the dictionary during debugging.

UPDATE 2008-03-02:Looking once more at .NET’s ReadOnlyCollection<T> implementation, I noticed that the implementation doesn’t make a copy of the supplied collection; it simply wraps it! After giving it some thought, it made a lot of sense to me. By wrapping the original collection, the read-only collection copies the original collection’s behavior. Copying the behavior is important, because otherwise you would end up creating a read-only version of each and every type implementing ICollection or IDictionary for which you’d like to have a read-only wrapper, simply because each type possibly behaves differently.

For the ReadOnlyDictionary<TKey, TValue> it is even clearer. Whether the dictionary contains a key or not, is determined by how equality is defined for type TKey. I already noticed this behavioral problem in my original implementation, which is why I included a constructor with an IEqualityComparer<TKey> argument. But this simply isn’t enough, because supplying an IEqualityComparer<TKey> is possibly not suitable for every dictionary implementation. Remember that we expect an IDictionary<TKey, TValue> to be given by the user, so there actually isn’t that much we know about the implementation of the supplied object.

My conclusion is that copying the supplied dictionary is actually a design flaw. The only reasonable thing the ReadOnlyDictionary can do is to wrap the given dictionary. This however leads to an implementation that is not truly read-only, but so is the framework’s ReadOnlyCollection implementation. Therefore we shift the responsibility for this to the user of our implementation.

UPDATE 2010-05-20:I updated the formatting and XML comments in a way that StyleCop likes it.

UPDATE 2011-03-22:Jacek noted correctly in the comments that the ReadOnlyDictionary made the assumption that the wrapped dictionary always implemented the old non-generic ICollection and IDictionary interfaces. While most types in the framework that implement IDictionary<TKey, TValue> also implement ICollection and IDictionary, not all types do, and more importantly, types simply don't have to. To fix this, I had to remove the IDictionary interface from the ReadOnlyDictionary. It still implements ICollection though.

/// <summary> /// Gets or sets the value associated with the specified key. /// </summary> /// <returns> /// The value associated with the specified key. If the specified key /// is not found, a get operation throws a /// <see cref="T:System.Collections.Generic.KeyNotFoundException" />, /// and a set operation creates a new element with the specified key. /// </returns> /// <param name="key">The key of the value to get or set.</param> /// <exception cref="T:System.ArgumentNullException"> /// Thrown when the key is null. /// </exception> /// <exception cref="T:System.Collections.Generic.KeyNotFoundException"> /// The property is retrieved and key does not exist in the collection. /// </exception> public TValue this[TKey key] { get { return this.source[key]; } set { ThrowNotSupportedException(); } }

/// <summary>This method is not supported by the /// <see cref="T:ReadOnlyDictionary`2"/>.</summary> /// <param name="key"> /// The object to use as the key of the element to add.</param> /// <param name="value"> /// The object to use as the value of the element to add.</param> void IDictionary<TKey, TValue>.Add(TKey key, TValue value) { ThrowNotSupportedException(); }

/// <summary> /// Returns an enumerator that iterates through the collection. /// </summary> /// <returns> /// A IEnumerator that can be used to iterate through the collection. /// </returns> IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() { IEnumerable<KeyValuePair<TKey, TValue>> enumerator = this.source;

return enumerator.GetEnumerator(); }

/// <summary> /// Returns an enumerator that iterates through a collection. /// </summary> /// <returns> /// An IEnumerator that can be used to iterate through the collection. /// </returns> IEnumerator IEnumerable.GetEnumerator() { return this.source.GetEnumerator(); }

The code samples on my weblog are colorized using javascript, but
you disabled javascript (for my website) on your browser.
If you're interested in viewing the posted code snippets in
color, please enable javascript.

27 comments:

Just one comment ... this is missing alot of the benefits of a read-only dictionary implementation-wise. A read-only dictionary has many optimizations that can be added to it in terms not only of multi-threaded code but also in optimizing its internal table since its optimize once for many reads.Greg Young (URL) - 08 05 08 - 04:26

Greg, Thank you for your comment. I agree with you. The given implementation lacks a lot of benefits (like speed optimizations). But please note that the implementation simply is a wrapper around an IDictionary, just like .NET's ReadOnlyCollection is a wrapper around IList. I tried to write an implementation that could end up in the .NET framework, therefore I think it’s crucial to have an implementation that is similar to that of the ReadOnlyCollection. Because of the wrapping behavior of the collection, it’s (almost) impossible to optimize it. It just dispatches method calls to the real object.

If you like, you could show me a ReadOnlyDictionary implementation that has the benefits you described (perhaps on your blog?).

While I didn't talk about thread safety, it was the main reason for me to create a ReadOnlyDictionary. As you imply, a read-only dictionary gets thread safety for free.

The advantage of a 'wrapped' implementation (like the one on my blog) is that -as I wrote- it works for every type implementing IDictionary, while this isn't the case then using a 'copy' implementation. However, the advantage of making a copy is that it is thread safe by default. Thread safety of the 'wrapped' implementation depends on the underlying type. It is possible that an underlying type isn't thread safe, even if it only has readers. The Dictionary<TKey, TValue> for example, can safely be used as wrapped object; the MSDN docs states: "A Dictionary can support multiple readers concurrently, as long as the collection is not modified." Still this is something the user of a 'wrapped' implementation should be aware of. The same holds of course for the ReadOnlyCollection<T>.Steven (URL) - 21 05 08 - 23:30

This code is really well done. Thanks for posting this Steven. However, I don't see a copyright license notice for this code, other than your claim of copyright at the bottom of the page. I would like to use it in an open source project I'm working on. Would you consider releasing it under an open source license? Something like BSD would be the easiest and would offer the most flexibility for people who want to use it.Scott Whitlock - 15 03 09 - 16:18

Scott, I never really thought of this, but I consider every peace of code I post on my blog as free for all. I will look into this to find the correct license and make this explicit on my blog (probably the LGPL or perhaps the MIT license). But you are absolutely free to use this code in any open source or commercial project. No limitations, no costs, but of course as-is. It would be nice if you add a comment that links back to my blog or this article, but you don't have to.

Let me know which OS project you're working on. I might be interested.

Thanks. LGPL might limit my ability to use it, since I'd be limited in what licenses I choose, and I haven't settled on one yet (I'm still talking to an IP lawyer to make sure I don't shoot myself in the foot). I do intend to license it in a way that would allow commercial use, and haven't settled on whether or not to use a copy-left license yet, like CDDL or GPLv3 with linking exception.

Whatever you choose, I would definitely link back to this blog for attribution.

MIT is similar to the BSD license (and both are permissive). Apache 2.0 is also permissive. I would be careful with anything more copy-left than those, since Microsoft might get a little annoyed if their eventual implementation of ReadOnlyDictionary looks similar to this. Given the small size of this code chunk, perhaps something like the Code Project Open License would be suitable: http://www.codeproject.com/info/cpol10.a..Scott Whitlock - 15 03 09 - 20:06

Nice work, just what I was looking for. Have included in my project (with copyright notices intact).

Thanks for the implementation. I've ported it to Vb.Net and I have no way of sharing it. Have you a way of doing that?

Also while porting some minor things:
* At the constructor the ArgumentException can not be thrown because only assignments are done.
* The IsSynchronized should return true because the access to the dictionary is thread safe.Nacho - 21 04 10 - 18:14

You are right about the error about ArgumentException in the documentation. I fixed it. Thanks for that.

I don’t believe the ReadOnlyDictionary’s IsSynchronized should always return true. Whether the dictionary is thread-safe, is dependant on the wrapped dictionary. For this reason the .NET’s ReadOnlyCollection’s IsSynchronized property will always return false. For the ReadOnlyCollection, there is no way to determine whether the wrapped list is actually thread-safe. The same holds for the ReadOnlyDictionary.

I suspect the other reason for wrapping the original collection is because the intent of a read-only collection for MOST cases is a data security thing: it provides a way of protecting the collection from adds/deletes/clear when exposing it to a consumer.

Your internal code still needs to be able to manipulate the collection potentially, and therefore things like thread-safety are not really in the scope of a read-only collection from this point of view. I suppose one should think of it as a way of granting permission rather than a re-implementation of the collection.

Generally a read operation on a collection requires no locking on the collection item's "getter". So any thread optimization explicitly around read-only should already be there. The only time you need to lock is when the collection is modified.

The exception to this is if you are anticipating threaded behavior and providing methods to combine the two actions in some sort of GetOrAdd operation on it such as that provided on the .Net 4 ConcurrentDictionary class. The nice thing about your implementation is that it will also work with the ConcurrentDictionary because a GetOrAdd is not required in a readonly context.

I agree that the IsSynchronized should not return true. Anytime you are implementing a wrapper pattern, you should bubble up things having to do with synchronization with that of the underlying object (which you have done). I suppose if one created a "snapshot" type of dictionary where the collection is modified only in the constructor, you could get away with setting IsSynchronized to true there (and to back up your design, it would be returning the value of the wrapped collection). I imagine that a class like that would look a lot like the one you have above, except the referenced collection would be internal. :)Dan (URL) - 02 08 10 - 02:18

Oops.. forgot to mention..

I think IsFixedSize should also return the value of the wrapped collection. For the same reasons of potential edits of the size of the underlying collection you've stressed everywhere else..Dan (URL) - 02 08 10 - 03:21

Dan, thanks for your comment. The reason for the IsFixedSize property to always return true is because of its definition. The MSDN documentation for IDictionary.IsFixedSize say the following:
“A collection with a fixed size is simply a collection with a wrapper that prevents adding and removing elements; therefore, if changes are made to the underlying collection, including the addition or removal of elements, the fixed-size collection reflects those changes.”

When the IsFixedSize property returns false, it doesn’t mean the collection can’t be changed, only that the consumer can not add and remove elements. Because of this same reason the BCL's ReadOnlyCollection also always returns true.Steven (URL) - 02 08 10 - 08:53

FYI Implementing IEnumerable<KeyValuePair<TKey, TValue>>, ICollection, and IEnumerable are all redundant, as they are all implemented by correction: IDictionary<TKey, TValue>Chadwick Posey - 27 08 10 - 22:05

You are right about that. I updated the code. Thank you.Steven (URL) - 27 08 10 - 22:20

Essentially it's a much simpler solution subclassing ReadOnlyCollection, which gets the work done in a more elegant manner.SoftwareRockstar (URL) - 30 10 10 - 07:33

SoftwareRockstar a.k.a. Muhammad Haroon,

While your alternative is nice and small, please keep in mind that it has performance characteristics that might surprise other developers. It is just a ReadOnlyCollection under the covers which leads to lookup speed of O(n) instead of O(1). You might want to warn the readers of your blog about this.Steven (URL) - 30 10 10 - 13:40

You forgot to update the constructor comment, it still says the elements are copied.Dan Berindei - 25 11 10 - 09:54

Those casts to non-generic ICollection and IDictionary in a couple of places look risky. You never know if the underlying dictionary actually implements them.Jacek Mokrzycki - 22 03 11 - 17:53

Very well spotted Jacek. I updated the code to fix this. The ReadOnlyDictionary now only implements the non-generic ICollection and not IDictionary. The removal of IDictionary is unfortunate, but a design with it is simply incorrect.

There's still an issue with your ICollection.CopyTo(...) implementation. You check whether the given array is of KeyValuePair type, instead you should check if it's either object[], ValueType[] or DictionaryEntry[]. Unfortunately that means you can't use the generic ICollection.CopyTo method of the wrapped dictionary, because KeyValuePair is not convertible to DictionaryEntry. I would suggest checking if the element type of array is Type.AssignableFrom DictionaryEntry, casting array to object[] and then manually copying KeyValuePairs to DictionaryEntries. Lots of code for a wrapper but if you look inside ReadOnlyCollection with .NET Reflector you can see it's not that slim either.Jacek Mokrzycki - 23 03 11 - 11:47

Jacek, and again you are right. It doesn't have to be that much code to fix this actually. When we abuse the List for this. Consider it fixed :-)Steven (URL) - 23 03 11 - 20:43

Steven, I'm sorry, but again I've to point out a bug in your code. Throwing all the KeyValuePairs into a generic list and then copying it to array not only won't do the trick, because List doesn't know how to convert generic KeyValuePair to non-generic DictionaryEntry, but it will also add unnecessary strain on memory (you actually allocate a temporary buffer). If you insist on using List, how about introducing a LINQ query:

I agree that performance can be optimized, but your worries about converting values to non-generic DictionaryEntries does not apply, because that only applies to the non-generic IDictionary interface, which is not implemented anymore. It's unfortunate that the ReadOnlyDictionary not implements the old IDictionary interface, because the class will not be usable in some legacy scenario's. At least the current implementation is works as expected.Steven (URL) - 27 03 11 - 21:50

I'm experimenting with an extension method for this to ease the transition from Dictionary to ReadOnlyDictonary as the object is passed out into the "real world" as a "snap-shot" of the state of things at the point it is requested.

With this code, just add "using DictionaryExtensions;" to enable the method .AsReadOnly to get a copy of a Dictionary wrapped in a ReadOnlyDictionary: