一、Task.WhenEach:等任务"先到先得",终于不用再写黑魔法了
说到并发任务,你一定用过这两个:
Task.WhenAll:等所有任务完成Task.WhenAny:等任意一个完成
但有个场景,这两个都很难优雅处理:「你有一堆任务,想每完成一个就立刻处理一个,不等其他任务,也不用轮询。」
以前的"标准"写法是用 Task.WhenAny 手写一个循环:
这段代码有什么问题?首先写起来别扭,其次 tasks.Remove() 是 O(n) 操作,任务多了有性能问题,而且代码意图不够清晰。
「.NET 9 带来了 Task.WhenEach」,它返回 IAsyncEnumerable<Task<T>>,任务按完成顺序依次产生,可以直接 await foreach:
「实际场景」:批量调用多个外部 API(如同时查 N 个用户的信用评分),响应时间不同,你希望每有一个结果回来就立刻存库,而不是等所有请求都完成再批量处理。Task.WhenEach 完美契合这个需求,首个结果的处理延迟从"最慢的那个请求"缩短到"最快的那个请求"。
二、LINQ 三连:CountBy、AggregateBy、Index——你手写过多少次这些逻辑?
2.1 CountBy:统计频次,再也不用 GroupBy().Select() 套娃
需求:统计一段文字中每个词出现的频率,找出出现最多的词。
以前你要这样写:
「新的 CountBy」:
更实用的场景——统计订单按状态的数量:
2.2 AggregateBy:分组聚合,终结 GroupBy 的笨重写法
CountBy 只能计数,AggregateBy 则支持任意聚合操作:
旧写法需要 GroupBy + Select 两步,且会创建中间集合;AggregateBy 是一次遍历完成,内存效率更高。
2.3 Index:遍历时同时获取下标,告别 Select((item, i) => ...)
在 LINQ 链式调用中需要元素的下标,以前只能用 Select 的重载:
「新的 .Index()」:
三个 LINQ 新方法的共同特点:「把"我写了很多次但从来不觉得优雅"的常见操作,变成了一个语义准确的方法名」。代码的可读性直接体现了意图,review 的人不需要解读 GroupBy 的层层嵌套。
三、System.Threading.Lock:终于有了一个正经的锁类型
这可能是 .NET 9 里「最被低估的新特性」。
以前写线程同步,你用的是 lock 关键字配合一个 object:
这样做最大的问题是:_syncRoot 是一个裸 object,调试时什么信息都看不到,也无法知道这个锁的使用情况。
「.NET 9 引入了 System.Threading.Lock 类型」,lock 语句识别到它时会自动使用更高效的新 API:
还支持更灵活的 EnterScope() 用法,配合 using 语句精确控制锁的生命周期:
底层实现上,Lock 比 Monitor(传统 lock 背后的机制)效率更高,且在调试器中有更好的可视性。「如果你的项目有大量锁竞争,这一个改动可能带来可观的性能提升,且零代码改动风险。」
四、params 终于进化了:不再强制转数组
C# 从 1.0 就有 params,但长达 20 年,它只支持数组:
「C# 13(.NET 9)把 params 扩展到所有集合类型」,包括 Span<T>、ReadOnlySpan<T>、IEnumerable<T>、List<T> 等:
这个改进的价值不只是少写 .ToArray()。当 params 配合 ReadOnlySpan<T> 使用时,编译器可以在调用点直接将字面量参数分配在「栈」上,完全没有堆分配,GC 压力为零。对于高频调用的方法,这是实实在在的性能提升。
「.NET 9 的基础类库(BCL)中已经有 606 个方法添加了 ReadOnlySpan<T> 版本的 params 重载。」 你重新编译已有项目,编译器会自动选择更优的重载,无需修改任何调用代码,免费获得性能提升。
五、field 关键字:半自动属性,终结手写 backing field
问题背景:你有一个简单属性,但需要在 setter 里加点逻辑(比如值校验)。以前必须手写 backing field:
「C# 14 的 field 关键字」让你直接在属性体内访问编译器生成的 backing field,无需手动声明:
field 关键字的适用场景总结:
属性赋值需要「校验」(范围检查、非空检查) 属性需要「懒加载」( field ??= new())属性需要在 getter 做「格式化或计算」,但 setter 保存原始值 减少类中 private字段的数量,让类定义更紧凑
❝⚠️ 注意:如果你的类里已有一个名为
❞field的属性或字段,field关键字会被该成员遮蔽,编译器会发出警告。这也是这个特性迟到了近 10 年才发布的原因之一——C# 团队为向后兼容性考量了很久。
六、HybridCache:缓存 Stampede 问题的终结者
如果你的应用同时用了 IMemoryCache(内存缓存)和 IDistributedCache(Redis 等分布式缓存),你一定遇到过这个头疼的问题:
「Cache Stampede(缓存踩踏)」:缓存过期的瞬间,大量并发请求同时发现缓存失效,一拥而上直接打到数据库,造成数据库瞬间高负载甚至宕机。
传统解法要么复杂(双重检查锁定),要么不够优雅。「.NET 9 引入了 HybridCache,一个 API 同时管理 L1(内存)+ L2(分布式)两级缓存,内置防 Stampede 机制」:
HybridCache 和传统缓存方式的对比:
对于分布式部署的生产环境,HybridCache 几乎是 IMemoryCache + IDistributedCache 的完美替代方案。
写在最后
这 6 个特性,有的解决了"憋了多年的小问题"(params 进化、field 关键字),有的是"你以为简单但其实有坑的场景的正确解法"(Task.WhenEach、Lock),有的则是"生产系统里真的会用到的防御机制"(HybridCache)。
它们的共同点是:「不光鲜,不够"AI",没有大厂案例背书」,所以很少出现在技术分享里。但对于真正在写业务代码的开发者,这些才是每天都能用上的东西。