Пізнє зв’язування (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

 


Зв’язані теми