Skip to content
Go back

Immutability 在 .NET 里为什么有价值

.NET 不可变对象概念图

聊 immutability,经常容易聊偏。有人会把它讲成函数式编程爱好者的洁癖,有人会把它讲成“别乱改对象”的基础礼仪,还有人会把它压缩成一句语法建议:多用 init,少用 set

Barret 这篇文章最有价值的地方,是它把问题拉回到了一个很朴素、也很工程化的角度:不可变性真正买来的,不是某种风格标签,而是更稳定的状态边界和更低的调试成本。

很多时候,开发者真正开始接受 immutability,不是因为看懂了某篇函数式理论,而是因为终于被某个“对象在流程中途被改了、但不知道是谁改的” bug 折磨过一轮。到了那个时刻,你会突然意识到:如果某些数据从创建开始就不允许随手变,很多问题根本不会出现,或者至少会更快定位。

不可变对象最核心的价值,不是不能改,而是“谁也别偷偷改”

Barret 在文里一开始给了一个非常直白的定义:immutability 指的是对象一旦创建,值就不再变化。这定义听起来简单,但它真正值钱的地方并不是“值不变”这四个字,而是背后的协作约束。

在一个可变对象模型里,数据可以在很多地方被顺手改写。你把一个对象传给 A,A 改一点;又传给 B,B 再补一个状态;再往后进到 C,C 觉得自己只是“修正一下”字段。最后这个对象看起来还活着,但它身上已经叠了很多隐性修改路径。问题一出现,你得先回答一个很痛苦的问题:到底是谁、在什么时候、基于什么理由改了它?

不可变对象切掉的,恰恰就是这条模糊路径。它不是说数据永远不能变化,而是说:如果你要变化,就请显式地产生一个新对象,而不是在原对象上原地抹一笔。

这个区别很关键。原地修改会把时间线藏起来;创建新状态会把变化显性化。

在 .NET 里谈 immutability,语法只是入口,真正重要的是状态管理方式

Barret 用 setinit 对比切入,很适合作为 .NET 开发者的入口。因为在 C# 里,不可变性确实常常先从这些语言特性开始感知:

这些工具都能帮助你表达“对象创建后不再随意修改”。但如果只把 immutability 理解成“把 setter 去掉”,又会太浅。

真正更重要的是:你的状态流转是不是被设计成可推理的。

也就是说,不可变性不是语法效果,而是一种建模策略。你是在允许对象一路被多个环节叠加修改,还是把每次变化都变成一个新的状态结果?两种方式写出来都可能是合法 C#,但带来的维护体验完全不同。

这也是为什么 immutability 往往和“可预测性”绑在一起。不是因为不可变对象更高级,而是因为它减少了那些不在函数签名、不在接口契约、却真实存在的隐式副作用。

不可变性最容易被低估的收益,是它显著降低了“状态追凶”的成本

我很认同 Barret 文章里那个很现实的判断:很多开发者在没有被 mutable state 狠狠坑过之前,很难真正感受到 immutability 的价值。

这很正常,因为不可变性提供的收益,本来就不是那种一上来就让人惊艳的“多快多少”。它更像是一种在复杂度上升后越来越值钱的保险。

一个对象如果在整个生命周期里都可能被改,你调试时就得考虑很多路径:

这类问题之所以痛苦,不是因为它们理论上解不开,而是因为它们会迅速拉长问题定位路径。不可变对象不能让 bug 消失,但它会显著缩短这种“状态追凶”过程。因为一旦对象不能原地改,问题范围就被天然压缩了:你更容易确定某个状态是在创建时就不对,还是某个新对象生成逻辑有问题。

这对大团队和长链路系统尤其重要。不是每次都能靠脑补追踪对象命运,很多时候更可靠的办法是直接减少可变状态入口。

不可变性不是绝对教条,而是在哪些对象上值得花这个约束成本

当然,immutability 也不该被讲成“所有对象一律不可变”的宗教口号。真实工程里,状态总会变化,系统也不可能只靠纯值对象活着。

更成熟的做法通常是分层看待:

所以 immutability 的价值,不在于全面禁止修改,而在于你有没有识别出那些“一旦被改坏,排查成本会很高”的状态对象,然后优先把它们稳定住。

Barret 这篇文章虽然是入门口径,但最值得延伸理解的就是这点:不可变性不是品味,而是风险控制。你把它用在越关键、越共享、越容易被误改的对象上,收益越明显。

在今天的 AI coding 语境里,不可变性其实比以前更值钱

这点原文没有展开太多,但放回现在很值得补一句。

AI 代码生成的一个典型特点,是它很擅长快速铺结构,但对“哪些状态以后绝对不该被随手修改”这种长期维护边界,不一定天然足够敏感。它能很快给你补全一堆 set;,让东西先跑起来;可一旦系统慢慢长大,可变状态的隐性成本就会一点点冒出来。

这时候,不可变性反而像一种很适合今天的约束。因为它把“这里不该乱动”变成了语言和类型层的事实,而不是只存在于开发者脑内的约定。

换句话说,在 AI 更容易生成“可运行代码”的时代,immutability 这种约束型设计反而更有价值。它不是为了显得严肃,而是为了让后续维护者——包括人和 AI——少走一些隐式状态坑。

.NET 开发里最实际的收益,往往不是高深,而是少出那类特别烦的小事故

我觉得 immutability 在 .NET 里的魅力,很大一部分就来自它特别接地气。你不需要先做大规模函数式改造,也不需要全盘颠覆项目结构。很多时候,只是把一类本来就不该被改的对象改成更稳定的写法,收益就已经能出来。

比如:

这些变化不一定会让系统“更酷”,但经常会让系统更安静。少一点诡异状态,少一点难以复现的 bug,少一点“昨天还好好的今天为什么坏了”的追查成本。这类收益通常不会出现在 benchmark 图里,但会实打实体现在维护体验里。

原文配图:time for change

如果把这篇文章收成一句最实在的话

那大概就是:Immutability 在 .NET 里的价值,不是让代码看起来更现代,而是让状态变化变得更显式、更可预测、更不容易被偷偷改坏。

你不需要把整个系统都做成纯函数式世界,但只要在那些关键对象上认真引入不可变性,很多调试上的痛苦、状态上的歧义和维护上的隐形成本,都会明显下降。

参考


Tags


Previous

.NET 里批量更新数据库该怎么优化

Next

Vertical Slice Architecture 里怎么处理代码重复