我完全理解System.Array是不可变的。
鉴于此,为什么它具有Add()方法?
它不会出现在Get-Member的输出中。
$a = @('now', 'then')
$a.Add('whatever')
是的,我知道这会失败,而且我知道为什么会失败。我不是在请求使用[System.Collections.ArrayList]
或[System.Collections.Generic.List[object]]
的建议。
答案 0 :(得分:6)
[System.Array]
实现[System.Collections.IList]
,而后者具有.Add()
方法。
Array
实现IList
(这也是一个涵盖 resizable 集合的接口)可能令人惊讶-听起来好像{ {3}} [1]
在C#中,很难偶然发现,因为您需要显式转换为IList
或使用IList
类型的变量才能访问.Add()
方法。
相比之下,从版本3开始, PowerShell甚至将类型的 explicit 接口实现作为给定类型实例的直接成员。(显式接口实现是在实现中显式引用接口的接口,例如historical reasons for it而不是.Add()
;显式接口实现不是实现类型的公共接口的直接部分,因此C#需要强制转换/接口类型的变量以访问它们。
作为此设计的副产品,在{em> PowerShell 中的 .Add()
方法可以在System.Array
实例上直接调用 ,这使得更容易发现问题,因为您可能没有意识到自己正在调用 interface 方法。对于数组,IList.Add()
实现(正确地)抛出一个异常,指出该Collection was of a fixed size
;后者是IList.Add()
类型的例外,这是实现接口的类型应如何报告不支持接口的 parts 的方式。
什么是有帮助的,NotSupportedException
甚至只是引用方法而无需调用-只需省略()
-即可检查方法以确定是否为类型或接口实现的本机:
PS> (1, 2).Add # Inspect the definition of a sample array's .Add() method
OverloadDefinitions
-------------------
int IList.Add(System.Object value)
如您所见,输出显示.Add()
方法属于Ilist
接口。
[1] 可选阅读:关于可变性的.NET中与集合相关的接口
免责声明:这不是我的专业领域。如果我的解释不正确/可以改善,请告诉我们。
与集合相关的接口的层次结构的根是Get-Member
cmdlet(从v1开始是非通用的)和ICollection
(从v2开始是通用的)。
(他们依次实现ICollection<T>
/ IEnumerable
,其唯一成员是.GetEnumerator()
方法。)
值得称赞的是,虽然非通用ICollection
接口没有对集合的可变性做任何假设,但不幸的是,它的通用对应项(ICollection<T>
)却做到了-它包括用于 modify 的方法(文档甚至将接口的用途声明为“ 操作通用集合”(添加了重点))。在非通用v1世界中,发生了同样的事情,只是在其下一个级别: :非通用IEnumerable<T>
包括集合修改方法。
通过在这些界面中包含变异方法,甚至可以是只读/固定大小 列表/集合(其元素的数量和顺序无法更改,但其元素的数量和顺序无法更改元素值可能)和完全不可变列表/集合(那些另外不允许更改其元素的值的对象)来实现变异方法,同时使用IList
异常表示不支持它们。
从v1.1开始存在只读集合实现(例如NotSupportedException
),但就接口而言,直到.NET v4.5中引入了ReadOnlyCollectionBase
和IReadOnlyCollection<T>
(后者以及IImmutableList<T>
中的所有类型,只能作为可下载的NuGet包提供)。
但是,由于从(实现)其他接口派生的接口永远不能排除成员,因此IReadOnlyCollection<T>
和IImmutableCollection<T>
都不能从ICollection<T>
派生,因此必须直接从可枚举的共享根IEnumerable<T>
中派生。
同样,实现IReadOnlyCollection<T>
的更专业的接口(例如System.Collections.Immutable
namespace)也不能实现IList<T>
和ICollection<T>
。
从根本上说,从一开始就可以提供以下解决方案,该解决方案颠倒当前逻辑:
使主要的采集接口与突变无关,这意味着:
创建子接口:
使用ICollection
和IList
的示例,我们将获得以下接口层次结构:
IEnumerable<T> # has only a .GetEnumerator() method
ICollection<T> # adds a .Count property (only)
IResizableCollection<T> # adds .Add/Clear/Remove() methods
IList<T> # adds read-only indexed access
IListMutableElements<T> # adds writeable indexed access
IResizableList<T> # must also implement IResizableCollection<T>
IResizableListMutableElements<T> # adds writeable indexed access
IImmutableList<T> # guarantees immutability
注意:上面的注释中只提到了显着的方法/属性。
请注意,这些新的ICollection<T>
和IList<T>
接口将提供 no 突变方法(没有.Add()
方法,...,没有可分配的索引)。 / p>
IImmutableList<T>
与IList<T>
的不同之处在于,保证完全不可变(并且,目前提供仅复制副本的方法)。
System.Array
然后可以安全且完全地实现IList<T>
,而界面的使用者不必担心NotSupportedExceptions
。
答案 1 :(得分:6)
要向@ mklement0的答案“添加”:[System.Array]实现[System.Collections.IList],它指定了一种Add()
方法。
但是要回答为什么Add()
不起作用?好吧,我们没有查看其他属性,即IsFixedSize
:
PS > $a = @('now', 'then')
PS > $a.IsFixedSize
True
因此,[System.Array]
只是一个固定大小的[System.Collections.IList]
。当我们回顾Add()
方法时,它显式定义了如果列表是只读或固定大小,则抛出NotSupportedException
。
我相信本质不是,“让我们拥有一个无缘无故地抛出错误消息的功能” ,或者对其进行扩展,除了实现接口外,没有其他原因,但这实际上是在警告您您在合法地做一些本不应该做的事情。
这是典型的接口思想,您可以使用IAnimal
方法来创建GetLeg()
类型。该方法将在所有动物中使用90%,这是将其实现到基本接口中的一个很好的理由,但是当您将它用于Snake
对象时会引发错误,因为您没有先检查{首先使用{1}}属性。
.HasFeet
方法对于列表接口来说是一个非常好的方法,因为它是非只读和非固定长度列表的基本方法。我们很愚蠢,在调用无法正常工作的Add()
方法之前不检查列表是否不是IsFixedSize
。也就是说,这属于Add()
检查类别,然后再尝试使用它们。