OOP Course
Сьогодні

Підрозділ 14.1

Введення у рефлексію. Клас System.Type

Вводить поняття рефлексії, простір імен System.Reflection, основні класи для дослідження типів і способи отримання System.Type через typeof, GetType та Type.GetType.

14.1. Введення у рефлексію. Клас System.Type

Рефлексія є процесом виявлення типів під час виконання програми. Кожна програма містить набір використовуваних класів, інтерфейсів, а також їх методів, властивостей та інших цеглинок, з яких складається програма. І рефлексія дозволяє визначити всі ці складові елементи застосунку — причому не статично під час написання коду, а динамічно під час виконання.

Щоб зрозуміти, чому рефлексія взагалі можлива, потрібно усвідомити ключову особливість .NET: компілятор зберігає у скомпільованій збірці (.dll або .exe) не лише виконуваний код, а й метадані — детальний опис усіх типів, їхніх членів, параметрів методів, атрибутів. Це так звані метадані CLR (Common Language Runtime). Саме цей опис і читає рефлексія, відповідаючи на запитання: «які поля є у цього класу?», «які методи він має?», «які інтерфейси реалізує?».

Рефлексія: модель метаданих CLR та клас System.Type

Модель метаданих CLR

Збірка .dll або .exe — це не просто скомпільований байт-код. Вона має дворівневу структуру:

  • Метадані — таблиці з описом усіх типів, методів, полів, властивостей, їх модифікаторів доступу, параметрів і атрибутів. CLR завантажує ці таблиці в пам'ять і надає до них доступ через System.Reflection.
  • IL-код (Intermediate Language) — інструкції, що виконуються після JIT-компіляції. Метадані й IL зберігаються разом, тому рефлексія може не лише досліджувати тип, але й через MethodInfo.Invoke викликати його методи.

Ієрархія об'єктів рефлексії відображає структуру збірки:

Assembly (.dll)
  └── Module (зазвичай один на збірку)
        └── Type (клас, struct, інтерфейс, enum…)
              ├── MethodInfo[]      — методи
              ├── ConstructorInfo[] — конструктори
              ├── FieldInfo[]       — поля
              ├── PropertyInfo[]    — властивості
              └── EventInfo[]       — події

Усі класи MethodInfo, FieldInfo, PropertyInfo, ConstructorInfo, EventInfo є нащадками абстрактного класу MemberInfo, який визначає загальний функціонал: ім'я члена, тип, що його оголошує, і набір атрибутів.

Де застосовується рефлексія

Рефлексія — це фундамент, на якому побудовані ключові інструменти сучасного .NET-розробника:

  • DI-контейнери (Autofac, Microsoft.Extensions.DependencyInjection): сканують збірку на наявність типів, аналізують конструктори, визначають залежності і автоматично їх інжектують.
  • ORM (Entity Framework Core): відображає властивості класу на колонки таблиці бази даних, генерує SQL-запити, читає і записує значення полів через рефлексію.
  • Серіалізація (System.Text.Json, Newtonsoft.Json): перетворює об'єкт у JSON/XML, читаючи властивості типу через GetProperties().
  • Тестові фреймворки (NUnit, xUnit): знаходять усі методи з атрибутом [Test] або [Fact] і викликають їх через MethodInfo.Invoke.
  • Плагінна архітектура: завантаження сторонніх збірок у рантаймі, пошук у них типів, що реалізують певний інтерфейс, і їх динамічне підключення.

Це пояснює, чому рефлексія є важливою темою, навіть якщо у власному коді ви рідко пишете typeof(...) або GetMethods() напряму: інструменти, якими ви користуєтесь щодня, використовують рефлексію під капотом.

Важливе застереження: продуктивність

Рефлексія надає виняткову гнучкість, але коштує продуктивністю. Виклик методу через MethodInfo.Invoke у 10–100 разів повільніший за прямий виклик, оскільки кожного разу виконуються перевірки видимості, типів параметрів і розпакування object. Виклик GetProperties() теж не безкоштовний — він алокує масив PropertyInfo[].

Практичне правило: рефлексія доречна в ініціалізаційному коді (один раз при старті застосунку), в інструментах і фреймворках — але не у гарячому шляху виконання (цикли, обробка запитів). У критично важливих сценаріях результати кешуються або замінюються Expression<T> / source generators.

Простір імен System.Reflection

Основний функціонал рефлексії зосереджений у просторі імен System.Reflection. Ключові класи:

  • Assembly — представляє збірку і дозволяє завантажувати, досліджувати її типи
  • AssemblyName — зберігає ідентифікаційну інформацію збірки (ім'я, версію, культуру)
  • MemberInfo — абстрактний базовий клас для всіх членів типу
  • EventInfo — подія типу
  • FieldInfo — поле типу (включно з приватними)
  • MethodInfo — метод (включно з getters/setters властивостей)
  • PropertyInfo — властивість
  • ConstructorInfo — конструктор
  • ParameterInfo — параметр методу або конструктора
  • Module — модуль всередині збірки

Усі ці класи надають не лише інформацію про член, але й можливість діяти: MethodInfo.Invoke — викликати метод, FieldInfo.SetValue — змінити значення поля, ConstructorInfo.Invoke — створити екземпляр.

Клас System.Type — центральний об'єкт рефлексії

Клас System.Type є точкою входу до всієї інформації про тип. Він інкапсулює повний опис класу, структури, інтерфейсу або переліку і надає методи для отримання всіх його складових.

Ключові властивості Type:

Властивість Повертає
Name Коротке ім'я: "PatientRecord"
FullName Повне ім'я з namespace: "Med.PatientRecord"
Namespace Простір імен
Assembly Збірка, де визначено тип
BaseType Базовий клас (Type?, null для object)
IsClass true якщо клас (reference type)
IsValueType true якщо struct або enum
IsInterface true якщо інтерфейс
IsAbstract true якщо abstract (або інтерфейс)
IsSealed true якщо sealed
IsGenericType true якщо List<T>, Dictionary<K,V> тощо
IsArray true якщо масив
IsEnum true якщо enum
IsPublic true якщо публічний тип

Методи Type для отримання членів:

Метод Повертає
GetMembers() Всі публічні члени + успадковані
GetMethods() Методи як MethodInfo[]
GetConstructors() Конструктори як ConstructorInfo[]
GetFields() Поля як FieldInfo[]
GetProperties() Властивості як PropertyInfo[]
GetEvents() Події як EventInfo[]
GetInterfaces() Реалізовані інтерфейси як Type[]
GetCustomAttributes() Атрибути типу

Кожен метод також має перевантаження з BindingFlags для тонкого контролю — детальніше у розд. 14.2.

Три способи отримати об'єкт Type

Спосіб 1: `typeof(T)` — compile-time

Оператор typeof є найшвидшим і найбезпечнішим способом: тип T відомий на етапі компіляції, компілятор перевіряє коректність, ніяких рантайм-помилок:

Type t = typeof(PatientRecord);
Console.WriteLine(t.Name);    // PatientRecord
Console.WriteLine(t.IsClass); // True

Якщо передати неіснуючий тип — отримаємо помилку компіляції, що значно краще за рантайм-виняток. Це рекомендований спосіб для більшості сценаріїв.

Спосіб 2: `obj.GetType()` — runtime, є екземпляр

Метод GetType() успадкований від object і доступний для будь-якого об'єкта. Повертає фактичний тип об'єкта, а не тип змінної-посилання:

PatientRecord p = new PatientRecord("P001", "Петренко", "I10.9");
Type t = p.GetType(); // PatientRecord

// Поліморфний приклад:
object obj = new PatientRecord("P001", "Петренко", "I10.9");
Console.WriteLine(obj.GetType().Name); // PatientRecord, НЕ object

Саме ця поліморфна поведінка робить GetType() незамінним у колекціях гетерогенних об'єктів.

Спосіб 3: `Type.GetType(string)` — runtime, є рядок імені

Статичний метод Type.GetType дозволяє отримати тип за рядком з його повним іменем. Це єдиний спосіб, коли тип взагалі невідомий під час написання коду — наприклад, ім'я береться з конфігурації або бази даних:

// Перший параметр — повне ім'я з namespace
// Другий — кидати TypeLoadException якщо не знайдено (false = повернути null)
// Третій — ігнорувати регістр
Type? t = Type.GetType("Med.PatientRecord", false, true);

if (t is not null)
    Console.WriteLine(t.FullName);
else
    Console.WriteLine("Тип не знайдено");

Якщо тип знаходиться в іншій збірці, після повного імені через кому вказується ім'я збірки:

Type? t = Type.GetType("Med.PatientRecord, MedLibrary", false, true);

Пошук реалізованих інтерфейсів

Метод GetInterfaces() повертає масив Type[] усіх інтерфейсів, що реалізує тип, — включно з успадкованими:

Type t = typeof(HospitalDoctor);

Console.WriteLine("Реалізовані інтерфейси:");
foreach (Type i in t.GetInterfaces())
{
    Console.WriteLine($"  {i.Name}");
}

interface ISchedulable  { void ScheduleAppointment(string patientId); }
interface IReportWriter { void WriteReport(); }

class HospitalDoctor : ISchedulable, IReportWriter
{
    public string Name { get; }
    public HospitalDoctor(string name) => Name = name;
    public void ScheduleAppointment(string pid) => Console.WriteLine($"Записую {pid} до {Name}");
    public void WriteReport() => Console.WriteLine($"Щоденний звіт лікаря {Name}");
}

Оскільки кожен інтерфейс представлений об'єктом Type, для нього також можна застосувати GetMethods(), GetProperties() тощо — отже, рефлексія однаково добре описує як конкретні класи, так і абстрактні інтерфейси.

Дослідження типу PatientRecord — runnable приклад

Порівняння typeof і GetType() для поліморфних об'єктів — runnable приклад

Розроблено Tomka Yurii · © 2026 ·