Аспектно-ориентированное программирование на C# с использованием PostSharp
Что такое АОП?
В любых крупных проектах появляются задачи, которые затрагивают множество различных классов. Например логирование, кеширование, профилирование и т.д. Чаще всего это приводит вот к такому коду:
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.