Аналог partial class для разных assemblies

Рейтинг: 4Ответов: 2Опубликовано: 28.09.2014

Мне необходимо реализовать несколько сайтов на ASP.NET MVC (C#).

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

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

К тому же хотелось бы избежать конверсий типов или указания generic аргументов в коде сайта:

var user = (MyUser)User.Load(id);
var user = User.Load<MyUser>(id);

Например, базово класс User имеет поля id и name. На сайте 1 нужно добавить поле email. А на сайте 2 нужно добавить поле phone (вместо email).

Если бы была одна библиотека (assembly), то это легко решалось бы с помощью partial class.

Есть у кого идеи, как решить данную задачу?

Ответы

▲ 10

Вы смотрите на partial классы под неверным углом зрения. Думаю лучшее объяснение того в чем здесь неточность - это описание причин, по которым частичные классы были созданы.

В первой версии C# частичных классов не было и это приводило к сложностям в работе с генерируемым кодом. Например, если вы сейчас используете Windows Forms, то код формы, который генерируется дизайнером, располагается в одном файле, а код, который пишете для формы вы - в другом. В C# 1 все лежало в одном файле и это вызывало серьезные проблемы. С одной стороны вы должны были вести себя осторожно, чтобы не сломать сгенерированный код, с другой стороны генератор кода должен был обходить стороной ваш код. И эти проблемы возможны не только в gui-фреймворках, генерирующих код для создания графического интерфейса. Генерация кода используется, например, в ORM. Наконец, вы сами можете захотеть написать свой генератор.

Появление частичных классов в C# 2 решило эту проблему. Именно для этого были созданы частичные классы - чтобы была возможность описать один класс в нескольких файлах. Это позволило изолировать генерируемый код в отдельных файлах.

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

  • при написании unit-тестов обычно на каждый тестируемый класс создается один класс с тестами. Класс с тестами часто становится очень большим, намного большим чем тестируемый класс. В таких случаях класс с тестами можно сделать частичным и разделить на несколько файлов
  • при рефакторинге кода часто встречается задача декомпозиции большого класса. Частичные классы в этом случае могут упростить работу. Можно сначала сделать класс частичным и безопасно разделить его на несколько файлов. И только когда все швы окончательно проясняться, выполнить реальное разделение на несколько классов.

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

То что собираетесь сделать вы - совсем другое дело. Вы собираетесь описать несколько классов с помощью синтаксиса частичных классов. То есть вы хотите описать несколько классов так, как будто это один класс.

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

Поэтому ответ на ваш вопрос такой - аналога partial class для разных assemblies не существует, вряд ли когда то будет существовать и не стоит пытаться его сымитировать.

▲ 3

partial-классы между сборками существовать не могут. Зато можно сделать так, чтобы два проекта собирались в итоге в одну и ту же сборку.

Делается это так. Открываем проект с библиотекой, и дописываем ближе к концу что-то наподобие вот этого:

<Target Name="GetModuleCodeFiles" Outputs="@(Compile)" />

Теперь открываем файл с основным проектом, и дописываем вот такое:

<Target Name="ImportModule" BeforeTargets="Build">
    <MSBuild Projects="путь к проекту библиотеки" Targets="GetModuleCodeFiles" RebaseOutputs="true">
        <Output TaskParameter="TargetOutputs" ItemName="Compile" />
    </MSBuild>
</Target>

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

Разумеется, это еще не все - ведь проект состоит не только из файлов с исходным кодом. Как минимум, придется таким же образом импортировать зависимости из библиотеки (References) - а также переписать логику загрузки пакетов из репозитория NuGet. Если в библиотеке есть ресурсы (EmbeddedResource) - понадобится импортировать еще и их. Но это все - решаемые проблемы.

Также может возникнуть проблема с R# - он не понимает динамического добавления файлов в проект. Для него понадобится включить проект с библиотекой как обычную зависимость - а потом не забыть убрать средствами MSBuild перед компиляцией:

<ItemGroup>
    <ProjectReference Remove="путь к библиотеке" />
</ItemGroup>

Так что в целом то, что вы хотите, вполне достижимо. Но подумайте - не будет ли наследование с обобщенными типами проще?