Аспектно-ориентированное программирование. PostSharp

Аспектно-ориентированное программирование

АОП — парадигма программирования, в основе которой лежит идея выделения сквозной функциональсти в отдельные сущности — аспекты (aspects). Сквозной называют функциональсть, рассредоточенную по разным частям программы. В качестве примеров сквозной функциональности можно привести задачи логирования, трассировки, обработки исключений, проверки пред- и постусловий в контрактном программировании и проверки прав доступа. АОП может так же использоваться для решения задач защиты и многопоточности.

PostSharp

Фреймворк PostSharp — это реализация аспектно-ориентированного подхода для .NET. PostSharp в отличие от многих своих аналогов работает как пост-компилятор, то есть он вносит изменения в MSIL (Microsoft Intermediate Language).
PostSharp позволяет легко создавать атрибуты, которые меняют поведение методов, полей и типов. Для этого нужно унаследовать класс атрибута от одного из предоставляемых библиотекой базовых классов, реализовать его виртуальные методы и применить этот атрибут.


Пример 1:
using System;
using PostSharp.Aspects;

namespace HelloAspects
{
    class Program
    {
        private static void Main()
        {
            hello();
        }

        [SayGoodbye]
        private static void hello()
        {
            Console.WriteLine("Hello!");
        }
    }

    [Serializable]
    class SayGoodbyeAttribute : OnMethodBoundaryAspect
    {
        public override void OnExit(MethodExecutionArgs args)
        {
            Console.WriteLine("Goodbye.");
        }
    }
}

В результате на консоль будет выведено:

Hello!
Goodbye.

В данном примере показано, как при помощи атрибута SayGoodbye изменилось поведение метода hello().

Метод OnExit называют советом (advice), он всегда выполняется (даже если выпадет исключение, так как OnExit вызывается из блока finally) после тела метода, к которому применяется атрибут. Помимо него класс OnMethodBoundaryAspect предоставляет ещё три совета:
  • OnEntry — выполняется перед телом метода;
  • OnSuccess — выполняется после успешного выполнения тела метода;
  • OnException — выполнятся после тела метода в случае, если в методе выпало необработанное исключение.

Хотя пример 1 и иллюстрирует некоторые возможности PostSharp, от него мало практической пользы. В примере 2 показана реализация трассировки при помощи АОП.

Пример 2:
using System;
using System.Diagnostics;
using PostSharp.Aspects;

namespace HelloAspects
{
    class Program
    {
        private static void Main()
        {
            Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));
            hello();
        }

        [Trace]
        private static void hello()
        {
            Console.WriteLine("Hello!");
        }
    }

    [Serializable]
    public class TraceAttribute : OnMethodBoundaryAspect
    {
        public override void OnEntry(MethodExecutionArgs args)
        {
            Trace.WriteLine(string.Format("Entering {0}.{1}.", args.Method.DeclaringType.Name, args.Method.Name));
        }

        public override void OnExit(MethodExecutionArgs args)
        {
            Trace.WriteLine(string.Format("Leaving {0}.{1}.", args.Method.DeclaringType.Name, args.Method.Name));
        }
    }
}


В чём же преимущество использования АОП в данном примере? Представим, что у нас есть несколько классов, в каждом из которых много методов и нам необходимо реализовать трассировку. Если не использовать АОП, то придётся в теле каждого метода прописывать Trace.WriteLine… Используя же АОП мы выделяем эту сквозную функциональность в отдельную сущность (аспект) и применяем её к методам при помощи атрибута.

Разумеется, в PostSharp есть аспекты и помимо OnMethodBoundaryAspect:
  • EventInterceptionAspect
  • LocationInterceptionAspect
  • OnExceptionAspect
    и многие другие.

PostSharp — это удобный инструмент для внедрения АОП в программы, написанные с использованием среды .NET. АОП дополняет ООП, выделяя сквозную функциональсть в отдельные аспекты, избавляется от дублирования кода (принцип DRY – Don't Repeat Yourself) и упрощает архитектуру приложения.

P.S. Этот пост был написан на основе работы по учёбе, которую я делал в институте.
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 24

    +2
    Глядя на все эти Mono-cecil чудеса всегда хочется задать вопрос: как потом дебажить?
      +2
      Установкой брейкпоинтов в методы аттрибута, например. Какие-то высосанные из пальца сложности ИМХО.
        0
        А с установкой брейкпоинтов в исходные методы проблем не будет?
          +2
          Не должно быть.
          В данном примере вы можете поставить брейкпоинт как в методах атрибута, так и в методе hello.
            0
            Спасибо, это важный момент.
            0
            Mono-cecil по моему и debug информацию правит, так что все ок.
        0
        Интересно! А если методов много, есть ли способ делать трассировку всех вызываемых методов из определенной сборки?
          +2
          Разумеется можно. Добавьте в AssemblyInfo.cs интересующей вас сборки
          [assembly: Trace]
            0
            Спасибо, выручили! А раньше приходилось руками писать :)
          +1
          инструмент очень полезный. но думаю было б лучше если б рассмотрели на примере поживее — например разграничение прав доступа или управление транзакциями к базе.
            0
            В целом этого вполне достаточно, думаю каждый из нас знает про трэйсинг.
              0
              Достаточно для того, чтобы понять как это использовать в целом.

              Конкретные примеры по типичным задачам — большое подспорье при изучении подходов.
            0
            Вот топик — сборник полезных аспектов.
              0
              Интересно как эти чудеса будут работать в medium trust environment? В противном случае для реального приложения придется держать выделеный сервер.
                +2
                Так вы же IL меняете, сразу как у себя приложение собираете. Поэтому они будут работать так же, как если бы вы руками писали каждый раз Trace.WriteLine(«blah-blah»). Если с АОП не будет работать,, то и без него тоже.
                Или я что-то не так понял?
                0
                Насколько я помню, для четвертого фреймворка постшарп уже только платный, по крайней мере для коммерческих приложений. Да и время сборки он заметно увеличивает. То есть инструмент интересный, но как и все — не без ограничений
                  0
                  Почти во всех популярных IoC/DI — фреймворках есть поддержка АОП. Например, в Spring.net и Castle.
                  Время сборки увеличено не будет, только время первого старта приложения. Причем, никакие левые атрибуты вешать не придется, код будет чистым poco. А тулзы типа PostSharp — это для энтузиастов.
                    +1
                    Собственно поэтому я и юзаю Spring.Aop из Spring.Net, но и у него есть тоже пара недостатков в сравнении с PostSharp, связанные как раз с тем, что там аспекты навешиваются не в CompileTime, а в RunTime через генерацию динамических проксиков:
                    1. Нельзя просто создавать объекты через конструктор, так как тогда они не будут обернуты в динамические проксики с аспектами. Но это совсем не проблема, если все создается через IoC.
                    2. Если через Introduction Advice (в терминах Spring.Aop) добавлять к классу реализацию какого-то интерфейса, например INotifyPropertyChanged, то внутри самого класса нельзя сказать (INotifyPropertyChanged)this, так как понятно, что this — это ссылка на сам объект, а не его проксик с аспектами, и сам объект это интерфейс не реализует. Пару раз нарывался.
                    3. Забыл уже конкретику, но была проблема с биндингами WPF на объект, обернутый в проксик с аспектами, связанный с особенностью реализации Weak Events в биндингах WPF. Проблема решаемая, но очень неочевидная и в свое время пришлось ковырять исходники .Net, чтобы с ней разобраться.

                    У АОП через проксики есть и плюсы — например, можно на лету делать подмену таргета (объекта, который оборачивается в проксик). Через это, например, делается ленивая загрузка свойств в NHibernate, при этом исходный объекто остается POCO.
                      0
                      А вообще, хорошую инфу про разные способы реализации АОП в .Net, их плюсы и минусы, видел тут — http://ayende.com/Blog/archive/2007/07/02/7-Approaches-for-AOP-in-.Net.aspx
                    0
                    есть способ поставить, например, Trace на класс, но чтобы он «вызывался» на все методы этого класса?
                      0
                      Да, просто напишите атрибут перед классом.
                      [Trace]
                      class ClassA
                      {
                      ...
                      
                      0
                      мне кажется, что логирование это динамическая штука, т.е. в зависимости от контекста ее нужно включать или выключать. а PostSharp вынуждает прямо в исходниках низкоуровневых классов вставлять атрибуты?
                      а если не хочется менять исходники или их нет?
                      не является ли dynamic proxy более подходящим инструментом для данного типа задач? ведь он требует менять только контекст, а не низкоуровневые классы?
                        0
                        Ну если нужно включить логи для всех классов сборки, можно указать атрибут в AssemblyInfo.cs (т.е. нет необходимости вписывать это в каждый класс).

                        Согласен, для случая, когда нет исходников больше подходит dynamic proxy. Но логи — это лишь одна из задач, которую можно решить при помощи АОП.
                          0
                          Используйте аттрибут Conditional для аттрибута (забавно звучит :) )

                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                        Самое читаемое