ReadOnlyCollection或IEnumerable用于公开成员集合?

Eri*_*ebo 118 .net c# collections ienumerable readonly-collection

如果调用代码只迭代集合,是否有任何理由将内部集合公开为ReadOnlyCollection而不是IEnumerable?

class Bar
{
    private ICollection<Foo> foos;

    // Which one is to be preferred?
    public IEnumerable<Foo> Foos { ... }
    public ReadOnlyCollection<Foo> Foos { ... }
}


// Calling code:

foreach (var f in bar.Foos)
    DoSomething(f);
Run Code Online (Sandbox Code Playgroud)

正如我所看到的,IEnumerable是ReadOnlyCollection接口的一个子集,它不允许用户修改集合.因此,如果IEnumberable接口足够,那么就是要使用的接口.这是推理它的正确方法还是我错过了什么?

谢谢/ Erik

Jon*_*eet 93

更现代的解决方案

除非您需要内部集合是可变的,否则您可以使用该System.Collections.Immutable包,将您的字段类型更改为不可变集合,然后直接公开它 - Foo当然,假设它本身是不可变的.

更新的答案更直接地解决问题

如果调用代码只迭代集合,是否有任何理由将内部集合公开为ReadOnlyCollection而不是IEnumerable?

这取决于您对调用代码的信任程度.如果您完全控制将要调用此成员的所有内容,并且您保证不会使用任何代码:

ICollection<Foo> evil = (ICollection<Foo>) bar.Foos;
evil.Add(...);
Run Code Online (Sandbox Code Playgroud)

那么肯定,如果你直接退回收藏品,不会造成任何伤害.我通常会尝试比这更偏执.

同样,正如你所说:如果你只需要 IEnumerable<T>,那么为什么要把自己绑在更强大的东西上呢?

原始答案

如果您使用的是.NET 3.5,则可以通过使用对Skip的简单调用来避免复制避免简单的转换:

public IEnumerable<Foo> Foos {
    get { return foos.Skip(0); }
}
Run Code Online (Sandbox Code Playgroud)

(还有很多其他选项可以轻松包装 - 关于SkipSelect/Where的好处是没有委托可以为每次迭代毫无意义地执行.)

如果您不使用.NET 3.5,您可以编写一个非常简单的包装器来执行相同的操作:

public static IEnumerable<T> Wrapper<T>(IEnumerable<T> source)
{
    foreach (T element in source)
    {
        yield return element;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意,对此存在性能损失:AFAIK Enumerable.Count方法针对转换为IEnumerables的集合进行了优化,但不适用于Skip(0)生成的范围 (15认同)
  • -1这只是我还是这个问题的答案?知道这个"解决方案"很有用,但是op要求在返回ReadOnlyCollection和IEnumerable之间进行比较.这个答案已经假定您想要在没有任何理由支持该决定的情况下返回IEnumerable. (6认同)
  • @ShaunLuttin:是的,正是 - 这会抛出。但在我的回答中,我没有投射“ReadOnlyCollection” - 我投射了一个“IEnumerable&lt;T&gt;”,它实际上不是只读的......它*而不是暴露“ReadOnlyCollection” (2认同)

Voj*_*vic 41

如果您只需要遍历集合:

foreach (Foo f in bar.Foos)
Run Code Online (Sandbox Code Playgroud)

然后返回IEnumerable就足够了.

如果您需要随机访问项目:

Foo f = bar.Foos[17];
Run Code Online (Sandbox Code Playgroud)

然后将它包装在ReadOnlyCollection中.

  • @w0051977 这取决于你的意图。当您公开一些在获得对它的引用后永远不会改变的东西时,按照 Jon Skeet 的建议使用“System.Collections.Immutable”是完全有效的。另一方面,如果您公开可变集合的只读视图,那么这个建议仍然有效,尽管我可能会将其公开为“IReadOnlyCollection”(当我编写此内容时它不存在)。 (2认同)

Stu*_*lar 31

如果你这样做,那么没有什么能阻止你的调用者将IEnumerable转换回ICollection然后修改它.ReadOnlyCollection消除了这种可能性,尽管仍然可以通过反射访问底层的可写集合.如果集合很小,那么解决此问题的一种安全且简单的方法是返回副本.

  • 你不同意不会错.如果我正在设计一个用于外部分发的库,我宁愿有一个偏执的界面,而不是处理试图滥用API的用户引发的错误.请参阅http://msdn.microsoft.com/en-us/library/k2604h5s(VS.71).aspx上的C#设计指南. (24认同)
  • 这是那种"偏执的设计",我衷心不同意.我认为选择不充分的语义来执行策略是不好的做法. (13认同)
  • 你不需要做任何一件事.您可以返回只读迭代器而无需复制... (9认同)
  • 是的,在设计用于外部分发的库的特定情况下,为您所暴露的任何内容设置一个偏执的界面是有意义的.即便如此,如果Foos属性的语义是顺序访问,请使用ReadOnlyCollection然后返回IEnumerable.不需要使用错误的语义;) (5认同)