Поделиться через


Выбор сборок, на которые ссылаются проекты

Сборки используются двумя разными способами во время компиляции. Первый способ — компиляция, что позволяет коду потребителя пакета компилироваться с помощью API в сборке, а IntelliSense — предоставлять рекомендации. Второй — среда выполнения, в которой сборка копируется в каталог bin и используется во время выполнения программы. Некоторые авторы пакетов хотят, чтобы только их собственные сборки (или только некоторые из них) были доступны для потребителей пакетов во время компиляции, но должны предоставить все зависимости для среды выполнения. В этом документе рассматривается, как этого достичь.

Мы рекомендуем использовать один пакет для каждой сборки и упаковывать зависимости от других сборок. Когда NuGet восстанавливает проект, он выполняет выбор ресурсов и поддерживает включение, исключение и ограничение доступа для различных классов ресурсов. Чтобы зависимости пакета не могли стать ресурсами времени компиляции для всех пользователей пакета, ресурсы compile можно сделать частными. В созданном пакете в результате этого compile будет исключен из зависимости. Обратите внимание, что частные активы по умолчанию, если ничего не задано, — contentfiles;build;analyzers. Поэтому следует использовать PrivateAssets="compile;contentfiles;build;analyzers" в PackageReference или ProjectReference.

<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 должен использовать атрибут XML exclude.

<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, не будет API Visual Studio, доступных в IntelliSense, а если они выполняют сборку проекта, то проект должен явно установить эти пакеты. Это преимущество использования зависимости пакета с исключенным ресурсом. Если проектам, использующим пакет, нужно также использовать зависимости, они могут добавить ссылку на пакет, чтобы сделать API доступными для себя.

Наконец, некоторые авторы пакетов путались в выборе сборок NuGet для пакетов, поддерживающих несколько целевых платформ, если пакет также содержит несколько сборок. Если основная сборка поддерживает разные целевые платформы для сборки служебной программы, возможно, будет неочевидно, в какие каталоги lib/ разместить все сборки. Если вы разделите каждый пакет по имени сборки, вам будет понятнее, в какие папки lib/ поместить каждую сборку. Обратите внимание, что это не подразумевает наличия пакетов Package1.net48 и Package1.net6.0. Это означает наличие lib/net48/Package1.dll и lib/net6.0/Package6.0 в Package1, а lib/netstandard2.0/Package2.dll и lib/net5.0/Package2.dll — в Package2. Когда NuGet восстанавливает проект, NuGet независимо выбирает ресурсы для этих двух пакетов.

Также обратите внимание, что включенные и исключенные ресурсы зависимостей используются только проектами с PackageReference. Любой проект, устанавливающий пакет с помощью packages.config, установит зависимости и предоставит API. packages.config поддерживается только более старыми шаблонами проектов .NET Framework в Visual Studio. Проекты в стиле SDK, даже если они предназначены для .NET Framework, не поддерживают packages.config и поэтому поддерживают включение и исключение ресурсов зависимостей.

PackageReference и packages.config предоставляют разные функции. Способ создания пакета зависит от того, требуется ли поддержка потребителей пакета, которые используют PackageReference, packages.config или оба варианта.

Целевой пакет MSBuild NuGet не поддерживает автоматическое включение ссылок проекта в пакет. В нем будут перечислены только указанные проекты в качестве зависимостей пакетов. Существует проблема на GitHub, в которой члены сообщества делятся способами достичь этого результата. Обычно используются метаданные элемента MSBuild PackagePath для размещения файлов в любом месте пакета, как описано в документации по включению содержимого в пакет, и использование 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, пакет должен использовать элемент <references> в файле nuspec . Например:

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

Целевые объекты пакета MSBuild не поддерживают элемент <references>. При использовании пакета MSBuild см. документацию по упаковке с использованием NUSPEC-файла.

Примечание.

Проект packages.config использует процесс ResolveAssemblyReference для копирования сборок в выходной каталог bin\<configuration>\. Сборка проекта копируется, после чего система сборки проверяет манифест сборки на наличие вызываемых по ссылке сборок, а затем копирует эти сборки и рекурсивно повторяет эту операцию для всех сборок. Это означает, что если какая-либо из сборок загружена только отражением (Assembly.Load, MEF или другой платформой внедрения зависимостей), то она не может быть скопирована в выходной каталог проекта bin\<configuration>\, несмотря на то, что находится в bin\<tfm>\. Это также означает, что это работает только для сборок .NET, а не для машинного кода, вызываемого с помощью P/Invoke.

Поддержка PackageReference и packages.config

Внимание

Если пакет содержит элемент nuspec <references> и не имеет сборок в каталоге ref\<tfm>\, NuGet объявит сборки, перечисленные в элементе nuspec <references>, одновременно как сборки времени компиляции и выполнения. Это означает, что когда вызываемые по ссылке сборки будут пробовать загрузить любую другую сборку из каталога lib\<tfm>\, произойдет исключение времени выполнения. Поэтому важно использовать оба nuspec <references> для поддержки packages.config, а также дублировать сборки в папке ref/ для поддержки PackageReference. Папку пакета runtimes/ не нужно использовать, она была добавлена в предыдущий раздел для полноты.

Пример

Мой пакет будет содержать три сборки (MyLib.dll, MyHelpers.dll и MyUtilities.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