Аспектно-ориентированное программирование (AOP) или почему мы отказались от использования самого маститого вендора AOP — PostSharp. Часть 3

Описание Fody и MethodDecorator

Lisa Botty
ITA Labs
5 min readJul 24, 2018

--

Введение

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

В этой части статьи мы подробно рассмотрим другой AOP-фреймворк — Fody, которым мы решили заменить PostSharp. И попробуем ответить на вопрос, что мы приобретаем и что теряем при этом переходе.

Одним из решающим для нас отличием от PostSharp было то, что Fody является бесплатным open source продуктом, использующим MIT-лицензию. Эта лицензия позволяет свободно использовать и модифицировать Fody и в закрытых коммерческих проектах.

Fody и его плагины

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

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

Для реализации логики постобработки Fody использует плагины. Плагины, как правило, представляют собой отдельные проекты со своими лицензиями, и, как и Fody, выложенные на GitHub.

Перечислим наиболее примечательные, на наш взгляд, плагины:

· MethodDecorator, на котором мы подробно остановимся чуть позже;

· PropertyChanged, генерирует код для реализации интерфейса INotifyPropertyChanged;

· MethodCache, генерирует код, который кэширует результаты вызовов методов.

Помимо этих плагинов существует десятки других. Полный и актуальный список плагинов доступен на GitHub-странице Fody: https://github.com/Fody/Fody#addins-list

Использование Fody

Для того, чтобы использовать Fody в своём проекте достаточно:

1. Добавить в проект NuGet-пакет Fody.

2. Добавить в проект отдельные NuGet-пакеты необходимых плагинов.

3. Добавить в корневой каталог проекта или солюшена файл FodyWeavers.xml такого содержания:

Где WeaverName — это название плагина.

После этого достаточно применить в исходных кодах проекта кастомные атрибуты, которые предоставляют плагины, к своим классам, и при следующей компиляции эти классы будут обработаны Fody.

Возможности плагина MethodDecorator

Как уже было сказано выше, Fody сам по себе не реализует никакой логики постобработки, а только обеспечивает передачу управления в в свои плагины.

Поэтому при замене PostSharp недостаточно рассматривать только Fody в отдельности, а необходимо еще подобрать и плагины, реализующие необходимую функциональность.

MethodDecorator — плагин Fody, реализующий функциональность, аналогичную аспекту OnMethodBoundaryAspect у PostSharp, а именно — добавление своей логики в начало и конец метода.

Для примера, приведем код уже знакомого нам атрибута TraceAttribute, адаптированного под MethodDecorator:

Сразу можно отметить, что в новой реализации TraceAttribute сохранены названия методов OnEntry и OnExit, и оба эти метода сохранили свою семантику.

Но на этом сходства заканчиваются. Отметим следующие различия:

1. Для сохранения имени метода используется отдельное поле _methodName, значение которого задается в новом методе Init, специфичным для MethodDecorator.

2. В сигнатурах методов OnEntry, OnExit нет параметров.

А теперь рассмотрим, какой код генерирует MethodDecorator для этого атрибута:

Что можно отметить из приведенного фрагмента кода?

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

Преимуществом такого подхода можно было бы назвать отсутствие необходимости думать о многопоточности при реализации аспекта. В частности, при реализации аспекта можно свободно использовать поля класса для сохранения состояния между вызовами методов Init, OnEntry, OnException. Но при переносе кода адаптированного под PostSharp, где проблемы с многопоточностью уже решены, это не является преимуществом.

Подводя итог, в целом, описанная выше функциональность и является единственной возможностью MethodDecorator. И хотя она позволяет реализовать аспекты, сделанные на основе OnMethodBoundaryAspect из PostSharp, при этом необходимо учитывать разницу в генерируемом коде и то, как она может сказаться на производительности приложения.

Другие возможности Fody+MethodDecorator?

Если вспомнить предыдущую часть статьи, помимо OnMethodBoundaryAspect в наших проектах используются и атрибуты, сделанные на основе аспекта MethodInterceptionAspect. Плюс код некоторых аспектов опирался на уникальную возможность PostSharp: инициализацию аспектов во время компиляции через метод.

Если подобная функциональность в плагинах Fody? Ответ: нет.

Для наглядности, приведем табличку, где приведем используемые нами возможности PostSharp и их аналоги в Fody (если они есть):

Возможности PostSharp и аналог в Fody

  1. Аспект OnMethodBoundaryAspect — Аналог в Fody: реализовано плагином MethodDecorator

2. Возможность инициализации на этапе билда (метод CompileTimeInitialize) — Аналога в Fody нет

3. Аспект MethodInterceptionAspect — Аналога в Fody нет

Поэтому при переходе на Fody встает вопрос и о том, каким образом заменить данную функциональность из PostSharp?

Что дальше?

При прочтении данной статьи может сложиться впечатление, что перспективы перехода с PostSharp на Fody выглядят сомнительными. Fody не только не покрывает используемые нами возможности PostSharp, но и делает это хуже для аналогичной функциональности.

Но, и Fody, и MethodDecorator являются продуктами с открытым исходным кодом, поэтому при необходимости их можно довести до ума своими руками, чем мы и занялись.

Перед нами, прежде всего, встали такие проблемы:

1. «Научить» MethodDecorator генерировать более оптимальный, с точки зрения производительности, код, аналогично PostSharp.

2. Придумать, как сделать альтернативу MethodInterceptionAspect для Fody.

В следующей части мы подробно рассмотрим, как реализованы плагины для Fody, заглянем под «капот» плагина MethodDecorator и продемонстрируем решение указанных выше проблем.

--

--