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

Модель метаданих 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() тощо — отже, рефлексія однаково добре описує як конкретні класи, так і абстрактні інтерфейси.