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.
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
There was plenty of agony over this decision, as evident in the comments on this feedback article.
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.
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>
, andSystem.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 throwNotSupportedException
.
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.
精彩评论