Опубликован: 02.12.2009 | Уровень: специалист | Доступ: платный | ВУЗ: Тверской государственный университет
Лекция 10:

Декларативность. Атрибуты и теги

< Лекция 9 || Лекция 10: 12345 || Лекция 11 >

Атрибутный класс ConditionalAttribute

Атрибутный класс ConditionalAttribute позволяет программисту создавать так называемые условно компилируемые классы и методы.

Вот как выглядит заголовок атрибутного класса:

[SerializableAttribute] 
[AttributeUsageAttribute(AttributeTargets.Class|AttributeTargets.Method, AllowMultiple=true)] 
[ComVisibleAttribute(true)] 
public sealed class ConditionalAttribute : Attribute

Как видно из описания, атрибут Conditional можно задавать для классов и методов. У класса ConditionalAttribute есть позиционный параметр, содержательно задающий параметр компиляции. Когда для некоторой сущности (класса или метода) задается атрибут Conditional, то конструктору класса передается строка с фактическим значением параметра. Его значение сравнивается со значением параметра условной компиляции, заданного в свойствах конфигурации проекта. Если значения параметров совпадают, то условие компиляции для сущности выполняется. Дальнейшие действия зависят от того, чем является сущность - классом или методом.

Только атрибутные классы могут быть условно компилируемыми. Для обычных классов атрибут Conditional задаваться не может. Если для условного атрибутного класса условие компиляции выполняется, то задание этого атрибута для некоторой сущности будет иметь эффект. В случае невыполнения условия компиляции задание атрибута игнорируется.

Если некоторый метод задан с атрибутом Conditional, то метод является условно компилируемым. Это означает, что в момент компиляции компилятор проверит выполнение условия компиляции, сравнив значения двух параметров, заданного в конфигурации и переданного конструктору атрибута Conditional. Если условие компиляции истинно (значения параметров совпадают), то метод будет компилироваться, а иначе не будет компилироваться. Вызов метода, для которого условие компиляции не выполняется, игнорируется, но не приводит к ошибке в период выполнения.

Рассмотрим класс, в котором заданы три метода с атрибутом условной компиляции:

[Conditional("DEMO")]
  void MainMethod()
        {
            Console.WriteLine("Это демо версия");
        }
        [Conditional("PROF")]
        void MainMethod(string s)
        {
            Console.WriteLine("Это профессиональная версия");
        }
        [Conditional("DEBUG")]
        void DebugMethod()
        {
            Console.WriteLine("Это отладочная версия");
        }

У первых двух методов имена совпадают, но сигнатуры отличаются. Содержательно один из методов соответствует демоверсии, а второй - профессиональной версии системы. Третий метод содержательно задает метод, используемый только на этапе отладки. Все три метода являются условно компилируемыми, и выполнение условия компиляции зависит от значения константы, переданной конструктору атрибута Conditional, и констант условной компиляции, заданных в свойствах конфигурации проекта.

Все три метода закрыты и непосредственно клиентами не вызываются. Рассмотрим метод, доступный клиентам класса:

public void ClientMethod(string s)
        {
            MainMethod();
            MainMethod(s);
            DebugMethod();
        }

В этом методе вызываются все три метода с условной компиляцией. Какие из этих трех методов реально будут работать, зависит от того, какая конфигурация проекта будет вызвана и какие параметры в ней заданы. По умолчанию строятся две конфигурации проекта - Debug и Release. Первая используется на этапе отладке, вторая - с включенным режимом оптимизации задает рабочую версию системы. В конфигурации Debug по умолчанию определены две константы условной компиляции - DEBUG и TRACE. В конфигурации Release определена по умолчанию только вторая из этих констант. В каждой конфигурации на странице свойств проекта можно задать произвольное число собственных констант условной компиляции. Зададим для конфигурации Debug константу DEMO, а для конфигурации Release - PROF. На рис. 9.6 показана страница свойств проекта для конфигурации Debug с заданными значениями констант.

Константы условной компиляции конфигурации Debug

увеличить изображение
Рис. 9.6. Константы условной компиляции конфигурации Debug

Рассмотрим теперь клиентский класс Testing и его метод, вызывающий исследуемые методы:

public void TestConditional()
    {
        Console.WriteLine("Demo or Profi?");
        ClassDemo demo = new ClassDemo();
        demo.ClientMethod("");        
    }

Запустим наш проект на выполнение в конфигурации Debug. В этой конфигурации определены константы DEBUG и DEMO, так что два метода из трех вызываемых должны сработать. Результаты можно увидеть на рис. 9.7.

Результаты работы в конфигурации Debug

Рис. 9.7. Результаты работы в конфигурации Debug

Если же запустить проект в конфигурации Release, где определена только одна константа PROF, то будет работать только "профессиональный метод", а вызов остальных двух методов будет проигнорирован.

Собственные атрибутные классы

Ничто не мешает создать собственный атрибутный класс и связывать сущности программы с атрибутами этого класса. Действия компилятора в этом случае сводятся к тому, что он информацию, заданную атрибутом, размещает как часть метаинформации проекта. Понятно, что компилятору ничего не известно о сути определяемого нами атрибутного класса, и поэтому включение атрибута никак не отражается на коде, создаваемом компилятором. Чтобы собственные атрибуты оказали влияние на код, необходимо включить процесс отражения. Это означает, что в какой-то части программы информация об атрибутах, сопровождающих сущности, должна извлекаться, анализироваться, и на этой основе должны приниматься те или иные решения.

Когда следует создавать собственные атрибутные классы? Если разрабатывается DLL - динамическая библиотека классов, подключаемая к различным проектам, то создание атрибутных классов, сопровождающих эту библиотеку, и связывание программных сущностей библиотеки с этими атрибутами может быть весьма полезным. Проекты, присоединяющие эту библиотеку, могут из анализа атрибутов извлечь полезную информацию, позволяющую корректно работать с классами библиотеки. В более общей ситуации некое хост-приложение работает с дополнительными приложениями (add in), используя отражение и информацию, извлекаемую из атрибутов для обеспечения корректного взаимодействия.

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

Рассмотрим пример создания собственного атрибутного класса. Атрибуты этого класса позволят запоминать историю создания и модификации программных сущностей проекта. В атрибуте будет сохраняться информация об авторе, который создает или вносит изменения в код, дате внесения изменений и сопутствующий комментарий.

[AttributeUsage (AttributeTargets.All,
        AllowMultiple = true, Inherited = true)]
    class HistoryAttribute: Attribute
    {
        //позиционные параметры
        string author;
        string date;

       // именованный параметр
        public string comment;

Атрибут Usage заданный для нашего атрибутного класса History, указывает, что атрибут может быть задан для всех программных сущностей и может появляться в нескольких экземплярах. Поля этого класса содержат информацию, которую предполагается задавать в момент определения атрибута. Автор и дата относятся к обязательным параметрам. Именованный параметр comment может опускаться.

Дополним класс заданием конструктора и методами свойствами, осуществляющими доступ к закрытым полям класса:

public HistoryAttribute(string author, string date)
        {
            this.author = author; this.date = date;
        }
        public string Author
        {
            get { return author; }
        }
        public string Date
        {
            get { return date; }
        }
    }

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

[History("В. Биллиг", "30.03.2009", 
        comment = "Совместный проект. Будем работать!")]
    [History("М. Дехтярь", "30.03.2009",
        comment = "Проект: Наш класс")]
    [History("И. Муссикаев", "30.03.2009",
        comment = "Три автора")]
    class OurClass
    {
        [History("И. Муссикаев", "3.04.2009",
         comment = "Первый вариант алгоритма")]
        [History("И. Муссикаев", "7.04.2009",
        comment = "Улучшеный вариант")]
        public void First()
        {
        } 
        
        [History("М. Дехтярь", "3.04.2009",
        comment = "Первый вариант алгоритма")]
        [History("М. Дехтярь", "7.04.2009",
        comment = "Улучшение характеристик сложности")]
        public void Second()
        {
        }
        
        [History("В. Биллиг", "2.04.2009",
        comment = "Первый вариант реализации алгоритма")]
        [History("В. Биллиг", "5.04.2009",
                comment = "Изменение спецификаций")]
        public void Third()
        {
        }
    }

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

public void TestOurClass()
    {
        //История разработки класса
        Console.WriteLine("История разработки класса");
        Type clstype = typeof(OurClass);
        Attribute[] attrs;
        HistoryAttribute history;
        attrs = Attribute.GetCustomAttributes(clstype);
        foreach(Attribute attr in attrs)
        {
            history = attr as HistoryAttribute;
            if (history != null)
                Console.WriteLine("автор: {0}", history.Author);
                Console.WriteLine(" дата начала разработки: {0} ",
                        history.Date);
                Console.WriteLine("комментарий: {0} ",
                    history.comment);
        }
        Console.WriteLine("История разработки методов");
        MethodInfo[] methods = clstype.GetMethods();
        foreach (MethodInfo method in methods)
        {
            attrs = Attribute.GetCustomAttributes(method);
            foreach(Attribute attr in attrs)
            {
                history = attr as HistoryAttribute;
                if (history != null)
                Console.WriteLine("Метод: {0} ", method.Name);
                Console.WriteLine("автор: {0}", history.Author);
                Console.WriteLine(" дата начала разработки: {0}",
                        history.Date);
                Console.WriteLine("комментарий: {0} ", 
                        history.comment);
            }
        }
    }

На рис. 9.8 приведены результаты работы этого метода.

История разработки

Рис. 9.8. История разработки
< Лекция 9 || Лекция 10: 12345 || Лекция 11 >
Федор Антонов
Федор Антонов

Здравствуйте!

Записался на ваш курс, но не понимаю как произвести оплату.

Надо ли писать заявление и, если да, то куда отправлять?

как я получу диплом о профессиональной переподготовке?

Илья Ардов
Илья Ардов

Добрый день!

Я записан на программу. Куда высылать договор и диплом?

Дмитрий Штаф
Дмитрий Штаф
Россия
Дмитрий Слапогузов
Дмитрий Слапогузов
Россия, Бийск