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

时间:2008-11-12 20:50:27

标签: c# collections c#-2.0

我的班级中有一个名为ICollection<T>的{​​{1}}我想以只读方式公开(请参阅this question)。我看到界面定义了一个属性foos,这似乎是合适的......我的问题是:如何让.IsReadOnly只读的类的消费者明白?

我不想依赖他们在尝试未实现的方法(例如foos)之前记住查询.IsReadOnly。理想情况下,我希望将.Add()公开为foos,但它不会实现ReadOnlyCollection<T>。我应该通过一个名为IList<T>的方法而不是通过属性公开foo吗?如果是这样,这会不会让那些期望GetReadOnlyFooCollection的人感到困惑吗?

这是C#2.0,因此ReadOnlyCollection<T>等扩展方法不可用......

7 个答案:

答案 0 :(得分:15)

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

ReadOnlyCollection<T> readOnlyCollection = foos.ToList<T>().AsReadOnly();

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

编辑:

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

    }

注意:您应该记住,一旦获得ReadOnlyFoos集合,就不再与您的foos ICollection“同步”。请参阅thread you referenced

答案 1 :(得分:3)

我的建议是返回使用ReadOnlyCollection&lt; T&gt;对于场景直接。这使得用法对调用用户显而易见。

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

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

答案 2 :(得分:3)

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

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

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

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

  3. 哪种样式的包装是合适的取决于客户端代码对它收到的东西的期望。有些情况需要避免:

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

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

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

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

    5. 确实没有特别安全的&#34;对象可以采用从一些客户端收到的集合并且需要向其他客户端公开的操作。在许多情况下,始终复制一切都是最安全的行动方式,但很容易导致这样一种情况:不需要复制的集合在任何一端都可以复制数百或数千次。返回的引用通常是最有效的方法,但它也可能在语义上很危险。

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

答案 3 :(得分:0)

有时您可能想要使用界面,可能是因为您想在单元测试期间模拟集合。请参阅我的blog entry,了解如何使用适配器将自己的接口添加到ReadonlyCollection。

答案 4 :(得分:0)

我通常会返回IEnumerable<T>

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

如果您班级的消费者真的需要处理他们在集合中的元素,那么将IEnumerable传递给List<T>的构造函数就足够了。

答案 5 :(得分:0)

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

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

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

答案 6 :(得分:-4)

返回T []:

private ICollection<T> items;

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