JavaRush /Курсы /C# SELF /Использование рефлексии

Использование рефлексии

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

1. Введение

В C# любой класс (или структура) — это набор членов. К ним относятся:

  • Поля (fields): переменные, хранящие данные.
  • Свойства (properties): “умные” обёртки с get/set.
  • Методы (methods): функциональность.
  • Конструкторы (constructors): способы создать экземпляр.
  • События (events): публикация-подписка по-умному.

Представьте, что у вас есть магическая лупа, с помощью которой вы можете заглянуть внутрь любого типа, узнать его начинку и даже изменить его. Вот это и есть рефлексия.

Пространство имён и основные типы рефлексии

Прежде чем начать, не забудьте подключить пространство имён:

using System.Reflection;

Ключевые классы и интерфейсы:

Класс/интерфейс Описание
Type
Главный вход — всё начинается с него
FieldInfo
Информация о поле
PropertyInfo
Информация о свойстве
MethodInfo
Информация о методе
ConstructorInfo
Информация о конструкторе
EventInfo
Информация о событии
MemberInfo
Общий “родитель” для всех выше

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

Что ищем Пример вызова Описание
Публичные поля
GetFields(BindingFlags.Public | ...)
Всё, что видно другим
Приватные поля
GetFields(BindingFlags.NonPublic | ...)
Спрятанные внутренние
Только экземпляр
BindingFlags.Instance
Только нестатические
Только статик
BindingFlags.Static
Только статические
Всё сразу
BindingFlags.Public | NonPublic | Instance | Static
Полный список

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)
GetField, GetFields
FieldInfo
.GetValue(obj), .SetValue(obj, x)
Свойство (prop)
GetProperty, GetProperties
PropertyInfo
.GetValue(obj), .SetValue(obj, x)
Метод (method)
GetMethod, GetMethods
MethodInfo
.Invoke(obj, параметры)
Конструктор
GetConstructor, GetConstructors
ConstructorInfo
.Invoke(параметры)

Зачем всё это в реальной жизни?

  • В ORM-фреймворках (например, Entity Framework строит SQL-запросы по вашим моделям).
  • В системах сериализации и парсерах (например, JSON-сериализаторы).
  • В универсальных библиотеках для логирования, тестирования, генераторов UI.
  • На собеседованиях — любят спросить “а как бы вы написали универсальный копировщик объекта?”.
  • Для написания плагинов, когда ваша программа подгружает и вызывает код, написанный другими.

Даже если вам сейчас кажется, что это какая-то магия из фильмов про хакеров, поверьте — пригодится!

7. Типичные ошибки и особенности

Очень часто разработчики путаются с BindingFlags — не видят приватные поля или, наоборот, получают слишком много методов. Всегда проверяйте сочетание флагов: для приватных полей используйте NonPublic, для статических — Static и т.д.

При вызове методов через Invoke передавайте аргументы в том же порядке и типе, что и в объявлении. Неверный порядок/тип приведёт к TargetParameterCountException или ArgumentException. Результат возвращаемого значения приходит как object — не забудьте привести тип (или использовать pattern matching).

Не забывайте о производительности: рефлексия медленнее прямых вызовов. В горячих местах кэшируйте найденные Type/MethodInfo/PropertyInfo или заранее генерируйте делегаты.

Вы часто увидите служебные методы вроде get_PropertyName и set_PropertyName. Это методы доступа к свойствам — их вручную создавать не нужно.

Если меняете приватные поля или свойства — вы взламываете инкапсуляцию. Неправильное использование может привести к трудноуловимым ошибкам. Как говорил дядюшка Бен: “С большой силой приходит большая ответственность” — используйте осторожно, особенно в библиотечных проектах.

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