Дмитpий Hecтepук

Блог о программировании — C#, F#, C++, архитектура, и многое другое

Коротко про type forwarding

15 комментариев

Представьте, что у вас есть SDK, состоящий из огромного количества библиотек, и что любому пользователю вашего SDK нужно для нормальной работы подключить штук 15-20 DLLек, причем конечный пользователь может даже не знать заранее, какие именно библиотеки нужны. Эта ситуация – весьма неприятная. Все мы знаем, что Visual Studio 2010 добавляет новые ссылки на библиотеки с черепашьей скоростью, и что трудозатраты по добавлению ссылок равны факториалу (!!!) от количества ссылок которые вы добавляете.

Очевидно, нужно искать возможность как-то улучшить user experience. Не имея возможности просто сделать ILMerge на все сборки, у нас тем не менее есть возможность сделать нечто не менее эффективное – а именно создать прокси-сборку для всех типов внутри сборок, и воспользоваться фичей под названием type forwarding, для того чтобы вызывались именно конечные сборки нашего SDK.

Как работает type forwarding?

Поддержка type forwarding была добавлена в .Net 2.0 для того чтобы авторы библиотек могли мигрировать типы из одной сборки в другую, не ломая в процессе код пользователей и не требуя перекомпиляции.

Вот как это работает. Есть у меня сборка FirstAssembly, а в ней тип С:

// FirstAssembly.dll
public class С {}

Далее, другой пользователь пишет свой код, ссылаясь на FirstAssembly.dll:

С с = new С();

Все хорошо, но вдруг я решил передвинуть мой тип C в сборку SecondAssembly.dll, при этом не меняя пространства имен и имени самого типа. Тогда я делаю следующее:

  • Удаляю реализацию класса С в FirstAssembly.dll и переношу его в SecondAssembly.dll
  • Добавляю ссылку на SecondAssembly.dll в FirstAssembly.dll
  • Иду в FirstAssembly, открываю там AssemblyInfo.cs и в нем пишу магическую строчку
    [assembly: TypeForwardedTo(SomeNamespace.C)]
    

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

Атрибут сборки на момент компиляции FirstAssembly.dll превращается в следующую директиву:

.class extern forwarder SomeNamespace.C
{
  .assembly extern SecondAssembly
}

Иначе говоря, все, что у нас получается – это лишняя директива которая подсказывает, что если клиент ищет в нашей сборке тип Somenamespace.C но не находит его, имеет смысл поискать этот тип в сборке SecondAssembly.dll.

Обсуждение

Во-первых, следует заметить что какой-то особой поддержки type forwarding со стороны Visual Studio не существует – в лучшем случае, при попытке сослаться на тип из сборки который «переехал», помимо обычной ошибки Visual Studio намекнет вам, что тип «мигрировал» и что для успешной компиляции вам стоит добавить ссылку на SecondAssembly.dll.

В контексте SDK, следует заметить, что

  • Вы не можете просто поставлять набор из огромного количества директив TypeForwardedTo и компилировать код. В момент компиляции нужны ссылки на реальные сборки или же на некие прокси, которые в точности повторяют структуру тех сборок, на которые нужно ссылаться.
  • В момент исполнения, мы не можем «выкинуть» прослойку, т.к. тогда непонятно откуда брать информацию о местоположении реальных сборок.
  • Фактически, требуются две прокси-сборки. Одна – это прокси для компиляции, вторая – с набором директив type forwarding для исполнения.

Теперь я пытаюсь понять, правильный ли это подход, и если да – как заставить Mono.Cecil делать из «реальных» сборок прокси с пустышками вместо методов. ▪

Advertisements

Written by Dmitri

6 апреля 2011 в 14:33

Опубликовано в .NET

Tagged with

комментариев 15

Subscribe to comments with RSS.

  1. Жаль, нельзя форварднуть класс с указанием его нового имени (в первую очередь namespace; но, если не ошибаюсь, MSIL ничего не знает о namespace)

    И еще интересно, как это дело протестировать… А то при очередном Adjust Namespaces все сломается.

    AlexIdsa

    6 апреля 2011 at 17:49

  2. почему Вы написали, что нельзя использовать ILMerge?

    igor

    6 апреля 2011 at 18:03

    • Нельзя конкретно в моем случае. А в целом, ILmerge очень полезная штука и я ей пользуюсь везде где могу.

      Dmitri

      6 апреля 2011 at 18:11

      • кстати, меня давно мучает такие вопросы:

        — если есть много .dll файлов и я не знаю точно какие загружаются, а какие нет. и если в этом случае с помощью ilmerge я получу один .dll файл, то всегда фактически будет загружаться все (все исходные .dll), хотя реально может быть используется только маленькая часть?

        — если есть две группы .dll файлов, кототрые используют третью группу. стоит ли в этом случае, создать с помощью ilmerge 2 .dll файла, которые внутри себя будут включать эту третью. ведь мы, получается, дублируем код?

        спасибо

        igor

        6 апреля 2011 at 18:27

        • — да, это так, ведь DLL нельзя загрузить частично. с дрой стороны, сборку можно загружать частично :) дело в том, что сборка может формироваться из нескольких модулей, которые будут подгружаться только когда они нужны. к сожалению, visual studio с модулями не очень дружит, да и вообще, мало кто ими пользуется.

          — не очень понял, что значит «дублируем код»? если обе первые группы загружены в одном аппдомене, то и третья группа будет загружена в него одинажды. а если это разные аппдомены, то извините :)

          Dmitri

          7 апреля 2011 at 11:47

  3. 1. Насчет факториала вы не перегнули палку? :)

    2. Насчет Mono.Cecil. А зачем вам пустышки вместо методов? Просто обрабатываем все сборки и для каждого типа прописываем forwarding в общую прокси-сборку. Или я чего-то не уловил?

    AlexIdsa

    6 апреля 2011 at 18:24

    • 1. Может и перегнул. Но О(n2) точно. Каждый раз когда добавляет любую сборку, проверяет каждую из существующих что это не то же самое. Вроде так. И студия делает это ой как медленно.

      2. Нет, против этой сборки нельзя скомпилироваться — компилятор не настолько умен чтобы пойти и найти сборку в которой есть forwarding, подтащить ее, и так далее. К тому же, даже если бы он это умел, IntelliSense и прочие вещи тоже падают, а у них наверняка свои представления по этому поводу. Та конструкция про которую вы пишете нужна на момент исполнения, но на момент компиляции нужна совсем другая.

      Dmitri

      6 апреля 2011 at 21:01

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

    igor

    6 апреля 2011 at 18:31

    • Ну это конкретно мой кейс — я не могу просто взять и замержить все библиотеки. На практике же, все свои SDK делают по разному, единого рецепта нет.

      Dmitri

      6 апреля 2011 at 20:58

  5. Последнее время я задумываюсь над альтернативным ILMerge варианту: в VS можно делать ссылки на cs-файлы из другого проекта. Преимущества я в этом вижу такие:
    1. в VS явно видно как будет слита или разделена моя сборка (соответственно ошибки при компиляции тоже видно раньше, чем сработал бы ILMerge)
    2. можно пойти от разработки максимально большой сборки, разбив её по потребностям на более мелкие в отдельных проектах
    Минусы тоже есть, как минимум, надо добавлять ссылки сразу в несколько проектов. Если в рамках больой сборки тип немного переместится, то для мелких сборок, скорее всего придётся делать этот самый type forwarding. Наверное есть ещё проблемы — надо подумать.

    PermMSUG

    12 мая 2011 at 6:17


Оставить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: