最佳实践:如何公开只读ICollection

我在我的class级中有一个名为foosICollection ,我希望将其公开为只读(请参阅此问题 )。 我看到界面定义了一个属性.IsReadOnly ,这似乎是合适的……我的问题是:如何让类消费者明白foos是只读的?

在尝试使用未实现的方法(如.Add()之前,我不想依赖它们记住查询.IsReadOnly 。 理想情况下,我想将foos暴露为ReadOnlyCollection ,但它没有实现IList 。 我应该通过一个名为GetReadOnlyFooCollection而不是通过属性来暴露foo吗? 如果是这样,这不会混淆那些期望ReadOnlyCollection吗?

这是C#2.0,所以像ToList()这样的扩展方法不可用……

我似乎已经决定用克隆的对象返回IEnumerable

 public IEnumerable GetFooseList() { foreach(var foos in Collection) { yield return foos.Clone(); } } 
  • 需要在Foos上使用Clone方法。

这样就不会对集合进行任何更改。 请记住,ReadonlyCollection是“漏洞”,因为它内部的对象可以像另一篇文章中的链接中所提到的那样进行更改。

你可以像这样把“foos”变成一个ReadOnlyCollection:

 ReadOnlyCollection readOnlyCollection = foos.ToList().AsReadOnly(); 

然后,您可以将其作为class级的属性公开。

编辑:

  class FooContainer { private ICollection foos; public ReadOnlyCollection ReadOnlyFoos { get { return foos.ToList().AsReadOnly();} } } 

注意:您应该记住,一旦获得ReadOnlyFoos集合,就不再与您的foos ICollection“同步”。 查看您引用的主题 。

自编写问题以来,.NET 4.0添加了一个IReadOnlyCollection接口; 将它用作声明的返回类型可能会很好。

但是,这确实留下了返回什么类型的实例的问题。 一种方法是克隆原始集合中的所有项目。 另一种方法是始终返回只读包装器。 如果它实现了IReadOnlyCollection则第三个将返回原始集合。 在某些情况下,每种方法都是最好的方法,但在其他方法中则不太理想(或者可能是彻头彻尾的可怕)。 不幸的是,微软没有提供标准的方法来提出两个非常重要的问题:

  1. 您是否承诺永远和永远包含与您现在相同的物品?

  2. 您是否可以安全地直接暴露给不应修改内容的代码。

适当的包装风格取决于客户端代码期望对其收到的内容做什么。 有些场景需要避免:

  1. 一个对象是由客户端识别为不可变的类型提供的,但不是直接返回它,而是使用客户端不识别为不可变的类型进行复制。 因此,客户端被迫再次复制该集合。

  2. 一个对象是由客户端识别为不可变的类型提供的,但在返回之前,它的包装方式使得客户端无法判断该集合是否是不可变的,因此被迫复制。

  3. 不应该变异的可变类型的对象由客户端提供(强制转换为只读接口)。 然后将其直接暴露给另一个客户端,该客户端确定它是一个可变类型并继续修改它。

  4. 接收对可变集合的引用,并在返回到需要不可变对象的客户端之前封装在只读包装器中。 返回集合的方法承诺它是不可变的,因此客户拒绝制作自己的防御副本。 然后,客户准备不充分,可能会收集该集合。

对象可以采用从某些客户端收到的集合并且需要向其他客户端公开的任何特别“安全”的操作过程。 在许多情况下,始终复制所有内容都是最安全的操作过程,但它很容易导致这样一种情况:不需要复制的集合最终会被复制数百或数千次。 返回的引用通常是最有效的方法,但它也可能在语义上是危险的。

我希望微软能够添加一种标准方法,通过该方法可以向上述问题询问集合,或者更好的是,要求它们生成当前状态的不可变快照。 不可变集合可以非常便宜地返回其当前状态的不可变快照(只返回自身),甚至一些可变集合类型可以以远低于完整枚举成本的成本返回不可变快照(例如List可能是由两个T[][]数组支持,其中一个数组保存对256个项目的可共享不可变数组的引用,另一个数组保存对256个项目的不可修改的可变数组的引用。制作列表的快照只需要克隆内部包含自上次快照以来已被修改的项目的数组 – 可能比克隆整个列表便宜得多。不幸的是,因为没有标准的“制作不可变快照”接口[注意ICloneable不计算,因为可变复制的克隆list是可变的;虽然可以通过将可变克隆封装在只读包装中来创建不可变快照,这只能用于可克隆的东西,甚至是不可克隆的类型 ld仍然支持“可变快照”function。]

我的建议是直接为场景返回使用ReadOnlyCollection 。 这使得使用对于调用用户是显式的。

通常我会建议使用适当的界面。 但鉴于.NET Framework目前没有合适的IReadOnlyCollection,您必须使用ReadOnlyCollection类型。

使用ReadOnlyCollection时也必须注意,因为它实际上不是只读的: Immutability和ReadOnlyCollection

有时您可能想要使用界面,可能是因为您想在unit testing期间模拟集合。 请参阅我的博客条目 ,使用适配器将自己的接口添加到ReadonlyCollection。

我通常会返回一个IEnumerable

一旦你只收集了一个集合(所以像AddRemoveClear这样的方法不再起作用),那么集合支持的数据并不多于可枚举的东西 – 我相信只有CountContains

如果你的类的消费者真的需要处理它们在集合中的元素,那么将IEnumerable传递给List的构造函数就足够了。

返回T []:

 private ICollection items; public T[] Items { get { return new List(items).ToArray(); } }