NuGet 依赖管理发展历史(packages.config → PackageReference)
一、NuGet 是什么?
NuGet 是 .NET 官方的包管理器,相当于:
| 平台 | 包管理器 |
|---|---|
| Java | Maven / Gradle |
| Node.js | npm |
| Python | pip |
| Rust | Cargo |
| Go | Go Modules |
| .NET | NuGet |
负责:
- 下载第三方库
- 管理版本
- 自动解析依赖
- Restore(恢复依赖)
- 更新包
二、NuGet 的发展历程
第一代:packages.config(约 2011~2017)
这是 .NET Framework 时代最经典的管理方式。
目录结构:
Solution
│
├── packages
│ Newtonsoft.Json.13.0.3
│ NLog.5.0.0
│ EmbedIO.3.5.2
│
├── ProjectA
│ packages.config
│
└── ProjectB
packages.config
每个项目都有:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="EmbedIO" version="3.5.2" />
<package id="Scriban" version="7.2.5" />
<package id="Newtonsoft.Json" version="13.0.3" />
</packages>
优点
- 简单
- 容易理解
- 所有 DLL 都在项目附近
缺点(也是后来被淘汰的原因)
1、每个项目都有 packages
例如:
ProjectA
packages
ProjectB
packages
ProjectC
packages
同一个 Newtonsoft.Json:
ProjectA/packages/Newtonsoft.Json
ProjectB/packages/Newtonsoft.Json
ProjectC/packages/Newtonsoft.Json
重复下载很多次。
2、不能识别传递依赖(Transitive Dependency)
例如:
EmbedIO
│
├── SwanLite
│ │
│ └── System.Memory
│
└── System.Buffers
packages.config 会写成:
<package id="EmbedIO"/>
<package id="SwanLite"/>
<package id="System.Memory"/>
<package id="System.Buffers"/>
几年以后:
packages.config
100 个 Package
↓
真正需要
30 个
↓
70 个历史遗留
越来越乱。
3、不会自动删除垃圾依赖
例如:
安装:
EmbedIO
后来:
卸载 EmbedIO
NuGet 不知道:
System.Memory
System.Buffers
Unsafe
是不是还能删。
因此不会清理。
4、Restore 不会整理依赖
执行:
nuget restore
只会:
- 下载缺失包
- 修复版本
不会:
- 删除没用的包
- 重建依赖树
第二代:PackageReference(官方推荐)
2017 年以后微软推出了 PackageReference。
这是目前所有新项目推荐的方式。
它没有:
packages.config
依赖直接写进:
*.csproj
例如:
<ItemGroup>
<PackageReference Include="EmbedIO"
Version="3.5.2"/>
<PackageReference Include="Newtonsoft.Json"
Version="13.0.3"/>
</ItemGroup>
最大区别
PackageReference 只保存:
你主动安装的 Package
例如:
EmbedIO
真正依赖:
EmbedIO
↓
SwanLite
↓
System.Memory
↓
Unsafe
csproj:
<PackageReference Include="EmbedIO"/>
只有这一句。
其它全部由 NuGet 自动计算。
删除包以后
例如:
删除:
<PackageReference Include="EmbedIO"/>
Restore:
dotnet restore
NuGet:
重新计算依赖树
↓
SwanLite
↓
Memory
↓
Unsafe
↓
全部自动消失
这就是 PackageReference 最大优势。
三、PackageReference 的包存放位置
默认:
C:\Users\<用户名>\.nuget\packages
例如:
C:\Users\Admin\.nuget\packages
embedio
scriban
newtonsoft.json
所有项目共享。
为什么这样设计?
以前:
ProjectA
Newtonsoft.Json
ProjectB
Newtonsoft.Json
ProjectC
Newtonsoft.Json
下载三份。
现在:
.nuget
Newtonsoft.Json
所有项目共用。
节省大量磁盘空间。
四、如何修改下载目录(推荐)
很多开发机器 C 盘空间比较紧张,因此建议把全局缓存改到其他盘。
方法一(推荐):NuGet.Config
修改:
%AppData%\NuGet\NuGet.Config
加入:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<config>
<add key="globalPackagesFolder"
value="D:\NuGetPackages" />
</config>
</configuration>
以后:
D:\NuGetPackages
embedio
scriban
newtonsoft.json
所有项目都会使用该目录。
方法二:环境变量
设置系统环境变量:
NUGET_PACKAGES
例如:
NUGET_PACKAGES=D:\NuGetPackages
优先级高于默认目录。
查看当前缓存位置
dotnet nuget locals global-packages --list
例如:
global-packages:
C:\Users\Admin\.nuget\packages
清空缓存
dotnet nuget locals all --clear
五、是否建议迁移?
新项目
强烈建议使用 PackageReference。
优点:
- 官方推荐
- 自动解析依赖
- 自动管理传递依赖
- Restore 更快
- 不会产生大量垃圾依赖
- 与 .NET Core / .NET 5+ 保持一致
老项目(.NET Framework 4.8)
如果项目:
- 使用 Visual Studio 2019/2022
- NuGet 包比较新
- 没有特别老的第三方包
建议迁移。
Visual Studio:
右键项目
↓
Migrate packages.config to PackageReference...
即可自动完成迁移。
哪些项目暂时不要迁移?
例如:
- VS2015 以前
- 非常老的 NuGet 包
- 特殊 build.targets
- 自定义 MSBuild 脚本较多
建议先验证兼容性。
六、迁移后的注意事项
迁移成功以后:
应该删除:
packages.config
并且:
csproj 不应该再出现:
<Import Project="packages\..." />
以及:
<Target Name="EnsureNuGetPackageBuildImports">
这些都是 packages.config 时代遗留内容。
如果仍然存在,应删除或重新迁移。
七、Top-level Packages 与 Transitive Packages
迁移时:
Visual Studio 会区分:
Top-level Packages
Transitive Packages
建议:
✅ 保留:
Top-level Packages
不要勾选:
Transitive Packages
原因:
传递依赖由 NuGet 自动管理即可。
只有当你的代码直接引用某个传递包(例如 System.Memory、Microsoft.Bcl.AsyncInterfaces 等)时,才需要将其作为顶层依赖保留。
八、我的建议(适用于 .NET Framework 4.8)
如果是现在新建或长期维护的项目,我推荐:
- 优先使用 PackageReference,不要再使用
packages.config。 - 将 NuGet 全局缓存目录修改到 D:\NuGetPackages(或其他非 C 盘目录),避免占用系统盘空间。
- 保持
.csproj中只保留真正主动引用的包,让 NuGet 自动管理传递依赖。 - 定期执行
dotnet nuget locals all --clear清理缓存(需要时即可,不必频繁)。