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

时间:2012-05-05 14:37:19

标签: c# immutability

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

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

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

  1. 返回IEnumerable(No:限制功能);
  2. ReadOnlyCollection(不是:这是一个列表);
  3. 返回副本(否:错误形式恕我直言,允许客户修改返回的集合或许不知道它不会改变真实对象,加上它有性能开销);
  4. 实现我自己的ReadOnlySet(否:需要从ISet派生,因此意味着我需要实现mutators,可能会触发异常,我宁愿编译时间错误 - 而不是运行时)。
  5. 我错过了什么吗?我不合理吗?我唯一的选择是在我的包装器上提供全套访问器吗?我的原始意图是不正确的,以保持绝大多数客户的包装干净?

    所以有两个问题:

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

    2. 为什么ReadOnlyCollection在它真的是ReadOnlyList时会恼人地调用ReadOnlyCollection?我要咬紧牙关并使用它直到我发现它是一个List(并且我使用了Set)。

4 个答案:

答案 0 :(得分:8)

  

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

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

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

    满足每个人意味着不仅要排除所有不可变性的混乱,还要创建大量的界面(祝他们为他们选择好名字)并将所有这些不变性概念融入到语言中,以使不变性成为一流的公民 - 请记住,这里没有第二次机会,一旦你发布了一个公共类,它的公共接口永远是不可变的(双关语)。虽然所有这些都可能很好,但我对cost/benefit ratio持怀疑态度。

  • 定义IReadOnlyListIReadOnlySet等并不难,如果你确实需要它们。我认为它们不存在因为再减去100分。

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

  

为什么ReadOnlyCollection会烦人地调用ReadOnlyCollection   真的是一个ReadOnlyList吗?我要咬紧牙关并使用它   直到我发现它是一个List(我使用Set)。

我相信BCL团队希望能够及时回过头来解决这个问题,因为几乎可以肯定这是一个不可避免地潜入任何可比范围库的小不一致之处。由于ReadOnlyCollection实现了IList,因此它应该被称为ReadOnlyList

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


¹我们在这里徘徊“不变”,但这个词没有单一含义。我认为使用“只读”会更合适,但为了保持一致,我会选择你的选择。

答案 1 :(得分:1)

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

如果您的集合仅以ISet公开,消费者只会看到界面上定义的成员,无论您的包装器包含什么 - 这似乎不是一件坏事。< / p>

答案 2 :(得分:1)

答案 3 :(得分:1)

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

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

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