Что такое Reflection в C#?

Ответ

Reflection (отражение) в C# — это механизм времени выполнения, который позволяет инспектировать метаданные сборок, модулей и типов, а также динамически создавать экземпляры классов, вызывать методы и получать или устанавливать значения полей.

Основное применение:

  • Динамическая загрузка сборок и создание экземпляров (например, для плагинов).
  • Фреймворки сериализации/десериализации (например, JSON, XML).
  • ORM (Object-Relational Mapper) для маппига свойств объектов на столбцы таблиц.
  • Инструменты анализа кода и тестирования.

Пример использования:

using System.Reflection;

// Получаем тип класса string
Type stringType = typeof(string);

// Получаем метод ToUpper
MethodInfo toUpperMethod = stringType.GetMethod("ToUpper", new Type[0]);

// Вызываем метод на конкретном экземпляре
object result = toUpperMethod.Invoke("hello", null);
Console.WriteLine(result); // Выведет: HELLO

Ключевые нюансы и ограничения:

  • Производительность: Операции рефлексии значительно медленнее статического вызова кода, так как требуют проверок во время выполнения. В критичных по производительности местах следует избегать рефлексии или кэшировать полученные объекты (например, MethodInfo).
  • Безопасность: Рефлексия может нарушать инкапсуляцию, предоставляя доступ к приватным (private) и защищённым (protected) членам при использовании флага BindingFlags.NonPublic. Это требует соответствующих разрешений безопасности.
  • Исключения: Использование может приводить к исключениям времени выполнения, таким как ArgumentNullException, MissingMethodException, TargetInvocationException.
  • Поддержка AOT: В средах с компиляцией Ahead-of-Time (AOT), таких как некоторые мобильные платформы или .NET Native, использование рефлексии может быть ограничено и требует специальной настройки.

Ответ 18+ 🔞

Давай разберём эту вашу рефлексию в C#, а то как будто про какую-то магию пишут, а на деле всё просто и местами даже больно.

Reflection (отражение) — это по сути твой внутренний шпион внутри программы. Во время работы она может сама себя ощупать, узнать, из каких классов состоит, какие в них методы прячутся, и даже вызвать их, хотя изначально про них нихрена не знала. Не статический вызов, а именно в рантайме.

Где это, блядь, применяется? Ну, например:

  • Плагины. Загрузил сборку (DLL) как придётся, покопался в ней рефлексией, нашёл классы, которые от IPlugin наследуются — и вот тебе, система расширений готова.
  • Сериализаторы (JSON, XML). Как думаешь, System.Text.Json узнаёт, какие у твоего класса свойства? Правильно, рефлексией. Читает метаданные и пишет их в строку.
  • ORM вроде Entity Framework. Как он маппит свойства твоего User на столбцы Users в базе? Опаньки, снова отражение — бежит по свойствам и смотрит, какие есть.
  • Тестовые раннеры (NUnit, xUnit). Как они находят все методы, помеченные [Test]? Ну ты понял.

Вот тебе живой пример, смотри:

using System.Reflection;

// Берём тип, как он есть. Допустим, string
Type stringType = typeof(string);

// Ищем у него метод "ToUpper". Без параметров.
MethodInfo toUpperMethod = stringType.GetMethod("ToUpper", new Type[0]);

// Вызываем этот метод на реальной строке. Invoke — это и есть волшебный пендель.
object result = toUpperMethod.Invoke("hello", null);
Console.WriteLine(result); // Выведет: HELLO

Видишь? Мы взяли тип, вытащили из него метод как объект MethodInfo и дернули его. Как будто мы не знали на этапе компиляции, что у строки есть ToUpper.

А теперь, блядь, ложка дёгтя, и немалая:

  1. Производительность — пиздец. Вызов через Invoke — это не прямые скобочки (). Там куча проверок, распаковок. Это в сотни раз медленнее. Если делаешь в цикле на миллион итераций — забудь. Совет: если уж очень надо, найденные MethodInfo или PropertyInfo кэшируй в словарик, чтобы не искать каждый раз по новой.

  2. Безопасность. Рефлексия может залезть в private-поле твоего класса и вытащить оттуда то, что ты прятал. Для этого есть BindingFlags.NonPublic. С одной стороны, мощно, с другой — это как дать ребёнку отвёртку и оставить его одного с розеткой. Нужны соответствующие права, иначе runtime тебя просто остановит.

  3. Исключения летят как из пулемёта. Передал не те аргументы в Invoke — получи TargetInvocationException или ArgumentException. Метод не нашёлся — лови MissingMethodException. Код должен быть готов к этому говну.

  4. AOT (Ahead-of-Time компиляция) — отдельная песня. Это для iOS, некоторых сценариев .NET Native и т.д. Там компилятор заранее всё собирает в нативный код и выкидывает всё, что не используется статически. А если твоя рефлексия пытается найти тип по имени из конфига, которого на этапе компиляции не видно? Всё, приехали, TypeLoadException в рантайме. Приходится явно подсказывать линкеру, что трогать нельзя.

Итог: Инструмент хуёвой силы, но и ответственности. Пользоваться им нужно точечно, понимая цену, а не тыкать везде, где лень нормальный интерфейс или фабрику написать.