ImmutableList不可替代List作只读场景:它是结构共享的树形集合,必须按不可变范式使用,否则性能暴跌或逻辑静默失效;其索引访问为O(log n),远慢于ImmutableArray的O(1)。
直接用
替代
做只读场景,大概率出问题;它不是“只读包装”,而是带结构共享、值语义明确的树形集合,必须按不可变范式使用——否则要么性能暴跌,要么逻辑静默失效。
ImmutableList.Create() 和 ImmutableList.Empty 的区别在哪
两者都创建空实例,但语义和开销不同:
返回一个含 0 个元素的树节点,有装箱/构造开销,适合后续链式调用(如
)
是静态只读单例,无分配、无装箱,适合纯占位或作为 Builder 起点
误用
:多一层
包装,白跑一次遍历,还引入冗余引用
为什么 Add() 后不接返回值就等于没写
因为所有修改方法(
、
、
)都不改变原实例,只返回新实例:
→ 原
仍是旧引用,新增元素丢失
→ 正确,
指向新树节点
链式调用如
可行,但每步都新建中间对象;100 次循环调用会生成 100 个临时树,内存和 GC 压力陡增
Builder 模式什么时候必须用
批量变更(>10 次增删改)或循环中累积操作时,不用 Builder 就是自找麻烦:
开销极小,底层是可变数组,非树结构
、
、
全部是 O(1) 操作
才真正构建平衡树,仅一次内存分配
已知容量?用
预分配,避免 builder 内部数组扩容拷贝
ImmutableList 和 ImmutableArray 哪个更适合只读高频访问
看场景选,不是越“不可变”越好:
需要频繁索引访问(如配置表查值)、且构建后永不修改 → 选
:底层是紧凑数组,
是纯指针偏移,无虚调用、无装箱、无树跳转
需要后续仍可能小范围增删(如事件队列、状态快照链)→ 选
:树结构支持高效插入/删除,但每次访问要多跳 1–2 层指针
误把
当“高性能只读数组”用:访问延迟比
高 3–5 倍,尤其在热路径上明显
真正容易被忽略的是:ImmutableList 的“不可变”不等于“零开销”。它的结构共享节省了复制内存,但换来的是间接访问成本。如果你的集合只读、固定、访问密集,优先走
这条路——这才是 .NET 里最接近“冻结数组”的做法。
ImmutableListListImmutableList.Create() .Add(1).Add(2)ImmutableList.Empty new List().AsReadOnly().ToImmutableList() ReadOnlyCollectionAdd()Remove()SetItem()list.Add(42);listvar newList = list.Add(42);newListlist.Add(1).Remove(0).Insert(0, 99)var builder = list.ToBuilder();builder.Add()builder.RemoveAt()builder[0] = xbuilder.ToImmutable()ImmutableList.CreateBuilder(capacity) ImmutableArrayarray[i]ImmutableListImmutableListImmutableArrayImmutableArray.CreateBuilder().AddRange(...).ToImmutable() 