Рефлексия (отражение, 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 возвращает поддерживающие методы вместе со всеми остальными: