【问题标题】:Why does a System.Array object have an Add() method?为什么 System.Array 对象有 Add() 方法?
【发布时间】:2019-01-10 18:50:48
【问题描述】:

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

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

它没有出现在 Get-Member 的输出中。

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

是的,我知道这会失败,我也知道失败的原因。我不是在寻求使用[System.Collections.ArrayList][System.Collections.Generic.List[object]] 的建议。

【问题讨论】:

  • 补充一下你的问题,如果有人知道,你如何找到一个类型使用 powershell 实现的接口?我发现自己在 msdn 中想弄清楚很多事情。
  • @TheIncorrigible1 您可以像这样在Type instance 上调用GetInterfaces() method$a.GetType().GetInterfaces()

标签: arrays powershell interface


【解决方案1】:

[System.Array]实现[System.Collections.IList],后者有一个.Add()方法。

Array 实现了IList,这是一个还涵盖可调整大小集合的接口,可能令人惊讶 - 听起来有historical reasons for it[1] .

在 C# 中,这个惊喜很难偶然发现,因为您需要显式转换为IList 或使用IList 类型的变量才能访问.Add() 方法.

相比之下,从版本 3 开始,PowerShell 甚至将一个类型的 显式 接口实现显示为给定类型实例的 直接成员(显式接口实现是那些在其实现中显式引用接口的实现,例如IList.Add() 而不仅仅是.Add();显式接口实现不是实现类型的公共接口的直接部分,这就是C# 需要强制转换/接口类型的原因变量来访问它们)。

作为这种设计的副产品,PowerShell 中,.Add() 方法可以在System.Array 实例上直接调用,这样更容易出错因为你可能没有意识到你正在调用一个接口方法。在数组的情况下,IList.Add() 实现(正确地)抛出一个异常,指出Collection was of a fixed size;后者是 NotSupportedException 类型的例外,这就是实现接口的类型应如何报告不支持接口的部分

有帮助的是 Get-Member cmdlet 甚至只是引用一个方法而不调用它 - 只需省略 () - 允许您检查一个方法以确定它是否是该类型的本机或接口实现:

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 中关于可变性的集合相关接口

免责声明:这不是我的专业领域。如果我的解释不正确/可以改进,请告诉我们。

集合相关接口的层次结构的根是ICollection(非泛型,自v1 起)和ICollection<T>(泛型,自v2 起)。

(它们依次实现IEnumerable / IEnumerable<T>,其唯一成员是.GetEnumerator() 方法。)

虽然非泛型 ICollection 接口值得称赞的是没有对集合的可变性做出任何假设,但它的泛型对应物 (ICollection<T>) 不幸的是 - 它包含用于修改集合的方法(文档甚至将接口的目的声明为“操纵通用集合”(强调))。在非泛型 v1 世界中,同样的情况也发生了,只是低于一级:非泛型 IList 包含集合修改方法。

通过在这些接口中包含突变方法,即使是只读/固定大小列表/集合(那些元素的数量和顺序不能改变,但它们的元素值可能)和完全不可变列表/集合(那些也不允许更改其元素的被强制实现变异方法,同时通过NotSupportedException 异常表明不支持它们。

虽然只读集合实现自 v1.1 以来就已经存在(例如,ReadOnlyCollectionBase),但就接口而言,直到 .NET v4 才存在。 5 中引入了IReadOnlyCollection<T>IImmutableList<T>(后者以及System.Collections.Immutable namespace 中的所有类型,仅作为可下载的NuGet 包提供)。

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

从根本上说,从头开始会提供以下解决方案,它颠倒当前逻辑

  • 使主要的集合接口mutation-agnostic,这意味着:

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

    • 根据特定的可变性级别添加成员。
    • 如果需要,保证不变性。

使用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

注意:上面的 cmets 中只提到了显着的方法/属性。

请注意,这些新的ICollection&lt;T&gt;IList&lt;T&gt; 接口将提供no 变异方法(没有.Add() 方法,...,没有可分配的索引)。

IImmutableList&lt;T&gt;IList&lt;T&gt; 的不同之处在于保证完全不变(并且目前提供仅复制的突变方法)。

System.Array 可以安全且完整地实现IList&lt;T&gt;,而接口的消费者不必担心NotSupportedExceptions

【讨论】:

    【解决方案2】:

    要“添加”到@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 对象使用它时会抛出错误,因为您没有首先检查 .HasFeet属性优先。

    Add() 方法对于列表接口来说是一个非常好的方法,因为它是非只读和非固定长度列表的必要方法。我们是愚蠢的,在调用不起作用的Add() 方法之前不检查列表是否不是IsFixedSize。也就是说,这属于$null在尝试使用之前检查的类别。

    【讨论】:

    • 公平点,但您可能会争辩说应该有一个中间的IFixedSizeList 接口,这样可以避免这些问题。令人难以理解的是,PowerShell 甚至隐式地公开了显式实现的 IList 成员,就好像它们是数组的直接成员一样,所以对于一个普通的 PowerShell 用户来说,.Add() 似乎只是数组上可用的另一种方法——接口出处是被遮住了。
    • 感谢您的精彩解释。我只是认为该工具可以为开发人员做更多的事情,以避免编写此类代码。
    • @lit:PowerShell 将接口实现作为直接成员通常在动态、类型灵活的语言(如 PowerShell)中是有意义的;在某些情况下——比如这个突出的例子——不幸的副作用是你可能会在没有完全理解原因的情况下陷入错误。 PowerShell 不能真正明确地防止这些错误,因为它必须预测所有类型接口组合,这显然是不切实际的(并且挑选一些引人注目的无意义组合并不是一个好主意)。
    猜你喜欢
    • 2016-11-02
    • 2011-10-30
    • 2012-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-06
    • 1970-01-01
    • 2011-06-08
    相关资源
    最近更新 更多