JavaRush /Курсы /C# SELF /Рефлексия: System.Reflecti...

Рефлексия: System.Reflection

C# SELF
63 уровень , 0 лекция
Открыта

1. Введение

Рефлексия (от англ. Reflection) — это возможность вашей программы исследовать собственную структуру во время исполнения. Представьте, что ваша программа может спросить себя: 'Какие у меня есть методы? Какие свойства есть у этого типа?' и получить ответ в рантайме.

Примеры применения рефлексии в реальной жизни

  • Создание плагинов и модулей: Вам не нужно заранее знать, с каким классом вы будете работать — достаточно подгрузить DLL и узнать его методы на лету.
  • Автоматизация тестирования: Тестовые фреймворки, например xUnit и NUnit, находят тестовые методы через рефлексию.
  • Сериализация: С помощью рефлексии можно автоматически превращать любой объект в JSON/XML без ручного перебора свойств.
  • Валидация данных по атрибутам: Скажем, вы добавили атрибут [Required] к свойству — и хотите автоматически проверить все такие свойства.

Зачем это знать на собеседованиях

Навык работы с рефлексией часто спрашивают на собеседованиях, потому что он демонстрирует ваше понимание устройства .NET и умение решать задачи динамического характера. Если вы хотите писать свои фреймворки или расширения, рефлексия незаменима.

2. Пространство имён и базовые классы рефлексии

Весь функционал рефлексии в C# доступен через пространство имён System.Reflection. В начале файла не забывайте:

using System.Reflection;

Основные типы рефлексии

Класс/Тип Описание
Type
Представляет описание типа в .NET (например, класс, интерфейс, enum и т.д.)
PropertyInfo
Свойство типа
MethodInfo
Метод типа
FieldInfo
Поле типа
ConstructorInfo
Конструктор типа
Assembly
Представляет сборку (EXE или DLL)

Основные методы класса Type

Метод Описание
GetProperties()
Возвращает все публичные свойства
GetFields()
Возвращает все публичные поля
GetMethods()
Возвращает все публичные методы
GetConstructors()
Возвращает все публичные конструкторы
GetInterfaces()
Возвращает все интерфейсы, которые реализует тип
GetCustomAttributes()
Возвращает все атрибуты, применённые к типу

Часто к этим методам можно передать 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: Использование рефлексии в производительном коде.
Рефлексия медленнее прямых вызовов. Для высокопроизводительного кода используйте кэширование метаданных или делегаты.

2
Задача
C# SELF, 63 уровень, 0 лекция
Недоступна
Получение списка свойств, методов и полей типа
Получение списка свойств, методов и полей типа
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ