C# 4.0 полное руководство - 2011 - Герберт Шилдт
Шрифт:
Интервал:
Закладка:
Найдено: 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 {