ImmutableList 是自带结构共享、线程安全、值语义明确的不可变集合类型,非只读包装;必须通过静态工厂方法创建,修改操作返回新实例,批量变更应使用 Builder 模式,随机访问为 O(log n)。

ImmutableList<t></t> 不是“只读包装”,也不是“禁止修改的 ListList<t></t> 做只读场景,大概率踩坑;但用对了模式,它比 ReadOnlyCollection<t></t> 更可靠、比反复拷贝 List<t></t> 更省内存。
创建 ImmutableList 必须用静态工厂方法
它没有公共构造函数,也不能用 new ImmutableList<int>()</int>。所有实例必须通过 ImmutableList.Create()、ImmutableList.Empty<t>()</t> 或从其他集合转换而来:
-
var list = ImmutableList.Create(1, 2, 3);—— 最常用,适合已知元素的初始化 -
var empty = ImmutableList<int>.Empty;</int>—— 比Create()更轻量,无装箱开销 -
var fromArray = ImmutableArray.Create(1, 2, 3).ToImmutableList();—— 适合从紧凑数组转树形结构 -
var fromEnumerable = source.ToImmutableList();—— 注意:这是 LINQ 扩展方法,需引用System.Linq,且会遍历整个源序列
错误写法:new List<int> {1,2,3}.AsReadOnly().ToImmutableList()</int> —— 多此一举,还引入了不必要的中间对象。
修改操作永远返回新实例,原对象不变
调用 Add()、Remove()、SetItem() 等方法,不会改变当前实例,而是返回逻辑上“修改后”的新 ImmutableList<t></t>:
-
var newList = oldList.Add(4);——oldList仍为 [1,2,3],newList是 [1,2,3,4] - 链式调用可行:
list.Add(4).Remove(1).Insert(0, 99),但每次调用都生成一个新实例 - 性能敏感场景慎用链式:100 次
Add()会创建 100 个中间树节点,实际只保留最终结果
常见误判现象:list.Add(x); 看似执行了,但没接返回值 → 实际什么都没发生,原变量仍指向旧实例。
批量增删请务必用 Builder 模式
需要循环添加/删除几十次以上时,直接链式调用效率低,应先获取 Builder,累积变更后再一次性冻结:
-
var builder = list.ToBuilder();—— 开销极小,底层是可变数组 -
builder.Add(x); builder.RemoveAt(i); builder[0] = y;—— 支持全部可变操作 -
var final = builder.ToImmutable();—— 此刻才真正构建不可变结构,仅一次分配 - 若已知容量,用
ImmutableList.CreateBuilder<t>(capacity)</t>预分配,避免 builder 内部扩容
注意:builder.ToImmutable() 返回的是新 ImmutableList<t></t>,builder 本身仍可继续用(但通常不建议)。
别把 ImmutableList 当 ReadOnlyCollection 用
两者语义完全不同:
-
ReadOnlyCollection<t></t>只是List<t></t>的只读门面,底层集合仍可被其他引用修改 -
ImmutableList<t></t>一旦创建,内存中再无任何路径能修改其内容,包括反射(除非用Unsafe) - 若你只需要“对外只读”,且确定没人持有底层
List<t></t>引用,用ReadOnlyCollection<t></t>更轻;但若需跨线程共享或保证绝对不变性,必须选ImmutableList<t></t> -
IReadOnlyList<t></t>接口只是契约,不能保证实现类是否真不可变 ——ImmutableList<t></t>实现了它,但List<t></t>也实现了
最容易被忽略的一点:ImmutableList<t></t> 底层是平衡树,随机索引访问是 O(log n),不是 O(1)。高频随机读场景(如配置项查表),优先考虑 ImmutableArray<t></t> 或 ReadOnlyMemory<t></t> 视图。


















