后端与接口
将 .NET 8 项目升级到 .NET 9…
当需要把 TargetFramework 从 net8.0 升级到 net9.0、修复升级后的编译错误与行为变更时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
将 .NET 8 项目升级到 .NET 通用
解决思路
- 评估项目:识别构建与测试方式(构建脚本、.sln/.slnx、.csproj);运行 dotnet --version 确认已安装 .NET 9 SDK,未安装则停止并告知用户;通过 SDK 属性(Microsoft.NET.Sdk.Web → ASP.NET Core;Microsoft.NET.Sdk.WindowsDesktop 配合 <UseWPF> 或 <UseWindowsForms> → WPF/WinForms)、PackageReferences(Microsoft.EntityFrameworkCore.* → EF Core;Microsoft.Extensions.Http → HttpClientFactory)、是否存在 Dockerfile、是否使用 P/Invoke 或原生互操作、是否使用 BinaryFormatter、是否使用 System.Text.Json、是否使用 X509Certificate 构造函数来判断涉及的技术领域;记录需要加载的参考文档;在当前 net8.0 目标上做一次干净构建(dotnet build --no-incremental 或删除 bin/obj)建立基线并记录已有警告。
- 更新目标框架:在每个 .csproj(或集中管理的 Directory.Build.props)中把 <TargetFramework>net8.0</TargetFramework> 改为 <TargetFramework>net9.0</TargetFramework>;多目标项目在 <TargetFrameworks> 中加入 net9.0 或替换 net8.0。
- 把所有 Microsoft.Extensions.*、Microsoft.AspNetCore.*、Microsoft.EntityFrameworkCore.* 及其他 Microsoft 包引用更新到 9.0.x 版本;若使用集中包管理(Directory.Packages.props),在其中更新版本。
- 运行 dotnet restore,注意:.NET 9 SDK 需要 Visual Studio 17.12+ 才能以 net9.0 为目标(17.11 只能面向 net8.0 及更早);关注针对 .NET Standard 1.x 和 .NET 7 目标的新警告,考虑更新或移除过时的目标框架。
- 运行干净构建,收集所有错误与新警告,进入下一步处理。
- 解决编译错误与源不兼容变更,按项目类型加载对应参考文档:任何 .NET 9 项目加载 references/csharp-compiler-dotnet8to9.md、references/core-libraries-dotnet8to9.md、references/sdk-msbuild-dotnet8to9.md;ASP.NET Core 加载 references/aspnet-core-dotnet8to9.md;EF Core 加载 references/efcore-dotnet8to9.md;加密 API 加载 references/cryptography-dotnet8to9.md;System.Text.Json、HttpClient、网络加载 references/serialization-networking-dotnet8to9.md;WinForms 或 WPF 加载 references/winforms-wpf-dotnet8to9.md;Docker 容器、原生互操作加载 references/containers-interop-dotnet8to9.md;运行时配置、部署加载 references/deployment-runtime-dotnet8to9.md。
- 检查 params span 重载解析:String.Join、String.Concat、Path.Combine、Task.WhenAll 等新增的 params ReadOnlySpan<T> 重载会优先绑定,在 Expression lambda 中调用这些方法会失败(CS8640/CS9226),参见 references/core-libraries-dotnet8to9.md。
- 修复 StringValues 重载歧义:params Span<T> 特性会在 String.Concat、String.Join、Path.Combine 等方法上与 StringValues 隐式转换产生歧义,通过显式转换参数解决,参见 references/core-libraries-dotnet8to9.md。
- 处理新的过时警告 SYSLIB0054–SYSLIB0057:SYSLIB0054 用 Volatile.Read/Volatile.Write 替换 Thread.VolatileRead/VolatileWrite;SYSLIB0057 用 X509CertificateLoader 方法替换 X509Certificate2/X509Certificate 的二进制/文件构造函数;SYSLIB0055(ARM AdvSimd 有符号重载)和 SYSLIB0056(Assembly.LoadFrom 带哈希算法)参见 references/core-libraries-dotnet8to9.md。
- 修复 C# 13 在 record struct 上的 InlineArray:record struct 类型上的 [InlineArray] 特性现在被禁止(CS9259),改为普通 struct,参见 references/csharp-compiler-dotnet8to9.md。
- 修复 C# 13 迭代器安全上下文:C# 13 中迭代器引入安全上下文,迭代器内从外部 unsafe 类继承 unsafe 的局部函数会报错,给局部函数添加 unsafe 修饰符,参见 references/csharp-compiler-dotnet8to9.md。
- 修复 C# 13 集合表达式重载解析:空集合表达式([])不再用 span 与非 span 来打破重载平局,现在优先精确元素类型,参见 references/csharp-compiler-dotnet8to9.md。
- 处理 String.Trim(params ReadOnlySpan<char>) 被移除:针对 .NET 9 预览版编译、向 Trim/TrimStart/TrimEnd 传入 ReadOnlySpan<char> 的代码必须重新构建,该重载在 GA 中已移除,参见 references/core-libraries-dotnet8to9.md。
- 处理 BinaryFormatter 总是抛出异常:若项目使用 BinaryFormatter,停止并告知用户,这是一个重大决策,参见 references/serialization-networking-dotnet8to9.md。
- 修复 HttpListenerRequest.UserAgent 可空:该属性现在为 string?,添加空值检查,参见 references/serialization-networking-dotnet8to9.md。
- 修复 Windows Forms 可空性标注变更:部分 WinForms API 参数从可空改为不可空,更新调用点,参见 references/winforms-wpf-dotnet8to9.md。
- 处理 Windows Forms 安全分析器(WFO1000):新分析器会对未显式配置序列化的属性产生错误,参见 references/winforms-wpf-dotnet8to9.md。
- 每批修复后重新构建,重复直到构建干净。
- 处理行为变更(不导致编译错误但可能改变运行时行为),先检查高影响项:浮点到整数转换现在饱和——x86/x64 上 float/double 转整数类型现在饱和而非回绕,参见 references/deployment-runtime-dotnet8to9.md。
- EF Core:待处理模型变更异常——Migrate()/MigrateAsync() 在模型有待处理变更时现在抛出异常;搜索任何 HasData 调用中的 DateTime.Now、DateTime.UtcNow 或 Guid.NewGuid(),必须替换为固定常量(如 new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc)),参见 references/efcore-dotnet8to9.md。
- EF Core:显式事务异常——在用户事务内调用 Migrate() 现在抛出异常,参见 references/efcore-dotnet8to9.md。
- HttpClientFactory 默认使用 SocketsHttpHandler——把主处理器强制转换为 HttpClientHandler 的代码会得到 InvalidCastException,参见 references/serialization-networking-dotnet8to9.md。
- HttpClientFactory 默认脱敏请求头——Trace 级别日志中所有请求头的值现在被脱敏,参见 references/serialization-networking-dotnet8to9.md。
- 环境变量优先于 runtimeconfig.json——来自环境变量的运行时配置现在覆盖 runtimeconfig.json,参见 references/deployment-runtime-dotnet8to9.md。
- ASP.NET Core 开发环境中的 ValidateOnBuild/ValidateScopes——HostBuilder 现在默认在开发环境启用 DI 验证,参见 references/aspnet-core-dotnet8to9.md。
- 审查其他行为变更(可能引发运行时异常或细微行为差异):FromKeyedServicesAttribute 不再注入非键控服务回退(抛 InvalidOperationException);容器镜像不再安装 zlib(依赖系统 zlib 的应用会失败);Intel CET 默认启用(不兼容 CET 的原生库可能导致进程终止);BigInteger 最大长度现为 (2^31)-1 位;JsonDocument 反序列化 JSON null 现在返回非空 JsonDocument 且 JsonValueKind.Null,而非 C# null;System.Text.Json 元数据读取器现在反转义元数据属性名;ZipArchiveEntry 名称/注释现在遵循 UTF-8 标志;IncrementingPollingCounter 初始回调现在异步;InMemoryDirectoryInfo 会把 rootDir 前置到文件;RuntimeHelpers.GetSubArray 返回不同类型;PictureBox 抛出 HttpRequestException 而非 WebException;StatusStrip 使用不同的默认渲染器;IMsoComponent 支持改为可选加入;SafeEvpPKeyHandle.DuplicateHandle 会增加句柄引用计数;HttpClient 指标无条件报告 server.port;HttpClient EventSource 事件和 IHttpClientFactory 日志中 URI 查询字符串被脱敏;dotnet watch 与旧框架的 Hot Reload 不兼容;WPF GetXmlNamespaceMaps 返回 Hashtable 而非 String。
- 更新基础设施:更新 Dockerfile 基础镜像(FROM mcr.microsoft.com/dotnet/sdk:8.0 → 9.0,FROM mcr.microsoft.com/dotnet/aspnet:8.0 → 9.0);注意 .NET 9 容器镜像不再安装 zlib,若应用依赖 zlib,在 Dockerfile 中添加 RUN apt-get update && apt-get install -y zlib1g。
- 更新 CI/CD 流水线中的 SDK 版本引用;若使用 global.json,更新为 {"sdk": {"version": "9.0.100", "rollForward": "latestFeature"}};审查 rollForward 策略——若设为 "disable" 或 "latestPatch",升级后 SDK 可能无法正确解析,推荐 "latestFeature" 以允许 SDK 前滚到最新的 9.0.x 功能带。
- 确认 Visual Studio 版本:.NET 9 SDK 需要 VS 17.12+ 才能以 net9.0 为目标,VS 17.11 只能面向 net8.0 及更早。
- 处理 Terminal Logger:dotnet build 现在在交互式终端默认使用 Terminal Logger,解析 MSBuild 控制台输出的 CI 脚本可能需要 --tl:off 或 MSBUILDTERMINALLOGGER=off。
- 处理 dotnet workload 输出格式变更:更新任何解析 workload 命令输出的脚本。
- 处理 .NET Monitor 镜像标签简化:标签简化为仅版本号,影响引用特定标签的容器编排。
- 验证:运行完整干净构建 dotnet build --no-incremental;运行所有测试 dotnet test;若应用容器化,构建并测试容器镜像;对应用做冒烟测试,特别关注 BinaryFormatter 使用(运行时将抛出)、浮点到整数转换行为、EF Core 迁移应用、HttpClientFactory 处理器强制转换与日志、开发环境中的 DI 验证、运行时配置设置(环境变量优先级);审查 diff 确保未引入意外的行为变更。
适用边界 · 注意事项
- 项目已经以 net9.0 为目标且构建干净——迁移已完成;若目标是 net10.0,应使用 migrate-dotnet9-to-dotnet10 技能作为下一步。
- 从 .NET 7 或更早版本升级——应先处理先前版本的破坏性变更。
- 从 .NET Framework 迁移——那是独立且更大的工作;或全新项目直接以 .NET 9 起步(无需迁移)。