Проблематика сови і глобуса: підключення двох збірок з ідентичними просторами імен і назв класів

 

Сьогодні ввечері, з gelas завели розмову про те, як працюють пакетні менеджери на різних платформах. В ході бесіди, дійшли до обговорення ситуації, коли в проект .NET Core необхідно підключити дві бібліотеки, які містять класи з однаковою назвою в однакових просторах імен. Оскільки .NET Core я займаюся досить щільно, я захотів перевірити, як можна вирішити подібну проблему. Що з цього вийшло описано далі

 

Дисклеймер. Часто зустрічаються такі ситуації? Мені за більш ніж 10 років роботи .NET, з подібною ситуацією в реальному проекті не доводилося стикатися ні разу. Але от провести експеримент було цікаво.

 

На всяк випадок уточню, що експеримент я буду проводити використовуючи:

 

  • macOS 10.13,
  • .NET Core SDK 2.1.302
  • Rider 2018.2

 

Отже, змоделюємо ситуацію, коли до нас потрапили дві бібліотеки, в яких є необхідні нам класи, які ми повинні використовувати в нашому проекті. При цьому доступу до вихідного коду у нас немає, а декомпілювати ці збірки, щоб поміняти в них неймспейси, а потім скомпілювати назад ми теж не можемо.

 

Підготовка експерименту

 

І так, для початку підготуємо одну сову і два глобуса. Як сови у нас буде виступати проект з таргетом на netcoreapp2.1. В якості глобусів створимо два проекти, один  з таргетом на netcoreapp2.1, а другий на netstandard2.0

 

 

У кожен проект помістимо по класу Globe, які будуть знаходиться в ідентичних неймспейсах, але реалізація при цьому у них буде різна:

 

Перший файл:

 

using System;

namespace Space
{
 public class Globe
{
 public string GetColor() => "Green";
}
}

 

Другий файл:

 

using System;

namespace Space
{
 public class Globe
{
 public string GetColor() => "Blue";
}
}

 

Читайте також  Смартконтракти Waves. Перший досвід

Спроба номер один

 

Оскільки за умовами задачі ми повинні працювати із зовнішніми збірками, а не проектами, то додамо відповідно до проекту посилання ніби вони дійсно є просто бібліотеками. Для цього спочатку скомпилируем всі проекти, щоб у нас з’явилися потрібні нам Globe1.dll і Globe2.dll. Потім додамо на них посилання в проект в такому вигляді:

 

 

Тепер спробуємо створити змінну класу Globe:

 

 

Як бачимо, вже на цьому етапі IDE попереджає нас про те, що є проблема з розумінням того, звідки повинен бути взятий потрібний нам клас Globe.

 

Спочатку здається, що ситуація досить типова і на неї вже повинні бути готовий, відлитий у граніті, відповідь на Stack Overflow. Як виявилося, для .NET Core рішення подібної задачі поки ще запропоновано не було. Або мій Гугл мене підвів. Але дещо корисне на Stack Overflow знайти вдалося.Єдина розумна публікація, яку вдалося нагуглити — була за 2006 рік і описувала подібну ситуацію для класичної версії .NET. При цьому, вельми схожа проблема обговорюється в репозиторії проекту NuGet.

 

Поки не дуже багато корисної інформації, але вона все ж є:

 

  • У класичній версії .NET був реалізований механізм псевдонімів
  • Згідно специфікації, C# підтримує використання псевдонімів в коді

 

Залишилося зрозуміти, як це зробити .NET Core.

 

На жаль, в поточній версії документації досить скромно розповідається про можливості підключення зовнішніх пакетів/зборів. А опис csproj файлу також жодним чином не проливає світло на можливості створення псевдонімів. Але тим не менш, методами проб і помилок, мені вдалося з’ясувати, що псевдоніми для збірок в .NET Core все-таки підтримуються. І оформляються вони наступному чином:

Читайте також  Генератор коду для Laravel — на введення RAML, на висновок JSON-API

 

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
 <Reference Include="Globe1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..Globe1binDebugnetcoreapp2.1Globe1.dll</HintPath>
<Aliases>Lib1</Aliases>
</Reference>

 <Reference Include="Globe2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..Globe2binDebugnetstandard2.0Globe2.dll</HintPath>
<Aliases>Lib2</Aliases>
</Reference>
</ItemGroup>

</Project>

 

Тепер залишилося навчитися використовувати ці псевдоніми. В цьому нам допоможе раніше вже згадуване ключове слово extern:

 

В документації про нього пишуть наступне:

У деяких випадках може знадобитися поставити посилання на дві версії збірок з однаковими повними іменами типів. Наприклад, вам необхідно використовувати дві або більше версій збірки в одному додатку. З допомогою зовнішнього псевдоніма збірки можна включити простору імен для кожної збірки в оболонку всередині просторів імен верхнього рівня, іменованих за цим псевдонімом, що дозволяє використовувати їх в одному файлі.

При кожному оголошенні псевдоніма extern вводиться додатковий простір імен верхнього рівня, що відповідає глобальному простору імен (але не знаходиться всередині нього). Таким чином посилання на типи з кожної збірки без неоднозначності можуть створюватися з допомогою їх повного імені, коренем якого є відповідний псевдонім простору імен.

Тут правда не варто забувати про те, що extern також використовується в C# для оголошення методу з зовнішньою реалізацією з некерованого коду. У цьому випадку extern зазвичай використовується з атрибутом DllImport. Більш детально про це можна почитати у відповідному розділі документації.

 

Отже, спробуємо використовувати наші псевдоніми:

 

extern alias Lib1;
extern alias Lib2;
using System;

namespace Owl
{ 
...
 public class SuperOwl
{
 private Lib1::Space.Globe _firstGlobe;
 private Lib2::Space.Globe _secondGlobe;

 public void IntegrateGlobe(Lib1::Space.Globe globe) => _firstGlobe = globe;

 public void IntegrateGlobe(Lib2::Space.Globe globe) => _secondGlobe = globe;

...

}
}

 

Цей код вже навіть працює. Причому працює правильно. Але все-таки хочеться зробити його трішки елегантніше. Зробити це можна досить простим способом:

 

extern alias Lib1;
extern alias Lib2;
using System;
using SpaceOne=Lib1::Space;
using SpaceTwo=Lib2::Space;

 

Читайте також  Троянський пінгвін: Робимо вірус для Linux

Тепер можна використовувати звичайний і очевидний синтаксис:

 

var globe1 = new SpaceOne.Globe()
var globe2 = new SpaceTwo.Globe()

 

Випробування

 

Проведемо випробування нашої сови:

 

 

Як бачимо, код відпрацював нормально і без помилок. Інтеграція сови і глобусів успішно завершена!

Степан Лютий

Обожнюю технології в сучасному світі. Хоча частенько і замислююся над тим, як далеко вони нас заведуть. Не те, щоб я прям і знаюся на ядрах, пікселях, коллайдерах і інших парсеках. Просто приходжу в захват від того, що може в творчому пориві вигадати людський розум.

You may also like...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *