ReadOnlyCollection是只读视图而非深拷贝,其底层引用可变List,内部修改会实时反映;正确用法是private readonly List封装+AsReadOnly()暴露IReadOnlyList更推荐。

ReadOnlyCollection
ReadOnlyCollection 的本质是包装器,不是深拷贝
它底层持有一个 IList 引用(通常是 List),所有读操作(get_Item、Count、GetEnumerator)都转发给内部列表,但禁止调用 Add、Remove、Clear 等写方法。
- 如果你传入的是一个可变的
List,且该列表后续被其他代码修改,ReadOnlyCollection的Count和元素值会实时变化 - 它不阻止你把内部列表偷偷暴露出去——比如返回
AsReadOnly()后,又把原始List存在另一个字段里,那就完全失效 - 构造开销极小(O(1)),只是保存引用,没有遍历或复制
如何真正防止外部篡改?用 AsReadOnly() + 封装好内部列表
最常用也最安全的做法:把 List 设为 private readonly,只暴露 ReadOnlyCollection 属性。
private readonly List<string> _items = new(); public ReadOnlyCollection<string> Items => _items.AsReadOnly(); <p>// 外部只能读,无法 Add/Remove // 内部仍可通过 _items 安全修改 </p></string></string>
- 不要写
public ReadOnlyCollection—— 这样初始化后内部列表根本没地方存,没法增删Items { get; } = new List ().AsReadOnly(); - 避免返回
_items.AsReadOnly()每次都新建对象(虽轻量但没必要),属性 getter 中复用更合理 - 如果需要线程安全读写,
ReadOnlyCollection本身不提供同步,得配合lock或用ConcurrentBag等替代方案
和 IReadOnlyList、IEnumerable 的关键区别在哪
三者都“不能 Add”,但语义和能力不同:
-
IReadOnlyList:支持索引访问([i])、Count,是ReadOnlyCollection实现的接口,更轻量、更通用;推荐作为公开 API 的返回类型 -
IEnumerable:只支持遍历,无索引、无Count(除非是ICollection),延迟执行,适合流式处理 -
ReadOnlyCollection:是具体类,提供了Contains、IndexOf等便利方法,但体积略大;适合内部封装或需额外方法时使用
对外暴露优先用 IReadOnlyList,既保持只读契约,又不限制实现自由度。
常见报错:Collection was modified;Enumeration operation may not execute
这不是 ReadOnlyCollection 报的错,而是你在遍历它时,背后那个原始 List 被其他线程或代码改了。
- 错误示例:
foreach (var x in collection) { if (x == "a") list.Remove(x); }—— 即使collection是ReadOnlyCollection,只要list是它的源,就崩 - 修复思路:遍历前先
ToArray()快照,或改用for (int i = list.Count - 1; i >= 0; i--)倒序删 - 别指望
AsReadOnly()能帮你躲过并发修改异常——它不提供任何线程保护
真正难的不是怎么调 AsReadOnly(),而是想清楚:这个集合谁负责修改?生命周期归谁管?要不要线程安全?只读是给调用方看的契约,不是运行时防护盾。











