Skip to content

Линковка с библиотекой

Это не совсем классическое понимание линковки, но наиболее близкое по смыслу, и чтобы не вводить избыточных термином мы будем использовать его. Классическое понимание линковки это финальное "склеивание" из как правило, ранее независимо скомпилированных частей в формате .obj для Си, в один испольняемый файл .exe или библиотеку .dll. В нашем случае, библиотека TacLibrary.dll собирается средствами Unity 3D и для нашего partial-подхода требуется "слинковать" содержимое классов в самой библиотеке TacLibrary.dll с тем расширением partial-классов, которые захочет сделать конечный разработчик игр, учитывая уже всю её специфику.

Для этого, конечному разработчику рекомендуется в структуре проекта выделить директорию TacLibraryExt, и поместить туда все свои расширения классов с той же структурой директорий и с теме же именами файлов, а в корень поместить юнити-ссылку на сборку TacLibrary.asmref, которую можно скопировать из исходного кода библиотеки в поддериктории TacLibrary/DLL. Это повзолит совместить все части классов (partial class) в одной сборке. К сожалению, это ограничение накладывается языком C# при применении partial class - все части должны быть в том же namespace и той же сборке dll.

Суть простыми словами

  1. Исходники библиотеки поставляются как пакет .unitypackage (в папке TacLibrary), а не как уже скомбилированная библиотека .dll

  2. Проблема: вам, как конечному разработчику нужно добавить свои методы в классы библиотеки TacLibrary через partial. Но по правилам C# все части класса должны компилироваться вместе, в одной сборке.

  3. Решение: вам нужно скопировать ссылку на сборку (TacLibrary.asmref) в свой проект и положить свои расширения в отдельную, но "связанную" папку TacLibraryExt.

  4. Что делает Unity: когда он видит структуру TacLibrary (ядро) + TacLibraryExt (расширения) + общий .asmref, его система сборки понимает: "Ага, это один большой проект. Нужно взять все .cs-файлы из обеих папок и скормить их компилятору C# одновременно, как единое целое".

  5. Что делает компилятор C#: он получает кучу файлов, видит в них partial class ClassName в одном namespace и просто "сливает" их содержимое в памяти в один класс на этапе компиляции.

В итоге: Это не линковка в классическом системном смысле, а просто хитрая организация файлов в Unity-проекте, чтобы обойти ограничение C# на partial-классы.

Преимущества такого подхода

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

  • В случае наследования: class MySpecialUnit : TacLibrary.Unit { ... }

Проблема: Вам приходится создавать новый тип. Всё, что завязано на базовый TacLibrary.Unit должно различать эти типы, чего автоматически часто не происходит, нарушается полиморфизм ядра библиотеки.

Решение: Расширяя тот же самый класс TacLibrary.Unit , вам не нужно дублировать те же по сути сущности и делать фейковое различие, например, между "абстрактным персонажем" и "персонажем в игре "Убить Билла"".

  • в случае агрегации/композиции: class MyUnit { public TacLibrary.Unit CoreUnit; ... }

Проблема: Появляется лишняя сущность, бо́льшая связанность, нужно проксировать вызовы, управлять жизненным циклом двух объектов.

Решение: Расширение происходит внутри исходного класса. Нет обёрток, нет прокси. Логика остаётся в едином контексте того же объекта.

По сути мы делаем безболезненную инъекцию вида

    public static class VectorExt
    {
        public static Vector2 To2(this Vector3_ v)
        {
            return new Vector2(v.x, v.z);
        }
    }

только в этом случае целиком для класса с новым расширенным поведением.

В итоге такой подход:

  1. Устраняет искусственное дублирование сущностей (проблема наследования)

  2. Избегает архитектурного "шума" (проблема агрегации)

  3. Обеспечивает "бесшовное" расширение (аналогия с extension-методами)

  4. Сохраняет целостность типов на этапе компиляции

см. так же Легкость архитектуры