ReadOnlyCollection is not an immutable collection

In this blog post we discover how we can mutate a ReadOnlyCollection to have more or less entries than its original state. Readonly does not mean it is immutable. Also we will check out the ImmutableArray.

Readonly does not mean immutable

The ReadOnlyCollection which we have since the .NET Framework 2.0 times is a nice way of telling "Here is a collection which you can not change." In terms of implementation it is just a thin layer on top of a collection, which prohibits operations like Add, Remove or Clear. But that does not mean that the underlying elements can't change.

A ReadOnlyCollection is not an immutable representation, it is basically just a view of a collection or list. That said, if the original list changes so is the ReadOnlyCollection as well.

var numbers = new List<int> { 1, 2 };
var readOnlyNumbersViaExtension = numbers.AsReadOnly();
var readOnlyNumbers = new ReadOnlyCollection<int>(numbers);

Console.WriteLine($"List count: {numbers.Count}");
Console.WriteLine($"ReadOnlyCollection via extension count: {readOnlyNumbersViaExtension.Count}");
Console.WriteLine($"ReadOnlyCollection via new count: {readOnlyNumbers.Count}");

The output is pretty obvious here:

List count: 2
ReadOnlyCollection via extension count: 2
ReadOnlyCollection via new count: 2

But what if we add a new element to our original list, what is now the output?

numbers.Add(3);

Console.WriteLine($"List count: {numbers.Count}");
Console.WriteLine($"ReadOnlyCollection via extension count: {readOnlyNumbersViaExtension.Count}");
Console.WriteLine($"ReadOnlyCollection via new count: {readOnlyNumbers.Count}");

And the output is:

List count: 3
ReadOnlyCollection via extension count: 3
ReadOnlyCollection via new count: 3

Like a database view, also our read only view to our list gets updated. Is this all bad? Of course not, because that has some advantages as well. The most important is, that the underlying operation to create a ReadOnlyCollection is super cheap. It can be done in O(1) time as there is no allocation involved in the first place.

Real Immutability

If we want to have real immutability we have to take different types. For example: ImmutableArray. If we do the same example as above, we get what we would expect from an immutable type:

var numbers = new List<int> { 1, 2 };
var immutableArray = numbers.ToImmutableArray();

Console.WriteLine($"List count: {numbers.Count}");
Console.WriteLine($"ImmutableArray count: {immutableArray.Length}");

numbers.Add(3);

Console.WriteLine($"List count: {numbers.Count}");
Console.WriteLine($"ImmutableArray count: {immutableArray.Length}");

Output:

List count: 2
ImmutableArray count: 2
List count: 3
ImmutableArray count: 2

We can see that the Length stays the same. The downside is obvious: We have to copy the whole array from our original list to our immutable array which costs O(n). By the way the same holds true for an ImmutableList<T>.

Conclusion

ReadOnlyCollection as shown are not immutable. They are just a readonly view of a list. You as consumer can't change them but that does not mean that the original creator/owner of the list can't. If you really need the current state which can't change you have to use the ImmutableArray or ImmutableList. Even big sites like this get this concept wrong:

an immutable collection can be used with less copying—we do not need to worry about it being modified. This can make programs simpler and easier to reason about.

The more you know 😉

6
An error has occurred. This application may no longer respond until reloaded. Reload x