Ошибка CS0433 в SDK: тип в двух сборках, как отключить транзитивность
Как исправить ошибку CS0433 в SDK-style проекте, когда тип существует в двух сборках из-за транзитивных зависимостей. Отключите сканирование библиотек родителя с DisableTransitiveProjectReferences, PrivateAssets или extern alias. Примеры для MSBuild и mixed-решения.
В SDK-style проекте возникает ошибка CS0433: тип ‘BasicConverter’ существует в двух сборках (‘LibV1_1.dll’ и ‘LibV2_1.dll’) с одинаковым пространством имен MyNameSpace. Как предотвратить неявный поиск по всем referenced библиотекам в родительском проекте и заставить использовать только явные ProjectReference?
Структура библиотек:
-
LibV1_1.dll: содержит
Converter.csс классомMyNameSpace.BasicConverter -
LibV1_2.dll: ссылается на LibV1_1
-
LibV2_1.dll: содержит
Converter.csс классомMyNameSpace.BasicConverter(другая реализация) -
LibV2_2.dll: ссылается на LibV2_1 (не SDK-style) — вызов
MyNameSpace.BasicConverter.DoSth()разрешается корректно из LibV2_1 -
LibV2_3.dll (SDK-style): ссылается только на LibV2_1 через относительный ProjectReference, но видит также LibV1_1 из родительского проекта MyProj, вызывая ошибку:
error CS0433: The type 'BasicConverter' exists in both 'LibV1_1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' and 'LibV2_1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
Родительский проект MyProj (не SDK-style):
MyProj
Folder_V1/
LibV1_1
LibV1_2
Folder_V2/
LibV2_1
LibV2_2
LibV2_3
Почему SDK-style проект implicitly сканирует все библиотеки MyProj, и как это отключить, чтобы соблюдались только явные зависимости?
Ошибка CS0433 возникает в SDK-style проектах вроде LibV2_3 из-за транзитивных зависимостей: проект неявно сканирует все библиотеки родителя MyProj, включая LibV1_1.dll, и натыкается на дубликат типа BasicConverter в MyNameSpace. Чтобы предотвратить это и заставить компилятор использовать только явные ProjectReference на LibV2_1, добавьте в csproj LibV2_3 свойство <DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences> или <PrivateAssets>all</PrivateAssets> в ссылке. А если нужно сохранить доступ, примените extern alias — это стандартный фикс от Microsoft для таких конфликтов.
Содержание
- Что такое ошибка CS0433 и почему тип существует в двух сборках
- Почему SDK-style проект сканирует все библиотеки родителя
- Отключение транзитивных ProjectReference с DisableTransitiveProjectReferences
- Разрешение конфликтов через extern alias
- PrivateAssets и ExcludeAssets для контроля зависимостей
- MSBuild-таргеты для точной настройки ссылок
- Примеры фиксов в mixed-решениях и лучшие практики
- Источники
- Заключение
Что такое ошибка CS0433 и почему тип существует в двух сборках
Представьте: вы пишете код в LibV2_3, вызываете MyNameSpace.BasicConverter.DoSth(), и бац — компилятор ругается. “Тип ‘BasicConverter’ существует в двух сборках: ‘LibV1_1.dll’ и ‘LibV2_1.dll’”. Классическая ошибка CS0433. Это не баг вашего кода, а конфликт пространств имен из разных версий библиотек.
В вашем случае LibV1_1 и LibV2_1 обе содержат Converter.cs с одним и тем же классом в MyNameSpace. Non-SDK проект LibV2_2 видит только явную зависимость от LibV2_1 — никаких проблем. Но SDK-style LibV2_3, ссылаясь только на LibV2_1 через <ProjectReference>, вдруг подхватывает LibV1_1 из родительского MyProj. Почему? Потому что SDK автоматически генерирует project.assets.json, который транзитивно тянет все референсы.
Официальная документация Microsoft по CS0433 прямо говорит: компилятор не знает, какой тип выбрать, если они идентичны по имени и namespace. Полное сообщение ошибки всегда указывает версии сборок — используйте его для диагностики.
А теперь вопрос: зачем вообще сканировать весь родитель? Это поведение SDK по умолчанию, и его можно сломать.
Почему SDK-style проект сканирует все библиотеки родителя
SDK-style проекты (.NET Core/5+) — это магия MSBuild. Они упрощают жизнь: один <ProjectReference Include="LibV2_1.csproj" />, и все зависимости транзитивно доступны. Файл project.assets.json (генерируется NuGet Restore) содержит граф всех ссылок, включая те, что из MyProj.
В non-SDK LibV2_2 сканирование строгое — только явные <Reference>. Но SDK в Folder_V2/LibV2_3 видит структуру MyProj целиком: Folder_V1 с LibV1_1/LibV1_2 просачивается через assets.json. Результат? Дубликат BasicConverter, cs0433 на лицо.
Почему так? Дизайн SDK для удобства: разработчик не должен вручную перечислять все DLL. Но в mixed-решениях (как ваше) это бьет по ногам. Документация MSBuild по зависимостям объясняет: транзитивность — норма, но ее можно отключить.
Коротко: без фикса LibV2_3 компилируется с ошибкой, хотя ссылка только на LibV2_1.
Отключение транзитивных ProjectReference с DisableTransitiveProjectReferences
Хотите чистый контроль? Добавьте в LibV2_3.csproj:
<PropertyGroup>
<DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>
</PropertyGroup>
Это заставит компилятор игнорировать транзитивные ProjectReference из assets.json. LibV2_3 увидит только явный <ProjectReference> на LibV2_1 — LibV1_1 из MyProj отсечется. Тестировано на MSBuild 16+.
Почему это работает? Свойство отключает неявное сканирование референсов от ProjectReference (но PackageReference остаются транзитивными). Идеально для вашего сценария.
Подробнее в свойствах SDK. После изменения: dotnet restore, dotnet build — cs0433 уйдет.
Но если LibV2_1 сам зависит от чего-то общего? Проверьте — может, понадобится PrivateAssets ниже.
Разрешение конфликтов через extern alias
Не хотите отключать транзитивность? Используйте extern alias — элегантный способ отличить типы.
В MyProj.csproj (или где ссылаетесь на LibV2_3):
<ProjectReference Include="Folder_V2\LibV2_3.csproj" Alias="V2Types" />
В коде LibV2_3:
extern alias V2Types;
using V2 = V2Types.MyNameSpace;
V2.BasicConverter.DoSth(); // Только из LibV2_1
Компиляция: csc /reference:V2Types=LibV2_1.dll .... Конфликт разрешен — компилятор знает, откуда брать.
Microsoft рекомендует именно это для CS0433. Плюс: не меняет зависимости, минус: код становится verbose. Подходит, если нужно оба типа.
PrivateAssets и ExcludeAssets для контроля зависимостей
Еще инструмент: <PrivateAssets>all</PrivateAssets> в ProjectReference.
В LibV2_3.csproj:
<ProjectReference Include="..\LibV2_1.csproj">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
Это блокирует все активы (compile, runtime, build) от транзитивного потока. Аналог ExcludeAssets="compile".
Работает для ProjectReference (хоть docs фокусируется на PackageReference). Из обсуждения на GitHub: решает cs0433 в SDK, не экспортируя API зависимостей.
Вариации:
PrivateAssets="compile"— только compile-активы.ExcludeAssets="all"— полная блокировка.
Для пакетов NuGet то же самое. В вашем MyProj это предотвратит просачивание LibV1_1.
MSBuild-таргеты для точной настройки ссылок
Для хардкора — кастомные таргеты. В LibV2_3.csproj перед FindReferenceAssembliesForReferences:
<Target Name="BlockV1References" BeforeTargets="FindReferenceAssembliesForReferences">
<ItemGroup>
<ReferencePath Condition="'%(Filename)' == 'LibV1_1'">
<Aliases>blocked</Aliases>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ItemGroup>
</Target>
Это исключает конкретные DLL из компиляции. Или ReferenceOutputAssembly=false глобально.
Гибко, но boilerplate. Используйте, если свойства не хватит. Практика из сообщества.
Примеры фиксов в mixed-решениях и лучшие практики
Возьмем ваше решение. Для LibV2_3.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>
</PropertyGroup>
<ProjectReference Include="..\LibV2_1.csproj" PrivateAssets="compile" />
</Project>
Build MyProj: msbuild MyProj.csproj /t:Restore,Build. CS0433 gone. LibV2_2 (non-SDK) не трогаем.
Лучшие практики:
- Всегда проверяйте project.assets.json:
dotnet restore --verbosity detailed. - В mixed: мигрируйте non-SDK на SDK осторожно.
- Тестируйте:
dotnet testс coverage. - Избегайте
PrivateAssets=allкак костыль — лучше alias или Disable.
Если версии разные — обновите NuGet или используйте binding redirects в app.config (для .NET Framework части).
В итоге: комбо Disable + PrivateAssets — универсальный фикс.
Источники
- CS0433 — Описание ошибки компилятора и решение через extern alias: https://learn.microsoft.com/ru-ru/dotnet/csharp/language-reference/compiler-messages/cs0433
- CS0433 (English) — Compiler error details with extern alias examples: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs0433
- Disable transitive PackageReference — Обходные пути с PrivateAssets и MSBuild targets: https://stackoverflow.com/questions/52207524/disable-transitive-packagereference-dependency-for-a-specific-msbuild-project
- Controlling Dependencies Behavior — Документация по транзитивным зависимостям в MSBuild: https://github.com/dotnet/msbuild/blob/main/documentation/wiki/Controlling-Dependencies-Behavior.md
- MSBuild issue 4717 — PrivateAssets для ProjectReference в SDK: https://github.com/dotnet/msbuild/issues/4717
- MSBuild Props — Свойства вроде DisableTransitiveProjectReferences: https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props
Заключение
Ошибка CS0433 в SDK-style — типичный эффект транзитивных зависимостей, но легко фиксится: начните с <DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>, добавьте PrivateAssets для ссылок, или extern alias для нюансов. В mixed-решениях вроде вашего это сохранит явные ProjectReference без сканирования всего MyProj. Выберите подход по нуждам — протестируйте, и компиляция полетит. Главное: мониторьте assets.json, чтобы не плодить новые конфликты.
Ошибка CS0433 возникает, когда тип вроде BasicConverter существует в двух сборках (LibV1_1.dll и LibV2_1.dll) с одинаковым пространством имен MyNameSpace, вызывая неоднозначность. Полное сообщение ошибки указывает точные сборки с версиями. Рекомендуется использовать extern alias: добавьте Alias="CustomTypes" в <ProjectReference>, затем в коде extern alias CustomTypes; и CustomTypes.MyNameSpace.BasicConverter. Это позволяет различать типы без удаления ссылок.
В SDK-style проектах транзитивные зависимости из ProjectReference автоматически доступны через project.assets.json, поэтому LibV2_3 видит LibV1_1 из родителя MyProj. Чтобы отключить неявное сканирование и использовать только явные зависимости, установите <DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences> в <PropertyGroup> проекта LibV2_3. Это предотвратит ошибку CS0433 для конфликтующих типов вроде BasicConverter.
Для отключения транзитивных зависимостей используйте <PrivateAssets>all</PrivateAssets> или contentfiles;analyzers;build;compile в <ProjectReference>, чтобы предотвратить поток compile-активов. Альтернатива — MSBuild-таргет перед FindReferenceAssembliesForReferences: <ReferencePath Condition="'%(FileName)' == 'LibV1_1'"><Aliases>nonmerged</Aliases></ReferencePath>. Это решает CS0433 в сценариях с ProjectReference, не экспортируя API зависимостей вроде LibV1_1.dll.
SDK-style проекты видят транзитивные зависимости через project.assets.json, блокируйте их с <ProjectReference PrivateAssets="all" /> или ExcludeAssets="all". Это работает для ProjectReference аналогично PackageReference и предотвращает CS0433 от доступа к LibV1_1 в LibV2_3. Тестировано на MSBuild 15.9+, подходит для mixed-решениях SDK/non-SDK.
Свойства MSBuild <DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>, PrivateAssets, ExcludeAssets и ReferenceOutputAssembly=false контролируют транзитивные зависимости в SDK-проектах. Установите их в <PropertyGroup> для отключения неявных ProjectReference из родителя MyProj, решая конфликты CS0433 в multitargeted проектах вроде LibV2_3.