1. Вступ
У C# будь-який клас (або структура) — це набір членів. До них належать:
- Поля (fields): змінні, що зберігають дані.
- Властивості (properties): «розумні» обгортки з get/set.
- Методи (methods): функціональність.
- Конструктори (constructors): способи створити екземпляр.
- Події (events): публікація-підписка по-розумному.
Уявіть, що у вас є магічна лупа, з якою ви можете зазирнути всередину будь-якого типу, дізнатися його вміст і навіть змінити його. Це й є рефлексія.
Простір імен і базові типи рефлексії
Перш ніж почати, не забудьте імпортувати простір імен:
using System.Reflection;
Ключові класи та інтерфейси:
| Клас/інтерфейс | Опис |
|---|---|
|
Вихідна точка — усе починається з нього |
|
Інформація про поле |
|
Інформація про властивість |
|
Інформація про метод |
|
Інформація про конструктор |
|
Інформація про подію |
|
Спільний «батько» для всіх перелічених |
2. Отримуємо члени типу через рефлексію
Отримуємо поля та властивості
Приклад: перелічення полів і властивостей
Припустімо, маємо такий клас:
public class Person
{
public string Name { get; set; }
public int Age;
private string Secret = "Таємне слово";
}
Отримуємо поля:
Type personType = typeof(Person);
FieldInfo[] fields = personType.GetFields(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var field in fields)
{
Console.WriteLine($"Поле: {field.Name}, Тип: {field.FieldType.Name}, Доступ: {field.Attributes}");
}
Отримуємо властивості:
PropertyInfo[] properties = personType.GetProperties(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var property in properties)
{
Console.WriteLine($"Властивість: {property.Name}, Тип: {property.PropertyType.Name}");
}
Пояснення
- BindingFlags — це набір «прапорців», які вказують, які члени шукати (public/private, instance/static тощо).
- Поля й властивості різняться: поле — шматочок даних, властивість — це методи get/set, що приховують поле.
Отримуємо методи
MethodInfo[] methods = personType.GetMethods(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var method in methods)
{
Console.WriteLine($"Метод: {method.Name}");
}
Ви здивуєтеся, скільки методів буде виведено. Там будуть і гетери/сетери властивостей, і службові, успадковані від object!
Корисний фільтр: щоб отримати лише власні «явні» методи, можна додати фільтрацію за імʼям або атрибутом.
Що шукати з якими BindingFlags
| Що шукаємо | Приклад виклику | Опис |
|---|---|---|
| Публічні поля | |
Усе, що видно іншим |
| Приватні поля | |
Приховані внутрішні |
| Лише екземпляр | |
Лише нестатичні |
| Лише статичні | |
Лише статичні |
| Усе одразу | |
Повний список |
3. Динамічне створення екземпляра типу
Чому це круто?
Можна створити об’єкт, не знаючи його точного типу на етапі компіляції.
Простий спосіб — через Activator.CreateInstance
// Отримуємо Type будь-яким способом, наприклад:
Type myType = typeof(Person);
// Створюємо об’єкт:
object obj = Activator.CreateInstance(myType);
// Тепер obj — це екземпляр Person, але як object
Якщо клас не має конструктора без параметрів, можна передати параметри:
public class Animal
{
public string Name { get; }
public Animal(string name) { Name = name; }
}
// ...
Type animalType = typeof(Animal);
object dog = Activator.CreateInstance(animalType, "Шарик");
Важливо!
Console.WriteLine(dog.GetType().Name); // Animal
Гнучкіший спосіб — через ConstructorInfo
ConstructorInfo ctor = animalType.GetConstructor(new Type[] { typeof(string) });
object dog = ctor.Invoke(new object[] { "Шарик" });
У реальному житті зазвичай використовують Activator, але іноді потрібні точні параметри або робота з приватними конструкторами — саме тут і стане у пригоді ConstructorInfo.
4. Доступ до полів і властивостей за імʼям
Отримуємо й змінюємо поле
Person p = new Person();
Type t = p.GetType();
FieldInfo field = t.GetField("Age");
field.SetValue(p, 42);
Console.WriteLine(field.GetValue(p)); // 42
Робота з приватними полями:
FieldInfo secret = t.GetField("Secret", BindingFlags.NonPublic | BindingFlags.Instance);
if (secret != null)
{
secret.SetValue(p, "Ой, таємницю розкрито");
Console.WriteLine(secret.GetValue(p));
}
else
{
Console.WriteLine("Поле Secret не знайдено");
}
Увага! Не зловживайте доступом до приватних полів — це злам інкапсуляції та потенційні помилки.
Отримуємо й змінюємо властивості
PropertyInfo pi = t.GetProperty("Name");
pi.SetValue(p, "Василь");
Console.WriteLine(pi.GetValue(p)); // Василь
Властивості — це не поля, а методи get/set за кадром.
Чому інколи не працює?
Якщо ваше поле або властивість приватні, обов’язково додавайте прапорці BindingFlags з потрібними значеннями (наприклад, NonPublic). Якщо у властивості немає сетера, ви не зможете нічого туди записати через рефлексію — правило зберігається.
5. Виклик методів за імʼям
Виклик простого методу без параметрів
public class Greeter
{
public void SayHello()
{
Console.WriteLine("Привіт!");
}
}
Greeter g = new Greeter();
Type t = g.GetType();
MethodInfo mi = t.GetMethod("SayHello");
mi.Invoke(g, null); // Привіт!
Виклик методу з параметрами
public class MathOp
{
public int Add(int x, int y) => x + y;
}
MathOp calc = new MathOp();
Type t = calc.GetType();
MethodInfo mi = t.GetMethod("Add");
object result = mi.Invoke(calc, new object[] { 7, 5 });
Console.WriteLine(result); // 12
Якщо метод статичний, у перший аргумент Invoke передайте null.
Робота з перевантаженими методами
MethodInfo mi = t.GetMethod(
"Add",
new Type[] { typeof(int), typeof(int) }
);
Або перебирайте й фільтруйте вручну:
foreach(var method in t.GetMethods()) {
if (method.Name == "Add" && method.GetParameters().Length == 2)
{
// ... наш метод!
}
}
6. Корисні нюанси
Основні члени, методи й властивості для аналізу типу
| Член класу | Метод для отримання інформації | Клас результату | Як читати/змінювати значення |
|---|---|---|---|
| Поле (field) | |
|
|
| Властивість (prop) | |
|
|
| Метод (method) | |
|
|
| Конструктор | |
|
|
Навіщо все це в реальному житті?
- В ORM‑фреймворках (наприклад, Entity Framework будує SQL-запити за вашими моделями).
- У системах серіалізації та парсерах (наприклад, JSON-серіалізатори).
- У універсальних бібліотеках для логування, тестування і побудови UI.
- На співбесідах — охоче питають: «як би ви написали універсальний копіювальник об’єкта?».
- Для написання плагінів, коли ваша програма завантажує й викликає код, написаний іншими.
Навіть якщо вам зараз здається, що це якась магія з фільмів про хакерів, повірте — стане у пригоді!
7. Типові помилки й особливості
Дуже часто розробники плутаються з BindingFlags — не бачать приватні поля або, навпаки, отримують забагато методів. Завжди перевіряйте поєднання прапорців: для приватних полів використовуйте NonPublic, для статичних — Static тощо.
Під час виклику методів через Invoke передавайте аргументи в тому самому порядку й типі, що й в оголошенні. Неправильний порядок або тип призведе до TargetParameterCountException або ArgumentException. Повернене значення має тип object — не забудьте виконати перетворення типу (або скористатися pattern matching).
Не забувайте про продуктивність: рефлексія повільніша за прямі виклики. У гарячих місцях кешуйте знайдені Type/MethodInfo/PropertyInfo або заздалегідь генеруйте делегати.
Ви часто побачите службові методи на кшталт get_PropertyName і set_PropertyName. Це методи доступу до властивостей — їх вручну створювати не треба.
Якщо змінюєте приватні поля чи властивості — ви ламаєте інкапсуляцію. Неправильне використання може призвести до важковловимих помилок. Як казав дядько Бен: «З великою силою приходить велика відповідальність» — використовуйте обережно, особливо в бібліотечних проєктах.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ