.net 8 中没有 frozenset,可用 immutablearray 实现等效效果:预分配构建、去重排序后使用 binarysearch 查找,确保一次性创建且不保留 builder 引用。

.NET 8 没有 FrozenSet<t></t> 类型,编译时遇到 The type or namespace name 'FrozenSet' could not be found 是正常现象——它不在 BCL 中,也不在 System.Collections.Immutable v8.0.0 里。
FrozenSet<t></t> 不存在,但你可以用 ImmutableArray<t></t> 实现等效效果
-
ImmutableArray<t></t>是 .NET 原生支持的、内存紧凑、无虚调用、零装箱的只读数组,构建后不可修改,底层是固定大小的托管数组 - 它不提供去重语义,所以若你真需要“集合”行为(即唯一性 + 查找),得手动配合
HashSet<t></t>预建索引,或排序后走Array.BinarySearch - 不要用
ImmutableHashSet<t></t>冒充“冻结”:它底层是哈希 trie,内存碎片多、访问有间接跳转,10k 元素下比排序ImmutableArray<t></t>多占约 35% 内存
示例路径:
- 用
ImmutableArray.CreateBuilder<string>(capacity)</string>预分配容量 -
Add所有元素(确保无重复,或先用HashSet<t></t>去重) - 调用
builder.ToImmutable()得到最终快照 - 后续所有访问都基于该实例,不重建、不包装、不转换
为什么不用 IReadOnlySet<t></t> 包装?
-
IReadOnlySet<t></t>只是接口,无法保证实现是否真正“冻结” - 常见实现如
HashSet<t>.AsReadOnly()</t>返回的是运行时只读包装,底层仍可被反射/内部字段篡改 -
ImmutableArray<t></t>的IsDefault和Length是字段直取,this[index]是数组直接索引,无额外抽象层
若你必须暴露为 IReadOnlySet<t></t>,建议只用于 API 兼容,内部仍以 ImmutableArray<t></t> 存储,并用 Array.BinarySearch 实现 Contains(需提前排序)。
真正“冻结”的关键不是名字,而是使用方式
- 冻结不是靠类型名,而是靠一次性构建 + 不再持有 builder + 不做任何 ToList/ToArray/ToHashSet 转换
- 错误做法:
- 在循环中反复
builder.Add(x).ToImmutable()→ 触发多次数组拷贝 - 构建完还保留
builder引用 → 可能意外复用或 Clear - 把
ImmutableArray<t></t>转成List<t></t>再排序 → 失去零分配优势
- 在循环中反复
- 正确姿势:
- builder 生命周期严格限定在初始化块内
- 最终变量声明为
readonly ImmutableArray<string> LookupTable</string> - 查找逻辑直接写在静态方法里,避免闭包捕获或状态泄漏
真正的冻结代价不在定义,而在约束——你得亲手守住那条“不再修改”的线。










