Аспектно-ориентированное программирование на C# с использованием PostSharp

19.01.2013 at 21:42

Что такое АОП?

В любых крупных проектах появляются задачи, которые затрагивают множество различных классов. Например логирование, кеширование, профилирование и т.д. Чаще всего это приводит вот к такому коду:

  AddToLog("Начало записи в файл.");
  // Здесь какие-либо действия.
  AddToLog("Конец записи в файл.");

Некрасиво, неудобно, легко забыть.
Парадигма аспектно-ориентированного программирования (АОП) предлагает выносить такую функциональность в отдельные классы (логично, не правда ли?) и подключать их к коду.
Для C# существует множество библиотек для АОП и они предлагают разные способы подключения дополнительной функциональности: Unity — конфиги, Spring.Net — создание прокси классов, PostSharp — атрибуты.
Подробнее про АОП можно почитать на википедии.

PostSharp

Сайт библиотеки
Библиотека платная, но есть бесплатная версия с урезанным функционалом. Ее нам будет вполне достаточно.
Скачиваем, подключаем к проекту. Приступаем к написанию кода.

Логирование

Для примера рассмотрим логирование запуска и завершения методов.
Создаем класс LoggingAttribute, наследуем его от OnMethodBoundaryAspect, для этого нужно будет подключить PostSharp.Aspects.
Переопределяем методы OnEntry, OnExit, OnException. В них будет обработка начала выполнения метода, завершения и генерация исключения в методе.
Получается вот такой класс:

using System;
using PostSharp.Aspects;

namespace AOPTestProject
{
  [Serializable]
  class LoggingAttribute : OnMethodBoundaryAspect
  {
    public override void OnEntry(MethodExecutionArgs args)
    {
      Console.WriteLine("Method {0} started.", args.Method.Name);
    }

    public override void OnExit(MethodExecutionArgs args)
    {
      Console.WriteLine("Method {0} executed.", args.Method.Name);
    }

    public override void OnException(MethodExecutionArgs args)
    {
      Console.WriteLine("Method {0} raised {1}: {2}", args.Method.Name, 
        args.Exception.GetType().Name, args.Exception.Message);
    }
  }
}

Использование атрибута

Указываем атрибут у метода, который нас интересует.

  [Logging]
  private static float DivideInts(int x, int y)
  {
    return x / y;
  }

  static void Main(string[] args)
  {
    try
    {
      var result = DivideInts(6, 2);
      var result1 = DivideInts(6, 0);
    }
    catch
    {
    }
    Console.ReadLine();
  }

Получаем вот такой результат:

Другие способы подключения атрибута

Понятно, что подключать атрибут к каждому методу неудобно. Вместо этого можно подключить его к классу — тогда он будет работать для всех методов класса.
А можно и подключить его прямо к сборке, например в файле AssemblyInfo.cs:

[assembly: AOPTestProject.Logging]

Теперь обработка включена для всех методов в сборке. Для нашего проекта результат будет выглядеть так:

Видно, что добавился вызов метода Main.