Introduction
The System.Collections.Generic
classes enable you to easily create typed data structures. However, it is missing generic classes that allow you to create immutable / readonly collection instances. In this article, I'll describe how to easily create an immutable collection from an existing generic instance, using C# 3.0 extension methods.
Background
Immutable collections are instances of collection classes whose contents cannot be modified. Immutability is required in certain programming scenarios where the contents of the collection must be static to ensure that it can be "safely" used by any consumer without modifying the state of the program.
Generic collection classes were introduced in the .NET framework 2.0 System.Collections.Generic
namespace. The generic collection classes enabled developers to easily create typed collections, whereas in .NET 1.x, you either had to use untyped collections or create a custom collection with typed methods.
Unfortunately, for most generic collections, .NET 2.0 does not contain corresponding immutable generic classes or methods. For example, if you create a typed System.Collections.Generic.List<T>
, then the List.Add<T>( T typedObject )
method is automatically created and cannot be overridden.
As a solution, it is possible to create a custom version of a generic collection interface which creates a facade for an existing mutable instance. The facade class can implement the same collection interface as the class it wraps, but will throw an exception when any of its mutator methods are called. The facade class can then be used like so:
using System.Collections.Generic;
using Com.WickedByte.Collections.Generic;
IList<string> mutable = new List<string>();
mutable.Add( "Hello " );
IList<string> readOnly = new ImmutableList<string>( mutable );
readOnly.Add( "World!" );
This approach works, but is rather unwieldy, because it requires developers to be familiar with each of the custom immutable classes that you create.
Fortunately, C# 3.0 introduces an elegant way to solve this problem by introducing extension methods. Extension methods allow you to add methods to any existing class without modifying the source of the existing class. In the C# 3.0 approach, I've created immutable wrapper classes that implement ICollection<T>
, IList<T>
, and IDictionary<TKey,TValue>
. However, I've made their visibility internal
, so that developers are not aware of the custom implementations. Instead of publically exposing the custom classes, I've created an extension method for each collection interface, called ToReadOnly()
. Developers need only to import the correct namespace, and will then be able to create an immutable instance with a single method call on the existing mutable generic instance.
Using the code
To use the code, import the Com.WickedByte.Collections.Generic
namespace, and then call ToReadOnly()
on any instance of ICollection<T>
, IList<T>
, or IDictionary<TKey,TValue>
.
using Com.WickedByte.Collections.Generic;
using System.Collections.Generic;
IList<string> helloWorld = new List<string>();
helloWorld.Add( "Hello " );
IList<string> justHello = helloWorld.ToReadOnly();
justHello.Add( "World!" );
It's important to note that if you retain a reference to the original mutable collection instance, then the immutable collection can still be modified indirectly.
I've only created extension methods for the three main collection interfaces: ICollection<T>
, IList<T>
, and IDictionary<TKey,TValue>
. You may wish to create your own extension methods for classes such as Stack<T>
or HashSet<T>
. When designing your facade class, it's important to notice whether any of the interface methods or properties return another collection. To preserve the integrity of the parent class, the returned collection must also be immutable. For example, in designing the immutable version of IDictionary<TKey,TValue>
, I noted that both the Keys
and Values
properties returned an ICollection<T>
. I simply returned an instance of my custom immutable ICollection<T>
for each of these properties.
History
Marshall's torrid relationship with programming started as a child using BASIC on a Commodore PET computer in the 70's. He continued programming through high school, but did not study Computer Science in college. At the time, compilers would fail without telling you why, so after much soul searching, he realized he didn't want to make a living by spending eight hours a day looking for a missing semi-colon.
By the time he was pursuing his Ph.D. in Communication and Marketing, Microsoft had released Visual Studio. The improvements in the IDE were enough to cause Marshall to have late night affairs with COM and ASP. Marshall spent the dotcom bubble years as a web developer. After the bubble burst, he worked independently as a Java developer for medical applications. When Microsoft released an early beta of the .NET Framework, he was convinced to switch his focus from the Java Platform to the new Framework. He spent some time at Philips Medical Systems writing the data-access layer for the Carevue Chart hospital system. He is currently Technical Director for ASE Technologies.
Marshall lives in Salem, Massachusetts but would rather be in Hawaii.