Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does IsReadOnly forbid you from internally changing a custom IList object?

Tags:

c#

.net

Remarks for IList.IsReadOnly state the following:

A collection that is read-only does not allow the addition, removal, or modification of elements after the collection is created.

Does this mean that a custom class implementing IList cannot add or remove elements internally or does it just forbid users from doing it using the interface mothods?

If internal modifications are allowed, does that mean that code that expects an IList with IsReadOnly true to never change is inherently broken?

If internal modifications are not allowed, does that mean that it is impossible to write a valid IList which can change internally, but not allow users to modify it?

like image 911
relatively_random Avatar asked Feb 17 '16 09:02

relatively_random


2 Answers

https://msdn.microsoft.com/en-us/library/cc645181%28v=vs.110%29.aspx

A collection that is read-only is simply a collection with a wrapper that prevents modifying the collection; therefore, if changes are made to the underlying collection, the read-only collection reflects those changes.

For another type, there's nothing to stop you exposing a truly immutable collection as IList<T> with that property returning true, but you don't have to.

We can also see that cases from the framework library that return true for IsReadOnly often allow the inner collection to be mutated.

List<int> initialList = new List<int> { 1 };
IList<int> readOnly = new ReadOnlyCollection<int>(initialList);
Console.WriteLine(readOnly.Count); // 1
Console.WriteLine(readOnly.IsReadOnly); // True
initialList.Add(2);
Console.WriteLine(readOnly.Count); // 2

Really, IsReadOnly tells you that the mutating methods like Add and the setter side of the indexer will fail, not that the object is immutable through all means.

An interesting consideration in this regard: Some places within the framework libraries themselves need read-only lists that are indeed truly read-only. Their public interface returns either ReadOnlyCollection<T> or IReadOnlyList<T> (e.g. BlockExpression.Expressions returns ReadOnlyCollection<T>) but they don't trust read-only collections passed to them. They use an internal type called TrueReadOnlyCollection<T> which is created as a wrapper of a fresh array, copied on construction so nothing else can change it. That type is trusted to never change and so can be shared between uses, but all other cases aren't.

like image 55
Jon Hanna Avatar answered Nov 07 '22 12:11

Jon Hanna


Internal code can indeed change it.

The IsReadOnly indicates that the list cannot be directly changed via that interface.

I agree that "code that expects an IList with IsReadOnly true to never change is inherently broken".

Microsoft has introduced the ImmutableList<T> class, and perhaps this is one of the reasons.

IReadOnlyList to the rescue? Er, unfortunately no:

List<int> test = new List<int> {1};
IReadOnlyList<int> readOnly = test; // Because List<T> implements IReadOnlyList<int>.

Console.WriteLine(readOnly[0]);  // Prints 1.
test[0] = 2;
Console.WriteLine(readOnly[0]);  // Prints 2. Oops.

It's for these reasons that some time ago I proposed that we should not declare interfaces for immutable types.

And here's another example using List<T>.AsReadOnly(). This demonstrates that you can mutate a list with IReadOnly set true by using only built-in .Net types:

Console.WriteLine(shouldBeReadOnly.IsReadOnly);  // Readonly? Yep! This prints True.
Console.WriteLine(shouldBeReadOnly[0]);    // Prints 1.
Console.WriteLine(shouldBeReadOnly.Count); // Prints 1.
test[0] = 2;
test.Add(-1);
Console.WriteLine(shouldBeReadOnly[0]);    // Prints 2. Oops. It wasn't readonly at all.
Console.WriteLine(shouldBeReadOnly.Count); // Prints 2.

Note that you can change the number of elements as well as the elements themselves.

like image 4
Matthew Watson Avatar answered Nov 07 '22 12:11

Matthew Watson