Специализированные атрибуты (Часть 1)

Прочитав данную статью, вы поймете, как PostSharp может перевести ваши специализированные атрибуты на новый уровень и позволит им добавить новое поведение к вашему коду. Вы узнаете, как инкапсулировать авторизацию, производительность инструментального оснащения или проверку полей в специализированные атрибуты, а также получить значительные преимущества.

Авторизация нагружает

Существует множество мощных структур авторизации, но каждая из них требует много работы: добавление стандартного кода для каждого метода. А что, если в середине проекта вы решите изменить одну структуру на другую? Вам придется модифицировать все методы авторизации, а их может быть тысяча.

Авторизация нагружает, потому что, как и множество нефункциональных требований (безопасность, транзакции, кэширование …), она пересекается со всеми функциональными требованиями. Если у вас сотня бизнес-процессов, которые требуют авторизацию, безопасность и поддержку транзакций, то вы, скорее всего, будете иметь инструкции авторизации, безопасности и транзакций в каждом из данных процессов. Поэтому нам необходим лучший путь инкапсуляции, способ, который позволит нам модифицировать каждый метод, к которому он будет применим.

Специализированные атрибуты являются хорошим решением данной проблемы.

Тривиальный специализированный атрибут авторизации

То, что нам необходимо, очень просто: специализированный атрибут, который записывает сообщение до и после выполнения метода, к которому он будет применен. Мы также захотим указать категорию трассировки, используя конструктор специализированного атрибута.

Итак, в идеале нам хотелось бы использовать специализированный атрибут следующим образом:

[Trace("MyCategory")]
void SomeTracedMethod()
{
   // Тело метода.

}

Пора это реализовать - мы можем начать с объявления специализированного атрибута стандартным образом. Все что нам нужно это поле, названное category и конструктор, инициализирующий данное поле:

public sealed class TraceAttribute : Attribute
{
    private readonly string category;

    public TraceAttribute( string category )
    {
        this.category = category;
    }
 
    public string Category { get { return category; } }
}

Затем мы можем добавить все вещи, которые позволяют данному атрибуту по-настоящему изменять методы, к которым он будет применен. Как уже оговаривалось во введении, мы будем использовать PostSharp для данной работы, поэтому давайте добавим его к проекту:


Все что нам необходимо выполнить, так это заставить наш специализированный атрибут наследовать PostSharp.Laos.OnMethodBoundaryAspect вместо System.Attribute. Этот класс определяет новые методы, которые будут вызваны во время выполнения целевых методов:

  • OnEntry ­– до того как метод будет выполнен.
  • OnSuccess – когда метод возвращается успешно (без исключений).
  • OnException – когда метод выходит с исключением.
  • OnExit – когда метод выходит, несмотря  на успешность.

Мы только реализуем методы, которые нам необходимы: OnEntry и OnExit.

public override void OnEntry(MethodExecutionEventArgs eventArgs)
{
  // Здесь необходимо добавить код трассировки.

}

public override void OnExit(MethodExecutionEventArgs eventArgs)
{
  //Здесь необходимо добавить код трассировки.

}

Реализация данных методов должна вызывать System.Diagnostics.Trace.WriteLine. Но как мы можем узнать название метода, в котором мы находимся? Без проблем - вся необходимая информация содержится в объекте MethodExecutionEventArgs, который передается OnEntry и OnExit. Нам интересно свойство eventArgs.Method, но если мы также хотим записывать значения параметров, мы можем получить это, используя метод GetArguments().

Вот конечная реализация нашего трассирующего специализированного атрибута:

[Serializable] 
public sealed class TraceAttribute : OnMethodBoundaryAspect
{
    private readonly string category;
   
    public TraceAttribute( string category )
    {
        this.category = category;
    }
    
    public string Category { get { return category; } }
    
    public override void OnEntry( MethodExecutionEventArgs eventArgs )
    {
        Trace.WriteLine(
            string.Format( "Entering {0}.{1}.",
                           eventArgs.Method.DeclaringType.Name,
                           eventArgs.Method.Name ),
            this.category );
    }
    
    public override void OnExit( MethodExecutionEventArgs eventArgs )
    {
        Trace.WriteLine(
            string.Format( "Leaving {0}.{1}.",
                           eventArgs.Method.DeclaringType.Name,
                           eventArgs.Method.Name ),
            this.category );
    }
}

Вы ничего не заметили? Класс оснащен специализированным атрибутом Serializable. Это необходимо для каждого атрибута, который составлен в PostSharp Laos.

Теперь давайте испробуем данный атрибут на примере:

internal static class Program 
{
    private static void Main()
    {
        Trace.Listeners.Add(new TextWriterTraceListener( Console.Out));
       
        SayHello();
        SayGoodBye();
    }
 
    [Trace( "MyCategory" )]
    private static void SayHello()
    {
        Console.WriteLine("Hello, world." );
    }
 
    [Trace("MyCategory")]
    private static void SayGoodBye()
    {
        Console.WriteLine("Good bye, world.");
    }
}

Давайте выполним программу и увидим запись вызванных методов.


Как же все сработало? Если вы посмотрите на окно Output в Visual Studio, то вы увидите, что PostSharp был вызван во время процесса сборки.


PostSharp на самом деле изменил выходные результаты компилятора C# и улучшил сборку таким образом, что методы нашего трассирующего атрибута вызываются во время выполнения программы.

Изучив результирующую сборку при помощи Lutz Roeder's Reflector, мы можем увидеть много интересного:

 

Как вы могли уже заметить, наш в прошлом маленький метод теперь намного сложнее потому, что PostSharp добавил инструкции вызова нашего специализированного атрибута во время выполнения.

Déjà vu?

Если вы думаете о том, что это очень похоже на Аспектно-ориентированное программирование (АОП), то вы правы - PostSharp Laos на самом деле представляет собой не что иное, как АОП-структуру.

Аспект определен как модуль или класс, реализующий сквозную функциональность. Аспект изменяет поведение остального кода, применяя совет в точках соединения, определённых некоторым срезом. В большинстве случаев в бизнес-приложениях, аспект является нефункциональным требованием, как авторизация, безопасность, управление транзакциями, обработка исключений или кэширование. Данное разделение является одним из основных принципов теории разработки программного обеспечения. Оно гласит о том, что фрагменты кода, реализующие одинаковое решение, должны быть сгруппированы в компоненты. Измерение качества разработки заключается в высоком уровне сцепления, но низком уровне связанности компонент.

АОП-структура позволяет инкапсулировать аспекты в модульные сущности - в PostSharp Laos, данные сущности являются специализированными атрибутами. Основным преимуществом данного подхода является простота: вы получаете АОП без типичной кривой обучаемости. В дополнение PostSharp Laos независим от языка, и его интеграция в Visual Studio (Intellisense, отладчик …) великолепна.

Другой функцией, которая отличает PostSharp, является то, что он оперирует на уровне MSIL после компиляции. Он не имеет ограничения решений на основе прокси - вы можете добавлять аспекты к закрытым методам (private) и вам не нужно наследовать классы от MarshalByRefObject и т.д.

Если вы заинтересованы в АОП, то вам стоит изучить данную область для вашей же пользы.

Шаг вперед: множественное применение специализированных атрибутов

Итак, у нас есть трассирующий специализированный атрибут. Но что, если мы хотим иметь сотни методов для трассировки? Необходимо ли нам добавлять данный специализированный атрибут к каждому из них? Конечно же - нет! Благодаря такому механизму, как множественное применение атрибутов, мы можем применить специализированный атрибут ко множеству методов в одной единой строке.

К примеру, добавление TraceAttribute к классу Program на самом деле применяет атрибут к каждому методу в классе:

[Trace( "MyCategory" )] 
internal static class Program
{
… 

И теперь если мы не хотим применить данный специализированный атрибут к методу Main, мы можем ограничить набор методов и названий методов, к которым он будет применен:

[Trace( "MyCategory", AttributeTargetMembers = "Say*")] 
internal static class Program
{

В качестве альтернативы мы можем добавить специализированный атрибут к уровню сборки для трассировки всех методов, определенных в этой сборке:

[assembly: Trace("MyCategory")] 

Тем не менее, необходимо избежать применения атрибута к самому классу TraceAttribute, потому что аспект не может аспектировать сам себя:

[Trace( null, AttributeExclude = true )]
[Serializable]
public sealed class TraceAttribute : OnMethodBoundaryAspect
{

Вывод

В данной статье мы продемонстрировали способ разработки специализированных атрибутов, которые действительно добавляют новое поведение вашим .NET программам. Для демонстрации мы использовали PostSharp Laos - аспектно-ориентированное решение от .NET Framework.

Три года спустя после создания PostSharp стал зрелым проектом. На момент написания статьи PostSharp был загружен множество раз, и на данный момент существует версия 1.0 и она находится на этапе выпуска. PostSharp, как сообщают, используется многими компаниями, как независимыми продавцами программных продуктов, так и интеграторами систем. Поставщик предоставляет поддержку, консультации и спонсированную разработку, тем самым заботясь о проекте.

Мы попытались показать в данной статье,что PostSharp на самом деле модифицирует MSIL-инструкции, тем самым дополнительные поведения вызываются во время выполнения. Вы можете увидеть, как все работает, при помощи рефлектора Lutz Roeder's Reflector. Данная статья представляла специализированные атрибуты, которые добавляют блок try-catch всем методам, но также вы можете перехватывать вызовы, выполненные в другой сборке для того, чтобы перехватывать доступ к полям или производить инъекции интерфейсов в типы.

В одной из следующих статей мы продемонстрируем способ разработки двух новых специализированных атрибутов: счетчика производительности и проверку полей.

Автор: Gael Fraiteur

Загрузить исходный код - 16.8 KB
Загрузить PostSharp (PostSharp - бесплатно доступен всем )