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 😉