<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>NuGet on 纪伟的个人博客</title><link>https://www.jiwei.space/tags/nuget/</link><description>Recent content in NuGet on 纪伟的个人博客</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Fri, 12 Jun 2026 10:00:00 +0800</lastBuildDate><atom:link href="https://www.jiwei.space/tags/nuget/index.xml" rel="self" type="application/rss+xml"/><item><title>NuGet 包管理的前世今生：从 packages.config 到 PackageReference 再到 Central Package Management</title><link>https://www.jiwei.space/posts/dotnet/nuget-packagemanagement-deep-dive/</link><pubDate>Fri, 12 Jun 2026 10:00:00 +0800</pubDate><guid>https://www.jiwei.space/posts/dotnet/nuget-packagemanagement-deep-dive/</guid><description>&lt;h2 id="写在前面"&gt;&lt;a href="#%e5%86%99%e5%9c%a8%e5%89%8d%e9%9d%a2" class="header-anchor"&gt;&lt;/a&gt;写在前面
&lt;/h2&gt;&lt;p&gt;.NET 生态有大量 NuGet 包，几乎每个项目都靠它们搭起来。但“一个项目到底依赖了哪些包”这件最基础的事，.NET 十几年间经历了从 &lt;code&gt;packages.config&lt;/code&gt; 到 &lt;code&gt;PackageReference&lt;/code&gt;，再到在 PackageReference 之上增加 Central Package Management 的演进。很多人对前两者的认知停留在“一个老一个新”，却没注意到：它们不只是文件位置不同，还代表两种依赖状态管理方式。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;依赖清单该“记录已安装结果”还是“声明版本约束”？&lt;/strong&gt; 把每个传递依赖的已安装版本都写进清单，结果直观，却要维护一份扁平列表；只声明直接依赖、让工具还原传递图，清单更简洁，但最终版本需要通过资产文件或锁文件审计。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;理解了这层张力，就能看清 NuGet 包管理的演进：&lt;code&gt;packages.config&lt;/code&gt; 保存安装后的扁平结果，&lt;code&gt;PackageReference&lt;/code&gt; 声明顶层依赖并在 restore 时维护传递图，Central Package Management（CPM）再把 PackageReference 的版本声明集中到仓库级文件。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一直接依赖-vs-传递依赖分歧的起点"&gt;&lt;a href="#%e4%b8%80%e7%9b%b4%e6%8e%a5%e4%be%9d%e8%b5%96-vs-%e4%bc%a0%e9%80%92%e4%be%9d%e8%b5%96%e5%88%86%e6%ad%a7%e7%9a%84%e8%b5%b7%e7%82%b9" class="header-anchor"&gt;&lt;/a&gt;一、直接依赖 vs 传递依赖：分歧的起点
&lt;/h2&gt;&lt;p&gt;在讲格式之前，先把一个概念钉死：你的项目依赖的包，分两类。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;直接依赖（direct dependency）&lt;/strong&gt;：你在代码里 &lt;code&gt;using&lt;/code&gt; 的、你主动装的那个包。你清楚地知道它的存在。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;传递依赖（transitive dependency）&lt;/strong&gt;：你的直接依赖自己又依赖的包。它们“搭便车”进来，你通常看不见、也不关心——直到它出了问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一棵最小的依赖树长这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;你的项目 MyApp
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── Newtonsoft.Json ← 直接依赖（你写的）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── Polly ← 直接依赖（你写的）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── Polly.Core ← 传递依赖（Polly 拉进来的）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── Serilog
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── Serilog.Extensions ← 传递依赖
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;packages.config&lt;/code&gt; 和 &lt;code&gt;PackageReference&lt;/code&gt; 的根本分歧，就从这棵树开始：&lt;strong&gt;这两类依赖，谁来记录？什么时候求解？&lt;/strong&gt; packages.config 把安装 / 更新时得到的直接与传递依赖版本都平铺记录下来；PackageReference 只保留顶层声明，让 restore 维护完整依赖闭包。&lt;/p&gt;
&lt;h2 id="二前世-packagesconfig保存扁平的安装结果"&gt;&lt;a href="#%e4%ba%8c%e5%89%8d%e4%b8%96-packagesconfig%e4%bf%9d%e5%ad%98%e6%89%81%e5%b9%b3%e7%9a%84%e5%ae%89%e8%a3%85%e7%bb%93%e6%9e%9c" class="header-anchor"&gt;&lt;/a&gt;二、前世 packages.config：保存扁平的安装结果
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;packages.config&lt;/code&gt; 是 NuGet 1.0 时代的原始格式；NuGet 1.0 发布于 &lt;strong&gt;2011 年 1 月 13 日&lt;/strong&gt;。它是一个 per-project（每个项目一个）的 XML 文件，内容是该项目已经安装的依赖清单：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;utf-8&amp;#34;?&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;packages&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;package&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Newtonsoft.Json&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;13.0.1&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;targetFramework=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;net48&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;package&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Castle.Core&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;5.1.1&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;targetFramework=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;net48&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;package&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Moq&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;4.20.70&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;targetFramework=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;net48&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/packages&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;关键特征：当你安装一个包时，NuGet 会把这个包 &lt;strong&gt;连同它的传递依赖&lt;/strong&gt; 一起装进项目，并全部写进 &lt;code&gt;packages.config&lt;/code&gt;。于是这个清单是“扁平”的——直接依赖和传递依赖混在一起，平铺成一长串。&lt;/p&gt;
&lt;p&gt;这些包被物理解压到一个 &lt;strong&gt;解决方案级&lt;/strong&gt;（solution-level）的 &lt;code&gt;packages/&lt;/code&gt; 文件夹里，所有项目共用：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MySolution/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── MyProject.csproj
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── packages.config ← 该项目的依赖清单（含传递）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── packages/ ← 解决方案级，所有项目共用
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── Newtonsoft.Json.13.0.1/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ └── lib/net45/Newtonsoft.Json.dll
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── Castle.Core.5.1.1/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── Moq.4.20.70/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── MySolution.sln
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;而项目是怎么“引用”这些 DLL 的？是直接在 &lt;code&gt;.csproj&lt;/code&gt; 里手写 &lt;code&gt;HintPath&lt;/code&gt;（提示路径），指向 &lt;code&gt;packages/&lt;/code&gt; 里的具体文件：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;Reference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Newtonsoft.Json&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;HintPath&amp;gt;&lt;/span&gt;..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll&lt;span class="nt"&gt;&amp;lt;/HintPath&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/Reference&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="安装与还原求解发生在安装时结果写进扁平清单"&gt;&lt;a href="#%e5%ae%89%e8%a3%85%e4%b8%8e%e8%bf%98%e5%8e%9f%e6%b1%82%e8%a7%a3%e5%8f%91%e7%94%9f%e5%9c%a8%e5%ae%89%e8%a3%85%e6%97%b6%e7%bb%93%e6%9e%9c%e5%86%99%e8%bf%9b%e6%89%81%e5%b9%b3%e6%b8%85%e5%8d%95" class="header-anchor"&gt;&lt;/a&gt;安装与还原：求解发生在安装时，结果写进扁平清单
&lt;/h3&gt;&lt;p&gt;packages.config 的还原（restore）极其直观，甚至有点“笨”：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;packages.config 全局缓存 解决方案 packages/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┌───────────────┐ ┌────────────────────┐ ┌────────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ A 1.0 │ 读清单 │ ~/.nuget/packages │ 逐包 │ A.1.0/ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ D 1.0 (传递) │ ───────────▶ │ /a/1.0/ │ ───────▶ │ D.1.0/ ← A 的传递 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└───────────────┘ (逐条) └────────────────────┘ 拷贝 └────────────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └──────── .csproj 里已有的 &amp;lt;Reference&amp;gt;&amp;lt;HintPath&amp;gt; 指向这里 ──────┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这里必须区分 &lt;strong&gt;install / update&lt;/strong&gt; 和 &lt;strong&gt;restore&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;安装或更新包时，NuGet 会读取包的 &lt;code&gt;.nuspec&lt;/code&gt;，解析依赖和版本约束，把选中的直接依赖与传递依赖全部平铺写入 &lt;code&gt;packages.config&lt;/code&gt;，并同步 &lt;code&gt;.csproj&lt;/code&gt; 引用；&lt;/li&gt;
&lt;li&gt;后续 restore 主要按照这份已经解析好的扁平清单恢复确切版本，不像 PackageReference 那样根据顶层声明重新计算完整传递图；&lt;/li&gt;
&lt;li&gt;当前 NuGet 可能先从 global-packages 文件夹取得包，再复制到由 &lt;code&gt;repositoryPath&lt;/code&gt; 指定的目录；传统默认位置通常是解决方案的 &lt;code&gt;packages/&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;一句话&lt;/strong&gt;：packages.config 不是“从来不求解”，而是&lt;strong&gt;安装 / 更新时求解，restore 时按持久化结果恢复&lt;/strong&gt;。PackageReference 则把完整依赖闭包的计算放进每次需要重新评估的 restore。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;在 2011 年的 .NET Framework 世界里，这个设计很直观：那时没有今天的 SDK 风格项目和跨平台 .NET，把安装结果摊平并写进项目，是符合当时 Visual Studio 工程模型的选择。&lt;/p&gt;
&lt;h3 id="痛点全景精确的代价"&gt;&lt;a href="#%e7%97%9b%e7%82%b9%e5%85%a8%e6%99%af%e7%b2%be%e7%a1%ae%e7%9a%84%e4%bb%a3%e4%bb%b7" class="header-anchor"&gt;&lt;/a&gt;痛点全景：精确的代价
&lt;/h3&gt;&lt;p&gt;但“把完整安装结果平铺进项目清单”这条路，会越走越痛：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;版本冲突（diamond dependency）&lt;/strong&gt;：项目同时依赖对 &lt;code&gt;CommonLib&lt;/code&gt; 有不同约束的包。NuGet 会在安装 / 更新时尝试解析出一个版本并把结果平铺进清单；如果约束无法兼容，仍需升级顶层包、调整版本范围或显式选择版本。扁平结果也掩盖了“这个包是谁带进来的”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;合并冲突&lt;/strong&gt;：&lt;code&gt;packages.config&lt;/code&gt; 和 &lt;code&gt;.csproj&lt;/code&gt; 可能同时变化，多人并行安装 / 更新包时更容易发生冲突。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;packages/&lt;/code&gt; 的两难&lt;/strong&gt;：提交进版本库？仓库瞬间膨胀几百 MB；不提交？换台机器、CI 上还得先 restore。两难。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多项目重复&lt;/strong&gt;：每个项目都拖着自己的 &lt;code&gt;packages.config&lt;/code&gt; 和一整套 &lt;code&gt;HintPath&lt;/code&gt;，同一个包在不同项目里版本漂移是家常便饭。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;传递依赖升级不直观&lt;/strong&gt;：通常应通过 NuGet UI、&lt;code&gt;Update-Package&lt;/code&gt; 等工具更新，而不是手改 XML；但扁平清单很难看出依赖来源，升级时仍要同时关注 &lt;code&gt;packages.config&lt;/code&gt;、项目引用和绑定重定向。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些痛点的共同根源，是把&lt;strong&gt;安装后的完整结果&lt;/strong&gt;持久化成一份需要维护的扁平清单。它记录了确切版本，但真正复现还取决于包源、目标框架、NuGet 配置，以及安装脚本 / 内容转换等副作用；“记录确切版本”不自动等于供应链意义上的完全可复现。&lt;/p&gt;
&lt;h2 id="三今生-packagereference只声明直接依赖"&gt;&lt;a href="#%e4%b8%89%e4%bb%8a%e7%94%9f-packagereference%e5%8f%aa%e5%a3%b0%e6%98%8e%e7%9b%b4%e6%8e%a5%e4%be%9d%e8%b5%96" class="header-anchor"&gt;&lt;/a&gt;三、今生 PackageReference：只声明直接依赖
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;PackageReference&lt;/code&gt; 随 NuGet 4.0 / Visual Studio 2017 / .NET Core SDK 项目登场。它的核心改变只有一句话：&lt;strong&gt;引用直接写进 &lt;code&gt;.csproj&lt;/code&gt;，而且只写直接依赖。&lt;/strong&gt; 传递依赖在还原时被自动求解。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Microsoft.NET.Sdk&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net6.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Newtonsoft.Json&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;13.0.1&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!-- ← 只声明直接依赖 --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Polly&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;8.4.1&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;注意：&lt;code&gt;Polly.Core&lt;/code&gt;（Polly 的传递依赖）不再出现在任何清单里——它在还原时被自动算出来。&lt;/p&gt;
&lt;p&gt;包也不再散落在解决方案的 &lt;code&gt;packages/&lt;/code&gt; 文件夹，而是统一住在 &lt;strong&gt;全局包缓存&lt;/strong&gt;（global packages folder）里，所有解决方案、所有项目共享：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;~/.nuget/packages/ ← 全局共享（Windows: %USERPROFILE%\.nuget\packages）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── newtonsoft.json/13.0.1/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── lib/netstandard2.0/Newtonsoft.Json.dll
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── polly/8.4.1/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── polly.core/8.4.1/ ← Polly 的传递依赖，求解后才出现
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # 项目里只剩 obj/project.assets.json，再无 packages/ 文件夹
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;一个要权衡的好处&lt;/strong&gt;：在同一用户、同一 global-packages 配置下，同一 id + version 的包通常只展开一份，多个项目直接复用；代价是依赖的真实形态不在项目目录里，得通过工具或资产文件查看。不同用户、容器、CI Agent 或自定义 &lt;code&gt;NUGET_PACKAGES&lt;/code&gt; 仍可能各有一份缓存。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="还原机制深挖从逐包拷贝到图求解"&gt;&lt;a href="#%e8%bf%98%e5%8e%9f%e6%9c%ba%e5%88%b6%e6%b7%b1%e6%8c%96%e4%bb%8e%e9%80%90%e5%8c%85%e6%8b%b7%e8%b4%9d%e5%88%b0%e5%9b%be%e6%b1%82%e8%a7%a3" class="header-anchor"&gt;&lt;/a&gt;还原机制深挖：从“逐包拷贝”到“图求解”
&lt;/h3&gt;&lt;p&gt;这是 PackageReference 的真正核心，也是它和 packages.config 最本质的区别。还原不再读一个扁平清单，而是 &lt;strong&gt;求解一张依赖图&lt;/strong&gt;，产物是 &lt;code&gt;project.assets.json&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.csproj（只声明直接依赖） 收集约束 → 求解传递图 project.assets.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┌────────────────────────┐ ┌───────────────────┐ ┌──────────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ &amp;lt;PackageReference │ │ 求解器： │ 产出 │ targets / libraries │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ Include=&amp;#34;Polly&amp;#34; /&amp;gt; │ ────▶ │ 选“满足全部约束 │ ────▶ │ compile / runtime / │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ │ 的最低适用版本” │ │ analyzers / build │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└────────────────────────┘ └───────────────────┘ └──────────────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; obj/*.nuget.g.props / *.targets 由 MSBuild 自动导入
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; （编译引用、分析器、构建任务全部由它接管）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;project.assets.json&lt;/code&gt; 是整个还原的灵魂——它把求解结果按“用途”拆开，告诉 MSBuild 哪些 DLL 该喂给编译器、哪些该拷进输出目录、哪些该当分析器跑。看一个精简后的片段：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;targets&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;.NETCoreApp,Version=v6.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Newtonsoft.Json/13.0.1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;package&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;compile&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;lib/netstandard2.0/Newtonsoft.Json.dll&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// ← 编译期可见
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;runtime&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;lib/netstandard2.0/Newtonsoft.Json.dll&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// ← 运行期可见
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;libraries&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Newtonsoft.Json/13.0.1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;package&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;newtonsoft.json/13.0.1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;files&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;lib/netstandard2.0/Newtonsoft.Json.dll&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;设计点&lt;/strong&gt;：&lt;code&gt;libraries&lt;/code&gt; 是“涉及到的全部包”的元数据目录，&lt;code&gt;targets&lt;/code&gt; 是“在某个目标框架下，这些包按用途如何归类”。这种 &lt;strong&gt;包元数据 / 引用用途&lt;/strong&gt; 的分离，正是 PackageReference 能优雅支持多目标框架（multi-target）的根基——同一份声明，按框架求解出不同的引用集。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="版本解析规则不是一句最低版本就能概括"&gt;&lt;a href="#%e7%89%88%e6%9c%ac%e8%a7%a3%e6%9e%90%e8%a7%84%e5%88%99%e4%b8%8d%e6%98%af%e4%b8%80%e5%8f%a5%e6%9c%80%e4%bd%8e%e7%89%88%e6%9c%ac%e5%b0%b1%e8%83%bd%e6%a6%82%e6%8b%ac" class="header-anchor"&gt;&lt;/a&gt;版本解析规则：不是一句“最低版本”就能概括
&lt;/h3&gt;&lt;p&gt;PackageReference 的传递还原主要有四条规则，不能只记“最低版本”：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;最低适用版本（lowest applicable version）&lt;/strong&gt;：在某个依赖约束下，优先选择可用的最低版本；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;浮动版本（floating versions）&lt;/strong&gt;：使用 &lt;code&gt;*&lt;/code&gt; 明确请求匹配范围内的较新版本；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;直接依赖优先（direct-dependency-wins）&lt;/strong&gt;：当前子图里的直接引用可以覆盖传递依赖选择，发生降级时会产生 NU1605；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;同层 / 旁系依赖（cousin dependencies）&lt;/strong&gt;：来自不同子图的约束合并后，选择满足它们的最低版本。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;回到那个钻石依赖的例子：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;你的项目 MyApp
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── PackageA ──► CommonLib (&amp;gt;= 1.0)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── PackageB ──► CommonLib (&amp;gt;= 2.0)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;在这个没有直接引用覆盖的简单例子里，两个旁系约束合并为 &lt;code&gt;&amp;gt;= 2.0&lt;/code&gt;，因此选择可用的最低版本 &lt;code&gt;2.0&lt;/code&gt;。但如果应用自己直接引用 &lt;code&gt;CommonLib 1.0&lt;/code&gt;，直接依赖优先规则可能选择 1.0 并报告 NU1605；如果上下界根本没有交集，则还原失败。&lt;strong&gt;最终规则作用于依赖子图，不是机械地把全图所有版本号取最大或最小。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;几个常见的冲突信号（看还原日志时的关键词）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;NU1605（版本降级，downgrade）&lt;/strong&gt;：你直接锁了 &lt;code&gt;CommonLib 1.0&lt;/code&gt;，但某个传递依赖要 &lt;code&gt;&amp;gt;= 2.0&lt;/code&gt;。NuGet 会警告你“检测到降级”，并要求你把直接引用抬到 &lt;code&gt;2.0&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NU1603（找不到依赖的预期下界）&lt;/strong&gt;：例如包声明 &lt;code&gt;Y (&amp;gt;= 4.0.0)&lt;/code&gt;，源里没有 4.0.0，NuGet 改用 5.0.0 等更高的近似匹配并发出警告。若包 id 根本不存在，通常应查看 NU1101；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NU1107（版本约束冲突）&lt;/strong&gt;：两个依赖要求互不兼容的版本，无法正常求解，需要由顶层项目显式选择可接受版本或升级相关包；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NU1201 / NU1202 等兼容性错误&lt;/strong&gt;：项目或包与当前目标框架不兼容，应检查 TFM 和包提供的资产，而不是把它和版本冲突混为一谈。&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;一句话&lt;/strong&gt;：packages.config 的 &lt;code&gt;version=&amp;quot;13.0.1&amp;quot;&lt;/code&gt; 记录当前已安装版本；PackageReference 的 &lt;code&gt;Version=&amp;quot;13.0.1&amp;quot;&lt;/code&gt; 是 restore 输入，表示最低版本 13.0.1，并受直接依赖优先等规则影响。前者保存结果，后者参与求解。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="得与失"&gt;&lt;a href="#%e5%be%97%e4%b8%8e%e5%a4%b1" class="header-anchor"&gt;&lt;/a&gt;得与失
&lt;/h3&gt;&lt;p&gt;PackageReference 赢得了简洁、多目标的优雅、磁盘复用，但也付出代价：&lt;strong&gt;你不再一眼看见项目最终用了哪些传递依赖、什么版本。&lt;/strong&gt; 答案藏进 &lt;code&gt;project.assets.json&lt;/code&gt;，得靠工具还原出来：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# .NET 10+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet package list --include-transitive
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# .NET 9 及更早 SDK 使用旧的 verb-first 写法&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet list package --include-transitive
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;固化求解结果的钥匙&lt;/strong&gt;：设置 &lt;code&gt;RestorePackagesWithLockFile=true&lt;/code&gt; 会生成 &lt;code&gt;packages.lock.json&lt;/code&gt;。对应用程序，应把锁文件提交到版本库，并在 CI 使用 &lt;code&gt;dotnet restore --locked-mode&lt;/code&gt;（或 &lt;code&gt;RestoreLockedMode=true&lt;/code&gt;）；这样输入与锁文件不一致时直接失败，而不是悄悄更新。普通 restore 在依赖输入变化时可以重新求解并改写锁文件。类库自己的锁文件也无法强制最终消费项目沿用同一套传递版本，因此要按项目角色决定是否提交。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="四两张图看懂差别"&gt;&lt;a href="#%e5%9b%9b%e4%b8%a4%e5%bc%a0%e5%9b%be%e7%9c%8b%e6%87%82%e5%b7%ae%e5%88%ab" class="header-anchor"&gt;&lt;/a&gt;四、两张图看懂差别
&lt;/h2&gt;&lt;p&gt;把前面散落的事实收拢成一张表：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;维度&lt;/th&gt;
 &lt;th&gt;packages.config&lt;/th&gt;
 &lt;th&gt;PackageReference&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;清单位置&lt;/td&gt;
 &lt;td&gt;独立 &lt;code&gt;packages.config&lt;/code&gt; XML 文件&lt;/td&gt;
 &lt;td&gt;嵌入 &lt;code&gt;.csproj&lt;/code&gt; 的 &lt;code&gt;&amp;lt;PackageReference&amp;gt;&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;是否列出传递依赖&lt;/td&gt;
 &lt;td&gt;是（全列，扁平）&lt;/td&gt;
 &lt;td&gt;否（只直接依赖）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;包的物理存储&lt;/td&gt;
 &lt;td&gt;解决方案级 &lt;code&gt;packages/&lt;/code&gt; 文件夹&lt;/td&gt;
 &lt;td&gt;全局缓存 &lt;code&gt;~/.nuget/packages&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;还原产物&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;packages/&lt;/code&gt; 文件夹（逐包拷贝）&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;obj/project.assets.json&lt;/code&gt;（求解图）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;版本语义&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;version&lt;/code&gt; 记录已安装版本；&lt;code&gt;allowedVersions&lt;/code&gt; 可限制更新&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;Version&lt;/code&gt; 是还原输入，支持最低版本、范围和浮动版本&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;多目标框架&lt;/td&gt;
 &lt;td&gt;不是一等公民，传统项目通常单目标&lt;/td&gt;
 &lt;td&gt;一等公民（可按 TFM 条件引用并分别求解）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;是否进版本库&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;packages/&lt;/code&gt; 两难&lt;/td&gt;
 &lt;td&gt;无需提交（全局缓存）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;老 .NET Framework&lt;/td&gt;
 &lt;td&gt;默认&lt;/td&gt;
 &lt;td&gt;需从 packages.config 迁移&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;C++ 项目&lt;/td&gt;
 &lt;td&gt;支持&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;不支持&lt;/strong&gt;（仍只能 packages.config）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;再看还原流程的并排对比，差别一目了然：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;【packages.config】 【PackageReference】
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 读已安装结果的扁平清单 读顶层 PackageReference
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 恢复清单中的确切包版本 按四类规则求解传递图
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; .csproj 里写死的 HintPath 指过去 生成 project.assets.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;（安装 / 更新阶段已完成依赖解析） （生成完整资产图，MSBuild 消费）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="五迁移实战从-packagesconfig-到-packagereference"&gt;&lt;a href="#%e4%ba%94%e8%bf%81%e7%a7%bb%e5%ae%9e%e6%88%98%e4%bb%8e-packagesconfig-%e5%88%b0-packagereference" class="header-anchor"&gt;&lt;/a&gt;五、迁移实战：从 packages.config 到 PackageReference
&lt;/h2&gt;&lt;p&gt;如果你的项目还停在 packages.config（典型的老 .NET Framework 工程），迁移到 PackageReference 有官方路径，但坑不少。&lt;/p&gt;
&lt;h3 id="vs-内置迁移工具"&gt;&lt;a href="#vs-%e5%86%85%e7%bd%ae%e8%bf%81%e7%a7%bb%e5%b7%a5%e5%85%b7" class="header-anchor"&gt;&lt;/a&gt;VS 内置迁移工具
&lt;/h3&gt;&lt;p&gt;在 Visual Studio 里：右键 &lt;code&gt;packages.config&lt;/code&gt; → &lt;strong&gt;“将 packages.config 迁移到 PackageReference”&lt;/strong&gt;（Migrate packages.config to PackageReference）。工具会：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;读 &lt;code&gt;packages.config&lt;/code&gt;，结合包元数据，尝试&lt;strong&gt;区分出哪些是直接依赖、哪些是传递依赖&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;把直接依赖写成 &lt;code&gt;.csproj&lt;/code&gt; 里的 &lt;code&gt;&amp;lt;PackageReference&amp;gt;&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;删掉旧的 &lt;code&gt;packages.config&lt;/code&gt; 和 &lt;code&gt;.csproj&lt;/code&gt; 里手写的 &lt;code&gt;&amp;lt;HintPath&amp;gt;&lt;/code&gt; 引用；&lt;/li&gt;
&lt;li&gt;把传递依赖交给求解器接管。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="真实坑迁移不是一键无脑的"&gt;&lt;a href="#%e7%9c%9f%e5%ae%9e%e5%9d%91%e8%bf%81%e7%a7%bb%e4%b8%8d%e6%98%af%e4%b8%80%e9%94%ae%e6%97%a0%e8%84%91%e7%9a%84" class="header-anchor"&gt;&lt;/a&gt;真实坑：迁移不是一键无脑的
&lt;/h3&gt;&lt;p&gt;迁移工具能处理大多数情况，但这几类包行为会变，需要你手动盯：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;content / contentFiles 行为不同&lt;/strong&gt;：packages.config 时代，包可以把文件塞进项目的 &lt;code&gt;content/&lt;/code&gt;（直接拷进你的源码目录）。PackageReference 不再这样做，改用 nuspec 里的 &lt;code&gt;contentFiles&lt;/code&gt; 声明（文件是只读的、按规则注入）。老包如果依赖往 &lt;code&gt;content/&lt;/code&gt; 拷文件，迁移后会“消失”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;developmentDependency&lt;/code&gt; 的去向&lt;/strong&gt;：packages.config 用 &lt;code&gt;developmentDependency=&amp;quot;true&amp;quot;&lt;/code&gt; 影响打包时的依赖传播。PackageReference 中常用 &lt;code&gt;PrivateAssets=&amp;quot;all&amp;quot;&lt;/code&gt; 表达“只供当前项目使用、不向下游传播”，目标相近但不是所有资产语义的一一等价。迁移器还会把含 &lt;code&gt;build&lt;/code&gt;、&lt;code&gt;buildCrossTargeting&lt;/code&gt;、&lt;code&gt;contentFiles&lt;/code&gt;、&lt;code&gt;analyzers&lt;/code&gt; 或 &lt;code&gt;developmentDependency=true&lt;/code&gt; 的包保留为顶层引用，因为这些资产不一定能靠传递依赖正确流动：&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- ❌ packages.config 时代的标记（迁移后失效） --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;package&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Microsoft.CodeAnalysis.Analyzers&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;3.3.4&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;developmentDependency=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;true&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;targetFramework=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;net48&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- ✅ 常见 PackageReference 写法：用 PrivateAssets 把它对下游隐藏 --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Microsoft.CodeAnalysis.Analyzers&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;3.3.4&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;PrivateAssets&amp;gt;&lt;/span&gt;all&lt;span class="nt"&gt;&amp;lt;/PrivateAssets&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;IncludeAssets&amp;gt;&lt;/span&gt;runtime; build; native; contentfiles; analyzers; buildtransitive&lt;span class="nt"&gt;&amp;lt;/IncludeAssets&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/PackageReference&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;build &lt;code&gt;*.props&lt;/code&gt; / &lt;code&gt;*.targets&lt;/code&gt; 的注入&lt;/strong&gt;：包里自带的 MSBuild props/targets 在 PackageReference 下是自动导入的，逻辑大致延续，但导入顺序、作用域偶有差异，复杂包需回归测试。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;老 ASP.NET（非 Core）Web 项目&lt;/strong&gt;：完整 .NET Framework ASP.NET 对 PackageReference 只有有限支持，官方右键迁移工具目前仍不支持 ASP.NET 项目。&lt;code&gt;web.config&lt;/code&gt; 的 XDT 转换、&lt;code&gt;content/&lt;/code&gt; 注入和 &lt;code&gt;install.ps1&lt;/code&gt; 等机制在 PackageReference 下也不会照旧执行，不能直接套用普通类库的迁移步骤。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;残留的旧引用（最常见的误操作）&lt;/strong&gt;：迁移后忘了清理 &lt;code&gt;.csproj&lt;/code&gt; 里残留的手写 &lt;code&gt;&amp;lt;HintPath&amp;gt;&lt;/code&gt;，等于新旧两套引用打架：&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- ❌ 迁移后还残留旧 packages.config 的影子：HintPath 指向已不存在的 packages/ --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;Reference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Newtonsoft.Json&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;HintPath&amp;gt;&lt;/span&gt;..\packages\Newtonsoft.Json.13.0.1\lib\net48\Newtonsoft.Json.dll&lt;span class="nt"&gt;&amp;lt;/HintPath&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/Reference&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- ✅ 迁移后应由 NuGet 接管引用，.csproj 里只留 PackageReference，删掉所有手写 HintPath --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Newtonsoft.Json&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;13.0.1&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="顺带澄清一个历史误会"&gt;&lt;a href="#%e9%a1%ba%e5%b8%a6%e6%be%84%e6%b8%85%e4%b8%80%e4%b8%aa%e5%8e%86%e5%8f%b2%e8%af%af%e4%bc%9a" class="header-anchor"&gt;&lt;/a&gt;顺带澄清一个历史误会
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;&lt;code&gt;dotnet migrate&lt;/code&gt; 不是 packages.config → PackageReference 的工具。&lt;/strong&gt; 它是 .NET Core 早期用来把 &lt;code&gt;project.json&lt;/code&gt;（DNX/RC 时代的产物）转成 SDK 风格 &lt;code&gt;.csproj&lt;/code&gt; 的命令，早已废弃。packages.config → PackageReference 的迁移走的是上面那个 VS 内置工具，命令行没有对应物。别拿 &lt;code&gt;dotnet migrate&lt;/code&gt; 去迁 packages.config——它根本不认这个文件。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="六第三幕-central-package-managementpackagereference-之上的集中管理层"&gt;&lt;a href="#%e5%85%ad%e7%ac%ac%e4%b8%89%e5%b9%95-central-package-managementpackagereference-%e4%b9%8b%e4%b8%8a%e7%9a%84%e9%9b%86%e4%b8%ad%e7%ae%a1%e7%90%86%e5%b1%82" class="header-anchor"&gt;&lt;/a&gt;六、第三幕 Central Package Management：PackageReference 之上的集中管理层
&lt;/h2&gt;&lt;p&gt;PackageReference 解决了单个项目内的版本地狱，却顺手制造了新问题：&lt;strong&gt;大解决方案里，同一个包在不同项目里版本漂移。&lt;/strong&gt; A 项目里 &lt;code&gt;Newtonsoft.Json 13.0.1&lt;/code&gt;，B 项目里 &lt;code&gt;12.0.3&lt;/code&gt;，C 项目里 &lt;code&gt;13.0.1&lt;/code&gt;——你以为是同一个库，其实是三个版本，行为不一致、升级要逐个改。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Central Package Management（CPM，集中式包管理）&lt;/strong&gt; 就是来治这个的。它从 NuGet 6.2 开始正式提供，让仓库在一个地方声明包版本。CPM &lt;strong&gt;不是与 packages.config、PackageReference 并列的第三种引用格式&lt;/strong&gt;：项目仍使用 &lt;code&gt;&amp;lt;PackageReference&amp;gt;&lt;/code&gt;，只是把版本元数据提升到 &lt;code&gt;Directory.Packages.props&lt;/code&gt; 集中维护。&lt;/p&gt;
&lt;p&gt;做法是在仓库/解决方案根放一个 &lt;code&gt;Directory.Packages.props&lt;/code&gt;，打开开关：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- Directory.Packages.props --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;Project&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;ManagePackageVersionsCentrally&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/ManagePackageVersionsCentrally&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!-- ← 总开关 --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;PackageVersion&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Newtonsoft.Json&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;13.0.1&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!-- ← 版本集中声明 --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;PackageVersion&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Polly&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;8.4.1&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;而在各项目的 &lt;code&gt;.csproj&lt;/code&gt; 里，&lt;code&gt;&amp;lt;PackageReference&amp;gt;&lt;/code&gt; &lt;strong&gt;不再写 Version&lt;/strong&gt;，版本由上面集中接管：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- 某项目 .csproj：只声明“我要用谁”，不写版本 --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Newtonsoft.Json&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;&amp;lt;!-- ← 无 Version，继承 PackageVersion --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Polly&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;边界提示&lt;/strong&gt;：CPM 开启后，项目里的 &lt;code&gt;&amp;lt;PackageReference Version=&amp;quot;...&amp;quot;&amp;gt;&lt;/code&gt; 会触发 NU1008，常规升级应修改 &lt;code&gt;Directory.Packages.props&lt;/code&gt;。但 NuGet 支持显式的项目级例外：&lt;code&gt;&amp;lt;PackageReference Include=&amp;quot;PackageA&amp;quot; VersionOverride=&amp;quot;3.0.0&amp;quot; /&amp;gt;&lt;/code&gt; 会覆盖中央版本。该能力默认允许，也可设置 &lt;code&gt;CentralPackageVersionOverrideEnabled=false&lt;/code&gt; 在仓库中禁用，真正做到不允许局部覆盖。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="层级与传递依赖边界"&gt;&lt;a href="#%e5%b1%82%e7%ba%a7%e4%b8%8e%e4%bc%a0%e9%80%92%e4%be%9d%e8%b5%96%e8%be%b9%e7%95%8c" class="header-anchor"&gt;&lt;/a&gt;层级与传递依赖边界
&lt;/h3&gt;&lt;p&gt;CPM 还有两个经常被忽略的边界：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个项目默认只自动导入从项目目录向上找到的&lt;strong&gt;最近一个&lt;/strong&gt; &lt;code&gt;Directory.Packages.props&lt;/code&gt;。大型仓库若使用多层文件，需要在子级文件中显式导入父级，不能假设它们会自动合并；&lt;/li&gt;
&lt;li&gt;默认集中的是项目显式引用的包版本。若要在没有顶层 &lt;code&gt;PackageReference&lt;/code&gt; 的情况下钉住传递包，可启用 &lt;code&gt;CentralPackageTransitivePinningEnabled=true&lt;/code&gt;。但打包类库时，NuGet 可能把被钉住的传递包提升为 nuspec 中的显式依赖，必须评估对消费者的影响。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CPM 补回的是“控制”，但这次是 &lt;strong&gt;集中式&lt;/strong&gt; 的控制：一处声明、默认统一、批量升级（.NET 10+ 可用 &lt;code&gt;dotnet package list --outdated&lt;/code&gt;；旧 SDK 使用 &lt;code&gt;dotnet list package --outdated&lt;/code&gt;）。如果允许 &lt;code&gt;VersionOverride&lt;/code&gt;，个别项目仍可有例外。把三代放在一起，演进轨迹清晰可见：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;packages.config → PackageReference + Central Package Management
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 精确、但分散 自动求解、但分散 自动求解、且集中
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (完整安装结果平铺) (声明顶层依赖) (版本集中、允许受控例外)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; [控制] [自动化] [控制 + 自动化]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ◀──────── 钟摆 ─────────▶◀───────── 再次回摆 ─────────▶
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;注意它不是回到 packages.config，更不是替换 PackageReference：传递依赖仍由求解器自动计算，被集中的主要是顶层包的版本声明；启用传递钉选时才会进一步影响传递包。&lt;/p&gt;
&lt;h2 id="七版本号版本范围与浮动版本"&gt;&lt;a href="#%e4%b8%83%e7%89%88%e6%9c%ac%e5%8f%b7%e7%89%88%e6%9c%ac%e8%8c%83%e5%9b%b4%e4%b8%8e%e6%b5%ae%e5%8a%a8%e7%89%88%e6%9c%ac" class="header-anchor"&gt;&lt;/a&gt;七、版本号、版本范围与浮动版本
&lt;/h2&gt;&lt;p&gt;要把“约束”说清楚，绕不开版本号本身。NuGet 现在遵循 &lt;strong&gt;语义化版本（Semantic Versioning，SemVer）2.0&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MAJOR.MINOR.PATCH[-prerelease][+build]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 1 . 0 . 3 -beta.1 +meta.5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │ │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │ │ │ └─ 构建元数据（不影响版本顺序）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │ │ └─ 预发布标识（排序低于正式版）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │ └─ 补丁：向后兼容的缺陷修复
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ └─ 次版本：向后兼容的新功能
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─ 主版本：不兼容的破坏性变更（Major 升级要警惕）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;在 PackageReference 里，&lt;code&gt;Version&lt;/code&gt; 不必是死值，它支持 &lt;strong&gt;版本范围（version range）&lt;/strong&gt; 语法——这正是“约束”得以成立的语法基础：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;写法&lt;/th&gt;
 &lt;th&gt;含义&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;1.0&lt;/code&gt; 或 &lt;code&gt;[1.0,)&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;&amp;gt;= 1.0&lt;/code&gt;，最低版本（最常用，NuGet 默认）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;(1.0,)&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;&amp;gt; 1.0&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;[1.0]&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;恰好 &lt;code&gt;1.0&lt;/code&gt;（精确版本约束）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;[1.0, 2.0]&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;&amp;gt;= 1.0&lt;/code&gt; 且 &lt;code&gt;&amp;lt;= 2.0&lt;/code&gt;（闭区间）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;[1.0, 2.0)&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;&amp;gt;= 1.0&lt;/code&gt; 且 &lt;code&gt;&amp;lt; 2.0&lt;/code&gt;（最常用：锁同一个主版本）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;1.0.*&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;浮动：&lt;code&gt;1.0&lt;/code&gt; 主次固定，补丁取最新（&lt;code&gt;*&lt;/code&gt; 只能出现在最右段）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;packages.config 也有范围，但用途不同。&lt;/strong&gt; 其中的 &lt;code&gt;version=&amp;quot;13.0.1&amp;quot;&lt;/code&gt; 记录当前安装结果；可选的 &lt;code&gt;allowedVersions=&amp;quot;[13.0,14.0)&amp;quot;&lt;/code&gt; 用来限制后续更新范围。PackageReference 则直接把 &lt;code&gt;Version&lt;/code&gt; 作为 restore 输入，可以表达最低版本、区间或浮动版本，并在求解依赖图时使用。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="八避坑清单"&gt;&lt;a href="#%e5%85%ab%e9%81%bf%e5%9d%91%e6%b8%85%e5%8d%95" class="header-anchor"&gt;&lt;/a&gt;八、避坑清单
&lt;/h2&gt;&lt;p&gt;把工程中真实踩过的坑列一下，给迁移和日常提个醒：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;packages/&lt;/code&gt; 该不该提交&lt;/strong&gt;：packages.config 时代的老问题。提交则仓库膨胀，不提交则换机器要 restore。PackageReference 模式下 &lt;code&gt;packages/&lt;/code&gt; 根本不存在了，这个纠结随之消失——&lt;strong&gt;别再纠结，能迁就迁。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;先按错误码分类，不要混成“引用丢失”&lt;/strong&gt;：NU1107 是版本约束冲突；NU1106 表示依赖约束无法求解（如循环、空图或版本无交集）；NU1201 / NU1202 等通常与目标框架兼容性有关。这些一般会让 restore 失败，而不是“还原成功但引用丢失”。修复后再用 .NET 10+ 的 &lt;code&gt;dotnet package list --include-transitive&lt;/code&gt;（旧 SDK 用 &lt;code&gt;dotnet list package&lt;/code&gt;）检查最终图。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;传递依赖冲突定位（NU1605）&lt;/strong&gt;：看到“Detected package downgrade”，说明你直接锁的版本低于某个传递依赖所需。解法不是改传递依赖，而是把你自己的直接引用版本抬上去。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;packages.config 与 PackageReference 混用&lt;/strong&gt;：一个解决方案里部分项目 packages.config、部分 PackageReference 是允许的（边界场景），但两者项目间互相以包引用时会扯出奇怪问题。新项目一律用 PackageReference，别再开 packages.config 的口子。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPM 下漏删项目里的 Version&lt;/strong&gt;：开启集中管理后，某个项目残留 &lt;code&gt;Version=&amp;quot;...&amp;quot;&lt;/code&gt; 会构建报错。全仓库搜一次 &lt;code&gt;Version=&lt;/code&gt; 在 &lt;code&gt;&amp;lt;PackageReference&amp;gt;&lt;/code&gt; 行上的残留即可。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;全局缓存损坏&lt;/strong&gt;：先列出各目录并按需清理。&lt;code&gt;all --clear&lt;/code&gt; 会同时清除 global-packages、HTTP cache、临时目录和插件缓存，之后大量包需要重新下载；Visual Studio 或构建进程正在占用文件时还可能失败：&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet nuget locals all --list
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet nuget locals global-packages --clear
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet nuget locals http-cache --clear
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 确认确实需要全部重建时再用&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet nuget locals all --clear &lt;span class="c1"&gt;# 或 nuget locals all -clear&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="九决策清单我的项目该用哪种"&gt;&lt;a href="#%e4%b9%9d%e5%86%b3%e7%ad%96%e6%b8%85%e5%8d%95%e6%88%91%e7%9a%84%e9%a1%b9%e7%9b%ae%e8%af%a5%e7%94%a8%e5%93%aa%e7%a7%8d" class="header-anchor"&gt;&lt;/a&gt;九、决策清单：我的项目该用哪种
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;场景&lt;/th&gt;
 &lt;th&gt;推荐格式&lt;/th&gt;
 &lt;th&gt;说明&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;新项目（.NET Core / .NET 5+）&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;PackageReference&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;SDK 风格 .NET 项目的默认模型&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;老 .NET Framework（非 SDK 工程）&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;迁到 PackageReference&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;用 VS 迁移工具；留意 contentFiles / Web 项目限制&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;C++ / C++/CLI 项目&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;packages.config&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;PackageReference 不支持 C++，别强迁&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;多项目大仓 / monorepo&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;PackageReference + CPM&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;Directory.Packages.props&lt;/code&gt; 治版本漂移&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;应用需要可重复还原（CI 一致性）&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;PackageReference + 提交 lock 文件 + locked mode&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;生成并提交 &lt;code&gt;packages.lock.json&lt;/code&gt;，CI 用 &lt;code&gt;dotnet restore --locked-mode&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="十设计思想nuget-包管理教会了我们什么"&gt;&lt;a href="#%e5%8d%81%e8%ae%be%e8%ae%a1%e6%80%9d%e6%83%b3nuget-%e5%8c%85%e7%ae%a1%e7%90%86%e6%95%99%e4%bc%9a%e4%ba%86%e6%88%91%e4%bb%ac%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;十、设计思想：NuGet 包管理教会了我们什么
&lt;/h2&gt;&lt;p&gt;两种引用模型与 CPM 管理层的演进，留下几条可以迁移到别处的思考：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;“可见的安装结果”和“自动维护的依赖图”是依赖管理的长期张力&lt;/strong&gt;——packages.config 持久化扁平结果，代价是维护成本和来源不透明；PackageReference 声明顶层依赖并自动求解，代价是最终图需要借助工具查看；CPM 再把版本声明集中起来。没有银弹，只有权衡。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;把顶层声明作为求解输入，是工具从“恢复已安装列表”走向“维护依赖图”的关键跃迁&lt;/strong&gt;。packages.config 的 &lt;code&gt;version&lt;/code&gt; 记录已安装结果，PackageReference 的 &lt;code&gt;Version&lt;/code&gt; 则参与 restore 求解；但 NuGet 仍受最低适用版本、直接依赖优先等多条规则约束，不能简化成单一的 &lt;code&gt;&amp;gt;=&lt;/code&gt; 运算。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;传递依赖必须自动求解，但求解结果必须可审计&lt;/strong&gt;。把传递图交给求解器是对的，但不能黑箱——&lt;code&gt;project.assets.json&lt;/code&gt; 和 lock 文件就是“信任但要核实”的凭证。自动化的地方，都要留一扇可审计的窗。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;集中化能降低多项目的版本漂移&lt;/strong&gt;。CPM 把版本声明收拢到一处，同时保留 PackageReference 的传递还原；&lt;code&gt;VersionOverride&lt;/code&gt;、多层 &lt;code&gt;Directory.Packages.props&lt;/code&gt; 和传递钉选则提供了受控的例外机制。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="结语"&gt;&lt;a href="#%e7%bb%93%e8%af%ad" class="header-anchor"&gt;&lt;/a&gt;结语
&lt;/h2&gt;&lt;p&gt;packages.config 把安装后的直接依赖和传递依赖平铺记录，restore 按结果恢复；PackageReference 只要求项目声明顶层依赖，把完整依赖图交给还原器维护；Central Package Management 则作为 PackageReference 之上的一层，把版本声明集中起来，降低多项目版本漂移。&lt;/p&gt;
&lt;p&gt;所以下一次当你敲下 &lt;code&gt;dotnet restore&lt;/code&gt;，看着那行 &lt;code&gt;Restore completed&lt;/code&gt; 时，你会知道它背后发生了什么：一张依赖图被求解、一个 &lt;code&gt;project.assets.json&lt;/code&gt; 被写下、一组引用被接入构建。需要进一步控制时，可以用版本范围表达约束、用 CPM 集中版本，再用提交到仓库的锁文件和 locked mode 约束应用的 CI 还原。&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;参考资料：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/nuget/what-is-nuget" target="_blank" rel="noopener"
 &gt;NuGet 是什么：packages.config 与 PackageReference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/nuget/consume-packages/package-references-in-project-files" target="_blank" rel="noopener"
 &gt;PackageReference 项目文件参考与锁文件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/nuget/concepts/dependency-resolution" target="_blank" rel="noopener"
 &gt;NuGet 包依赖解析规则&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/nuget/consume-packages/migrate-packages-config-to-package-reference" target="_blank" rel="noopener"
 &gt;从 packages.config 迁移到 PackageReference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/nuget/consume-packages/central-package-management" target="_blank" rel="noopener"
 &gt;Central Package Management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/nuget/concepts/package-versioning" target="_blank" rel="noopener"
 &gt;NuGet 版本与版本范围&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/nuget/consume-packages/managing-the-global-packages-and-cache-folders" target="_blank" rel="noopener"
 &gt;NuGet 全局包、HTTP 与临时缓存目录&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/dotnet/core/tools/dotnet-package-list" target="_blank" rel="noopener"
 &gt;.NET 10：dotnet package list&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;/blockquote&gt;</description></item></channel></rss>