Что такое рефлексия программирование
Основные принципы программирования: интроспекция и рефлексия
Авторизуйтесь
Основные принципы программирования: интроспекция и рефлексия
Часто во время работы программы нам бывает нужна информация о данных — например, какой у них тип или являются ли они экземпляром класса (в ООП). Опираясь на эти знания, нам нужно проводить над данными некоторые операции, или даже изменять их — но необходимого вида данных у нас может и не быть! Если вы ничего не поняли, не расстраивайтесь — мы подробно во всём разберёмся. Всё, что я здесь описал — это иллюстрация целей двух возможностей, присутствующих почти в каждом современном языке программирования: интроспекции и рефлексии.
Интроспекция
Интроспекция — это способность программы исследовать тип или свойства объекта во время работы программы. Как мы уже упоминали, вы можете поинтересоваться, каков тип объекта, является ли он экземпляром класса. Некоторые языки даже позволяют узнать иерархию наследования объекта. Возможность интроспекции есть в таких языках, как Ruby, Java, PHP, Python, C++ и других. В целом, инстроспекция — это очень простое и очень мощное явление. Вот несколько примеров использования инстроспекции:
В Python самой распространённой формой интроспекции является использование метода dir для вывода списка атрибутов объекта:
В Ruby интроспекция очень полезна — в частности из-за того, как устроен сам язык. В нём всё является объектами — даже класс — и это приводит к интересным возможностям в плане наследования и рефлексии (об этом ниже). Если вы хотите узнать об этом больше, советую прочитать мини-цикл Metaprogramming in Ruby.
Прим. перев. Также не будет лишним прочитать нашу статью, посвящённую интроспекции в Ruby.
Вот несколько простых примеров интроспекции с использованием IRB (Interactive Ruby Shell):
Вы также можете узнать у объекта, экземпляром какого класса он является, и даже «сравнить» классы.
Однако интроспекция — это не рефлексия; рефлексия позволяет нам использовать ключевые принципы интроспекции и делать действительно мощные вещи с нашим кодом.
Рефлексия
Интроспекция позволяет вам изучать атрибуты объекта во время выполнения программы, а рефлексия — манипулировать ими. Рефлексия — это способность компьютерной программы изучать и модифицировать свою структуру и поведение (значения, мета-данные, свойства и функции) во время выполнения. Простым языком: она позволяет вам вызывать методы объектов, создавать новые объекты, модифицировать их, даже не зная имён интерфейсов, полей, методов во время компиляции. Из-за такой природы рефлексии её труднее реализовать в статически типизированных языках, поскольку ошибки типизации возникают во время компиляции, а не исполнения программы (подробнее об этом здесь). Тем не менее, она возможна, ведь такие языки, как Java, C# и другие допускают использование как интроспекции, так и рефлексии (но не C++, он позволяет использовать лишь интроспекцию).
По той же причине рефлексию проще реализовать в интерпретируемых языках, поскольку когда функции, объекты и другие структуры данных создаются и вызываются во время работы программы, используется какая-то система распределения памяти. Интерпретируемые языки обычно предоставляют такую систему по умолчанию, а для компилируемых понадобится дополнительный компилятор и интерпретатор, который следит за корректностью рефлексии.
Мне кажется, что мы сказали много об определении рефлексии, но смысла это пока несёт мало. Давайте взглянем на примеры кода ниже (с рефлексией и без), каждый из которых создаёт объект класса Foo и вызывает метод hello.
Этот список отнюдь не исчерпывает возможности рефлексии. Это очень мощный принцип, который к тому же является обычной практикой в метапрограммировании. Тем не менее, при использовании рефлексии нужно быть очень внимательным. Хотя у неё и есть свои преимущества, код, использующий рефлексию, значительно менее читаем, он затрудняет отладку, а также открывает двери по-настоящему плохим вещами, например, инъекции кода через выражения eval.
Eval-выражения
Некоторые рефлективные языки предоставляют возможность использования eval-выражений — выражений, которые распознают значение (обычно строку) как выражение. Такие утверждения — это самый мощный принцип рефлексии и даже метапрограммирования, но также и самый опасный, поскольку они представляют собой угрозу безопасности.
Рассмотрим следующий пример кода на Python, который принимает данные из стороннего источника в Сети (это одна из причин, по которой люди пользуются eval-выражениями):
Защита программы будет нарушена, если кто-то передаст в метод get_data() такую строку:
Для безопасного использования eval-утверждений нужно сильно ограничивать формат входных данных — и обычно это лишь занимает лишнее время.
Заключение
Интроспекция и рефлексия — это очень мощные инструменты современных языков, и их понимание может позволить вам писать по-настоящему крутой код. Ещё раз отметим: интроспекция — это изучение атрибутов объекта, а рефлексия — это манипуляция ими. Будьте внимательны при использовании рефлексии, поскольку она может сделать ваш код нечитаемым и уязвимым. Чем больше сила, тем больше и ответственность — вот девиз всего, что связано с метапрограммированием.
Разбираемся с рефлексией на примерах в C#
Приветствую всех, сегодня поговорим о рефлексии, для чего она нужна, ее применения. Тема эта очень интересная, и ее методы использования часто приходится применять в больших проектах.
Рефлексия (отражение) — это процесс, во время которого программа может отслеживать и модифицировать собственную структуру и поведение во время выполнения, это своего рода процесс обнаружения типов во время выполнения.
Рефлексия позволяет: перечислять члены типа, создавать новые экземпляры объекта, запускать на выполнение члены объекта, извлекать информацию о типе, извлекать информацию о сборке, исследовать пользовательские атрибуты, примененные к типу, создавать и компилировать новые сборки.
Метаданные описывают все классы и члены классов, определённые в сборке, а также классы и члены классов, которые текущая сборка вызывает из другой сборки.
Манифест сборки— коллекция данных, с описанием того, как элементы любой сборки (статической или динамической) связаны друг с другом. Манифест сборки содержит все
метаданные, необходимые для задания требований сборки к версиям и удостоверения безопасности, а также все метаданные, необходимые для определения области действия сборки и разрешения ссылок на ресурсы и классы. Манифест сборки может храниться в PE-файле (EXE или DLL) с кодом MSIL или же в отдельном PE-файле, содержащем только данные манифеста.
Модули — это контейнеры типов, расположенные внутри сборки. Модуль может быть контейнером в простой, или многофайловой сборке. Несколько модулей в одной сборке применяются в редких случаях, когда нужно добавить код на разных языках в одну сборку или обеспечить поддержку выборочной загрузки модулей.
Парадигма программирования, положенная в основу отражения, называется рефлексивным программированием. Это один из видов метапрограммирования.
System.Reflection — Пространство имен содержит типы, предназначенные для извлечения сведений о сборках, модулях, членах, параметрах и других объектах в управляемом коде путем обработки их метаданных. Эти типы также можно использовать для работы с экземплярами загруженных типов, например для подключения событий или вызова методов.
Класс Type.
Type является корневым классом для функциональных возможностей рефлексии и основным способом доступа к метаданным. С помощью членов класса Type можно
получить сведения об объявленных в типе элементах: конструкторах, методах, полях, свойствах и событиях класса, а также о модуле и сборке, в которых развернут данный класс.
Рефлексия
Введение в рефлексию. Класс System.Type
Рефлексия представляет собой процесс выявления типов во время выполнения приложения. Каждое приложение содержит набор используемых классов, интерфейсов, а также их методов, свойств и прочих кирпичиков, из которых складывается приложение. И рефлексия как раз и позволяет определить все эти составные элементы приложения.
Основной функционал рефлексии сосредоточен в пространстве имен System.Reflection. В нем мы можем выделить следующие основные классы:
Assembly : класс, представляющий сборку и позволяющий манипулировать этой сборкой
AssemblyName : класс, хранящий информацию о сборке
MemberInfo : базовый абстрактный класс, определяющий общий функционал для классов EventInfo, FieldInfo, MethodInfo и PropertyInfo
EventInfo : класс, хранящий информацию о событии
FieldInfo : хранит информацию об определенном поле типа
MethodInfo : хранит информацию об определенном методе
PropertyInfo : хранит информацию о свойстве
ConstructorInfo : класс, представляющий конструктор
Module : класс, позволяющий получить доступ к определенному модулю внутри сборки
ParameterInfo : класс, хранящий информацию о параметре метода
Эти классы представляют составные блоки типа и приложения: методы, свойства и т.д. Но чтобы получить информацию о членах типа, нам надо воспользоваться классом System.Type.
Класс System.Type представляет изучаемый тип, инкапсулируя всю информацию о нем. С помощью его свойств и методов можно получить эту информацию. Некоторые из его свойств и методов:
Метод FindMembers() возвращает массив объектов MemberInfo данного типа
Метод GetConstructors() возвращает все конструкторы данного типа в виде набора объектов ConstructorInfo
Метод GetEvents() возвращает все события данного типа в виде массива объектов EventInfo
Метод GetFields() возвращает все поля данного типа в виде массива объектов FieldInfo
Метод GetInterfaces() получает все реализуемые данным типом интерфейсы в виде массива объектов Type
Метод GetMembers() возвращает все члены типа в виде массива объектов MemberInfo
Метод GetMethods() получает все методы типа в виде массива объектов MethodInfo
Метод GetProperties() получает все свойства в виде массива объектов PropertyInfo
Свойство Name возвращает имя типа
Свойство Assembly возвращает название сборки, где определен тип
Свойство Namespace возвращает название пространства имен, где определен тип
Свойство IsArray возвращает true, если тип является массивом
Свойство IsClass возвращает true, если тип представляет класс
Свойство IsEnum возвращает true, если тип является перечислением
Свойство IsInterface возвращает true, если тип представляет интерфейс
Получение типа
Получение типа через typeof :
Здесь определен класс User с некоторым набором свойств и полей. И чтобы получить его тип, используется выражение Type myType = typeof(User);
Получение типа с помощью метода GetType класса Object :
В отличие от предыдущего примера здесь, чтобы получить тип, надо создавать объект класса.
Первый параметр указывает на полное имя класса с пространством имен. В данном случае класс User находится в пространстве имен TestConsole. Второй параметр указывает, будет ли генерироваться исключение, если класс не удастся найти. В данном случае значение false означает, что исключение не будет генерироваться. И третий параметр указывает, надо ли учитывать регистр символов в первом параметре. Значение true означает, что регистр игнорируется.
В данном случае класс основной программы и класс User находятся в одном проекте и компилируются в одну сборку exe. Однако может быть, что нужный нам класс находится в другой сборке dll. Для этого после полного имени класса через запятую указывается имя сборки:
Теперь исследуем тип и получим всю информацию о нем.
Рефлексия и кодогенерация в C++
Язык С++ по сей день является одним из самых востребованных и гибких языков программирования. Но иногда возможностей языка не хватает, несмотря на то что стандарт развивается и расширяется. С такой проблемой столкнулся и я в процессе разработки 2D движка для игр. Я стоял перед необходимостью решения несколько нетривиальных задач, таких как сериализация, анимирование и связка с редактором. Для этого отлично подходит рефлексия. Готовые решения из сети, к сожалению, мне не подошли, поэтому пришлось конструировать свой велосипед.
Далее описаны детали реализации и демо проект. Кому интересно — добро пожаловать под кат.
Требования
Существует мнение, что правильно поставленные условия задачи практически решают эту задачу. Именно поэтому перед началом реализации были собраны необходимые требования и ограничения:
Синтаксис
Следуя требованию о простоте был выработан довольно простой синтаксис определения объекта поддерживающего рефлексию: необходимо чтобы класс был унаследован от IObject, содержал в теле макрос IOBJECT(*имя класса*); и у членов класса были указаны необходимые атрибуты через комментарии.
Пример класса с поддержкой рефлексии:
Примеры использования рефлексии:
Реализация
Как вы наверное уже догадались, работает все просто: пользователь объявляет классы, которые поддерживают рефлексию, а отдельная утилита генерирует код, который инициализирует типы в системе. В данной статье я опишу принцип работы, с минимальным включением исходного кода, так как его наличие раздует статью. Но к статье прилагается демо проект со всеми исходниками, который можно посмотреть и протестировать.
Итак, подробнее о структуре рефлексии.
Тип объекта
Каждому классу соответствует описание типа, в котором содержится имя класса, информация о наследовании, члены и методы класса. С помощью типа можно создать экземпляр класса. Так же в типе класса есть довольно интересный функционал: можно получить указатель на член класса по его пути и наоборот, получить путь к члену класса по его указателю. Чтобы понять что делают последние две функции достаточно взглянуть на пример использования:
Зачем это нужно? Ближайший пример — анимация. Допустим, есть анимация, которая анимирует параметр value из примера. Но как сохранить такую анимацию в файл? Здесь и нужны эти функции. Мы сохраняем ключи анимации с указанием пути к анимируемому параметру «aObject/bObject/value», и при загрузке из файла по этому пути находим нужную нам переменную. Такой подход позволяет анимировать совершенно любые члены любых объектов и сохранять/загружать в файл.
Однако, есть небольшой недостаток, который необходимо учитывать. Поиск указателя на член класса по пути происходит быстро и линейно, но обратный процесс совершенно не линеен и может занять много времени. Алгоритму приходится «прочесывать» все члены класса, их члены и так далее, пока не встретит нужный нам указатель.
Члены класса
Каждый член класса определяется с помощью типа FieldInfo, который содержит в себе тип члена, его имя и список атрибутов. Так же тип члена класса позволяет получить значение члена класса для определенного экземпляра и наоборот, присвоить значение. Тип, имя и атрибуты заполняются сгенерированным кодом. Присваивание и получение значения работают через адресную арифметику. На этапе инициализации типа высчитывается смещение в байтах относительно начала памяти под объект, затем, при присваивании или получении значения к адресу объекта прибавляется это смещение.
Функции класса
Каждой функции класса соответствует объект с интерфейсом FunctionInfo, который хранит в себе тип возвращаемого значения, имя функции и список параметров. Почему именно интерфейс и как вызвать функцию из этого интерфейса? Чтобы вызвать функцию нам нужен указатель на нее. Причем, указатель на функцию класса должен быть такого вида:
Для хранения таких указателей определены классы определены шаблонные классы:
Шаблонная магия пугает, но работает все довольно просто. На этапе инициализации типа для каждой функции создается объект класса SpecFunctionInfo или SpecConstFunctionInfo в зависимости от константности функции и помещается в тип объекта Type. Затем, когда нам нужно вызвать функцию из интерфейса FunctionInfo, мы статически преобразуем интерфейс в ISpecFunctionInfo и вызываем у него виртуальную функцию Invoke, переопределенный вариант которой выполняет функцию по сохраненному адресу.
Атрибуты
Функция атрибутов — это добавление мета информации для членов классов. С помощью атрибутов можно указывать сериализуемые члены, тип их сериализации, отображение в редакторе, параметры анимации и так далее. Атрибуты наследуются от интерфейса IAttribute и добавляются к членам класса на этапе инициализации типа. Пользователю лишь необходимо обозначит необходимые атрибуты через комментарии к членам класса.
Модуль рефлексии
Итак, вся структура типов у нас есть, необходимо все это где-то хранить. С этой простой задачей справляется класс-синглтон Reflection. С его помощью можно получить тип по имени, создать экземпляр типа или получить список всех зарегистрированных типов.
Кодогенерация
Почему именно кодогенерация? Есть решения рефлексии с помощью хитрых макросов, прописывания функции сериализации и тому подобное. Но у таких подходов один общий минус — фактически приходится вручную описывать типы. В рефлексии от этого никуда не избавиться, поэтому проще всего доверить эту рутинную работу отдельной подпрограмме, которая работает до компиляции и генерирует отдельный исходный файл с инициализацией типов.
По-хорошему, это конечно же должен делать сам компилятор, ведь у него есть вся информация о классах, членах и методах. Но решение должно быть не зависимым от компилятора, в конце концов глупо навязывать свой определенный компилятор разработчику, тем более для 2D игр с кучей целевых платформ.
Именно поэтому кодогенерация отдельной утилитой. К сожалению, утилита в моем проекте не может похвастаться изящностью и стопроцентной стабильностью, но на моем уже довольно немаленьком проекте она работает прекрасно. В будущем она наверняка будет переписана и дополнена, так как у нее есть как минимум один существенный минус — она написана на C#, что требует Windows платформы, либо наличия Mono. Это путь наименьшего сопротивления, и я выбрал его потому что на этапе демо версии мне нужно как можно больше функционала в проекте. Тем не менее в этой статье я опишу этапы ее работы и с какими трудностями я столкнулся.
Работа утилиты делится на два этапа:
Парсинг исходников проекта
Здесь мой подход отличается от работы компиляторов.
Генерация итогового исходника с инициализацией типов
Данный этап включает в себя генерирование следующих частей:
В конечном итоге мы получаем исходных файл с функцией, которая инициализирует все типы приложения. Пользователю лишь необходимо ее вызвать при старте приложения.
Введение в PHP Reflection API
Привет, Хабр! Представляю вашему вниманию перевод статьи «Introduction to PHP Reflection API» автора Mustafa Magdi.
Как в PHP анализировать структуру данных
Вступление
Когда я начал программировать на PHP, то не знал о возможностях Reflection API. Главная причина в том, что мне не нужно было проектировать свои простые классы, модули или даже пакеты. Затем я обнаружил, что это играет главную роль во многих областях. В статье мы рассмотрим Reflection API по следующим пунктам:
1. Что такое Reflection API
В информатике отражение или рефлексия (холоним интроспекции, англ. reflection) означает процесс, во время которого программа может отслеживать и модифицировать собственную структуру и поведение во время выполнения. — Wikipedia.
Что означает возможность остановить и взглянуть внутрь своего кода (reverse-engineering )? Давайте посмотрим на следующий фрагмент кода:
Класс Profile — чёрный ящик. Используя Reflection API вы сможете прочитать, что там находится внутри:
Таким образом ReflectionClass выступает аналитиком для нашего класса Profile, и в этом состоит главная идея Reflection API.
PHP даёт вам ключ к любому запертому ящику, таким образом мы имеем ключи
для следующего:
ReflectionClass: сообщает информацию о классе.
ReflectionFunction: сообщает информацию о функции.
ReflectionParameter: извлекает информацию о параметрах функции или метода.
ReflectionClassConstant: сообщает информацию о константе класса.
Полный список вы можете изучить на php.net
2. Установка и конфигурирование
Для использования классов Reflection API нет необходимости что-либо устанавливать или настраивать, так как они входят в состав ядра PHP.
3. Примеры использования
Далее представлено несколько примеров того, как мы можем использовать Reflection API:
Пример 1:
Получить родительский класс для определённого класса:
Пример 2:
Получить документацию метода getUserName() :
Пример 3:
Может использоваться как instanceOf и is_a() для валидации объектов:
Пример 4:
В некоторых ситуациях вы можете застрять с unit-тестированием и задаться вопросом: «Как я могу протестировать закрытую функцию?!»
Не беспокойтесь, вот хитрость:
Предыдущие примеры довольно просты, но есть другие примеры, в которых вы можете увидеть, как Reflection API используется более обширно:
4. Заключение
PHP предоставляет полноценный Reflection API, который помогает легко достичь различные области ООП-структур.
5. Ссылки
Также можно посмотреть пример использования Reflection API в пакете Codeception в классе Stub.
Этот класс через рефлексию помогает мо́кать методы и свойства в unit-тестах.
Следует добавить, что Reflection API работает довольно медленно, по этому не стоит сильно увлекаться. Использовать рекомендуется в тестах или во время отладки, но если можно обойтись без него, то лучше так и сделать. И категорически не рекомендуется использовать в рабочем коде проекта, т.к. это ещё и не безопасно.