One of the most common misunderstandings in .NET development is the idea that variables of type IEnumerable or ReadOnlyCollection are thread-safe. Andrew Arnott of Microsoft explains,
If someone hands you a ReadOnlyCollection<T>, an IReadOnlyList<T>, or an IEnumerable<T> the only guarantee is that you can’t change the data – there is no guarantee that the person that handed you the collection won’t change it. Yet, you often need some confidence that it won’t change. These types don’t offer events to notify you when their contents change, and if they do change, might that occur on a different thread, possibly while you’re enumerating its contents? Such behavior would lead to data corruption and/or random exceptions in your application.
In order to offer truly thread-safe collections for scenarios where you would be tempted to use IEnumerable or ReadOnlyCollection, Microsoft’s Base Class Library (BCL) team is offering a preview of a new set of immutable collections. Based on techniques found in functional programming languages, methods that would normally mutate a collection will instead create new collections. Data sharing is possible between the old and new collection, allowing for some efficiencies.
An interesting characteristic of the immutable collections is that they don’t have a public constructor. Instead work always begins with ImmutableXxx<T>.Empty. Andrew writes,
Using a static Empty property is a departure from a well-entrenched pattern, and there is a reason for it. A constructor must allocate an object, yet given the immutability of these collections, a new object could only ever represent an empty list. Since mutating the collection creates a new collection, using a constructor would cause the creation of multiple objects to represent an empty list, which would be wasteful. A static property allows us to return an empty list singleton which all code in your app can share.
Builders and Collections
Building an immutable collection can be very expensive due to memory allocations. We already see this with strings, which are for all intents and purposes an immutable collection of char. To alleviate this the immutable collections will expose a ToBuilder method. This returns a builder object that can be cheaply modified. Once done, simply use the ToImmutable to get an immutable collection again.
Performance
Performance for immutable collections can be tricky. As you can see in the chart below, the order for most of the immutable collections is actually quite good. And it’s consistent, you don’t have to worry about tripping over the max size of a collection’s internal array which would trigger a full copy of the collection. And unlike normal collections, immutable collections will actually release unused space as items are removed.
But there is a cost. Each operation has to allocate another object in memory, which can strain the garbage collector. The biggest win is when you would otherwise be making snapshot copies of the collection anyways. But in the end the recommendation is still to “use the simplest code to get the job done and then tune for performance as necessary.”
This preview is available via the Microsoft.Bcl.Immutable NuGet package.