[Из песочницы] Yet another AOP in .NET
У многих .NET разработчиков, использовавших в своей практике WPF, Silverlight или Metro UI, так или иначе возникал вопрос «а как можно упростить себе реализацию интерфейса INotifyPropertyChanged и свойств, об изменениях которых нужно сигнализировать?».Самый простой «классический» вариант описания свойства, поддерживающего оповещение о своем изменении, выглядит так:
public string Name { get { return _name; } set { if (_name!= value) { _name = value; NotifyPropertyChanged («Name»); } } } Чтобы не повторять в каждом сеттере похожие строки, можно сделать вспомогательную функцию. Более того — начиная с .NET 4.5 в ней можно использовать атрибут [CallerMemberName], чтобы явно не указывать имя вызывающего ее свойства. Но основной проблемы это не решает — все равно для каждого нового свойства необходимо явно описывать поле и геттер с сеттером. Такая механическая работа неинтересна, утомительна и может приводить к ошибкам при копировании и вставке.Хотелось бы немного «магии», которая позволит небольшими усилиями (например, одной строчкой кода) сделать вот такой класс совместимым с INotifyPropertyChanged:
public class Person { public string FirstName { get; set; } public string LastName { get; set; } public DateTime Birth { get; set; } } Следует отметить, что это не единственная задача, в которой хотелось бы упростить себе жизнь при добавлении сквозной функциональности. Типовые задачи логирования вызванных методов определенного класса, замеров их времени выполнения, проверка наличия прав для вызова — это все, что требует общего решения, избавляющего от надобности вписывать похожие куски кода в каждую место где это нужно.Более-менее опытный разработчик тут же скажет — «Это же аспектно-ориентированное программирование!» и будет прав. И если начать перечислять уже существующие библиотеки под .NET платформу, в которых в той или иной мере имеется возможность использовать АОП, то список будет не такой уж и короткий: PostSharp, Unity, Spring .NET, Castle Windsor, Aspect .NET… И это далеко не все, но тут следует задуматься о механизмах, реализующих вставку сквозной функциональности, о их достоинствах и недостатках. Можно выделить два основных способа:
Подстановка во время компиляции (PostSharp) Генерация прокси-классов во время выполнения (Unity, Spring .NET, Castle Windsor) Подстановка во время компиляции — наиболее выгодный способ, так как не требует никаких дополнительных затрат вычислительной мощности при выполнении программы, что особенно важно для мобильных устройств. Привычная генерация прокси-классов хоть и проще в реализации, но помимо вычислительных затрат еще и имеет ограничения — методы или свойства должны содержаться в интерфейсе или быть виртуальными, чтобы их можно было перехватывать через прокси-класс.PostSharp предлагает очень большие возможности по использованию аспектно-ориентированного программирования, но это коммерческий продукт, что для многих проектов может быть неприемлемо. В качестве альтернативы мы разработали и продолжаем совершенствовать Aspect Injector — фреймворк, позволяющий применять аспекты на этапе компиляции и обладающий простым, но в то же время гибким интерфейсом.
Простейший пример использования Aspect Injector — логирование вызовов методов. Сперва необходимо описать класс аспекта:
public class MethodTraceAspect { [Advice (InjectionPoints.Before, InjectionTargets.Method)] public void Trace ([AdviceArgument (AdviceArgumentSource.TargetName)] string methodName) { Console.WriteLine (methodName); } } Данное описание говорит о том, что при применении этого аспекта к любому другому классу, в начале каждого его публичного метода будет добавлен вызов Trace () с именем самого метода в качестве параметра. [Aspect (typeof (MethodTraceAspect))] public class Target { public void Create () { /* … */ } public void Update () { /* … */ } public void Delete () { /* … */ } } После такого объявления Create, Update, Delete будут печатать в консоль свои названия при каждом вызове. Следует отметить, что атрибут Aspect можно применять не только к классам, но и к конкретным членам класса, если нужна «точечная врезка».Если вернуться к исходной задаче реализации интерфейса INotifyPropertyChanged — с помощью Aspect Injector можно создать и успешно использовать следующий аспект:
[AdviceInterfaceProxy (typeof (INotifyPropertyChanged))] public class NotifyPropertyChangedAspect: INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged = (s, e) => { };
[Advice (InjectionPoints.After, InjectionTargets.Setter)] public void RaisePropertyChanged ( [AdviceArgument (AdviceArgumentSource.Instance)] object targetInstance, [AdviceArgument (AdviceArgumentSource.TargetName)] string propertyName) { PropertyChanged (targetInstance, new PropertyChangedEventArgs (propertyName)); } } У всех публичных свойств всех классов, к которым будет привязан этот аспект, в конце сеттера выполнится RaisePropertyChanged. Причем интерфейс, указанный в атрибуте AdviceInterfaceProxy будет добавлен к классу самим фреймворком во время компиляции.На странице проекта можно найти более детальную информацию об атрибутах и их параметрах, доступных на данный момент. Будем благодарны за любые отзывы и предложения по развитию Aspect Injector!
