Многие .
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 — журналирование вызовов методов.
Сначала нужно описать класс аспекта: 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 можно применять не только к классам, но и к конкретным членам класса, если необходима «точечная вставка».
Вот пример декомпилированного кода, полученного после компиляции примера выше: public class Target
{
private readonly MethodTraceAspect __a$_MethodTraceAspect;
public void Create()
{
this.__a$_MethodTraceAspect.Trace("Create");
}
public void Update()
{
this.__a$_MethodTraceAspect.Trace("Update");
}
public void Delete()
{
this.__a$_MethodTraceAspect.Trace("Delete");
}
public Target()
{
this.__a$_MethodTraceAspect = new MethodTraceAspect();
}
}
Здесь вы также заметите, что атрибут Aspect был удален во время генерации кода, что позволило полученной сборке не ссылаться на сборку Aspect Injector.
Если вернуться к исходной задаче реализации интерфейса 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! Теги: #.
NET #aop #программирование #.
NET
-
Еще 5 Макетов От Vwo
19 Oct, 24 -
Демка "Фоллаута"
19 Oct, 24 -
Sap Подарила Музею Редкие Компьютеры
19 Oct, 24