选择项目引用的程序集

程序集在生成期间以两种不同的方式使用。 第一种是用于编译,这使得包使用者的代码可针对程序集中的 API 进行编译,并且用于 Intellisense 来提供建议。 第二种是运行时,其中程序集复制到 bin 目录,并在程序执行期间使用。 某些包作者仅希望在编译时向包使用者提供自己的程序集(或其部分程序集),但需要提供运行时的所有依赖项。 本文档介绍实现这一结果的方法。

建议每个程序集具有一个包,并具有与其他程序集的包依赖项。 当 NuGet 还原项目时,它会选择资产,并支持包括、排除和创建不同的专用资产类。 为了防止包的依赖项成为使用该包的任何人的编译时资产,可将 compile 资产设为专用资产。 在生成的包中,将导致 compile 从依赖项中排除。 请注意,在设置为“无”时,默认专用资产为 contentfiles;build;analyzers。 因此,应在 PackageReferenceProjectReference 中使用 PrivateAssets="compile;contentfiles;build;analyzers"

<ItemGroup>
  <ProjectReference Include="..\OtherProject\OtherProject.csproj" PrivateAssets="compile;contentfiles;build;analyzers" />
  <PackageReference Include="SomePackage" Version="1.2.3" PrivateAssets="compile;contentfiles;build;analyzers" />
</ItemGroup>

如果要通过自定义 nuspec 文件创建包,而不是让 NuGet 自动生成一个包,则 nuspec 应使用 exclude XML 属性 。

<dependencies>
  <group targetFramework=".NETFramework4.8">
    <dependency id="OtherProject" version="3.2.1" exclude="Compile,Build,Analyzers" />
    <dependency id="SomePackage" version="1.2.3" exclude="Compile,Build,Analyzers" />
  </group>
</dependencies>

建议使用此解决方案有三个原因。

首先,有用的程序集通常被新的程序集/包引用。 虽然实用工具程序集目前可能只供单个包使用,使得它试图在单个包中提供两个程序集,但如果第二个包将来想要使用“专用”实用工具程序集,实用工具程序集需要移动到新包中,并且需要更新旧包来将其声明为依赖项,或者实用工具包需要同时在现有的包和新包中提供。 如果程序集在两个不同的包中提供,并且一个项目引用这两个包,那么要是两个包中实用工具程序集的版本不同,NuGet 将无法帮助管理版本。

其次,有时使用包的开发人员可能还想要使用依赖项中的 API。 例如,请考虑使用包 Microsoft.ServiceHub.Client 版本 3.0.3078。 如果下载包并检查 nuspec 文件,可以看到它列出了两个以 Microsoft.VisualStudio. 开头的包作为依赖项,这意味着它在运行时需要这两个包,但它也会排除其编译资产。 这意味着,使用 Microsoft.ServiceHub.Client 的项目在 IntelliSense 中或在生成项目的情况下将没有 Visual Studio API 可用,除非项目显式安装这些包。 这是具有排除资产的包依赖项的一个优势。 对于使用包的项目,如果还想要使用依赖项,则可添加对包的引用,使 API 可供其自身使用。

最后,当包还具有多个程序集时,一些包作者过去在对支持多个目标框架的包选择 NuGet 的程序集时会感到困惑。 如果主程序集支持实用工具程序集的不同目标框架,那么可能不清楚将所有程序集放入哪些 lib/ 目录。 通过按程序集名称分隔每个包,可更直观地看到每个程序集应进入哪些 lib/ 文件夹。 请注意,这并不意味着具有 Package1.net48Package1.net6.0 包。 这意味着在 Package1 中具有 lib/net48/Package1.dlllib/net6.0/Package6.0,在 Package2 中具有 lib/netstandard2.0/Package2.dlllib/net5.0/Package2.dll。 当 Nuget 还原项目时,它将分别为两个包选择资产。

另请注意,依赖项包含/排除资产仅供使用 PackageReference 的项目使用。 使用 packages.config 安装包的任何项目都将安装依赖项,并且其 API 也可用。 只有 Visual Studio 较旧版本的 .NET Framework 项目模板支持 packages.config。 SDK 样式项目(即使是面向 .NET Framework 的项目)不支持 packages.config,因此支持依赖项包括/排除资产。

PackageReferencepackages.config 提供不同的功能。 无论你是希望支持使用 PackageReference 和/或 packages.config 的包使用者,都更改必须创作包的方式。

NuGet 的 MSBuild 包目标不支持在包中自动包含项目引用。 它仅将这些引用的项目作为包依赖项列出。 GitHub 上存在一个问题。在这里,社区成员分享了他们实现这一结果的方式,这通常涉及到使用 PackagePath MSBuild 项元数据将文件放在包中的任何位置(如有关将内容包括在包中的文档中所述),并使用 SuppressDependenciesWhenPacking 避免项目引用成为包依赖项。 还有社区开发的工具,它们可用于替代支持此功能的 NuGet 官方包。

PackageReference 支持

当包使用者使用 PackageReference 时,NuGet 会独立选择编译和运行时资产,如前文所述。

编译资产优先使用 ref/<tfm>/*.dll(例如 ref/net6.0/*.dll),但如果此项不存在,它将回退到 lib/<tfm>/*.dll(例如 lib/net6.0/*.dll)。

运行时资产优先使用 runtimes/<rid>/lib/<tfm>/*.dll(例如 runtimes/win11-x64/lib/net6.0/*.dll),但如果此项不存在,它将回退到 lib/<tfm>/*.dll

由于 ref\<tfm>\ 中的程序集未在运行时使用,因此可以使用“仅元数据”专用程序集来减小包的大小。

packages.config 支持

使用 packages.config 管理 NuGet 包的项目通常会向 lib\<tfm>\ 目录中的所有程序集添加引用。 添加 ref\ 目录以支持 PackageReference,因此在使用 packages.config 时不予考虑。 要使用 packages.config 以显式方式设置项目引用的程序集,包必须使用 nuspec 文件中的 <references> 元素。 例如:

<references>
    <group targetFramework="net45">
        <reference file="MyLibrary.dll" />
    </group>
</references>

MSBuild 包目标不支持 <references> 元素。 使用 MSBuild 包时,请参阅有关使用 .nuspec 文件进行打包的文档

注意

packages.config 项目使用名为 ResolveAssemblyReference 的进程将程序集复制到 bin\<configuration>\ 输出目录。 复制项目的程序集,然后构建系统查看引用程序集的程序集清单,然后复制这些程序集并递归重复所有程序集。 这意味着,如果任何程序集仅通过反射(Assembly.Load、MEF 或其他依赖关系注入框架)加载,那么即使在 bin\<tfm>\ 中,它也可能不会复制到项目的 bin\<configuration>\ 输出目录中。 这也意味着这仅适用于 .NET 程序集,不适用于通过 P/Invoke 调用的本机代码。

支持 PackageReferencepackages.config

重要

如果包中包含 nuspec <references> 元素,并且不包含 ref\<tfm>\ 中的程序集,NuGet 会将 nuspec <references> 元素中列出的程序集作为编译和运行时资产进行播发。 这意味着当引用的程序集需要加载 lib\<tfm>\ 目录中的任何其他程序集时,将存在运行时异常。 因此,必须使用 nuspec <references> 提供 packages.config 支持,同时复制 ref/ 文件夹中的程序集来提供 PackageReference 支持。 无需使用 runtimes/ 包文件夹,为了完整性,已在上一部分中添加了它。

示例

我的包将包含三个程序集 MyLib.dllMyHelpers.dllMyUtilities.dll,它们以 .NET Framework 4.7.2 为目标。 MyUtilities.dll 包含仅供其他两个程序集使用的类,因此我不希望在 IntelliSense 中或在编译时将这些类用于使用我的包的项目。 我的 nuspec 文件需要包含以下 XML 元素:

<references>
    <group targetFramework="net472">
        <reference file="MyLib.dll" />
        <reference file="MyHelpers.dll" />
    </group>
</references>

我需要确保我的包内容是:

lib\net472\MyLib.dll
lib\net472\MyHelpers.dll
lib\net472\MyUtilities.dll
ref\net472\MyLib.dll
ref\net472\MyHelpers.dll