Позднее связывание (late binding). Вызов метода. Пример. Класс System.Activator. Метод Invoke()

Позднее связывание (late binding). Вызов метода. Пример. Класс System.Activator. Метод Invoke()


Содержание



1. В чем состоит смысл позднего связывания?

Позднее связывание – это технология создания и вызова экземпляра некоторого типа (например класса) во время выполнения программы без предыдущего его подключения на этапе компиляции.

Позднее связывание позволяет программно использовать экземпляр типа некоторой сборки, информация о которой не закодирована жестко на этапе компиляции. Информация о сборке получается «на лету».

 

2. Какие средства предусмотрены в C# .NET для реализации позднего связывания? Класс System.Activator. Метод Invoke()

Для реализации позднего связывания используются возможности класса System.Activator. Экземпляр (объект) типа (например класса), который формируется «на лету», создается с помощью перегруженного метода Activator.CreateInstance().

Методы, размещаемые в данном типе (классе) могут быть получены с помощью класса MethodInfo. Более подробно о получении информации о методах сборки с помощью рефлексии можно узнать в теме:

Чтобы вызвать полученный «на лету» метод, используется метод Invoke() класса MethodInfo.

 

3. Какое основное пространство имен нужно подключить в программе для реализации позднего связывания?

Чтобы использовать возможности позднего связывания, нужно подключить пространство имен System.Reflection.

 

4. Пример, который демонстрирует позднее связывание для вызова методов из другой сборки

Пусть дана сборка, которая размещается в файле ClassLibrary1.dll. Эта сборка сформирована в Microsoft Visual Studio 2017 по шаблону «Class Library (.NET Framework)».

Сборка содержит один класс с именем Area. Этот класс содержит методы, которые определяют :

  • метод AreaTriangle(), вычисляющий площадь треугольника по его сторонам a, b, c;
  • метод AreaCircle(), вычисляющий площадь круга заданного радиуса r.

Программный код сборки ClassLibrary1.dll следующий:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// пространство имен MathLibrary
namespace MathLibrary
{
  // класс Area - содержит методы определения площади различных геометрических фигур
  public class Area
  {
    // метод, который определяет площадь треугольника по его сторонам
    public double AreaTriangle(double a, double b, double c)
    {
      // проверка, правильно ли заданы a, b, c
      if (((a + b) > c) || ((a + c) > b) || ((b + c) > a))
        return -1;

      double p;
      double s;
      p = (a + b + c) / 2; // полупериметр

      // формула Герона
      s = Math.Sqrt(p * (p - a) * (p - b) * (p - c));
      return s;
    }

    // метод, который определяет площадь круга по его радиусу
    public double AreaCircle(double r)
    {
      return 3.1415 * r * r;
    }
  }
}

Ниже приведен текст приложения типа Console Application, использующего позднее связывание для выполнения следующих операций над сборкой ClassLibrary1.dll:

  • загружает сборку ClassLibrary1.dll. Предварительно файл сборки ClassLibrary1.dll может быть сохранен в папке с данным приложением. Это осуществляется с помощью файл-менеджера (например Total Commander);
  • вызывает метод AreaTriangle() из класса Area сборки.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// подключить пространство имен для реализации позднего связывания
using System.IO;
using System.Reflection;

namespace ConsoleApp3
{
  class Program
  {
    static void Main(string[] args)
    {
      // реализация позднего связывания на примере сборки ClassLibrary1.dll
      Assembly asm = null; // объявить переменную типа "сборка"

      // 1. Получить доступ к сборке
      try
      {
        // попытка получить информацию о сборке
        asm = Assembly.Load("ClassLibrary1");

        // проверка, все ли в порядке
        if (asm == null) return;
      }
      catch (FileNotFoundException ex)
      {
        // если попытка неудачная, то выводится сообщение об ошибке
        Console.WriteLine(ex.Message);
        return;
      }

      // 2. Получить экземпляр типа ComplexOperations из сборки ClassLibrary1.dll
      Type tp;

      try
      {
        // попытка получить метаданные типа ComplexOperations
        tp = asm.GetType("MathLibrary.Area");

        // создать экземпляр класса ComplexOperations
        // с помощью позднего связывания
        object ob = Activator.CreateInstance(tp);

        // 3. Вызов метода AreaTriangle() из класса Area
        // 3.1. Получить экземпляр метода
        MethodInfo mi;
        mi = tp.GetMethod("AreaTriangle"); // получить экземпляр метода AreaTriangle()

        // 3.2. Сформировать параметры метода
        double a = 5, b = 4, c = 3;
        object[] parameters = new object[] { a, b, c };

        // 3.3. Вызов метода
        double area;
        area = (double)mi.Invoke(ob, parameters); // area = 6
        Console.WriteLine("Area = {0}", area);
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
      }
    }
  }
}

Объясним некоторые фрагменты кода.

Для реализации позднего связывания нужно подключить пространства имен System.IO и System.Reflection.

На первом шаге осуществляется доступ к сборке ClassLibrary1.dll с помощью метода

asm = Assembly.Load("ClassLibrary1");

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

asm = Assembly.LoadFrom("C:\\ABCDEF\\ClassLibrary1.dll");

В этом случае нужно задавать имя файла сборки с расширением *.dll.

На втором шаге создается экземпляр класса типа ComplexOperations. Сначала получаются метаданные типа с помощью строки

tp = asm.GetType("MathLibrary.Area");

Здесь, сначала задается имя пространства имен (MathLibrary). Затем задается имя класса в сборке (Area). Важно, чтобы класс в сборке был объявлен как public. В другом случае он будет недоступен.

Для создания экземпляра класса ComplexOperations используется метод CreateInstance() из класса Activator. Метод получает входным параметром экземпляр метаданных типа.

На третьем шаге вызывается метод AreaTriangle() из класса Area. Это реализуется с помощью позднего связывания.

Сначала создается экземпляр mi типа MethodInfo, что соответствует методу AreaTriangle(). Этот экземпляр содержит информацию о методе. Затем создается список параметров parameters типа массив object[], которые будут передаваться в метод.

Третий шаг завершается вызовом метода Invoke(), что соответствует методу AreaTriangle() класса Area.

 

5. Какой общий алгоритм вызова заданного метода из сборки?

Последовательность шагов следующая:

1. Подключить пространство имен System.Reflection

using System.Reflection;

2. Получить экземпляр сборки

Assembly asm = Assembly.Load("NameOfAssembly");

где NameOfAssembly – имя сборки, метод которой нужно вызвать «на лету».

3. Получить экземпляр типа Type, что соответствует метаданным типа. Это осуществляется с помощью метода GetType()

Type tp = asm.GetType("NameSpace.Type");

где

  • NameSpace – имя пространства имен, в котором объявляется тип (класс, структура, интерфейс, перечисление, делегат);
  • Type – имя типа, метод которого нужно вызвать.

4. Создать экземпляр нужного класса с помощью позднего связывания. Для этого используется метод CreateInstance() класса Activator

object ob = Activator.CreateInstance(tp);

Здесь tp – полученный ранее экземпляр типа.

5. Получить экземпляр mi типа MethodInfo, что соответствует методу из типа tp. Для этого используется метод GetMethod.

MethodInfo mi = tp.GetMethod("NameOfMethod");

где NameOfMethod – имя метода, который нужно выполнить с помощью позднего связывания.

6. Сформировать перечень параметров parameters типа object[]. Массив object[] получается стандартным путем с помощью оператора new. Например

object[] parameters = new object[] { "Text parameter", true, 2, 3.85f };

В этом примере формируются 4 параметра типов string, bool, int, float.

7. Вызвать экземпляр mi метода с помощью метода Invoke().

mi.Invoke(ob, parameters);

Если метод не получает параметров, то ему передается null. Например

mi.Invoke(ob, null);

 

6. Можно ли с помощью позднего связывания вызывать методы статических классов?

Нет, нельзя. Это связано с тем, что позднее связывание предусматривает создание экземпляра (объекта) класса. А, как известно, объект статического класса создавать нельзя.

Например. Невозможно вызывать методы класса System.Math с применением позднего связывания, поскольку класс System.Math объявлен как статический (static). Методы такого класса будут вызваны непосредственно:

double x = Math.Sqrt(81); // x = 9.0

 


Связанные темы