Рефлексия (отражение, reflection) в C#

Рефлексия (отражение, reflection) в C#

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

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

В пространстве имен System.Reflection и System.Reflection.Emit содержится ряд классов для рефлексии.

Рефлексия типов и System.Type

Экземпляр System.Type представляет метаданные для типа.

Получение экземпляра Type

Получить экземпляр System.Type можно вызвав метод GetType на любом объекте или с помощью оператора typeof :

Экземпляр Type также можно извлечь по имени, вызвав метод GetType на объекте сборки (тип Assambly ), содержащей данный тип, передав ему имя типа в виде строки:

Класс Assembly также содержит метод GetTypes , возвращающий все типы, содержащиеся в сборке:

Если объект Assambly отсутствует, тип можно получить через его имя с указанием сборки (полное имя типа, за которым следует полностью заданное имя сборки). При этом сборка неявно загружается, как если бы вызывался метод Assembly.Load(string) :

С помощью свойств экземпляра System.Type можно получить доступ к имени типа, его сборке, базовому типу, его видимости и т.д.:

Получить тип массива можно также вызвав MakeArrayType на типе его элементов. Методу можно передать целочисленный аргумент, тогда он вернет тип многомерного прямоугольного массива с указанным числом измерений:

Метод GetElementType делает обратное — он извлекает тип элементов массива из экземпляра типа массива:

Метод GetArrayRank возвращает количество измерений в прямоугольном массиве:

Вложенные типы можно извлечь вызвав метод GetNestedTypes на содержащем их типе:

Имена типов

Тип имеет свойства Namespace , Name и FullName . Как правило FullName состоит из первых двух свойств, исключение составляют вложенные типы и закрытые обобщенные типы.

Также тип имеет свойство AssemblyQualifiedName , которое возвращает FullName и через запятую полное имя его сборки.

Для вложенных типов имя родительского типа присутствует только в FullName , имя родительского типа отделяется от имени вложенного символом + :

Имена обобщенных типов содержат суффикс состоящий из символа ' , за которым следует количество параметров типа. Для несвязанных типов это правило применяется к Name и FullName , для связанных типов FullName дополняется перечислением имен всех параметров типа с указанием сборки:

Имена массивов и указателей также включают свои суффиксы:

Если экземпляр Type описывает параметр ref или out , то его имя снабжается суффиксом & :

Базовые типы и интерфейсы

Свойство BaseType возвращает базовый тип, а метод GetInterfaces — реализуемые интерфейсы:

Метод IsInstanceOfType возвращает true , если переданный ему объект является экземпляром типа, на котором он вызван. Метод аналогичен использованию оператора is :

Метод IsAssignableFrom возвращает true , если переданный ему тип реализует интерфейс или является производным классом от типа, на котором он вызван:

Метод IsSubclassOf работает также, но исключает интерфейсы.

Создание экземпляра типа

Существует два способа динамически создать объект из его типа:

  • вызов статического метода Activator.CreateInstance
  • вызов метода Invoke на экземпляре ConstructorInfo , который можно получить вызвав метод GetConstructor на экземпляре Type

Метод Activator.CreateInstance принимает экземпляр Type и дополнительные аргументы, передаваемые конструктору:

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

Метод Invoke более универсален, он может быть использован, когда значения аргументов не позволяют устранить неоднозначность между перегруженными конструкторами (например, если аргумент равен null ):

Для доступа к не публичному конструктору необходимо дополнительно передать BindingFlags .

Для динамического создания делегата необходимо вызвать Delegate.CreateDelegate :

К созданному таким образом делегату можно обратиться вызвав DynamicInvoke (как в примере выше), либо приведя его к типизированному делегату:

Вместо имени метода в CreateDelegate можно передавать MethodInfo .

Обобщенные типы

Type может представлять закрытый или несвязанный обобщенный тип. Как и на этапе компиляции экземпляр, экземпляр закрытого обобщенного типа может быть создан, несвязанного — нет:

Метод MakeGenericType преобразует несвязанный обобщенный в закрытый, принимая в качестве параметров желаемые аргументы типа:

Метод GetGenericTypeDefinition делает обратное:

Свойство IsGenericType возвращает true если тип является обобщенным, а свойство IsGenericTypeDefinition — если обобщенный тип является несвязанным:

Метод GetGenericArguments возвращает аргументы типа для закрытых обобщенных типов:

Для несвязанных обобщенных типов метод GetGenericArguments возвращает типы-заполнители, указанные при объявлении обобщенного типа:

Рефлексия членов типа

Метод GetMembers экземпляра Type возвращает члены типа. При вызове без аргументов метод возвращает все публичные члены типа и его базовых типов:

Методу GetMembers в качестве дополнительного параметра можно передать enum MemberTypes , что ограничит возвращаемые им виды членов. MemberTypes включает следующие члены: All , Custom , Field , NestedType , TypeInfo , Constructor , Event , Method , Property . Также можно использовать специальные методы, возвращающие отдельные виды членов: GetMethods , GetFields , GetProperties , GetEvents , GetConstructors и GetNestedTypes .

Метод GetMember извлекает отдельный член по имени, но возвращает массив, т.к. члены могут быть перегружены:

Существуют также методы GetMethod , GetField , GetPropertie , GetEvent , GetConstructor и GetNestedType , возвращающие конкретный вид члена по имени.

TypeInfo

Члены типа можно также получить с помощью свойств класса TypeInfo , которые возвращают не массив, а IEnumerable<T> . Чаще других используется свойство DeclaredMembers , которое в отличает от метода GetMembers не возвращает унаследованные члены:

TypeInfo также содержит свойства для возврата специфических видов членов: DeclaredProperties , DeclaredMethods , DeclaredEvents и т.д., а также методы для возврата конкретных членов по имени, например, GetDeclaredMethod . Последний не может применяться для возврата перегруженных методов, т.к. нет способа указать тип параметров. Вместо этого можно выполнить запрос LINQ:

MemberInfo

Все указанные выше методы возвращают массив объектов MemberInfo , или его производных классов. Класс MemberInfo имеет свойство MemberTypes типа enum MemberTypes , описанного выше. Также объект MemberInfo имеет свойство Name и два свойства возвращающие Type :

  • DeclaringType — возвращает тип содержащий член
  • ReflectedType — возвращает тип, на котором был вызван GetMembers

Если метод GetMembers был вызван на производном типе, то для свойства, содержащегося в базовом классе DeclaringType вернет базовый тип, а ReflectedType — производный.

MemberInfo имеет также два свойства-идентификатора члена:

  • MethodHandle — уникально для каждого метода внутри домена приложения
  • MetadataToken — уникально по всем типам и членам в рамках модуля сборки

MemberInfo является абстрактным базовым классом для следующих типов:

  • FieldInfo
  • PropertyInfo
  • MethodBase
    • ConstructorInfo
    • MethodInfo

    Его можно приводить к своим подтипам. Каждый подкласс имеет множество свойств и методов, отражающих все аспекты метаданных члена: его видимость, модификаторы, аргументы типа, параметры, возвращаемый тип и специальные атрибуты:

    Рефлексия специальных членов

    Индексатор типа можно получить с помощью метода GetDefaultMembers , С точки зрения рефлексии он представляет собой свойство типа вида DefaultMember .

    Член перечисления ( enum ) можно получить с помощью метода GetField . Члены перечисления с точки зрения рефлексии представляют собой статически поля перечисления.

    Оператор можно получить с помощью того же метода GetMethod передав ему строку "op_" + название оператора. Операторы при рефлексии рассматриваются как статические методы со специальными именами, начинающимися с op_ , например op_Addition .

    Финализатор можно получить с помощью GetMethod , передав ему в качестве названия метода строку "Finalize" .

    Свойства и события при рефлексии представляются несколькими объектами:

    • метаданные, представляющие свойство или событие (объект PropertyInfo или EventInfo )
    • один или два поддерживающих метода

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