深冻(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]
3thawed 加到 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
TrueContentEquals 按结构逐元素比较,环状自引用也能安全终止。容器五族「== 比身份、ContentEquals 比内容」的统一口径见判等与哈希。
注意
- 冻结值当只读数据用:要写就走
ToMutable()拿副本,改完如需再冻,再包一层freeze(...)。 - 深拷贝有代价:大容器的
freeze(...)与ToMutable()都是整份拷贝,热点路径别反复冻结解冻。
最后更新于 2026年10月9日
