为什么System.Array对象具有Add()方法?

时间:2018-08-02 22:44:15

标签: arrays powershell interface

我完全理解System.Array是不可变的。

鉴于此,为什么它具有Add()方法?

它不会出现在Get-Member的输出中。

$a = @('now', 'then')
$a.Add('whatever')

是的,我知道这会失败,而且我知道为什么会失败。我不是在请求使用[System.Collections.ArrayList][System.Collections.Generic.List[object]]的建议。

2 个答案:

答案 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中引入了ReadOnlyCollectionBaseIReadOnlyCollection<T>(后者以及IImmutableList<T>中的所有类型,只能作为可下载的NuGet包提供)。

但是,由于从(实现)其他接口派生的接口永远不能排除成员,因此IReadOnlyCollection<T>IImmutableCollection<T>都不能从ICollection<T>派生,因此必须直接从可枚举的共享根IEnumerable<T>中派生。 同样,实现IReadOnlyCollection<T>的更专业的接口(例如System.Collections.Immutable namespace)也不能实现IList<T>ICollection<T>

从根本上说,从一开始就可以提供以下解决方案,该解决方案颠倒当前逻辑

  • 使主要的采集接口与突变无关,这意味着:

    • 他们都不应该提供突变方法,
    • 他们也不应就不变性做出任何保证
  • 创建子接口

    • 根据可变性的具体级别添加成员。
    • 根据需要确保不变性

使用ICollectionIList的示例,我们将获得以下接口层次结构:

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()检查类别,然后再尝试使用它们。