
聊 immutability,经常容易聊偏。有人会把它讲成函数式编程爱好者的洁癖,有人会把它讲成“别乱改对象”的基础礼仪,还有人会把它压缩成一句语法建议:多用 init,少用 set。
Barret 这篇文章最有价值的地方,是它把问题拉回到了一个很朴素、也很工程化的角度:不可变性真正买来的,不是某种风格标签,而是更稳定的状态边界和更低的调试成本。
很多时候,开发者真正开始接受 immutability,不是因为看懂了某篇函数式理论,而是因为终于被某个“对象在流程中途被改了、但不知道是谁改的” bug 折磨过一轮。到了那个时刻,你会突然意识到:如果某些数据从创建开始就不允许随手变,很多问题根本不会出现,或者至少会更快定位。
不可变对象最核心的价值,不是不能改,而是“谁也别偷偷改”
Barret 在文里一开始给了一个非常直白的定义:immutability 指的是对象一旦创建,值就不再变化。这定义听起来简单,但它真正值钱的地方并不是“值不变”这四个字,而是背后的协作约束。
在一个可变对象模型里,数据可以在很多地方被顺手改写。你把一个对象传给 A,A 改一点;又传给 B,B 再补一个状态;再往后进到 C,C 觉得自己只是“修正一下”字段。最后这个对象看起来还活着,但它身上已经叠了很多隐性修改路径。问题一出现,你得先回答一个很痛苦的问题:到底是谁、在什么时候、基于什么理由改了它?
不可变对象切掉的,恰恰就是这条模糊路径。它不是说数据永远不能变化,而是说:如果你要变化,就请显式地产生一个新对象,而不是在原对象上原地抹一笔。
这个区别很关键。原地修改会把时间线藏起来;创建新状态会把变化显性化。
在 .NET 里谈 immutability,语法只是入口,真正重要的是状态管理方式
Barret 用 set 和 init 对比切入,很适合作为 .NET 开发者的入口。因为在 C# 里,不可变性确实常常先从这些语言特性开始感知:
initaccessor- 只读属性
- private setter
- readonly 字段
- record 类型
这些工具都能帮助你表达“对象创建后不再随意修改”。但如果只把 immutability 理解成“把 setter 去掉”,又会太浅。
真正更重要的是:你的状态流转是不是被设计成可推理的。
也就是说,不可变性不是语法效果,而是一种建模策略。你是在允许对象一路被多个环节叠加修改,还是把每次变化都变成一个新的状态结果?两种方式写出来都可能是合法 C#,但带来的维护体验完全不同。
这也是为什么 immutability 往往和“可预测性”绑在一起。不是因为不可变对象更高级,而是因为它减少了那些不在函数签名、不在接口契约、却真实存在的隐式副作用。
不可变性最容易被低估的收益,是它显著降低了“状态追凶”的成本
我很认同 Barret 文章里那个很现实的判断:很多开发者在没有被 mutable state 狠狠坑过之前,很难真正感受到 immutability 的价值。
这很正常,因为不可变性提供的收益,本来就不是那种一上来就让人惊艳的“多快多少”。它更像是一种在复杂度上升后越来越值钱的保险。
一个对象如果在整个生命周期里都可能被改,你调试时就得考虑很多路径:
- 是在这个方法里变的,还是之前就变了?
- 传进来时已经坏了,还是中途坏的?
- 是并发场景下改乱了,还是某个 helper 顺手处理过?
- 是共享引用导致的,还是某个 mapper / serializer 又动了点手脚?
这类问题之所以痛苦,不是因为它们理论上解不开,而是因为它们会迅速拉长问题定位路径。不可变对象不能让 bug 消失,但它会显著缩短这种“状态追凶”过程。因为一旦对象不能原地改,问题范围就被天然压缩了:你更容易确定某个状态是在创建时就不对,还是某个新对象生成逻辑有问题。
这对大团队和长链路系统尤其重要。不是每次都能靠脑补追踪对象命运,很多时候更可靠的办法是直接减少可变状态入口。
不可变性不是绝对教条,而是在哪些对象上值得花这个约束成本
当然,immutability 也不该被讲成“所有对象一律不可变”的宗教口号。真实工程里,状态总会变化,系统也不可能只靠纯值对象活着。
更成熟的做法通常是分层看待:
- 值对象、配置对象、DTO、消息契约 很适合不可变
- 跨线程共享的数据 越不可变越省心
- 领域对象里那些表达事实快照的部分 很适合稳定化
- 高频、短生命周期、内部局部使用的可控对象 有时没必要强行全做不可变
所以 immutability 的价值,不在于全面禁止修改,而在于你有没有识别出那些“一旦被改坏,排查成本会很高”的状态对象,然后优先把它们稳定住。
Barret 这篇文章虽然是入门口径,但最值得延伸理解的就是这点:不可变性不是品味,而是风险控制。你把它用在越关键、越共享、越容易被误改的对象上,收益越明显。
在今天的 AI coding 语境里,不可变性其实比以前更值钱
这点原文没有展开太多,但放回现在很值得补一句。
AI 代码生成的一个典型特点,是它很擅长快速铺结构,但对“哪些状态以后绝对不该被随手修改”这种长期维护边界,不一定天然足够敏感。它能很快给你补全一堆 set;,让东西先跑起来;可一旦系统慢慢长大,可变状态的隐性成本就会一点点冒出来。
这时候,不可变性反而像一种很适合今天的约束。因为它把“这里不该乱动”变成了语言和类型层的事实,而不是只存在于开发者脑内的约定。
换句话说,在 AI 更容易生成“可运行代码”的时代,immutability 这种约束型设计反而更有价值。它不是为了显得严肃,而是为了让后续维护者——包括人和 AI——少走一些隐式状态坑。
.NET 开发里最实际的收益,往往不是高深,而是少出那类特别烦的小事故
我觉得 immutability 在 .NET 里的魅力,很大一部分就来自它特别接地气。你不需要先做大规模函数式改造,也不需要全盘颠覆项目结构。很多时候,只是把一类本来就不该被改的对象改成更稳定的写法,收益就已经能出来。
比如:
- API 响应模型别在中途被补字段
- 命令 / 事件对象别被消费方顺手改掉
- 地址、金额、邮箱这类值对象别在业务流里被原地篡改
- 配置快照别在运行期被不透明改写
这些变化不一定会让系统“更酷”,但经常会让系统更安静。少一点诡异状态,少一点难以复现的 bug,少一点“昨天还好好的今天为什么坏了”的追查成本。这类收益通常不会出现在 benchmark 图里,但会实打实体现在维护体验里。

如果把这篇文章收成一句最实在的话
那大概就是:Immutability 在 .NET 里的价值,不是让代码看起来更现代,而是让状态变化变得更显式、更可预测、更不容易被偷偷改坏。
你不需要把整个系统都做成纯函数式世界,但只要在那些关键对象上认真引入不可变性,很多调试上的痛苦、状态上的歧义和维护上的隐形成本,都会明显下降。
参考
- The Value of Immutability in .NET — Barret Blake
- Records in C# — Microsoft Learn
- init-only setters — Microsoft Learn