为什么 C# 中没有真正的不可变集合

本文关键字:不可变 集合 为什么 | 更新日期: 2023-09-27 18:33:20

我目前正在学习C#,我有一个包含ISet的类。 我不希望客户端直接修改此集,大多数客户端只添加和删除,我通过我的类提供访问器来执行此操作。

但是,我有一个客户希望了解有关此集合及其内容的更多信息。 我真的不想为这个客户端使用很多方法来混淆包装类本身,所以我更希望能够以不可变的方式返回集合本身。

发现我不能 - 好吧,不是真的。 我似乎唯一的选择是:

  1. 返回一个 IEnumerable(否:限制性功能(;
  2. 只读集合(否:这是一个列表(;
  3. 返回
  4. 副本(否:恕我直言,形式错误,允许客户端修改返回的集合,可能不知道它不会更改真实对象,而且它具有性能开销(;
  5. 实现我自己的 ReadOnlySet(不:需要从 ISet 派生,因此这意味着我需要实现突变器,可能会触发异常,我宁愿编译时间错误 - 而不是运行时(。

我错过了什么吗? 我是不是不讲理? 我的唯一选择是在包装器上提供全套访问器吗? 我为绝大多数客户保持包装器清洁的初衷是否正确?

所以两个问题:

  1. 为什么没有标准的 C# 不可变集合接口? 这似乎是一个相当合理的要求?

  2. 为什么ReadOnlyCollection被烦人地称为ReadOnlyCollection,而它实际上是一个ReadOnlyList? 我打算咬紧牙关使用它,直到我发现它是一个列表(我使用一个集合(。

为什么 C# 中没有真正的不可变集合

为什么没有标准的 C# 不可变接口?这似乎是一个 相当合理的要求?

  • 一个标准的 C# 不可变¹ 接口已经存在:它被称为 IEnumerable,所有容器都实现它。

  • 更强大的不可变接口是有问题的,因为有很多种不可变性。如果BCL团队决定选择一个不变性的定义并将其提升到不变性状态,那么可以肯定的是,寻找不同类型不变性的人会抱怨这个选择。

    满足每个人意味着不仅要整理所有的不可变性混乱,还要创建大量的接口(祝你好运,也为它们挑选好的名字(,并将所有这些不变性概念很好地融入到语言中,使不变性成为一等公民——请记住,这里没有第二次机会,一旦你发布一个公共类,它的公共接口就是永远不可变的(双关语(。虽然所有这些都可能很好,但我对成本/收益比持怀疑态度。

  • 定义IReadOnlyList并不难,IReadOnlySet等等,如果你确实需要它们。我假设它们还不存在,因为再次减去 100 分。

  • 恕我直言,ReadOnlyCollection要么是 BCL 内部需要的特许权或类,并且暴露给世界,因为嘿,BCL 团队以非常低的成本提供免费功能(因为它无论如何都必须实现、记录和测试(。无论如何,我不认为它不是偶然生活在迷人的System.Collections.Generic社区。

为什么 ReadOnlyCollection 被烦人地称为 ReadOnlyCollection,当它 真的是只读列表吗?我要咬紧牙关并使用它 直到我发现它是一个列表(我使用一个集合(。

我相信 BCL 团队希望能够回到过去并解决这个问题,因为它几乎可以肯定是那些不可避免地潜入任何类似范围的库的小不一致之一。由于ReadOnlyCollection实现IList它绝对应该被称为 ReadOnlyList .

但是,鉴于"列表"比"集合"提供更多的功能,我不明白这会如何阻止您。两者都不是Set,所以在任何情况下你都必须在它们之上构建与集合相关的功能(这不是一个好主意;只需在Set之上构建只读语义(。


¹ 我们在这里经常折腾"不可变",但这个词没有单一的含义。我认为使用"只读"会更合适,但为了保持一致性,我会选择您选择的单词。

这可能会有所帮助,http://blogs.msdn.com/b/jaredpar/archive/2008/04/22/api-design-readonlycollection-t.aspx

我认为提供集合的只读"副本"而不实际将数据复制到相同或不同结构的另一个实例中的唯一方法是使用包装器并实现所有项目添加和删除方法来引发异常。

如果你的集合无论如何都只作为ISet公开,那么无论你的包装器包含什么,消费者只会看到接口上定义的成员 - 这似乎不是一件坏事。

我同意如果 .net 中对不可变性和只读包装器有更好的支持会很好,尽管我认为重要的是要注意概念之间存在巨大差异。 只读包装器向其创建者承诺,它的使用者将无法更改基础对象,但不向使用者承诺基础对象本身不会更改。 相比之下,一个不可变的对象向其创建者和消费者承诺其值不会改变。

我不确定为什么有许多不同类型的免疫力的概念应该是一个问题。 如果我有一个采用不限定T的通用ImmutableList<T>,我的期望是它将始终包含与创建时相同的T。 集合绝不会影响T的任何属性是否可以更改,因此不应期望它更改。

如果我有我的 druthers,大多数与集合相关的接口将包括可读、可变和不可变的变体(可变和不可变都将从可读扩展(。 我还会添加一个只写逆变 IAppendable 接口,以及一个派生自 IEnumerableIImmutableEnumerable(我会向 IEnumerable(和 IImmutableEnumerable(添加一个ToImmutable方法(;实现可以构造一个不可变的集合,但在某些情况下,这可能不是最好的方法。 例如,可变对象可能通过返回可变元素的可变副本数来实现IEnumerable。 如果副本数量很大,转换为简单的集合可能会非常浪费。