ReadOnlyCollection 仅是可变列表的只读包装,不保证线程安全或数据隔离;正确做法是私有封装 List 并用 AsReadOnly() 暴露 IReadOnlyList,必要时做防御性拷贝,避免直接暴露原始列表或误用数组。

ReadOnlyCollection
ReadOnlyCollection 怎么创建才安全
最常见错误是直接 new ReadOnlyCollection_items 设为 public,导致外部绕过只读视图直接改底层。
- 正确做法:用
private readonly List<t> _items</t>封装,再通过AsReadOnly()暴露IReadOnlyList<t></t>或ReadOnlyCollection<t></t> - 避免每次 getter 都调用
_items.AsReadOnly()—— 虽开销小,但没必要;复用一个实例更合理 - 如果原始列表可能被其他代码修改(比如跨线程、或来自外部传入),必须先做防御性拷贝:
new ReadOnlyCollection<t>(new List<t>(source))</t></t> - 别对数组调用
AsReadOnly()—— 编译器会报错:'T[]' does not contain a definition for 'AsReadOnly';数组本身已实现IReadOnlyList<t></t>,直接返回即可
ReadOnlyCollection 和 IReadOnlyList 选哪个返回
对外暴露只读集合时,优先返回 IReadOnlyList<t></t> 类型,而不是具体类 ReadOnlyCollection<t></t>。
-
IReadOnlyList<t></t>是接口,支持索引访问([i])和Count,语义清晰、体积轻、兼容性好,还允许你未来换成数组、Span或其他实现 -
ReadOnlyCollection<t></t>是具体类,多了Contains、IndexOf等方法,但体积略大,且把实现细节暴露给了调用方 - 不要写
public ReadOnlyCollection<t> Items { get; } = new List<t>().AsReadOnly();</t></t>—— 这样构造完原始列表就丢了,内部无法维护 - 返回数组(
new[] { ... })也合法,且零分配;若数据固定,比ReadOnlyCollection<t></t>更优
为什么遍历时抛出 “Collection was modified” 异常
这个错误不是 ReadOnlyCollection<t></t> 导致的,而是你在遍历它的同时,另一个线程或代码路径修改了它背后那个原始 List<t></t>。
-
ReadOnlyCollection<t></t>本身不提供线程安全保证;它的GetEnumerator()直接转发给内部List<t></t> - 常见场景:UI 线程遍历
Items,后台线程调用_items.Add()—— 即使没直接暴露_items,只要引用没隔离,就会触发异常 - 解决办法不是换只读包装,而是加同步:用
lock(_syncObj)包住读写操作,或改用ConcurrentBag<t></t>/ConcurrentQueue<t></t>等线程安全集合 - 注意:
foreach隐式调用GetEnumerator(),而for (int i = 0; i 是安全的(只要 <code>Count和索引访问不被中途破坏)
真正难的不是怎么包一层只读,而是确保那个被包装的原始列表不被意外共享、不被并发修改、不在构造后丢失控制权——这些点一旦漏掉,ReadOnlyCollection<t></t> 就只剩个“只读”的假象。


















