ReadOnlyCollection或IEnumerable暴露成员集合?

如果调用代码只遍历集合,是否有任何理由将内部集合公开为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); 

正如我所看到的IEnumerable是ReadOnlyCollection接口的一个子集,它不允许用户修改集合。 所以,如果IEnumberable接口是足够的那就是使用的那个。 这是一个适当的推理方式,或者我错过了什么?

谢谢/ Erik

更现代的解决scheme

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

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

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

这取决于你相信多less调用代码。 如果你完全控制了所有将要调用这个成员的东西,并保证没有代码可以使用:

 ICollection<Foo> evil = (ICollection<Foo>) bar.Foos; evil.Add(...); 

那么肯定的是,如果你直接返回集合,就不会造成危害。 我通常会比这个更加偏执狂。

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

原始答案

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

 public IEnumerable<Foo> Foos { get { return foos.Skip(0); } } 

(还有很多其他选项可以进行简单的包装 – 关于Skipselect/这里的好处是没有委托可以毫无意义地执行每次迭代。)

如果你不使用.NET 3.5,你可以写一个非常简单的包装来做同样的事情:

 public static IEnumerable<T> Wrapper<T>(IEnumerable<T> source) { foreach (T element in source) { yield return element; } } 

如果您只需要遍历集合:

 foreach (Foo f in bar.Foos) 

那么返回IEnumerable就够了。

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

 Foo f = bar.Foos[17]; 

然后将其包装在ReadOnlyCollection中

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

我尽可能地避免使用ReadOnlyCollection,它实际上比仅使用普通列表要慢很多。 看到这个例子:

 List<int> intList = new List<int>(); //Use a ReadOnlyCollection around the List System.Collections.ObjectModel.ReadOnlyCollection<int> mValue = new System.Collections.ObjectModel.ReadOnlyCollection<int>(intList); for (int i = 0; i < 100000000; i++) { intList.Add(i); } long result = 0; //Use normal foreach on the ReadOnlyCollection TimeSpan lStart = new TimeSpan(System.DateTime.Now.Ticks); foreach (int i in mValue) result += i; TimeSpan lEnd = new TimeSpan(System.DateTime.Now.Ticks); MessageBox.Show("Speed(ms): " + (lEnd.TotalMilliseconds - lStart.TotalMilliseconds).ToString()); MessageBox.Show("Result: " + result.ToString()); //use <list>.ForEach lStart = new TimeSpan(System.DateTime.Now.Ticks); result = 0; intList.ForEach(delegate(int i) { result += i; }); lEnd = new TimeSpan(System.DateTime.Now.Ticks); MessageBox.Show("Speed(ms): " + (lEnd.TotalMilliseconds - lStart.TotalMilliseconds).ToString()); MessageBox.Show("Result: " + result.ToString()); 

有时你可能想要使用一个接口,也许是因为你想在unit testing期间模拟收集。 请参阅我的博客条目 ,通过使用适配器将您自己的接口添加到ReadonlyCollection。