3

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

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

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

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

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

所以两个问题:

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

  2. 为什么 ReadOnlyCollection 实际上是一个 ReadOnlyList 时却令人讨厌地称为 ReadOnlyCollection?我打算硬着头皮使用它,直到我发现它是一个列表(并且我使用了一个集合)。

4

4 回答 4

8

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

  • 标准 C# 不可变¹ 接口已经存在:它被调用IEnumerable并且所有容器都实现它。

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

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

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

  • ReadOnlyCollection恕我直言,要么是 BCL 内部需要的特许权,要么是向世界公开的类,因为嘿,BCL 团队以非常低的成本提供免费功能(因为无论如何它都必须实施、记录和测试)。无论如何,我不认为它不是System.Collections.Generic偶然住在迷人的街区。

为什么 ReadOnlyCollection 实际上是一个 ReadOnlyList 时却令人讨厌地称为 ReadOnlyCollection?我打算硬着头皮使用它,直到我发现它是一个列表(并且我使用了一个集合)。

我确信 BCL 团队希望能够及时回到过去并修复它,因为它几乎肯定是那些不可避免地潜入任何类似范围的库的小不一致之一。由于ReadOnlyCollection实现IList了它肯定应该被调用ReadOnlyList

但是,鉴于“列表”提供功能比“集合”更多,我看不出这会如何阻止您。a 也不是Set,因此无论如何您都必须在它们之上构建与集合相关的功能(这不是一个好主意;只需在 之上构建只读语义Set)。


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

于 2012-05-05T15:22:13.503 回答
1

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

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

于 2012-05-05T14:58:58.947 回答
1

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

于 2012-05-05T15:08:35.230 回答
1

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

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

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

于 2012-06-16T05:53:38.350 回答