1. Введение
Рефлексия (от англ. Reflection) — это возможность вашей программы исследовать собственную структуру во время исполнения. Представьте, что ваша программа может спросить себя: 'Какие у меня есть методы? Какие свойства есть у этого типа?' и получить ответ в рантайме.
Примеры применения рефлексии в реальной жизни
- Создание плагинов и модулей: Вам не нужно заранее знать, с каким классом вы будете работать — достаточно подгрузить DLL и узнать его методы на лету.
- Автоматизация тестирования: Тестовые фреймворки, например xUnit и NUnit, находят тестовые методы через рефлексию.
- Сериализация: С помощью рефлексии можно автоматически превращать любой объект в JSON/XML без ручного перебора свойств.
- Валидация данных по атрибутам: Скажем, вы добавили атрибут [Required] к свойству — и хотите автоматически проверить все такие свойства.
Зачем это знать на собеседованиях
Навык работы с рефлексией часто спрашивают на собеседованиях, потому что он демонстрирует ваше понимание устройства .NET и умение решать задачи динамического характера. Если вы хотите писать свои фреймворки или расширения, рефлексия незаменима.
2. Пространство имён и базовые классы рефлексии
Весь функционал рефлексии в C# доступен через пространство имён System.Reflection. В начале файла не забывайте:
using System.Reflection;
Основные типы рефлексии
| Класс/Тип | Описание |
|---|---|
|
Представляет описание типа в .NET (например, класс, интерфейс, enum и т.д.) |
|
Свойство типа |
|
Метод типа |
|
Поле типа |
|
Конструктор типа |
|
Представляет сборку (EXE или DLL) |
Основные методы класса Type
| Метод | Описание |
|---|---|
|
Возвращает все публичные свойства |
|
Возвращает все публичные поля |
|
Возвращает все публичные методы |
|
Возвращает все публичные конструкторы |
|
Возвращает все интерфейсы, которые реализует тип |
|
Возвращает все атрибуты, применённые к типу |
Часто к этим методам можно передать BindingFlags для доступа к приватным или статическим членам, но пока мы ограничимся базовым сценарием.
3. Самый первый шаг: получение объекта Type
Все приёмы рефлексии строятся на работе с объектом типа Type.
Существует несколько способов получить этот объект, давайте рассмотрим каждый:
Способ 1: Через объект
string text = "Hello, Reflection!";
Type type1 = text.GetType();
Console.WriteLine(type1.Name); // String
Способ 2: Через имя типа
Type type2 = typeof(int);
Console.WriteLine(type2.FullName); // System.Int32
Способ 3: По строке (динамически)
Type type3 = Type.GetType("System.Double");
Console.WriteLine(type3); // System.Double
Будьте внимательны! Для пользовательских классов из других сборок нужно указать полное имя типа с указанием сборки.
4. Изучаем структуру типа: свойства, методы, поля и конструкторы
Вот пришёл момент истины: мы получили объект Type — и теперь можем узнать всё, что захотим.
Получение списка свойств
Type type = typeof(DateTime);
PropertyInfo[] properties = type.GetProperties();
foreach (var prop in properties)
{
Console.WriteLine($"{prop.PropertyType.Name} {prop.Name}");
}
Вывод:
Int32 Day
Int32 Month
Int32 Year
DayOfWeek DayOfWeek
...
Получение списка методов
MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
{
Console.WriteLine($"{method.ReturnType.Name} {method.Name}()");
}
Методов у крупных типов может быть очень много! Часто вам нужны не все, а только "свои" (например, исключая унаследованные методы из object). Этим займёмся чуть позже.
Получение списка полей
FieldInfo[] fields = type.GetFields();
foreach (var field in fields)
{
Console.WriteLine($"{field.FieldType.Name} {field.Name}");
}
Но большинство обычных классов не имеют публичных полей, ведь инкапсуляция — наше всё :)
Получение конструкторов
ConstructorInfo[] constructors = type.GetConstructors();
foreach (var ctor in constructors)
{
Console.WriteLine($"Конструктор: {ctor}");
}
5. Применяем рефлексию на практике: анализ нашего приложения
Напомню, что в нашем учебном проекте есть класс TaskItem, в котором мы храним задачи. Давайте исследуем его через рефлексию.
public class TaskItem
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime DueDate { get; set; }
public bool IsCompleted;
}
Вот как можно узнать всю "начинку" нашего типа в рантайме:
Type taskType = typeof(TaskItem);
Console.WriteLine("Свойства:");
foreach (var prop in taskType.GetProperties())
Console.WriteLine($" {prop.PropertyType.Name} {prop.Name}");
Console.WriteLine("Поля:");
foreach (var field in taskType.GetFields())
Console.WriteLine($" {field.FieldType.Name} {field.Name}");
Console.WriteLine("Методы:");
foreach (var method in taskType.GetMethods())
Console.WriteLine($" {method.ReturnType.Name} {method.Name}()");
Попробуйте добавить в класс новые свойства и посмотреть, как рефлексия их увидит. Так работают универсальные сериализаторы и инспекторы объектов.
6. Визуализация: как устроен объект Type (схема)
+-------------------------+
| Type |
+-------------------------+
| .Name |
| .FullName |
| .Namespace |
| .Assembly |
+-------------------------+
| .GetProperties() |
| .GetFields() |
| .GetMethods() |
| .GetConstructors() |
| .GetInterfaces() |
| .GetCustomAttributes() |
+-------------------------+
Так выглядит наш "портрет типа" — у него есть имя, сборка, набор членов и атрибуты.
Получение базовой информации о типе
Объект Type может рассказать, что это за тип:
Type t = typeof(double);
Console.WriteLine(t.Name); // Double
Console.WriteLine(t.FullName); // System.Double
Console.WriteLine(t.Namespace); // System
Console.WriteLine(t.IsClass); // False
Console.WriteLine(t.IsValueType); // True
Console.WriteLine(t.IsEnum); // False
Console.WriteLine(t.IsPrimitive); // True
7. Поиск конкретного свойства, метода или поля по имени
Свойство
var prop = taskType.GetProperty("Title");
if (prop != null)
{
Console.WriteLine($"Тип свойства 'Title': {prop.PropertyType}");
}
Метод
var method = taskType.GetMethod("ToString");
if (method != null)
{
Console.WriteLine($"Метод 'ToString' возвращает: {method.ReturnType}");
}
Поле
var field = taskType.GetField("IsCompleted");
if (field != null)
{
Console.WriteLine($"Тип поля 'IsCompleted': {field.FieldType}");
}
8. Используем рефлексию для динамического доступа к значениям
Теперь не просто смотрим, но и трогаем значения!
Чтение значения свойства
TaskItem item = new TaskItem { Id = 1, Title = "Протестировать рефлексию", DueDate = DateTime.Today };
PropertyInfo titleProp = item.GetType().GetProperty("Title");
if (titleProp != null)
{
object value = titleProp.GetValue(item);
Console.WriteLine($"Заголовок: {value}");
}
Установка значения свойства
titleProp.SetValue(item, "Изменённый заголовок");
Console.WriteLine(item.Title);
Работа с полями
FieldInfo completedField = item.GetType().GetField("IsCompleted");
completedField.SetValue(item, true);
Console.WriteLine(item.IsCompleted); // true
Для приватных полей и свойств нужны специальные флаги, например, BindingFlags.NonPublic. Об этом расскажем позже.
9. Вызов методов через рефлексию
Вызывать методы можно, даже не зная их при написании кода!
TaskItem task = new TaskItem { Title = "Рефлексия — круто!" };
MethodInfo method = task.GetType().GetMethod("ToString");
if (method != null)
{
object result = method.Invoke(task, null);
Console.WriteLine(result);
}
Если метод с параметрами, их нужно передавать как массив объектов:
public class Calculator
{
public int Add(int a, int b) => a + b;
}
var calc = new Calculator();
var addMethod = typeof(Calculator).GetMethod("Add");
object[] parameters = { 5, 3 };
object sumResult = addMethod.Invoke(calc, parameters);
Console.WriteLine(sumResult); // 8
10. Динамическое создание объектов через конструктор
Рефлексия позволяет создавать объекты даже без оператора new!
Type taskType = typeof(TaskItem);
object newTask = Activator.CreateInstance(taskType);
// Это тот же самый TaskItem (но тип — object)
Console.WriteLine(newTask.GetType().Name); // TaskItem
Можно передать параметры конструктора:
public class User
{
public string Name { get; }
public User(string name) { Name = name; }
}
Type userType = typeof(User);
object user = Activator.CreateInstance(userType, "Элис");
Console.WriteLine(((User)user).Name); // Элис
11. Типичные ошибки при работе с System.Reflection
Ошибка №1: Обращение к приватным членам без BindingFlags.
По умолчанию рефлексия видит только публичные члены. Без BindingFlags.NonPublic доступ к приватным полям или методам невозможен.
Ошибка №2: Игнорирование проверки на null.
Методы GetProperty, GetMethod и другие могут вернуть null, если член не найден. Без проверки это приведет к NullReferenceException.
Ошибка №3: Неправильный выбор перегрузки метода.
При наличии нескольких перегрузок метода GetMethod может вернуть не тот вариант. Используйте GetMethod с указанием типов параметров.
Ошибка №4: Использование рефлексии в производительном коде.
Рефлексия медленнее прямых вызовов. Для высокопроизводительного кода используйте кэширование метаданных или делегаты.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ