Рефлексия и интроспекция в C#: как заглянуть внутрь своего кода

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: магия отражения

Представьте, что вы пишете код, который может "смотреть" на сам себя. Не как в кривом зеркале, а как в рентгеновском аппарате — видеть структуру, свойства, методы, даже запускать их, не зная имени класса на этапе компиляции. Это не магия, это рефлексия в C# (отражение) и её "младшая сестра" — интроспекция C#.

Зачем это нужно? Представьте, что вы пишете плагинную систему, десериализатор JSON, или динамический вызов методов. Без рефлексии пришлось бы писать кучу if-else. А с ней — достаточно пары строк. Звучит интригующе? Поехали разбираться.

В этой статье мы не просто пройдёмся по теории. Мы напишем реальные примеры кода, которые можно сразу использовать в проектах. Вы узнаете, как получить информацию о любом типе, создать объект "из воздуха", вызвать приватный метод и даже прочитать атрибуты. И всё это — на языке C#, который так любят за его строгость и мощь.

Что такое рефлексия и интроспекция в C#? Разница понятий

Часто эти термины используют как синонимы, но между ними есть тонкая грань:

  • Интроспекция C# — это способность программы наблюдать за своей структурой. Вы можете узнать, какие поля есть у класса, какие методы он содержит, какие атрибуты навешаны. Но вы ничего не меняете.
  • Рефлексия C# — это более широкое понятие. Она включает интроспекцию, плюс возможность взаимодействовать с кодом: вызывать методы, создавать объекты, изменять значения полей.

Проще говоря: интроспекция — это "посмотреть", рефлексия — "посмотреть и потрогать". В C# за обе функции отвечает пространство имён System.Reflection. Это ваш главный инструмент.

Практический пример: получаем информацию о типе (Type)

Всё начинается с класса Type. Это фундамент рефлексии. Получить его можно тремя способами:

// Способ 1: через typeof()
Type typeFromKeyword = typeof(string);

// Способ 2: через экземпляр .GetType()
string example = "Привет, мир!";
Type typeFromInstance = example.GetType();

// Способ 3: через сборку (Assembly)
Type typeFromAssembly = Assembly.GetExecutingAssembly().GetType("System.String");

Теперь, имея объект Type, мы можем узнать о классе всё. Давайте напишем полезный метод для интроспекции:

using System;
using System.Reflection;

public class Inspector
{
    public static void ShowTypeInfo(Type type)
    {
        Console.WriteLine($"=== Информация о типе: {type.Name} ===");
        Console.WriteLine($"Полное имя: {type.FullName}");
        Console.WriteLine($"Это класс? {type.IsClass}");
        Console.WriteLine($"Это абстрактный? {type.IsAbstract}");
        Console.WriteLine($"Базовый класс: {type.BaseType?.Name}");
        
        Console.WriteLine("\nПубличные свойства:");
        foreach (PropertyInfo prop in type.GetProperties())
        {
            Console.WriteLine($" - {prop.PropertyType.Name} {prop.Name}");
        }
        
        Console.WriteLine("\nПубличные методы:");
        foreach (MethodInfo method in type.GetMethods())
        {
            Console.WriteLine($" - {method.ReturnType.Name} {method.Name}({string.Join(", ", method.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"))})");
        }
    }
}

// Использование
Inspector.ShowTypeInfo(typeof(List<int>));
/* Вывод:
=== Информация о типе: List`1 ===
Полное имя: System.Collections.Generic.List`1[[System.Int32, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]
Это класс? True
Это абстрактный? False
Базовый класс: Object

Публичные свойства:
 - Int32 Capacity
 - Int32 Count

Публичные методы:
 - Void Add(Int32 obj)
 - Void Clear()
 - Boolean Contains(Int32 item)
 ... и много других
*/

Это и есть чистая интроспекция C# — мы просто читаем метаданные. Полезно? Ещё бы! Так можно писать универсальные утилиты для логирования, сериализации или кодогенерации.

Рефлексия в действии: создание объектов и вызов методов

А теперь самое интересное — динамическое создание объектов. Допустим, у вас есть строка с именем класса, и вы хотите создать его экземпляр. Без рефлексии — никак.

Создание объекта через Activator

using System;

public class MyClass
{
    public string Message { get; set; }
    public MyClass() { Message = "Создан через конструктор по умолчанию"; }
    public MyClass(string msg) { Message = msg; }
    
    public void Show() => Console.WriteLine(Message);
}

// Динамическое создание
Type myType = typeof(MyClass);

// 1. Через конструктор без параметров
object instance1 = Activator.CreateInstance(myType);
((MyClass)instance1).Show(); // "Создан через конструктор по умолчанию"

// 2. Через конструктор с параметром
object instance2 = Activator.CreateInstance(myType, new object[] { "Привет из рефлексии!" });
((MyClass)instance2).Show(); // "Привет из рефлексии!"

Обратите внимание: Activator.CreateInstance возвращает object. Чтобы работать с ним, нужно привести к нужному типу. Но что, если тип неизвестен на этапе компиляции? Используйте dynamic или рефлексию для вызова методов.

Вызов метода с помощью MethodInfo

// Допустим, мы знаем только, что у объекта есть метод "Show"
MethodInfo showMethod = myType.GetMethod("Show");
showMethod.Invoke(instance1, null); // Вызов метода без параметров

// Вызов метода с параметром
MethodInfo privateMethod = myType.GetMethod("PrivateMethod", BindingFlags.NonPublic | BindingFlags.Instance);
privateMethod?.Invoke(instance1, new object[] { "секрет" });

Вот так, с помощью рефлексии в C#, мы можем вызвать даже приватный метод! Флаг BindingFlags.NonPublic даёт доступ к закрытым членам. Это мощный, но опасный инструмент — используйте его только если уверены в том, что делаете.

Работа с атрибутами через рефлексию

Атрибуты — это метки, которые добавляют метаданные к коду. Рефлексия позволяет их читать и даже проверять во время выполнения. Это основа для многих фреймворков (например, ASP.NET Core, Entity Framework).

Напишем пример: свой атрибут для валидации и проверка через рефлексию.

// Создаём свой атрибут
[AttributeUsage(AttributeTargets.Property)]
public class MinLengthAttribute : Attribute
{
    public int MinLength { get; }
    public MinLengthAttribute(int min) => MinLength = min;
}

// Применяем атрибут
public class UserModel
{
    [MinLength(3)]
    public string Name { get; set; }
    
    [MinLength(6)]
    public string Password { get; set; }
}

// Функция валидации через рефлек                

Блоги

Рекомендаций по книгам