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. Это методы доступа к свойствам — их вручную создавать не нужно.
Если меняете приватные поля или свойства — вы взламываете инкапсуляцию. Неправильное использование может привести к трудноуловимым ошибкам. Как говорил дядюшка Бен: “С большой силой приходит большая ответственность” — используйте осторожно, особенно в библиотечных проектах.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ