×

从 .NET 6 到 .NET 10,这些写法都过时了,该换了

独孤求败 独孤求败 发表于2026-05-17 00:09:41 浏览25 评论0

抢沙发发表评论

今天这篇文章主要分享下关于.NET 旧项目与.NET 10的关键性差异写法,起因是上周一个老同事问我他们公司的项目应该怎么与AI融合升级,当打开项目的时候发现还是用的.NET 6,并不是对旧项目存在技术歧视,在AI Coding的时代应该好好运用这个能力早日将技术负债干掉,追赶新一轮的软件行业洗牌。

1. Startup.cs

「.NET 6 之前的经典写法」

图片

「.NET 6+ 的现代写法」(Top-level statements + Minimal Hosting):

图片

「为什么换」:少了一个文件、少了一层抽象、配置和路由都在一个地方一眼看懂。新项目模板从 .NET 6 开始就是这个样子,「还在用 Startup 的等于在告诉所有人"我没跟上时代"」


2. Console.WriteLine($"用户 {userId} 登录") 这种日志写法

「.NET 6 时代很多人这么写」

图片

看着挺合理对吧?「但这是错的」

「正确的写法」

图片

「为什么换」

第一,字符串插值在日志级别被禁用时仍然会执行——你设置 LogLevel 为 Warning,那行 Information 日志虽然不输出,但字符串拼接的开销还在。结构化模板则是延迟执行。

第二,「结构化日志的字段可以被搜索」。在 Seq、Elasticsearch、Application Insights 里,你可以直接 UserId = 123 查出某个用户的所有日志。字符串插值出来的日志,永远只能用关键词模糊搜,效率天壤之别。

.NET 9 开始更狠——直接给你提供了 「LoggerMessage 源生成器」,性能比传统方式还快好几倍:

图片

编译时生成代码,零反射、零装箱、零字符串拼接。能用这个就用这个。


3. lock(new object()) 已经不是最佳实践了

「老写法」

图片

「.NET 9+ 推荐」

图片

「为什么换」

.NET 9 引入了正式的 System.Threading.Lock 类型,「底层比传统 Monitor 更高效」,调试器中可视性也更好。你的代码改动只有"一行类型声明",业务逻辑零修改,免费拿到性能提升和更清晰的语义。

这是那种"知道了就立刻全项目替换"的零成本升级。


4. 手写 backing field 做属性校验,已经没必要了

「老写法」

图片

「C# 13 / .NET 9 起的新写法」field 关键字):

图片

「为什么换」

C# 团队等了将近 10 年才把 field 关键字加进来——因为太多人有名为 field 的变量,向后兼容要慎重。但加进来之后,「所有"setter 里要做点事但又懒得手写 backing field"的场景」都被它解决了。

注意:field 关键字在 C# 14(.NET 10)正式发布,之前的版本需要 <LangVersion>preview</LangVersion> 才能用。


5. params object[] args 该换成 params ReadOnlySpan<T> 了

「老写法」

图片

「C# 13+ 新写法」

图片

「为什么换」

老写法每次调用都会在堆上分配一个数组,对高频调用方法是隐性的性能杀手。新写法编译器直接把字面量参数放在栈上,「零堆分配、零 GC 压力」

.NET 9 BCL 已经给 606 个方法补充了 ReadOnlySpan 版本的 params 重载——你重新编译一遍现有项目,编译器会自动选最优重载,「业务代码一行不改,免费拿到性能提升」


6. IEnumerable<T>.GroupBy().Select(g => new { Key, Count }) 该精简了

「老写法」

图片

「.NET 9 新写法」

图片

类似的还有 AggregateBy(任意聚合)、Index(带下标遍历):

图片

「为什么换」

LINQ 不是一成不变的"老 API",每个版本都在补漏。这三个方法解决的是过去「人人都手写过 N 次、又每次都觉得别扭」的场景。代码更短、意图更清晰,性能也更好(少了中间集合分配)。


7. 用 Task.WhenAny 手写"先到先得"循环?过时了

「.NET 8 之前的老写法」

图片

「.NET 9 新写法」

图片

「为什么换」

老写法不仅难看,tasks.Remove() 是 O(n) 操作,任务多了性能会衰减。Task.WhenEach 直接返回一个 IAsyncEnumerable<Task<T>>「按完成顺序产生任务」,配合 await foreach 极度优雅。

批量调用外部 API、并发查多个数据源的场景,用这个能让首结果处理延迟从"最慢请求"缩短到"最快请求"——业务感知会非常明显。


8. EF Core 改完数据再 SaveChanges,大数据场景该用批量 API 了

「老写法」

图片

「EF Core 7+ 起的批量 API」(在 EF Core 9/10 更完善):

图片

类似的还有 ExecuteDeleteAsync——删除大量记录时不要再 db.Remove + SaveChanges 了。

「为什么换」

旧写法本质上是 EF Core 在帮你「模拟批量操作」,每条数据都要往返一次内存、做变更追踪。新 API 直接生成一条原生 SQL,性能完全是两个量级。大数据量场景这个差异是用户能感知的。


9. 配置 HttpClient 的 Polly 重试策略,写法变了

「.NET 7 之前的经典写法」

图片

「.NET 8+ 推荐」(基于 Microsoft.Extensions.Http.Resilience):

图片

「为什么换」

Polly v8 重构了 API,从"策略对象"模型升级到了"管道"模型。AddStandardResilienceHandler() 直接给你一套「生产级默认配置」——重试 + 熔断 + 超时 + 限流 + Hedging 全都安排好了,不用再像以前那样手动一个一个组合。

老写法不算"错",但比起新 API 来说,「繁琐且容易配错」


10. AddDistributedMemoryCache + 自己写双重检查锁?.NET 9 一行替代

「.NET 9 之前的"标准"写法」

图片

「.NET 9+ 推荐」HybridCache):

图片

「为什么换」

HybridCache 是 .NET 9 新增的分层缓存抽象,「内置防 Cache Stampede(缓存踩踏)机制」——100 个并发请求同时打过来,只会有 1 个真正去查数据库,剩下的 99 个等待结果。

它还自动管理 L1(内存)+ L2(Redis 等分布式)两级缓存:本地有就用本地,没有去 Redis 取,再没有调用 factory。「老式手写双检锁的所有理由都不复存在了」


最后

技术债不可怕,可怕的是不知道自己背了多少。这篇文章如果让你看到了几条"我也是这么写的",那它就值了。

最后给一个务实建议:「不要为了"换新写法"专门搞一个 PR」,应该是下次动到那个文件时顺手改了。这种渐进式的现代化,比一次大改靠谱得多。


群贤毕至

访客