views:

108

answers:

4

A typed array implements both the System.Collections.IList and System.Collections.Generic.ICollection<T> interfaces, which both have their own IsReadOnly properties. But what on earth is going on here?

var array = new int[10];
Console.WriteLine(array.IsReadOnly); // prints "False"

var list = (System.Collections.IList)array;
Console.WriteLine(list.IsReadOnly); // prints "False"

var collection = (System.Collections.Generic.ICollection<int>)array;
Console.WriteLine(collection.IsReadOnly); // prints "True"

The IList view of the array behaves as I'd expect, returning the same as the array itself, however the ICollection<T> view of the array returns true.

Is there any rational explanation for this behaviour, or is it a compiler/CLR bug? (I'd be really surprised if it's the latter as you'd imagine this would have been found before now, but it's so counter-intuitive I can't think what the explanation could be...).

I'm using C#3.0/.NET 3.5 SP1.

A: 

The reason for this behavior comes down to System.Array having 2 IsReadOnly properties

The first is a normal property on the type array. This property satisfies the IsReadOnly property of the IList interface. For whatever reason in 1.0 of the CLR they deemed that the property should return true.

The second is the explicit property implementation for the type ICollection<T> (actually implemented by the CLR IIRC). In this case IsReadOnly returns true because the type Array cannot satisfy the mutating methods of ICollection<T> such as Add, Clear, etc ...

The real question is why the change between versions? I don't actually know but my guess is that the authors determined that it is more appropriate to view the Array as read only when it is seen as a separate collection. While it can satisfy part of the mutable methods it cannot satisfy them all. Hence it's safer to see it as readonly vs. mutable.

JaredPar
The explanation that `ICollection<T>.IsReadOnly` returns true because it can't satisfy all methods of the `ICollection<T>` interface doesn't really hold water because `IList.IsReadOnly` returns false and it can't satisfy all methods of the `IList` interface either. So I still don't know whether this is a bug, or if not what the rationale behind the decision was.
Greg Beech
@Greg, I believe this represents a change in thinking between v1 and v2 of the CLR. Likely due to customer complaints although I can't find any evidence of that.
JaredPar
A: 

From the docs for the Array Class:

In the .NET Framework version 2.0, the Array class implements the System.Collections.Generic.IList<T>, System.Collections.Generic.ICollection<T>, and System.Collections.Generic.IEnumerable<T> generic interfaces. The implementations are provided to arrays at run time, and therefore are not visible to the documentation build tools. As a result, the generic interfaces do not appear in the declaration syntax for the Array class, and there are no reference topics for interface members that are accessible only by casting an array to the generic interface type (explicit interface implementations). The key thing to be aware of when you cast an array to one of these interfaces is that members which add, insert, or remove elements throw NotSupportedException.

So, because the generic collections do not support add, insert, or delete, IsReadOnly returns true. As to why it doesn't return false for System.Collections.IList? My guess would be that

var array = new int[10];
Console.WriteLine(array.IsReadOnly); // prints "True"
array[0] = 5; // WTF? This is readonly.

was not what they wanted to see. When they added the generics interfaces in v2, these can only be called when the array has been cast, so returning true for those made more sense.

Logan5
That doesn't actually answer the question at all; there is no justification for why the IsReadOnly property returns a different value for the underlying array.
Greg Beech
clarified my answer
Logan5
+2  A: 

From MSDN:

IList is a descendant of the ICollection interface and is the base interface of all non-generic lists. IList implementations fall into three categories: read-only, fixed-size, and variable-size. A read-only IList cannot be modified. A fixed-size IList does not allow the addition or removal of elements, but it allows the modification of existing elements. A variable-size IList allows the addition, removal, and modification of elements.

The ICollection<T> interface does not have an indexer, so a fixed-size ICollection<T> is automatically readonly - there is no way to modify an existing item.

Possibly ICollection<T>.IsFixedSize would have been a better property name than ICollection<T>.IsReadOnly, but both imply the same thing - impossible to add or remove elements, i.e. the same as IList.IsFixedSize.

An array is a fixed-size list, but is not readonly as elements can be modified.

As an ICollection<T>, it is readonly, since an ICollection<T> has no way to modify elements.

This may appear confusing, but it is consistent and logical.

What is slightly inconsistent is that the generic IList<T> interface has an IsReadOnly property inherited from ICollection<T> whose semantics are therefore different from the non-generic IList.IsReadOnly. I imagine the designers were aware of this inconsistency but were unable to go back and change the semantics of the non-generic IList for backwards compatibility reasons.

To summarize, an IList can be:

  • Variable-size.

    IList.IsFixedSize = false

    IList.IsReadOnly = false

    ICollection<T>.IsReadOnly = false

  • Fixed-size (but elements can be modified, e.g. an Array)

    IList.IsFixedSize = true

    IList.IsReadOnly = false

    ICollection<T>.IsReadOnly = true

  • Read-only (elements can not be modified)

    IList.IsFixedSize = true

    IList.IsReadOnly = true

    ICollection<T>.IsReadOnly = true

Joe
+3  A: 

There was plenty of agony over this decision, as evident in the comments on this feedback article.

Hans Passant
Looks like this gives the internal reasoning (which appears to be "we know it's rubbish but it's too late in the release cycle to do anything about it"). So it's by design, but not a good one.
Greg Beech