программирование. Руководство su P# a n Reference в herbert schildt полное руководство с 0 герберт шилдт
Скачать 3.32 Mb.
|
} Console.WriteLine(")"); } Console.WriteLine (); // Найти подходящий конструктор, int х; for(x=0; х < ci.Length; х++) { Parametgrlnfo[] pi = ci[x].GetParameters(); if(pi.Length == 2) break; } if (x == ci.Length) { Console.WriteLine("Подходящий конструктор не найден."); return; } else Console.WriteLine("Найден конструктор с двумя параметрами.\n"); // Сконструировать объект, object[] consargs = new object[2]; consargs[0] = 10; consargs[1] = 20; object reflectOb = ci[x].Invoke(consargs) ; Console.WriteLine("ХпВызов методов для объекта reflectOb."); Console.WriteLine() ; Methodlnfo[] mi = t.GetMethods(); // Вызвать каждый метод, foreach(Methodlnfo m in mi) { // Получить параметры. Parameterlnfo[] pi = m.GetParameters() ; if(m.Name.CompareTo("Set")==0 && pi[0].ParameterType == typeof(int)) { // Это метод Set (int, int). object[] args = new object[2]; args[0] = 9; args[l] = 18; m. Invoke(reflectOb, args); } else if(m.Name.CompareTo("Set")==0 && object[] args = new object[1]; args[.0] = 14; if ((bool) m.Invoke(reflectOb, args)) Console.WriteLine ("Значение 14 находится между x и у"); else if(m.Name.CompareTo("Show")==0) { m.Invoke(reflectOb, null); } } } } Эта программа дает следующий результат. Доступные конструкторы: MyClass(Int32 i) MyClass(Int32 i, Int32 j) Найден конструктор с двумя параметрами. Конструирование класса MyClass(int, int) Значение х: 10, значение у: 20 Вызов методов для объекта reflectOb Сумма равна 30 Значение 14 находится между х и у В методе Set(int, int). Значение х: 9, значение у: 18 В методе Set(double, double). Значение х: 1, значение у: 23 Значение х: 1, значение у: 23 А теперь рассмотрим порядок применения рефлексии для конструирования объекта класса MyClass. Сначала получается перечень открытых конструкторов в следующей строке кода. Constructorlnfo[] ci = t.GetConstructors (); Затем для наглядности примера выводятся полученные конструкторы. После этого осуществляется поиск по списку конструктора, принимающего два аргумента, как показано в приведенном ниже фрагменте кода. for(x=0; х < ci.Length; х++) { Parameterlnfo[] pi = ci[x].GetParameters(); if(pi.Length == 2) break; } Если такой конструктор найден, как в данном примере, то в следующем фрагменте кода получается экземпляр объекта заданного типа. // Сконструировать объект, object[] consargs = new object[2]; consargs[0] = 10; consargs[1] = 20; object reflectOb = ci[x].Invoke(consargs); После вызова метода Invoke () переменная экземпляра reflectOb будет ссылаться на объект типа MyClass. А далее в программе выполняются соответствующие методы для экземпляра этого объекта. Следует, однако, иметь в виду, что ради простоты в данном примере предполагается наличие лишь одного конструктора с двумя аргументами типа int. Очевидно, что в реальном коде придется дополнительно проверять соответствие типов каждого параметра и аргумента. Получение типов данных из сборок В предыдущем примере все сведения о классе MyClass были получены с помощью рефлексии, за исключением одного элемента: типа самого класса MyClass. Несмотря на то что сведения о классе получались в предыдущем примере динамически, этот пример опирался на тот факт, что имя типа MyClass было известно заранее и использовалось в операторе typeof для получения объекта класса Туре, по отношению к которому осуществлялось косвенное или непосредственное обращение к методам рефлексии. В некоторых случаях такой подход может оказаться вполне пригодным, но истинные преимущества рефлексии проявляются лишь тогда, когда доступные в программе типы данных определяются динамически в результате анализа содержимого других сборок. Как следует из главы 16, сборка несет в себе сведения о типах классов, структур и прочих элементов данных, которые в ней содержатся. Прикладной интерфейс Reflection API позволяет загрузить сборку, извлечь сведения о ней и получить экземпляры объектов любых открыто доступных в ней типов. Используя этот механизм, программа может выявлять свою среду и использовать те функциональные возможности, которые могут оказаться доступными без явного их определения во время компиляции. Это очень эффективный и привлекательный принцип. Представьте себе, например, программу, которая выполняет роль "браузера типов", отображая типы данных, доступные в системе, или же инструментальное средство разработки, позволяющее визуально составлять программы из различных типов данных, поддерживаемых в системе. А поскольку все сведения о типах могут быть извлечены и проверены, то ограничений на применение рефлексии практически не существует. Для получения сведений о сборке сначала необходимо создать объект класса Assembly. В классе Assembly открытый конструктор не определяется. Вместо этого объект класса Assembly получается в результате вызова одного из его методов. Так, для загрузки сборки по заданному ее имени служит метод LoadFrom (). Ниже приведена его соответствующая форма: static Assembly LoadFrom(string файл_сборки) где файл_ сборки ‑обозначает конкретное имя файла сборки. Как только будет получен объект класса Assembly, появится возможность обнаружить определенные в нем типы данных, вызвав для него метод Get Types () в приведенной ниже общей форме. Туре [ ] GetTypesO Этот метод возвращает массив типов, содержащихся в сборке. Для того чтобы продемонстрировать порядок обнаружения типов в сборке, потребуются два исходных файла. Первый файл будет содержать ряд классов, обнаруживаемых в коде из второго файла. Создадим сначала файл MyClasses . cs, содержащий следующий код. // Файл, содержащий три класса и носящий имя MyClasses.cs. using System; class MyClass { int x; int y; public MyClass(int i) { Console.WriteLine("Конструирование класса MyClass(int). "); x = у = i ; Show () ; } public MyClass(int i, int j) { Console.WriteLine("Конструирование класса MyClass(int, int). ") ; x = i; у = j; Show(); } public int Sum() { return x+y; } public bool IsBetween(int i) { if ( (x < i) && (i < y)) return true; else return false; \ } public void Set(int a, int b) { Console.Write("В методе Set(int, int). "); x = a; У = b; Show(); } // Перегрузить.метод Set. public void Set(double a, double b) { Console.Write("В методе Set(double, double). "); x = (int) a; у = (int) b; Show () ; } public void Show() { Console.WriteLine ("Значение x: {0}, значение у: {1}", x, у); } } class AnotherClass { string msg; public AnotherClass(string str) { msg = str; } public void Show() { Console.WriteLine(msg); class Demo { static void Main() { Console.WriteLine("Это заполнитель."); } } Этот файл содержит класс MyClass, неоднократно использовавшийся в предыдущих примерах. Кроме того, в файл добавлены второй класс AnotherClass и третий класс Demo. Следовательно, сборка, полученная из исходного кода, находящегося в этом исходном файле, будет содержать три класса. Затем этот файл компилируется, и из него формируется исполняемый файл MyClasses . ехе. Именно эта сборка и будет опрашиваться программно. Ниже приведена программа, в которой будут извлекаться сведения о файле сборки MyClasses . ехе. Ее исходный текст составляет содержимое второго файла. /* Обнаружить сборку, определить типы и создать объект с помощью рефлексии. */ using System; using System.Reflection; class ReflectAssemblyDemo { static void Main() { int val; // Загрузить сборку MyClasses.exe. Assembly asm = Assembly.LoadFrom("MyClasses.exe"); // Обнаружить типы, содержащиеся в сборке MyClasses.exe. Туре[] alltypes = asm.GetTypes(); foreach(Type temp in alltypes) Console.WriteLine("Найдено: " + temp.Name); Console.WriteLine() ; // Использовать первый тип, в данном случае – класс MyClass. Type t = alltypes[0]; // использовать первый найденный класс Console.WriteLine("Использовано: " + t.Name); // Получить сведения о конструкторе. Constructorlnfo[] ci = t.GetConstructors() ; Console.WriteLine("Доступные конструкторы: "); foreach(Constructorlnfo с in ci) { // Вывести возвращаемый тип и имя. Console.Write(" " + t.Name + "("); // Вывести параметры. Parameterlnfo[] pi = с.GetParameters(); for(int i=0; i < pi.Length; i++) { Console.Write(pi[i].ParameterType.Name + " " + pi[i].Name); ■ if(i+1 < pi.Length) Console.Write(", "); } Console.WriteLine (")"); } Console.WriteLine (); // Найти подходящий конструктор, int x; for(x=0; x < ci.Length; x++) { Parameterlnfo[] pi = ci[x].GetParameters() ; if (pi.Length == 2) break; } if(x == ci.Length) { Console.WriteLine("Подходящий конструктор не найден."); return; } else Console.WriteLine("Найден конструктор с двумя параметрами.\n"); // Сконструировать объект, object[] consargs = new object[2]; consargs[0] = 10; consargs[1] = 20; object reflectOb = ci[x].Invoke(consargs) ; Console.WriteLine("\пВызов методов для объекта reflectOb."); Console.WriteLine(); MethodInfo[] mi = t.GetMethods(); // Вызвать каждый метод, foreach(Methodlnfo m in mi) { //• Получить параметры. Parameterlnfo[] pi = m.GetParameters(); if(m.Name.CompareTo("Set")==0 && pi[0].ParameterType == typeof(int)) { // Это метод Set(int, int). object[] args = new object[2]; args[0] = 9; args[l] = 18; m.Invoke(reflectOb, args) ; } else if(m.Name.CompareTo("Set")==0 && pi[0].ParameterType == typeof(double)) { // Это метод Set(double, double). object[] args = new object[2]; args[0] = 1.12; args[l] = 23.4; m.Invoke(reflectOb, args); } else if(m.Name.CompareTo("Sum")==0) { val = (int) m.Invoke(reflectOb, null); Console.WriteLine("Сумма равна " + val); } else if(m.Name.CompareTo("IsBetween")==0) { object[] args = new object[1]; args[0] = 14; if( Console.WriteLine("Значение 14 находится между x и у"); } else ifjm.Name.CompareTo("Show")==0) { • m.Invoke(reflectOb, null); } } } } При выполнении этой программы получается следующий результат. Найдено: MyClass Найдено: AnotherClass Найдено: Demo Использовано: MyClass Доступные конструкторы: MyClass(Int32 i) MyClass(Int32 i, Int32 j) Найден конструктор с двумя параметрами. Конструирование класса MyClass(int, int) Значение х: 10, значение у: 20 Вызов методов для объекта reflectOb Сумма равна 30 Значение 14 находится между х и у В методе Set (int, int) . Значение х: 9, значение у: 18 В методе Set(double, double). Значение х: 1, значение у: 23 Значение х: 1, значение у: 2 3 Как следует из результата выполнения приведенной выше программы, обнаружены все три класса, содержащиеся в файле сборки Му С lasses . ехе. Первым среди них обнаружен класс MyClass, который затем был использован для получения экземпляра объекта и вызова соответствующих методов. Отдельные типы обнаруживаются в сборке MyClasses . ехе с помощью приведенной ниже последовательности кода, находящегося в самом начале методачМал.п (). // Загрузить сборку MyClasses.exe. Assembly asm = Assembly.LoadFrom("MyClasses.ехе") ; // Обнаружить типы, содержащиеся в сборке MyClasses.exe. Туре[] alltypes = asm.GetTypes(); foreach(Type temp in alltypes) Console.WriteLine("Найдено: " + temp.Name); Этой последовательностью кода можно пользоваться всякий раз, когда требуется динамически загружать й опрашивать сборку. Но сборка совсем не обязательно должна быть исполняемым файлом с расширением . ехе. Сборки могут быть также в файлах динамически компонуемых библиотек (DLL) с расширением .dll. Так, если скомпилировать исходный файл MyClasses . cs в следующей командной строке: csc /t:library MyClasses.es то в итоге получится файл MyClasses .dll. Преимущество размещения кода в библиотеке DLL заключается, в частности, в том, что в этом случае метод Main () в исходном коде не нужен, тогда как всем исполняемым файлам требуется определенная точка входа, с которой должно начинаться выполнение программы. Именно поэтому класс Demo содержит метод Main () в качестве такой точки входа. А для библиотеки DLL метод Main () не требуется. Если же класс MyClass нужно превратить в библиотеку DLL, то в вызов метода LoadFrom () придется внести следующее изменение. Assembly asm = Assembly.LoadFrom("MyClasses.dll"); Полностью автоматизированное обнаружение типов Прежде чем завершить рассмотрение рефлексии, обратимся к еще одному поучительному примеру. Несмотря на то что в программе из предыдущего примера класс MyClass был полноценно использован без явного указания на его имя в программе, этот пример все же опирается на предварительную осведомленность о содержимом класса MyClass. Так, в программе были заранее известны имена методов Set и Sum из этого класса. Но с помощью рефлексии можно воспользоваться типом данных, ничего не зная о нем заранее. С этой целью придется извлечь все сведения, необходимые для конструирования объекта и формирования вызовов соответствующих методов. Такой подход может оказаться пригодным, например, при создании инструментального средства визуального проектирования, поскольку он позволяет использовать типы данных, имеющиеся в системе. Рассмотрим следующий пример, демонстрирующий полностью автоматизированное обнаружение типов. В этом примере сначала загружается сборка 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); Constructorlnfo [.] ci = t.GetConstructors(); // Использовать первый обнаруженный конструктор. Parameterlnfо[] 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("ХпВызов методов для объекта reflectOb."); Console.WriteLine(); // Игнорировать наследуемые методы. MethodInfo[] mi = t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public); // Вызвать каждый метод, foreach(Methodlnfo m in mi) { Console.WriteLine("Вызов метода {0} ", m.Name); // Получить параметры. Parameterlnfо[] 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[l] = 18; m.Invoke(reflectOb, args); } else if((pi[0].ParameterType == typeof(double)) && (pi[1].ParameterType == typeof(double))) { object[] args = new object [2]; args[0] = 1J12; args[l] = 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 { v get { return pri_remark; } } } Проанализируем этот класс атрибута построчно. Объявляемый атрибут получает имя RemarkAttribute. Его объявлению предшествует встроенный атрибут AttributeUsage, указывающий на то, что атрибут RemarkAttribute может применяться ко всем типам элементов. С помощью встроенного атрибута AttributeUsage можно сузить перечень элементов, к которым может присоединяться объявляемый атрибут. Подробнее о его возможностях речь пойдет далее в этой главе. Далее объявляется класс RemarkAttribute, наследующий от класса Attribute. В классе RemarkAttribute определяется единственное закрытое поле pri_remark, поддерживающее одно открытое и доступное для чтения свойство Remark. Это свойство содержит описание, связываемое с атрибутом. (Конечно, Remark можно было бы объявить как автоматически реализуемое свойство с закрытым аксессором set, но ради наглядности данного примера выбрано свойство, доступное только для чтения.) В данном классе определен также один открытый конструктор, принимающий строковый аргумент и присваивающий его свойству Remark. Этим пока что ограничиваются функциональные возможности класса RemarkAttribute, готового к применению. Присоединение атрибута Как только класс атрибута будет определен, атрибут можно присоединить к элементу. Атрибут указывается перед тем элементом, к которому он присоединяется, и для этого его конструктор заключается в квадратные скобки. В качестве примера ниже показано, как атрибут RemarkAttribute связывается с классом. [RemarkAttribute("В этом классе используется атрибут.")] class UseAttrib { // ... } В этом фрагменте кода конструируется атрибут RemarkAttribute, содержащий комментарий "В этом классе используется атрибут .11 Данный атрибут затем связывается с классом UseAttrib. Присоединяя атрибут, совсем не обязательно указывать суффикс Attribute. Например, приведенный выше класс может быть объявлен следующим образом. [Remark("В этом классе используется атрибут.")] class UseAttrib { // . . . } В этом объявлении указывается только имя Remark. Такая сокращенная форма считается вполне допустимой, но все же надежнее указывать полное имя присоединяемого атрибута, чтобы избежать возможной путаницы и неоднозначности. Получение атрибутов объекта Как только атрибут будет присоединен к элементу, он может быть извлечен в других частях программы. Для извлечения атрибута обычно используется один из двух методов. Первый метод, GetCustomAttributes () , определяется в классе Memberlnfо и наследуется классом Туре. Он извлекает список всех атрибутов, присоединенных к элементу. Ниже приведена одна из его форм. object[] GetCustomAttributes(bool наследование) Если наследование имеет логическое значение true, то в список включаются атрибуты всех базовых классов, наследуемых по иерархической цепочке. В противном случае атрибуты извлекаются только из тех классов, которые определяются указанным типом. Второй метод, GetCustomAttribute () , определяется в классе Attribute. Ниже приведена одна из его форм: static Attribute GetCustomAttribute(Memberlnfо элемент, Туре тип_атрибута) где элемент обозначает объект класса Member Inf о, описывающий тот элемент, для которого создаются атрибуты, тогда как тип_атрибута – требуемый атрибут. Данный метод используется в том случае, если цмя получаемого атрибута известно заранее, что зачастую и бывает. Так, если в классе UseAttrib имеется атрибут RemarkAttribute, то для получения ссылки на этот атрибут можно воспользоваться следующей последовательностью кода. // Получить экземпляр объекта класса MemberInfо, связанного // с классом, содержащим атрибут RemarkAttribute. Type t = typeof(UseAttrib); // Извлечь атрибут RemarkAttribute. Type tRemAtt = typeof(RemarkAttribute); RemarkAttribute ra = (RemarkAttribute) Attribute.GetCustomAttribute(t, tRemAtt); Эта последовательность кода оказывается вполне работоспособной, поскольку класс Memberlnfo является базовым для класса Туре. Следовательно, t – это экземпляр объекта класса Memberlnfo. Имея ссылку на атрибут, можно получить доступ к его членам. Благодаря этому информация об атрибуте становится доступной для программы, использующей элемент, к которому присоединен атрибут. Например, в следующей строке кода выводится содержимое свойства Remark. Console.WriteLine(га.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 (о).; } 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); RemarkAttribute 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 – перечисление, в котором определяются приведенные ниже значения. АН Assembly Class Constructor Delegate Enum Event Field GenericParameter Interface Method Module Parameter Property ReturnValue Struct Два этих значения или более можно объединить с помощью логической операции ИЛИ. Например, для указания атрибута, применяемого только к полям и свойствам, используются следующие значения. AttributeTargets.Field I 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.ReleaseO; // вызывается только в том случае, если // определен идентификатор RELEASE } } Эта программа дает следующий результат. Пробная версия, не предназначенная для распространения. Рассмотрим эту программу подробнее, чтобы стал понятнее результат ее выполнения. Прежде всего обратите внимание на то, что в этой программе определяется идентификатор TRIAL. Затем обратите внимание на определение методов Trial () и Release () . Каждому из них предшествует атрибут Conditional, общая форма которого приведена ниже: [Conditional идентификатор] где идентификатор обозначает конкретный идентификатор, определяющий условие выполнение метода. Данный атрибут может применяться только к методам. Если идентификатор определен, то метод выполняется, когда он вызывается. Если же идентификатор не определен, то метод не выполняется. Оба метода, Trial () nRelease(), вызываются в методе Main (). Но поскольку определен один лишь идентификатор TRIAL, то выполняется только метод Trial (), тогда как метод Release () игнорируется. Если же определить идентификатор RELEASE, то метод Release () будет также выполняться. А если удалить определение идентификатора TRIAL, то метод Trial () выполняться не будет. Атрибут Conditional можно также применить в классе атрибута, т.е. в классе, наследующем от класса Attribute. Так, если идентификатор определен, то атрибут применяется, когда он встречается в ходе компиляции. В противном случае он не применяется. На условные методы накладывается ряд ограничений. Во‑первых, они должны возвращать значение типа void, а по существу, ничего не возвращать. Во‑вторых, они должны быть членами класса или структуры, а не интерфейса. И в‑третьих, они не могут предшествовать ключевому слову override. Атрибут Obsolete Атрибут Obsolete (сокращенное наименование класса System. Obso‑leteAttribute) позволяет пометить элемент программы как устаревший. Ниже приведена общая форма этого атрибута: [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), то при использовании устаревшего элемента формируется сообщение об ошибке компиляции вместо предупреждения. Эта форма отличается тем, что программа, содержащая подобную ошибку, не будет скомпилирована в исполняемом виде. |