Текст книги "Полное руководство. С# 4.0"
Автор книги: Герберт Шилдт
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 34 (всего у книги 58 страниц)
Рассмотрим следующий пример, демонстрирующий полностью автоматизирован ное обнаружение типов. В этом примере сначала загружается сборка MyClasses.ехе, затем конструируется объект класса MyClass и далее вызываются все методы, объяв ленные в классе MyClass, причем о них ничего заранее неизвестно. // Использовать класс MyClass, ничего не зная о нем заранее. using System; using System.Reflection; class ReflectAssemblyDemo { static void Main() { int val; Assembly asm = Assembly.LoadFrom("MyClasses.exe"); Type[] alltypes = asm.GetTypes(); Type t = alltypes[0]; // использовать первый обнаруженный класс Console.WriteLine("Использовано: " + t.Name); ConstructorInfo[] ci = t.GetConstructors(); // Использовать первый обнаруженный конструктор. ParameterInfо[] cpi = ci[0].GetParameters(); object reflectOb; if(cpi.Length > 0) { object[] consargs = new object[cpi.Length]; // Инициализировать аргументы. fox(int n=0; n < cpi.Length; n++) consargs[n] = 10 + n * 20; // Сконструировать объект. reflectOb = ci[0].Invoke(consargs); } else reflectOb = ci[0].Invoke(null); Console.WriteLine("nВызов методов для объекта reflectOb."); Console.WriteLine(); // Игнорировать наследуемые методы. MethodInfo[] mi = t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public); // Вызвать каждый метод. foreach(Methodlnfo m in mi) { Console.WriteLine("Вызов метода {0} ", m.Name); // Получить параметры. ParameterInfo[] pi = m.GetParameters(); // Выполнить методы. switch(pi.Length) { case 0: // аргументы отсутствуют if(m.ReturnType == typeof(int)) { val = (int) m.Invoke(reflectOb, null); Console.WriteLine("Результат: " + val); } else if(m.ReturnType == typeof(void)) { m.Invoke(reflectOb, null); } break; case 1: // один аргумент if(pi[0].ParameterType == typeof(int)) { object[] args = new object[1]; args[0] = 14; if((bool) m.Invoke(reflectOb, args)) Console.WriteLine("Значение 14 находится между x и у"); else Console.WriteLine("Значение 14 не находится между х и у"); } break; case 2: // два аргумента if((pi[0].ParameterType == typeof(int)) && (pi[1].ParameterType == typeof (int))) { object[] args = new object[2]; args[0] = 9; args[1] = 18; m.Invoke(reflectOb, args); } else if((pi[0].ParameterType == typeof(double)) && (pi[1].ParameterType == typeof(double))) { object[] args = new object[2]; args[0] = 1.12; args[1] = 23.4; m.Invoke(reflectOb, args); } break; } Console.WriteLine(); } } }
Эта программа дает следующий результат. Использовано: MyClass Конструирование класса MyClass(int). Значение х: 10, значение у: 10 Вызов методов для объекта reflectOb. Вызов метода Sum Результат: 20 Вызов метода IsBetween Значение 14 не находится между х и у Вызов метода Set В методе Set(int, int). Значение х: 9, значение у: 18 Вызов метода Set В методе Set(double, double). Значение х: 1, значение у: 23 Вызов метода Show Значение х: 1, значение у: 23
Эта программа работает довольно просто, но все же требует некоторых пояснений. Во-первых, получаются и используются только те методы, которые явно объявлены в классе MyClass. Для этой цели служит форма BindingFlags метода GetMethods(), чтобы воспрепятствовать вызову методов, наследуемых от объекта. И во-вторых, ко личество параметров и возвращаемый тип каждого метода получаются динамически, а затем определяются и проверяются в операторе switch. На основании этой инфор мации формируется вызов каждого метода. Атрибуты
В C# разрешается вводить в программу информацию декларативного характера в форме атрибута, с помощью которого определяются дополнительные сведения (метаданные), связанные с классом, структурой, методом и т.д. Например, в програм ме можно указать атрибут, определяющий тип кнопки, которую должен отображать конкретный класс. Атрибуты указываются в квадратных скобках перед тем элементом, к которому они применяются. Следовательно, атрибут не является членом класса, но обозначает дополнительную информацию, присоединяемую к элементу. Основы применения атрибутов
Атрибут поддерживается классом, наследующим от класса System.Attribute. Поэтому классы атрибутов должны быть подклассами класса Attribute. В классе Attribute определены основные функциональные возможности, но далеко не все они нужны для работы с атрибутами. В именах классов атрибутов принято употреблять суффикс Attribute. Например, ErrorAttribute – это имя класса атрибута, опи сывающего ошибку.
При объявлении класса атрибута перед его именем указывается атрибут AttributeUsage. Этот встроенный атрибут обозначает типы элементов, к которым может применяться объявляемый атрибут. Так, применение атрибута может ограни чиваться одними методами. Создание атрибута
В классе атрибута определяются члены, поддерживающие атрибут. Классы атри бутов зачастую оказываются довольно простыми и содержат небольшое количество полей или свойств. Например, атрибут может определять примечание, описывающее элемент, к которому присоединяется атрибут. Такой атрибут может принимать сле дующий вид. [AttributeUsage(AttributeTargets.All)] public class RemarkAttribute : Attribute { string pri_remark; // базовое поле свойства Remark public RemarkAttribute(string comment) { pri_remark = comment; } public string Remark { get { return pri_remark; } } }
Проанализируем этот класс атрибута построчно.
Объявляемый атрибут получает имя RemarkAttribute. Его объявлению пред шествует встроенный атрибут AttributeUsage, указывающий на то, что атрибут RemarkAttribute может применяться ко всем типам элементов. С помощью встроен ного атрибута AttributeUsage можно сузить перечень элементов, к которым может присоединяться объявляемый атрибут. Подробнее о его возможностях речь пойдет далее в этой главе.
Далее объявляется класс RemarkAttribute, наследующий от класса Attribute. В классе RemarkAttribute определяется единственное закрытое поле pri_remark, поддерживающее одно открытое и доступное для чтения свойство Remark. Это свой ство содержит описание, связываемое с атрибутом. (Конечно, Remark можно было бы объявить как автоматически реализуемое свойство с закрытым аксессором set, но ради наглядности данного примера выбрано свойство, доступное только для чтения.) В данном классе определен также один открытый конструктор, принимающий стро ковый аргумент и присваивающий его свойству Remark. Этим пока что ограничивают ся функциональные возможности класса RemarkAttribute, готового к применению. Присоединение атрибута
Как только класс атрибута будет определен, атрибут можно присоединить к эле менту. Атрибут указывается перед тем элементом, к которому он присоединяется, и для этого его конструктор заключается в квадратные скобки. В качестве примера ниже показано, как атрибут RemarkAttribute связывается с классом. [RemarkAttribute("В этом классе используется атрибут.")] class UseAttrib { // ... }
В этом фрагменте кода конструируется атрибут RemarkAttribute, содержащий комментарий "В этом классе используется атрибут." Данный атрибут затем связывается с классом UseAttrib.
Присоединяя атрибут, совсем не обязательно указывать суффикс Attribute. Например, приведенный выше класс может быть объявлен следующим образом. [Remark("В этом классе используется атрибут.")] class UseAttrib { // ... }
В этом объявлении указывается только имя Remark. Такая сокращенная форма счи тается вполне допустимой, но все же надежнее указывать полное имя присоединяемо го атрибута, чтобы избежать возможной путаницы и неоднозначности. Получение атрибутов объекта
Как только атрибут будет присоединен к элементу, он может быть извлечен в дру гих частях программы. Для извлечения атрибута обычно используется один из двух методов. Первый метод, GetCustomAttributes(), определяется в классе MemberInfо и наследуется классом Туре. Он извлекает список всех атрибутов, присоединенных к элементу. Ниже приведена одна из его форм. object[] GetCustomAttributes(bool наследование)
Если наследование имеет логическое значение true, то в список включаются атрибуты всех базовых классов, наследуемых по иерархической цепочке. В противном случае атрибуты извлекаются только из тех классов, которые определяются указанным типом.
Второй метод, GetCustomAttribute(), определяется в классе Attribute. Ниже приведена одна из его форм: static Attribute GetCustomAttribute(MemberInfo элемент, Type тип_атрибута)
где элемент обозначает объект класса MemberInfo, описывающий тот элемент, для ко торого создаются атрибуты, тогда как тип_атрибута – требуемый атрибут. Данный метод используется в том случае, если имя получаемого атрибута известно заранее, что зачастую и бывает. Так, если в классе UseAttrib имеется атрибут RemarkAttribute, то для получения ссылки на этот атрибут можно воспользоваться следующей после довательностью кода. // Получить экземпляр объекта класса MemberInfо, связанного // с классом, содержащим атрибут RemarkAttribute. Type t = typeof(UseAttrib); // Извлечь атрибут RemarkAttribute. Type tRemAtt = typeof(RemarkAttribute); RemarkAttribute ra = (RemarkAttribute) Attribute.GetCustomAttribute(t, tRemAtt);
Эта последовательность кода оказывается вполне работоспособной, поскольку класс MemberInfo является базовым для класса Туре. Следовательно, t – это экземпляр объекта класса MemberInfo.
Имея ссылку на атрибут, можно получить доступ к его членам. Благодаря этому ин формация об атрибуте становится доступной для программы, использующей элемент, к которому присоединен атрибут. Например, в следующей строке кода выводится со держимое свойства Remark. Console.WriteLine(rа.Remark);
Ниже приведена программа, в которой все изложенные выше особенности приме нения атрибутов демонстрируются на примере атрибута RemarkAttribute. // Простой пример применения атрибута. using System; using System.Reflection; [AttributeUsage(AttributeTargets.All)] public class RemarkAttribute : Attribute { string pri_remark; // базовое поле свойства Remark public RemarkAttribute(string comment) { pri_remark = comment; } public string Remark { get { return pri_remark; } } } [RemarkAttribute("В этом классе используется атрибут.")] class UseAttrib { // ... } class AttribDemo { static void Main() { Type t = typeof(UseAttrib); Console.Write("Атрибуты в классе " + t.Name + ": "); object[] attribs = t.GetCustomAttributes(false); foreach(object о in attribs) { Console.WriteLine(o); } Console.Write("Примечание: "); // Извлечь атрибут RemarkAttribute. Type tRemAtt = typeof(RemarkAttribute); RemarkAttribute ra = (RemarkAttribute) Attribute.GetCustomAttribute(t, tRemAtt); Console.WriteLine(ra.Remark); } }
Эта программа дает следующий результат. Атрибуты в классе UseAttrib: RemarkAttribute Примечание: В этом классе используется атрибут. Сравнение позиционных и именованных параметров
В предыдущем примере для инициализации атрибута RemarkAttribute его конструктору была передана символьная строка с помощью обычного синтаксиса конструктора. В этом случае параметр comment конструктора RemarkAttribute() называется позиционным. Этот термин отражает тот факт, что аргумент связан с пара метром по его позиции в списке аргументов. Следовательно, первый аргумент пере дается первому параметру, второй аргумент – второму параметру и т.д.
Но для атрибута доступны также именованные параметры, которым можно при сваивать первоначальные значения по их именам. В этом случае значение имеет имя, а не позиция параметра.
ПРИМЕЧАНИЕ Несмотря на то что именованные параметры атрибутов, по существу, подобны именован ным аргументам методов, они все же отличаются в деталях.
Именованный параметр поддерживается открытым полем или свойством, которое должно быть нестатическим и доступным только для записи. Любое поле или свой ство подобного рода может автоматически использоваться в качестве именованного параметра. Значение присваивается именованному параметру с помощью соответ ствующего оператора, расположенного в списке аргументов при вызове конструктора атрибута. Ниже приведена общая форма объявления атрибута, включая именованные параметры. [attrib(список_позиционных_параметров, именованный_параметр_1 = значение, именованный_параметр_2 = значение, ...)]
Первыми указываются позиционные параметры, если они существуют. Далее сле дуют именованные параметры с присваиваемыми значениями. Порядок следования именованных параметров особого значения не имеет. Именованным параметрам не обязательно присваивать значение, и в этом случае используется значение, устанавли ваемое по умолчанию.
Применение именованного параметра лучше всего показать на конкретном при мере. Ниже приведен вариант класса RemarkAttribute, в который добавлено поле Supplement, предназначенное для хранения дополнительного примечания. [AttributeUsage(AttributeTargets.All)] public class RemarkAttribute : Attribute { string pri remark; // базовое поле свойства Remark // Это поле можно использовать в качестве именованного параметра. public string Supplement; public RemarkAttribute(string comment) { pri_remark = comment; Supplement = "Отсутствует"; } public string Remark { get { return pri_remark; } } }
Как видите, поле Supplement инициализируется в конструкторе символьной стро кой "Отсутствует". Другого способа присвоить ему первоначальное значение в кон структоре не существует. Но поскольку поле Supplement является открытым в классе RemarkAttribute, его можно использовать в качестве именованного параметра, как показано ниже. [RemarkAttribute("В этом классе используется атрибут.", Supplement = "Это дополнительная информация.")] class UseAttrib { // ... }
Обратите особое внимание на вызов конструктора класса RemarkAttribute. В этом конструкторе первым, как и прежде, указывается позиционный параметр, а за ним через запятую следует именованный параметр Supplement, которому присваивается конкретное значение. И наконец, закрывающая скобка, ), завершает вызов конструкто ра. Таким образом, именованный параметр инициализируется в вызове конструктора. Этот синтаксис можно обобщить: позиционные параметры должны указываться в том порядке, в каком они определены в конструкторе, а именованные параметры – в про извольном порядке и вместе с присваиваемыми им значениями.
Ниже приведена программа, в которой демонстрируется применение поля Supplement в качестве именованного параметра атрибута. // Использовать именованный параметр атрибута. using System; using System.Reflection; [AttributeUsage(AttributeTargets.All)] public class RemarkAttribute : Attribute { string pri_remark; // базовое поле свойства Remark public string Supplement; // это именованный параметр public RemarkAttribute(string comment) { pri_remark = comment; Supplement = "Отсутствует"; } public string Remark { get { return pri_remark; } } } [RemarkAttribute("В этом классе используется атрибут.", Supplement = "Это дополнительная информация.")] class UseAttrib { // ... } class NamedParamDemo { static void Main() { Type t = typeof(UseAttrib); Console.Write("Атрибуты в классе " + t.Name + "); object[] attribs = t.GetCustomAttributes(false); foreach(object о in attribs) { Console.WriteLine(o); } // Извлечь атрибут RemarkAttribute. Type tRemAtt = typeof(RemarkAttribute); RemarkAttrib.ute ra = (RemarkAttribute) Attribute.GetCustomAttribute(t, tRemAtt); Console.Write("Примечание: "); Console.WriteLine(ra.Remark); Console.Write("Дополнение: ") ; Console.WriteLine(ra.Supplement); } }
При выполнении этой программы получается следующий результат. Атрибуты в классе UseAttrib: RemarkAttribute Примечание: В этом классе используется атрибут. Дополнение: Это дополнительная информация.
Прежде чем перейти к следующему вопросу, следует особо подчеркнуть, что поле pri_remark нельзя использовать в качестве именованного параметра, поскольку оно закрыто в классе RemarkAttribute. Свойство Remark также нельзя использовать в ка честве именованного параметра, потому что оно доступно только для чтения. Напом ним, что в качестве именованных параметров могут служить только открытые поля и свойства.
Открытое и доступное только для чтения свойство может использоваться в качестве именованного параметра таким же образом, как и открытое поле. В качестве примера ниже показано, как автоматически реализуемое свойство Priority типа int вводится в класс RemarkAttribute. // Использовать свойство в качестве именованного параметра атрибута. using System; using System.Reflection; [AttributeUsage(AttributeTargets.All)] public class RemarkAttribute : Attribute { string pri_remark; // базовое поле свойства Remark public string Supplement; // это именованный параметр public RemarkAttribute(string comment) { pri_remark = comment; Supplement = "Отсутствует"; Priority = 1; } public string Remark { get { return pri_remark; } } // Использовать свойство в качестве именованного параметра. public int Priority { get; set; } } [RemarkAttribute("В этом классе используется атрибут.", Supplement = " Это дополнительная информация.", Priority = 10)] class UseAttrib { // ... } class NamedParamDemo { static void Main() { Type t = typeof(UseAttrib); Console.Write("Атрибуты в классе " + t.Name + "); object[] attribs = t.GetCustomAttributes(false); foreach(object о in attribs) { Console.WriteLine(o); } // Извлечь атрибут RemarkAttribute. Type tRemAtt = typeof(RemarkAttribute); RemarkAttribute ra = (RemarkAttribute) Attribute.GetCustomAttribute(t, tRemAtt); Console.Write("Примечание: "); Console.WriteLine(ra.Remark); Console.Write("Дополнение: "); Console.WriteLine(ra.Supplement); Console.WriteLine("Приоритет: " + ra.Priority); } }
Вот к какому результату приводит выполнение этого кода. Атрибуты в классе UseAttrib: RemarkAttribute Примечание: В этом классе используется атрибут. Дополнение: Это дополнительная информация. Приоритет: 10
В данном примере обращает на себя внимание порядок указания атрибутов перед классом UseAttrib, как показано ниже. [RemarkAttribute("В этом классе используется атрибут.", Supplement = " Это дополнительная информация.", Priority = 10)] class UseAttrib { // ... }
Именованные параметры атрибутов Supplement и Priority не обязательно ука зывать в каком-то определенном порядке. Порядок их указания можно свободно из менить, не меняя сами атрибуты.
И последнее замечание: тип параметра атрибута (как позиционного, так и имено ванного) должен быть одним из встроенных простых типов, object, Туре, перечисле нием или одномерным массивом одного из этих типов. Встроенные атрибуты
В C# предусмотрено несколько встроенных атрибутов, но три из них имеют осо бое значение, поскольку они применяются в самых разных ситуациях. Это атрибуты AttributeUsage, Conditional и Obsolete, рассматриваемые далее по порядку. Атрибут AttributeUsage
Как упоминалось ранее, атрибут AttributeUsage определяет типы элементов, к которым может быть применен объявляемый атрибут. AttributeUsage – это, по существу, еще одно наименование класса System.AttributeUsageAttribute. У него имеется следующий конструктор: AttributeUsage(AttributeTargets validOn)
где validOn обозначает один или несколько элементов, к которым может быть приме нен объявляемый атрибут, тогда как AttributeTargets – перечисление, в котором определяются приведенные ниже значения. Аll Assembly Class Constructor Delegate Enum Event Field GenericParameter Interface Method Module Parameter Property ReturnValue Struct
Два этих значения или более можно объединить с помощью логической операции ИЛИ. Например, для указания атрибута, применяемого только к полям и свойствам, используются следующие значения. AttributeTargets.Field | AttributeTargets.Property
В классе атрибута AttributeUsage поддерживаются два именованных параметра. Первым из них является параметр AllowMultiple, принимающий логическое значе ние. Если это значение истинно, то атрибут может быть применен к одному и тому же элементу неоднократно. Второй именованный параметр, Inherited, также принимает логическое значение. Если это значение истинно, то атрибут наследуется производны ми классами, а иначе он не наследуется. По умолчанию параметр AllowMultiple при нимает ложное значение (false), а параметр Inherited – истинное значение (true).
В классе атрибута AttributeUsage определяется также доступное только для чте ния свойство ValidOn. Оно возвращает значение типа AttributeTargets, определя ющее типы элементов, к которым можно применять объявляемый атрибут. По умол чанию используется значение AttributeTargets.All. Атрибут Conditional
Атрибут Conditional представляет, вероятно, наибольший интерес среди всех встроенных атрибутов. Ведь он позволяет создавать условные методы, которые вызыва ются только в том случае, если с помощью директивы #define определен конкретный идентификатор, а иначе метод пропускается. Следовательно, условный метод служит альтернативой условной компиляции по директиве #if.
Conditional – это, по существу, еще одно наименование класса System. Diagnostics.ConditionalAttribute. Для применения атрибута Conditional в ис ходный код программы следует включить пространство имен System.Diagnostics.
Рассмотрим применение данного атрибута на следующем примере программы. // Продемонстрировать применение встроенного атрибута Conditional. #define TRIAL using System; using System.Diagnostics; class Test { [Conditional("TRIAL")] void Trial() { Console.WriteLine("Пробная версия, не " + "предназначенная для распространения."); } [Conditional("RELEASE")] void Release() { Console.WriteLine("Окончательная рабочая версия."); } static void Main() { Test t = new Test(); t.Trial(); //вызывается только в том случае, если // определен идентификатор TRIAL t.Release(); // вызывается только в том случае, если // определен идентификатор RELEASE } }
Эта программа дает следующий результат. Пробная версия, не предназначенная для распространения.
Рассмотрим эту программу подробнее, чтобы стал понятнее результат ее выпол нения. Прежде всего обратите внимание на то, что в этой программе определяется идентификатор TRIAL. Затем обратите внимание на определение методов Trial() и Release(). Каждому из них предшествует атрибут Conditional, общая форма ко торого приведена ниже: [Conditional идентификатор]
где идентификатор обозначает конкретный идентификатор, определяющий усло вие выполнение метода. Данный атрибут может применяться только к методам. Если идентификатор определен, то метод выполняется, когда он вызывается. Если же иден тификатор не определен, то метод не выполняется.
Оба метода, Trial() и Release(), вызываются в методе Main(). Но поскольку определен один лишь идентификатор TRIAL, то выполняется только метод Trial(), тогда как метод Release() игнорируется. Если же определить идентификатор RELEASE, то метод Release() будет также выполняться. А если удалить определение идентификатора TRIAL, то метод Trial() выполняться не будет.
Атрибут Conditional можно также применить в классе атрибута, т.е. в классе, наследующем от класса Attribute. Так, если идентификатор определен, то атрибут применяется, когда он встречается в ходе компиляции. В противном случае он не при меняется.
На условные методы накладывается ряд ограничений. Во-первых, они должны воз вращать значение типа void, а по существу, ничего не возвращать. Во-вторых, они должны быть членами класса или структуры, а не интерфейса. И в-третьих, они не могут предшествовать ключевому слову override. Атрибут Obsolete
Атрибут Obsolete (сокращенное наименование класса System.ObsoleteAttribute) позволяет пометить элемент программы как устаревший. Ниже приведена общая фор ма этого атрибута: [Obsolete("сообщение")]
где сообщение выводится при компилировании элемента программы, помеченного как устаревший. Ниже приведен краткий пример применения данного атрибута. // Продемонстрировать применение атрибута Obsolete. using System; class Test { [Obsolete("Лучше использовать метод MyMeth2.")] public static int MyMeth(int a, int b) { return a / b; } // Усовершенствованный вариант метода MyMeth. public static int MyMeth2(int a, int b) { return b == 0 ? 0 : a/b; } static void Main() { // Для этого кода выводится предупреждение. Console.WriteLine("4 / 3 равно " + Test.MyMeth(4, 3)); // А для этого кода предупреждение не выводится. Console.WriteLine("4 / 3 равно " + Test.MyMeth2(4, 3)); } }
Когда по ходу компиляции программы в методе Main() встречается вызов метода MyMeth(), формируется предупреждение, уведомляющее пользователя о том, что ему лучше воспользоваться методом MyMeth2().
Ниже приведена вторая форма атрибута Obsolete: [Obsolete("сообщение", ошибка)]
где ошибка обозначает логическое значение. Если это значение истинно (true), то при использовании устаревшего элемента формируется сообщение об ошибке компиля ции вместо предупреждения. Эта форма отличается тем, что программа, содержащая подобную ошибку, не будет скомпилирована в исполняемом виде.