Skip to Content

深冻(Frozen)

frozen<T> 把一个值变成深不可变形态:构造时深拷贝、与源断开共享,之后任何写路径(索引器、变异方法、字段赋值)都被编译期拦截。配对的 ToMutable() 是唯一的解冻通道。

构造即深拷贝

freeze(expr) 对表达式求值后深拷贝冻结,冻结值与源从此互不相干:

val orig = new list<int>(); orig.Add(3); orig.Add(1); orig.Add(2); val frozenList = freeze(orig); orig[0] = 99; print(frozenList); print(orig);
[3, 1, 2] [99, 1, 2]

改源不波及冻结值,反向亦然。深拷贝是递归的:freeze([[1], [2]]) 连内层数组一并冻结,嵌套结构的内层同样被冻结,不存在部分可变的例外,环状自引用结构也能安全拷贝。

写面全拦截

冻结值的每一条写路径都在编译期拒绝,诊断统一为 MS3267,并点名写法:

error MS3267: cannot mutate a frozen value via 'indexer'; call ToMutable() for a mutable copy. error MS3267: cannot mutate a frozen value via 'Add'; call ToMutable() for a mutable copy. error MS3267: cannot mutate a frozen value via 'x'; call ToMutable() for a mutable copy.

三条分别对应索引器写(f[0] = 9)、容器变异方法(Add / Insert / Clear / Set)、字段赋值(f.x = 9)。嵌套层级同样拦:f[0][0] = 9 里的 f[0] 本身是 frozen<int[]>,每往里一层,再写再拦。

读面原样保留:Length / Count 属性、索引读、遍历都照常。freeze(freeze(x)) 是合法嵌套,成员调用逐层解包透传,行为与单层一致。

ToMutable:转一份非冻结态

需要改回去时,ToMutable() 从冻结值派生一份全新的可变副本;源是数组时定型翻回 T[]:

val thawed = frozenList.ToMutable(); thawed.Add(9); print(thawed); print(frozenList.Length);
[3, 1, 2, 9] 3

thawed 加到 9 个元素,frozenList 长度不变。

判等:引用同一性与内容判等

== 在冻结值上是引用同一性:同一个冻结对象与自己相等,两次独立冻结的同内容值不相等。按内容比较用 ContentEquals:

val a = freeze([1, 2, 3]); val b = freeze([1, 2, 3]); print(a == b, a.ContentEquals(b)); print(a == a);
False True True

ContentEquals 按结构逐元素比较,环状自引用也能安全终止。容器五族「== 比身份、ContentEquals 比内容」的统一口径见判等与哈希。

注意

  • 冻结值当只读数据用:要写就走 ToMutable() 拿副本,改完如需再冻,再包一层 freeze(...)。
  • 深拷贝有代价:大容器的 freeze(...) 与 ToMutable() 都是整份拷贝,热点路径别反复冻结解冻。
最后更新于 2026年10月9日