Введение: магия отражения
Представьте, что вы пишете код, который может "смотреть" на сам себя. Не как в кривом зеркале, а как в рентгеновском аппарате — видеть структуру, свойства, методы, даже запускать их, не зная имени класса на этапе компиляции. Это не магия, это рефлексия в 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; }
}
// Функция валидации через рефлек