Книга: C# 4.0: полное руководство

Применение рефлексии

Применение рефлексии

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

Прикладной интерфейс Reflection API весьма обширен и поэтому не может быть полностью рассмотрен в этой главе. Ведь для этого потребовалась бы целая книга! Но прикладной интерфейс Reflection API имеет ясную логическую структуру, а следовательно, уяснив одну его часть, нетрудно понять и все остальное. Принимая во внимание это обстоятельство, в последующих разделах демонстрируются четыре основных способа применения рефлексии: получение сведений о методах, вызов методов, конструирование объектов и загрузка типов данных из сборок.

Получение сведений о методах

Имея в своем распоряжении объект класса Туре, можно получить список методов, поддерживаемых отдельным типом данных, используя метод GetMethods(). Ниже приведена одна из форм, подходящих для этой цели.

MethodInfo[] GetMethods()

Этот метод возвращает массив объектов класса Methodlnfо, которые описывают методы, поддерживаемые вызывающим типом. Класс MethodInfo находится в пространстве имен System.Reflection.

Класс MethodInfo является производным от абстрактного класса MethodBase, который в свою очередь наследует от класса MemberInfо. Это дает возможность пользоваться всеми свойствами и методами, определенными в этих трех классах. Например, для получения имени метода служит свойство Name. Особый интерес вызывают два члена класса MethodInfo: ReturnType и GetParameters().

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

Метод GetParameters() возвращает список параметров, связанных с анализируемым методом. Ниже приведена его общая форма.

Parameterlnfо[] GetParameters();

Сведения о параметрах содержатся в объекте класса ParameterInfо. В классе Parameterlnfо определено немало свойств и методов, описывающих параметры. Особое значение имеют два свойства: Name — представляет собой строку, содержащую имя параметра, a ParameterType — описывает тип параметра, который инкапсулирован в объекте класса Туре.

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

// Анализ методов с помощью рефлексии.
using System;
using System.Reflection;
class MyClass {
  int x;
  int y;
  public MyClass(int i, int j) {
    x = i;
    y = j;
  }
  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) {
    x = a;
    y = b;
  }
  public void Set(double a, double b) {
    x = (int)a; y = (int)b;
  }
  public void Show() {
    Console.WriteLine(" x: {0}, у: {1}", x, y);
  }
}
class ReflectDemo {
  static void Main() {
    Type t = typeof(MyClass); // получить объект класса Type,
    // представляющий класс MyClass
    Console.WriteLine("Анализ методов, определенных " +
                "в классе " + t.Name);
    Console.WriteLine();
    Console.WriteLine("Поддерживаемые методы: ");
    MethodInfo[] mi = t.GetMethods();
    // Вывести методы, поддерживаемые в классе MyClass.
    foreach (MethodInfo m in mi) {
      // Вывести возвращаемый тип и имя каждого метода.
      Console.Write(" " + m.ReturnType.Name + " " + m.Name + "(");
      // Вывести параметры.
      ParameterInfo[] pi = m.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();
    }
  }
}

Эта программа дает следующий результат.

Поддерживаемые методы:
Int32 Sum()
Boolean IsBetween (Int32 i)
Void Set(Int32 a, Int32 b)
Void Set (Double a, Double b)'
Void Show()
String ToString()
Boolean Equals(Object ob j )
Int32 GetHashCode()
Type GetType()

Как видите, помимо методов, определенных в классе MyClass, в данной программе выводятся также методы, определенные в классе object, поскольку все типы данных в C# наследуют от класса object. Кроме того, в качестве имен типов указываются имена структуры .NET. Обратите также внимание на то, что метод Set() выводится дважды, поскольку он перегружается. Один из его вариантов принимает аргументы типа int, а другой — аргументы типа double.

Рассмотрим эту программу более подробно. Прежде всего следует заметить, что в классе MyClass определен открытый конструктор и ряд открытых методов, в том числе и перегружаемый метод Set().

Объект класса Туре, представляющий класс MyClass, создается в методе Main() в следующей строке кода.

Type t = typeof(MyClass); // получить объект класса Туре,
// представляющий класс MyClass

Напомним, что оператор typeof возвращает объект класса Туре, представляющий конкретный тип данных (в данном случае — класс MyClass).

С помощью переменной t и прикладного интерфейса Reflection API в данной программе затем выводятся сведения о методах, поддерживаемых в классе MyClass. Для этого в приведенной ниже строке кода сначала выводится список соответствующих методов.

MethodInfo[] mi = t.GetMethods();

Затем в цикле foreach организуется обращение к элементам массива mi. На каждом шаге этого цикла выводится возвращаемый тип, имя и параметры отдельного метода, как показано в приведенном ниже фрагменте кода.

foreach(MethodInfo m in mi) {
  // Вывести возвращаемый тип и имя каждого метода.
  Console.Write(" " + m.ReturnType.Name + " " + m.Name + "(");
  // Вывести параметры.
  ParameterInfo[] pi = m.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(", ");
  }
}

В этом фрагменте кода параметры, связанные с каждым методом, сначала создаются с помощью метода GetParameters() и сохраняются в массиве pi. Затем в цикле for происходит обращение к элементам массива pin выводится тип и имя каждого параметра. Самое главное, что все эти сведения создаются динамически во время выполнения программы, не опираясь на предварительную осведомленность о классе MyClass.

Вторая форма метода GetMethods()

Существует вторая форма метода GetMethods(), позволяющая указывать различные флажки для отфильтровывания извлекаемых сведений о методах. Ниже приведена эта общая форма метода GetMethods().

MethodInfo[] GetMethods(BindingFlags флажки)

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

Значение - Описание

DeclaredOnly - Извлекаются только те методы, которые определены в заданном классе. Унаследованные методы в извлекаемые сведения не включаются

Instance - Извлекаются методы экземпляра

NonPublic - Извлекаются методы, не являющиеся открытыми

Public - Извлекаются открытые методы

Static - Извлекаются статические методы

Два или несколько флажков можно объединить с помощью логической операции ИЛИ. Но как минимум флажок Instance или Static следует указывать вместе с флажком Public или NonPublic. В противном случае не будут извлечены сведения ни об одном из методов.

Форма BindingFlags метода GetMethods() чаще всего применяется для получения списка методов, определенных в классе, без дополнительного извлечения наследуемых методов. Это особенно удобно в тех случаях, когда требуется исключить получение сведений о методах, определяемых в классе конкретного объекта. В качестве примера попробуем выполнить следующую замену в вызове метода GetMethods() из предыдущей программы.

// Теперь получаются сведения только о тех методах,
// которые объявлены в классе MyClass.
MethodInfo[] mi = t.GetMethods(BindingFlags.DeclaredOnly |
                 BindingFlags.Instance |
                 BindingFlags.Public);

После этой замены программа дает следующий результат.

Анализ методов, определенных в классе MyClass
Поддерживаемые методы:
Int32 Sum()
Boolean IsBetween(Int32 i)
Void Set(Int32 a, Int32 b)
Void Set(Double a, Double b)
Void Show()

Как видите, теперь выводятся только те методы, которые явно определены в классе MyClass.

Вызов методов с помощью рефлексии

Как только методы, поддерживаемые определенным типом данных, становятся известны, их можно вызывать. Для этой цели служит метод Invoke(), входящий в состав класса Methodlnfо. Ниже приведена одна из форм этого метода:

object Invoke(object obj, object[] parameters)

где obj обозначает ссылку на объект, для которого вызывается метод. Для вызова статических методов (static) в качестве параметра obj передается пустое значение (null). Любые аргументы, которые должны быть переданы методу, указываются в массиве parameters. Если же аргументы не нужны, то вместо массива parameters указывается пустое значение (null). Кроме того, количество элементов массива parameters должно точно соответствовать количеству передаваемых аргументов. Так, если требуется передать два аргумента, то массив parameters должен состоять из двух элементов, но не из трех или четырех. Значение, возвращаемое вызываемым методом, передается методу Invoke(), который и возвращает его.

Для вызова конкретного метода достаточно вызвать метод Invoke() для экземпляра объекта типа Methodlnfо, получаемого при вызове метода GetMethods(). Эта процедура демонстрируется в приведенном ниже примере программы.

// Вызвать методы с помощью рефлексии.
using System;
using System.Reflection;
class MyClass {
  int x;
  int y;
  public MyClass(int i, int j) {
    x = i;
    y = j;
  }
  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 InvokeMethDemo {
  static void Main() {
    Type t = typeof(MyClass);
    MyClass reflectOb = new MyClass(10, 20);
    int val;
    Console.WriteLine("Вызов методов, определенных в классе " + t.Name);
    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)) {
        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)) {
        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
Сумма равна 30
Значение 14 находится между х и у
В методе Set (int, int). Значение х: 9, значение у: 18
В методе Set(double, double). Значение х: 1, значение у: 23
Значение х: 1, значение у: 23

Рассмотрим подробнее порядок вызова методов. Сначала создается список методов. Затем в цикле foreach извлекаются сведения об их параметрах. Далее каждый метод вызывается с указанием соответствующего типа и числа аргументов в последовательном ряде условных операторов if/else. Обратите особое внимание на перегрузку метода Set() в приведенном ниже фрагменте кода.

if(m.Name.CompareTo("Set")==0 &&
         pi(0].ParameterType == typeof(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)) {
  object[] args = new object[2];
  args[0] = 1.12;
  args[1 ] = 23.4;
  m.Invoke(reflectOb, args);
}

Если имя метода — Set, то проверяется тип первого параметра, чтобы выявить конкретный вариант этого метода. Так, если это метод Set(int, int), то его аргументы загружаются в массив args. В противном случае используются аргументы типа double.

Получение конструкторов конкретного типа

В предыдущем примере при вызове методов, определенных в классе MyClass, преимущества рефлексии не использовались, поскольку объект типа MyClass создавался явным образом. В таком случае было бы намного проще вызвать для него методы обычным образом. Но сильные стороны рефлексии проявляются наиболее заметно лишь в том случае, если объект создается динамически во время выполнения. И для этого необходимо получить сначала список конструкторов, а затем экземпляр объекта заданного типа, вызвав один из этих конструкторов. Такой механизм позволяет получать во время выполнения экземпляр объекта любого типа, даже не указывая его имя в операторе объявления.

Конструкторы конкретного типа получаются при вызове метода GetConstructors() для объекта класса Туре. Ниже приведена одна из наиболее часто используемых форм этого метода.

ConstructorInfo[] GetConstructors()

Метод GetConstructors() возвращает массив объектов класса ConstructorInfo, описывающих конструкторы.

Класс ConstructorInfo является производным от абстрактного класса MethodBase, который в свою очередь наследует от класса Memberlnfо. В нем также определен ряд собственных методов. К их числу относится интересующий нас метод GetConstructors(), возвращающий список параметров, связанных с конструктором. Этот метод действует таким же образом, как и упоминавшийся ранее метод GetParameters(), определенный в классе MethodInfо.

Как только будет обнаружен подходящий конструктор, для создания объекта вызывается метод Invoke(), определенный в классе ConstructorInfo. Ниже приведена одна из форм этого метода.

object Invoke(object[] parameters)

Любые аргументы, которые требуется передать методу, указываются в массиве parameters. Если же аргументы не нужны, то вместо массива parameters указывается пустое значение (null). Но в любом случае количество элементов массива parameters должно совпадать с количеством передаваемых аргументов, а типы аргументов — с типами параметров. Метод Invoke() возвращает ссылку на сконструированный объект.

В приведенном ниже примере программы рефлексия используется для создания экземпляра объекта класса MyClass.

// Создать объект с помощью рефлексии.
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. На основании этой информации формируется вызов каждого метода.

Оглавление книги


Генерация: 1.164. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз