Текст книги "C# 4.0: полное руководство"
Автор книги: Герберт Шилдт
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 42 (всего у книги 83 страниц)
// Создать объект с помощью рефлексии.
using System;
using System.Reflection;
class MyClass {
int x;
int y;
public MyClass(int i) {
Console.WriteLine("Конструирование класса MyClass(int, int). ");
x = y = i;
}
public MyClass(int i, int j) {
Console.WriteLine("Конструирование класса MyClass(int, int). ");
x = i;
y = 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;
y = b;
Show();
}
// Перегрузить метод Set.
public void Set(double a, double b) {
Console.Write("В методе(double, double). ");
x = (int)a;
y = (int)b;
Show();
}
public void Show() {
Console.WriteLine(«Значение x: {0}, значение у: {1}», x, y);
}
}
class InvokeConsDemo {
static void Main() {
Type t = typeof(MyClass);
int val;
// Получить сведения о конструкторе.
ConstructorInfo[] ci = t.GetConstructors();
Console.WriteLine("Доступные конструкторы: ");
foreach (ConstructorInfo с in ci) {
// Вывести возвращаемый тип и имя.
Console.Write(" " + t.Name + "(");
// Вывести параметры.
ParameterInfo[] 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++) {
ParameterInfo[] 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(«nВызов методов для объекта reflectOb.»);
Console.WriteLine();
MethodInfo[] mi = t.GetMethods();
// Вызвать каждый метод,
foreach (MethodInfo m in mi) {
// Получить параметры.
ParameterInfo[] 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[1] = 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[1] = 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 ((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
. Сначала получается перечень открытых конструкторов в следующей строке кода.
ConstructorInfo[] ci = t.GetConstructors();
Затем для наглядности примера выводятся полученные конструкторы. После этого осуществляется поиск по списку конструктора, принимающего два аргумента, как показано в приведенном ниже фрагменте кода.
for(x=0; х < ci.Length; х++) {
ParameterInfo[] 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
, появится возможность обнаружить определенные в нем типы данных, вызвав для него метод GetTypes()
в приведенной ниже общей форме.
Туре [] GetTypes()
Этот метод возвращает массив типов, содержащихся в сборке.
Для того чтобы продемонстрировать порядок обнаружения типов в сборке, потребуются два исходных файла. Первый файл будет содержать ряд классов, обнаруживаемых в коде из второго файла. Создадим сначала файл MyClasses.cs
, содержащий следующий код.
// Файл, содержащий три класса и носящий имя MyClasses.cs.
using System;
class MyClass {
int x;
int y;
public MyClass(int i) {
Console.WriteLine("Конструирование класса MyClass(int). ");
x = y = i;
Show();
}
public MyClass(int i, int j) {
Console.WriteLine("Конструирование класса MyClass(int, int). ");
x = i;
y = 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;
y = b;
Show();
}
// Перегрузить.метод Set.
public void Set(double a, double b) {
Console.Write("В методе Set(double, double). ");
x = (int)a;
y = (int)b;
Show();
}
public void Show() {
Console.WriteLine(«Значение x: {0}, значение у: {1}», x, y);
}
}
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.
Type[] alltypes = asm.GetTypes();
foreach (Type temp in alltypes)
Console.WriteLine("Найдено: " + temp.Name);
Console.WriteLine();
// Использовать первый тип, в данном случае – класс MyClass.
Type t = alltypes[0]; // использовать первый найденный класс
Console.WriteLine("Использовано: " + t.Name);
// Получить сведения о конструкторе.
ConstructorInfo[] ci = t.GetConstructors();
Console.WriteLine("Доступные конструкторы: ");
foreach (ConstructorInfo с in ci) {
// Вывести возвращаемый тип и имя.
Console.Write(" " + t.Name + "(");
// Вывести параметры.
ParameterInfo[] 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++) {
ParameterInfo[] 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 (MethodInfo m in mi) {
//• Получить параметры.
ParameterInfo[] 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[1] = 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[1] = 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 ((bool)m.Invoke(reflectOb, args))
Console.WriteLine(«Значение 14 находится между x и у»);
}
else if ( m.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.ехе с помощью приведенной ниже последовательности кода, находящегося в самом начале методачМаin().
// Загрузить сборку 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);
ConstructorInfo[] ci = t.GetConstructors();
// Использовать первый обнаруженный конструктор.
ParameterInfo[] cpi = ci[0].GetParameters();
object reflectOb;
if (cpi.Length > 0) {
object[] consargs = new object[cpi.Length];
// Инициализировать аргументы,
for (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 (MethodInfo 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
. На основании этой информации формируется вызов каждого метода.