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

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


















