Так как это была обычная типовая задача, а мой товарищ был не склонен думать долго над тем, что и так очевидно, то результат появился довольно быстро. REST-сервис был наш, на WCF, соответственно была введена общая сборка MyProj.Abstracitons, куда перекочевали интерфейсы сервисов. В ней же нам требовалось писать классы, которые реализовывали интерфейс сервиса и занимались проксированием запросов к нему и десериализацией результата. Идея была простая: на каждый сервис мы пишем по клиенту, который реализует тот же интерфейс, соответственно как только мы меняем любой метод в сервисе, у нас выскакивает ошибка компиляции. И мы предполагаем, что человек, меняя аргумент у функции проследит, чтобы она правильно сериализовывалась. Выглядело это примерно так: Где BaseClient<TService> — это такая тоненькая обертка над HttpClient, которая определяет, какой метод мы пытаемся вызвать (GetFoo в данном случае), вычисляет его URL, посылает запрос, забирает ответ, десериализовывает результат (если надо) и отдает его. То есть: В принципе, не сложно, оно даже работало, но после написания 20 метода у 30го класса, которые были абсолютно однотипными, люди постоянно забывали написать NoInlining, из-за чего все ломалось (Little quiz #1: как думаете, почему?), я себе задал вопрос «а нельзя ли как-то по-человечески к этому подойти?». Но, задача была уже вмержена в мастер, и сверху мне было сказано «иди фичи пили, а не фигней страдай». Однако, идея тратить по 3 часа в день на написание всяких врапперов мне совсем не нравилась. Не говоря про кучу атрибутов, то, что люди периодически забывали синхронизировать сериализацию со своими изменениями и всю подобную боль. Поэтому дожив до ближайших выходных и задавшись целью как-то улучшить ситуацию, за пару дней набросал альтернативное решение.
 Идея тут была еще проще: что нам мешает делать всё то же самое, но не руками, а генерировать динамически? У нас совершенно однотипные задачи: взять входные аргументы, преобразовать их в два словаря, один для аргментов queryString, остальные как аргументы тела запроса, и просто вызвать какой-нибудь типовой HttpClient с этими параметрами. В итоге, все проблемы с теми же SerializationConstant решались тем, что они писались только один раз в этом обработчике, что позволяло их реализовать корректно единожды, и всегда радоваться правильному результату. После не очень продолжительного курения документации и stackoverflow, MVP был готов.  Теперь, для использования сервиса, просто: Используем: В этом тесте, конечно, поднимается реальный WCF-сервис, который делает реальный запрос, так что строго говоря это не юнит-тест. Но, все мы учимся на своих ошибках, сейчас бы я замокал зависимости и сделал всё иначе, но, на тот момент я этого еще не умел. Всё очень просто, понятно, не требует особых магий вроде наследования специальных классов или навешивания атрибутов. Переменные и имена методов выводятся автоматически. В общем, красота. Тем более, что пункт 2 можно опустить, если не лень каждый раз указывать константную строку с именем сервиса. Как оно работает? На самом деле, на достаточно чёрной магии. Вот основной кусок, ответственный за генерацию прокси методов: обратите внимание на строчку c ReflectionHelper.GetPropertyInterfaceImplementation<IWcfRequest>(). Как думаете, зачем она понадобилась? Рефлексия ради рефлексии, человеку интереснее писать код, который генерирует то, что он хочет, вместо того, чтобы просто его написать? Основная суть тут в том, что мы с помощью Expression’ов генерируем тело метода, в котором мы все аргументы кладем либо в тело, либо в queryString, а потом используя расширение CompileToInstanceMethod компилируем его не в делегат, а сразу в метод класса. Делается это не очень сложно, хотя до получения рабочего варианта было проведено несколько десятков итераций, пока выкристализовался правильный: Самое печальное, что это еще относительно читаемый вариант, от которого пришлось отказаться после переезда на Core, потому что там убрали апишку CompileToMethod. Как следствие, можно генерировать анонимный делегат, но нельзя генерировать метод класса. А это-то нам и нужно было. Поэтому в коровской версии всё это заменяется на старый добрый ILGenerator. Типичный трюк, который я делаю в таком случае – просто пишу C# код, разбираю его ildasm’ом и смотрю, как он работает, в каких местах нужно подправить, чтобы покрыть общий случай. Если пытаться же писать IL самому, то в 99% случаев можно получить ошибку Common Language Runtime detected an invalid program :). Но в этом случае итоговый код понять намного тяжелее, чем относительно читаемые экспрешны. Вопрос выпиливания этой апишки из кора обсуждается здесь (нас интересует первый пункт в списке), хотя реквест выглядит довольно мертвым. Но не все так плохо, ведь было найдено еще более хорошее решение!
 

После переписывания и отлаживания всего этого дела в сотый раз я задался вопросом, почему нельзя всё это делать на этапе компиляции? Да, с помощью кэширования сгенерированных типов оверхед на использование клиентов ничтожный, мы платим всего лишь за вызов Activator.CreateInstance, что в контексте совершения целого HTTP-запроса мелочь, тем более, что их можно использовать как синглтон, т.к. никакого состояния кроме URL сервиса в нем нет. Но всё же, у нас тут прилично ограничений: Как раз в это время я слышал много интересного про Roslyn – новый модульный компилятор от Microsoft, который позволяет неплохо покопаться в процессе. Изначально я очень надеялся, что в нем как в LLVM можно просто написать middleware для нужной трансформации, однако после прочтения документации возникло впечатление, что на Roslyn полноценной кодогенерации без лишних телодвижений со стороны пользователя сделать нельзя: тут уж либо свой кастомный компилятор поверх (как например это сделано в проекте замены LINQ на циклы, но по понятным причинам это не очень удобно), либо анализатор в стиле «вы тут забыли запятую, давайте я вам её вставлю». И тут я наткнулся на интересный фич реквест в гитхаб репозитории языка на эту тему (тыц), но тут быстро обнаружилось две проблемы: во-первых до релиза этой фичи еще очень долго, а во-вторых мне достаточно быстро сказали, что она даже в рабочем виде мне никак не поможет. Хотя все было не так плохо, ибо в комментариях мне дали ссылку на интересный проект, который вроде бы должен был делать, что мне нужно. Поковырявшись несколько дней и, освоив базовый проект, я понял – оно работает! И работает так, как нужно. Просто какая-то магия. В отличие от написания собственного компилятора поверх обычного, тут мы пишем обычный nuget-пакет, который можем просто подключить в решение, и он во время билда сделает своё черное дело, в нашем случае – сгенерирует код клиента для сервиса. Полная интеграция со студией, делать ничего не надо – лепота. Правда, подсветка после первой установки решения работать не будет, но после ребилда и переоткрытия солюшена будет и подсветка, и IntelliSense! Правда, работает не всё: например, как заставить показывать расширеную документацию от интерфейса при помощи <inheritdoc /> я так и не понял, студия почему-то просто не хочет этого делать. Ну да ладно, основное дело сделано – классы сгенерированны, они работают, и результат генерации всегда можно подсмотреть и поправить, устанавливается одним кликом через нугет. Всё, как мы хотели. Для пользователя использование выглядит вот так: 
Просто пишем интерфейс, вешаем пару атрибутов, компилируем, и можем пользовать сгенерированным классом. PostSharp не нужен! (шутка). Итак, как же оно всё работает?
 Изначально я не собирался лезть глубоко, т.к. уже была готовая библиотечка, которая полностью отвечала моим требованиям, оставалось только написать анализатор и сделать пакет. Однако, реальность оказалась более жестокой, и ловя ошибки, то из-за моего неправильного использования предоставленного АПИ, то из-за ошибок или недоработок в самой библиотеки неизбежная расплата все же меня настигла. Пришлось и разбираться, и контрибьютить, чтобы в итоге все завелось как на картинке выше. Практически вся соль, на самом деле, заключается в новом тулчейне .Net Core: По сути это способ определять middleware при построении проекта. После этого компилятор понимает, что такое dotnet-codegen и умеет его вызывать. При сборке же проекта вы можете увидеть что-то подобное:
 В результате, текущая версия этой библиотеки позволяет вам повесить атрибут на какой-то класс, написать буквально 100 строк кода, который на основании него сгенерирует вам всё, что нужно. Есть ограничение, что нельзя генерировать классы для другой сборки, то есть сгенерированные классы всегда добавляются в ту же сборку, которая компилируется, но в принципе с этим можно жить.
 Когда я писал эту библиотеку, я рассчитывал, что она будет кому-то полезна, но потом несколько разочаровался, т.к. сваггер выполняет ту же задачу, но при этом кроссплатформенный, и имеет удобный интерфейс. Но, всё же в моем случае можно просто поменять тип, сохранить файл и сразу получить ошибку компиляции. То, ради чего всё и затевалось:  И что немаловажно, я получил море удовольствия, реализуя всё это дело, а также неплохо, как мне кажется, прокачался в знании языка и компилятора. Поэтому и решил написать статью: может, новый сваггер миру и не нужен, но зато если вам нужна кодогенерация, Т4 вы презираете или он вам не подходит, а рефлексия не наш вариант, то вот – отличный инструмент, который просто выполняет свою работу, замечательно интегрируется в текущий пайплайн и в итоге распространяется просто как нугет-пакет. Да еще и подсветка от студии в комплекте! (но только после первой генерации и переоткрытии солюшена). Сразу скажу, что я не пробовал этот процесс с не-core проектами, со взрослым фреймворком, возможно там будут какие-то сложности. Но учитывая, что таргеты этого пакета включают в себя portable-net45+win8+wpa81, portable-net4+win8+wpa81 и даже net20, то особых сложностей быть не должно. А даже если что-то не нравится, лишние зависимости или там NIH – всегда можно сделать свою, более кошерную, реализацию, благо кода так уж и много. Другой подводный камень — отладка, как дебажить всё это добро я не разобрался, код писался вслепую. Но автор родной библиотеки CodeGeneration.Roslyn определенно обладает нужными знаниями, достаточно посмотреть на структуру проекта, просто в итоге я обошелся без них. И теперь могу с чистой совестью сказать: я совершенно не жалею, что написал очередной сваггер.  Ссылки: Все мои проекты под MIT-лицензией, форкайте-изучайте-ломайте как хотите, никаких претензий не имею :) Изначально все это планировалось как вполне рабочий проект, появившийся в результате реальных требований, так что это всё можно использовать в продакшне, в крайнем случае после минорной допилки. Ну и ответы на вопросы, конечно же: Зигохистоморфирующий