Зависимости

Основным средством для добавления зависимостей в библиотеку .NET являются ссылки на пакеты NuGet. Ссылки на пакеты NuGet позволяют быстро подключить уже готовые функции, но для многих разработчиков .NET они часто становятся источником трудностей. Правильное управление зависимостями нужно для того, чтобы изменения в чужих библиотеках .NET не нарушали работу вашей библиотеки .NET, и наоборот.

Ромбовидные зависимости

В проекте .NET обычно используется несколько версий пакета в дереве зависимостей. Например, приложение зависит от двух пакетов NuGet, каждый из которых зависит от другой версии одного пакета. В схеме зависимостей это отображено в виде ромбовидной зависимости.

Diamond dependency

Во время сборки NuGet анализирует все пакеты, от которых зависит проект, в том числе зависимости зависимостей. Если в этом списке есть нескольких версий одного пакета, из них выбирается одна. Унификация пакетов очень важна, так как в .NET очень сложно реализовать параллельную работу нескольких версий одной сборки в одном приложении.

Большинство ромбовидных зависимостей решаются очень легко, но в определенных обстоятельствах они могут создавать проблемы:

  • Конфликт ссылок на пакет NuGet не позволяет разрешить конфликт версий при восстановлении пакетов.
  • Критические изменения между версиями приводят к ошибкам и исключениям во время выполнения.
  • Строгое именование пакета сборки, если изменилась версия сборки для приложения, выполняющегося на .NET Framework. В такой ситуации требуется переадресация привязок.

Невозможно предсказать, какие пакеты будут работать параллельно с вашим. Чтобы снизить вероятность проблем с библиотекой при возникновении ромбовидной зависимости, сократите до минимума число пакетов, от которых она зависит.

✔️ СЛЕДУЕТ проверить наличие ненужных зависимостей в библиотеке .NET.

Диапазон версий для зависимостей NuGet

Ссылка на пакет определяет диапазон допустимых пакетов. Как правило, эталонная версия пакета в файле проекта является минимальной версией, и максимальное значение отсутствует.

<!-- Accepts any version 1.0 and above. -->
<PackageReference Include="ExamplePackage" Version="1.0" />

NuGet использует достаточно сложные правила при разрешении зависимостей, но по умолчанию пытается найти минимально приемлемую версию. NuGet предпочитает минимально приемлемую версию, а не максимально доступную, чтобы снизить риск проблем с совместимостью.

Это правило минимально приемлемых версий позволяет не указывать верхний предел для диапазона версий пакета, даже если вы хотите избежать получения последней версии. NuGet и без этого будет искать наименьшую, то есть максимально совместимую, версию пакета.

<!-- Accepts 1.0 up to 1.x, but not 2.0 and higher. -->
<PackageReference Include="ExamplePackage" Version="[1.0,2.0)" />

<!-- Accepts exactly 1.0. -->
<PackageReference Include="ExamplePackage" Version="[1.0]" />

Наличие верхнего предела для версий пакета приведет к сбою NuGet в случае конфликта зависимостей. Предположим, одна из библиотек требует строго версию 1.0, а другая — версию не ниже 2.0. Использование версии 2.0 может привести к ошибкам, если в ней внесены критические изменения. Но строгое верхнее ограничение гарантирует ошибку.

Diamond dependency conflict

❌ НЕ СЛЕДУЕТ использовать ссылки на пакет NuGet без минимально допустимой версии.

❌ AVOID Ссылки на пакеты NuGet, требующие указания точной версии.

❌ НЕЖЕЛАТЕЛЬНО использовать ссылки на пакет NuGet с указанием верхнего предела.

Дополнительные сведения см. в разделе Управление версиями пакета.

Совместно используемые пакеты кода NuGet

Чтобы снизить внешние зависимости пакета NuGet, можно использовать ссылки на совместно используемые пакеты кода. Совместно используемые пакеты кода содержат файлы исходного кода, которые включаются в проект по ссылке. Так как эти файлы исходного кода напрямую включаются в проект и компилируются вместе с остальной частью проекта, не возникает никаких внешних зависимостей и возможностей для конфликта.

Совместно используемые пакеты кода — это отличный способ включать небольшие фрагменты и функции. Например, можно ссылаться на общий исходный пакет вспомогательных методов для выполнения http-вызовов.

Shared source package

<PackageReference Include="Microsoft.Extensions.Buffers.Testing.Sources" PrivateAssets="All" Version="1.0" />

Shared source project

Совместно используемые пакеты кода имеют ряд ограничений. Для них можно использовать только ссылки PackageReference, поэтому более старые проекты packages.config исключаются. Кроме того, общие исходные пакеты доступны только для проектов с тем же языком. Из-за этих ограничений общие исходные пакеты лучше всего использовать для совместного использования функциональных возможностей в проекте с открытым исходным кодом.

✔️ РЕКОМЕНДУЕТСЯ использовать ссылки на совместно используемые пакеты кода для небольших встроенных функций.

✔️ РЕКОМЕНДУЕТСЯ создать совместно используемый пакет кода, если он предоставляет небольшие встроенные функции.

✔️ СЛЕДУЕТ указывать ссылку PrivateAssets="All" для совместно используемых пакетов кода.

Этот вариант сообщает NuGet, что пакет будет использоваться только во время разработки и не должен предоставляться как открытая зависимость.

❌ НЕ СЛЕДУЕТ использовать совместно используемые пакеты кода в общедоступном API.

Совместно используемые типы компилируются прямо в сборку, которая содержит ссылку на них, и их невозможно передать в другую сборку. Например, совместно используемый тип IRepository в одном проекте не будет совпадать с тем же совместно используемым типом IRepository в другом проекте. Типы в совместно используемых пакетах кода должны иметь предел видимости internal.

❌ НЕ СЛЕДУЕТ публиковать совместно используемые пакеты кода на сайте NuGet.org.

Совместно используемые пакеты кода содержат исходный код и могут использоваться только в проектах с тем же типом языка. Например, совместно используемые пакеты кода на C# невозможно использовать в приложении F#.

Опубликуйте совместно используемые пакеты кода в локальном веб-канале или MyGet для их использования в проекте внутренним образом.