Array.IsReadOnly 不一致,取决于接口实现

Posted

技术标签:

【中文标题】Array.IsReadOnly 不一致,取决于接口实现【英文标题】:Array.IsReadOnly inconsistent depending on interface implementation 【发布时间】:2010-12-18 10:33:01 【问题描述】:

类型化数组实现了System.Collections.IListSystem.Collections.Generic.ICollection<T> 接口,它们都有自己的IsReadOnly 属性。但是这里到底发生了什么?

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"

数组的IList 视图的行为与我预期的一样,返回的结果与数组本身相同,但是数组的ICollection&lt;T&gt; 视图返回true。

对这种行为有任何合理的解释,还是编译器/CLR 错误? (如果是后者,我真的会很惊讶,因为你想象这会在现在之前被发现,但它太违反直觉了,我想不出解释会是什么......)。

我正在使用 C#3.0/.NET 3.5 SP1。

【问题讨论】:

有趣的是,两者的注释都说“只读的集合不允许在集合创建后添加、删除或修改元素。”。而且我认为逻辑等同于 IsReadOnly = !CanAdd || !可以删除 || !CanEdit 应该返回 true,因为您可以替换列表指定索引处的值。 一个但更多,但 Array.IsReadonly 文档是明确的..“对于所有数组,此属性始终为 false。” “Array 实现 IsReadOnly 属性,因为 System.Collections..::.IList 接口需要它。如果需要只读集合,请使用实现 System.Collections..::.IList 的 System.Collections 类接口。只读数组不允许在数组创建后添加、删除或修改元素。" 【参考方案1】:

这种行为的原因归结为 System.Array 具有 2 个 IsReadOnly 属性

第一个是类型数组上的普通属性。此属性满足 IList 接口的 IsReadOnly 属性。无论出于何种原因,在 CLR 1.0 中,他们都认为该属性应该返回 true。

第二个是 ICollection&lt;T&gt; 类型的显式属性实现(实际上由 CLR IIRC 实现)。在这种情况下,IsReadOnly 返回 true,因为类型 Array 不能满足 ICollection&lt;T&gt; 的变异方法,例如 Add、Clear 等...

真正的问题是为什么版本之间会发生变化?我实际上不知道,但我的猜测是作者确定将Array 视为只读时更合适,因为它被视为单独的集合。虽然它可以满足部分可变方法,但它不能满足所有可变方法。因此,将其视为只读与可变更安全。

【讨论】:

ICollection&lt;T&gt;.IsReadOnly因为不能满足ICollection&lt;T&gt;接口的所有方法而返回true的解释并不真正成立,因为IList.IsReadOnly返回false并且不能满足所有方法IList 接口。所以我仍然不知道这是否是一个错误,或者如果不是这个决定背后的理由是什么。 @Greg,我相信这代表了 CLR v1 和 v2 之间思想的转变。可能是由于客户投诉,尽管我找不到任何证据。【参考方案2】:

来自Array Class 的文档:

在 .NET Framework 2.0 版中,Array 类实现了 System.Collections.Generic.IList&lt;T&gt;, System.Collections.Generic.ICollection&lt;T&gt;,和 System.Collections.Generic.IEnumerable&lt;T&gt; 通用接口。这 提供给数组的实现 在运行时,因此不是 对文档构建可见 工具。结果,通用 接口不会出现在 数组的声明语法 类,并且没有参考 接口成员的主题是 只能通过将数组转换为 通用接口类型(显式 接口实现)。 关键 施放时要注意的事情 这些接口之一的数组是 添加、插入或 删除元素抛出 NotSupportedException.

因此,由于通用集合不支持添加、插入或删除,IsReadOnly 返回 true。 至于为什么System.Collections.IList 不返回false?我的猜测是

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

不是他们想看到的。当他们在 v2 中添加泛型接口时,这些只能在数组被强制转换时调用,因此对于那些返回 true 更有意义。

【讨论】:

这实际上并没有回答这个问题; IsReadOnly 属性为什么会为底层数组返回不同的值是没有道理的。【参考方案3】:

From MSDN:

IList 是 ICollection 接口是基础 所有非通用列表的接口。 IList 实现分为三种 类别:只读、固定大小和 可变大小。一个只读的 IList 无法修改。固定大小的 IList 不允许添加或删除 的元素,但它允许 修改现有元素。一种 可变大小的 IList 允许 添加、删除和修改 元素。

ICollection 接口没有索引器,因此固定大小的 ICollection 自动为只读 - 无法修改现有项目。

可能 ICollection.IsFixedSize 会是比 ICollection.IsReadOnly 更好的属性名称,但两者都意味着相同的东西 - 无法添加或删除元素,即与 IList.IsFixedSize 相同。

数组是一个固定大小的列表,但不是只读的,因为元素可以修改。

作为 ICollection,它是只读的,因为 ICollection 无法修改元素。

这可能看起来令人困惑,但它是一致且合乎逻辑的。

有点不一致的是,泛型 IList 接口具有从 ICollection 继承的 IsReadOnly 属性,因此其语义不同于非泛型 IList.IsReadOnly。我想设计师已经意识到了这种不一致,但出于向后兼容性的原因,他们无法返回并更改非泛型 IList 的语义。

总而言之,IList 可以是:

可变大小。

IList.IsFixedSize = false

IList.IsReadOnly = 假

ICollection.IsReadOnly = false

固定大小(但可以修改元素,例如数组)

IList.IsFixedSize = true

IList.IsReadOnly = 假

ICollection.IsReadOnly = true

只读(不能修改元素)

IList.IsFixedSize = true

IList.IsReadOnly = true

ICollection.IsReadOnly = true

【讨论】:

【参考方案4】:

对于这个决定有很多痛苦,这在 feedback article 上的 cmets 中很明显。

【讨论】:

看起来这给出了内部推理(这似乎是“我们知道它是垃圾,但在发布周期中做任何事情都为时已晚”)。所以这是设计使然,但不是一个好的设计。 反馈文章的链接似乎已损坏。还有其他链接吗?

以上是关于Array.IsReadOnly 不一致,取决于接口实现的主要内容,如果未能解决你的问题,请参考以下文章

SpringMVC:取决于 url 扩展的不一致映射行为

QSerialPort 不一致的行为取决于起始波特率

SwiftUI ObservableObject 的行为不一致,具体取决于调用站点的来源

python中不一致的小数指数

有条件设置时,约束的行为不一致

Unity SafeArea 在不同的起始轮换之间不一致