告别版本地狱:C# 中央包管理
痛点:散落各处的包版本
你是否遇到过这样的场景?
- 解决方案有 20 个项目,
Newtonsoft.Json出现了 5 种不同版本 - 升级一个包要改十几个
.csproj文件 - 代码合并时频繁因为版本号冲突
NuGet 在 .NET 6 中引入的中央包管理(CPM) 正是为了解决这些问题。
传统方式下,每个项目独立声明版本:
xml
<!-- 传统方式:版本散落在各个项目中 -->
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
启用中央包管理后,项目只需声明"我要用这个包":
xml
<!-- 中央包管理:版本由统一文件管理 -->
<PackageReference Include="Newtonsoft.Json" />
核心文件:Directory.Packages.props
中央包管理的核心是一个名为 Directory.Packages.props 的文件:
xml
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Serilog" Version="3.1.1" />
<PackageVersion Include="xunit" Version="2.6.2" />
</ItemGroup>
</Project>
迁移三步走
Step 1:创建版本清单
在解决方案根目录创建 Directory.Packages.props,用 <PackageVersion> 列出所有包版本。
Step 2:精简项目文件
移除各 .csproj 中 <PackageReference> 的 Version 属性:
xml
<!-- 修改前 -->
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<!-- 修改后 -->
<PackageReference Include="Newtonsoft.Json" />
Step 3:处理特殊情况
个别项目需要使用不同版本?用 VersionOverride 覆盖:
xml
<PackageReference Include="Newtonsoft.Json" VersionOverride="12.0.3" />
幕后机制:MSBuild 何时加载版本信息
MSBuild 在项目评估阶段(Evaluation Phase) 查找并加载 Directory.Packages.props,具体时机:
dotnet restore执行时dotnet build开始前的还原阶段- Visual Studio 加载项目时
版本信息在解析 <PackageReference> 之前就已就绪。
文件查找规则:向上追溯
MSBuild 从项目文件所在目录开始,逐级向上查找 ,直到找到 Directory.Packages.props 或到达根目录。
C:\
└── MyRepo\
├── Directory.Packages.props ← 解决方案级(推荐位置)
├── MySolution.sln
├── src\
│ ├── Directory.Packages.props ← 子目录级(可覆盖上级)
│ ├── ProjectA\
│ │ └── ProjectA.csproj ← 先找 src\,再找 MyRepo\
│ └── ProjectB\
│ └── ProjectB.csproj
└── tests\
└── TestProject\
└── TestProject.csproj ← 直接命中 MyRepo\ 下的文件
规则小结:
- 起点:
.csproj所在目录 - 方向:逐级向父目录
- 终止:找到第一个即停止
- 扩展:可通过
<Import>导入多个文件
为什么值得迁移
| 收益 | 说明 |
|---|---|
| 版本一致性 | 杜绝"版本漂移",全解决方案统一 |
| 一处修改 | 升级包只改一个文件 |
| 告别冲突 | PR 合并不再为版本号打架 |
| 依赖审计 | 一个文件纵览全部依赖 |
| 团队规范 | 强制统一管理,避免各自为战 |
| 文件瘦身 | .csproj 更清爽 |
踩坑提醒
- 全员参与:启用 CPM 后,解决方案内所有项目必须遵循,不能"一半传统一半中央"
- 传递依赖无需声明:只需声明直接引用的包,传递依赖自动处理
- 全局包引用:Analyzer 等全局包可直接在 props 文件中定义:
xml
<ItemGroup>
<GlobalPackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.507" />
</ItemGroup>