Рефлексия C# (или «отражение» в терминах Microsoft) — это, в первую очередь, процесс выявления типов (объектов типа Type
) во время выполнения приложения. Любое наше приложение C# состоит из объектов, реализующих те или иные классы и интерфейсы, а также из методов, свойств объектов и других элементов. Рефлексия же (отражение) позволяет определить все эти элементы прямо во время выполнения приложение и, в случае необходимости, производить манипуляции с выявленными объектами.
Когда удобно использовать рефлексию (отражение в C#)
Рефлексию удобно использовать в следующих ситуациях:
- При необходимости доступа к атрибутам в метаданных программы.
- Для проверки и создания экземпляров типов в сборке.
- Для создания типов во время выполнения.
- Для выполнения позднего связывания, которое обеспечивает доступ к методам в типах, созданных во время выполнения.
Основные классы
Основные методы и классы рефлексии сосредоточены в пространстве имен System.Reflection
. В этом пространстве имен можно выделить следующие основные классы:
Assembly
— класс, представляющий сборку и позволяющий манипулировать этой сборкойAssemblyName
— класс, хранящий информацию о сборкеMemberInfo
— базовый абстрактный класс, определяющий общий функционал для классовEventInfo
,FieldInfo
,MethodInfo
иPropertyInfo
EventInfo
— класс, хранящий информацию о событииFieldInfo
— хранит информацию об определенном поле типаMethodInfo
— хранит информацию об определенном методеPropertyInfo
— хранит информацию о свойствеConstructorInfo
— класс, представляющий конструкторModule
— класс, позволяющий получить доступ к определенному модулю внутри сборкиParameterInfo
— класс, хранящий информацию о параметре метода
Пример: получение информации о версии сборки в C# (класс AssemblyName)
Довольно часто в приложении необходимо выводить информацию о его версии, например, это может потребоваться при оформлении окна «About» вашего приложения. Для этого мы можем завести какую-либо константу, а можем использовать возможности, предоставляемые классом AssemblyName
. Продемонстрируем это на примере:
namespace Reflection { class Program { static void Main(string[] args) { //получаем информацию о сборке, //код которой выполняется в текущий момент времени Assembly assembly = Assembly.GetExecutingAssembly(); //получаем отображаемое имя сборки Console.WriteLine(assembly.FullName); //Получаем подробную информацию об уникальном идентификаторе сборки AssemblyName assemblyName = assembly.GetName(); //выводим простое имя сборки (без версии и т.д.) Console.WriteLine(assemblyName.Name); //выводим информацию о версии в виде одной строки Console.WriteLine($"Версия сборки: {assemblyName.Version}"); //выводим детальную информацию о сборке Console.WriteLine($"Major {assemblyName.Version.Major}"); Console.WriteLine($"Minor {assemblyName.Version.Minor}"); Console.WriteLine($"Build {assemblyName.Version.Build}"); Console.WriteLine($"MinorRevision {assemblyName.Version.MinorRevision}"); Console.WriteLine($"MajorRevision {assemblyName.Version.MajorRevision}"); } } }
Результатом выполнения этого кода будет следующий вывод в консоли:
Reflection
Версия сборки: 1.1.2.10
Major 1
Minor 1
Build 2
MinorRevision 10
MajorRevision 0
Кстати, используя класс Version
в C#, достаточно просто сравнивать различные версии сборок, например, так:
Version currentVersion = assemblyName.Version; Version newVersion = new Version("1.1.1.12"); Console.WriteLine($"Сравниваем текущую версию - {currentVersion} с новой - {newVersion}"); if (currentVersion == newVersion) Console.WriteLine("Версии равны"); else if (currentVersion < newVersion) Console.WriteLine("Новая версии больше текущей"); else Console.WriteLine("Новая версии меньше текущей");
Класс System.Type
Чтобы получить информацию о членах типа, нам необходимо использовать класс System.Type
. Этот класс представляет изучаемый тип, инкапсулируя всю информацию о нем. С помощью свойств и методов 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
, если тип представляет интерфейс
Получение типа
Чтобы управлять типом во время выполнения приложения и получать всю информацию о нем, необходимо этот тип каким-либо способом получить. В C# это можно сделать тремя способами:
- с помощью ключевого слова
typeof
, - с помощью метода
GetType()
классаObject
- применяя статический метод
Type.GetType()
.
Получение типа через typeof
Определим в нашем приложении любой класс, например, вот такой:
class Person { public string Name { get; set; } public string Family { get; set; } private byte age; public byte Age { get { return age; } set { if (value > 100) throw new Exception("Возраст человека не может быть больше 100 лет"); else age = value; } } }
Теперь воспользуемся ключевым словом typeof
чтобы получить тип:
Type type = typeof(Person); Console.WriteLine($"Type Name: {type.Name}");
Результат вывода в консоли:
Получение типа с помощью метода GetType
класса Object
Person user = new Person { Name = "Вася", Family = "Пупкин", Age = 18 }; Type type = user.GetType(); Console.WriteLine($"Type Name: {type.Name}");
Здесь, как можно увидеть, в отличие от предыдущего примера, чтобы получить тип Type
, необходимо вначале создать объект класса.
Использование статического метода Type.GetType()
И третий способ получения типа — статический метод Type.GetType()
:
Type type = Type.GetType("Reflection.Person", true, true); Console.WriteLine($"Type Name: {type.Name}");
Здесь следует обратить внимание на то, что первый параметр указывает на полное имя класса, включая и пространство имен, в котором этот класс находится. Второй параметр указывает, будет ли генерироваться исключение, если класс не удастся найти. В данном случае значение true
означает, что исключение будет генерироваться. И третий параметр указывает, надо ли учитывать регистр символов в первом параметре. Значение true
означает, что регистр игнорируется.
В данном случае класс основной программы и класс Person
находятся в одном проекте и компилируются в одну сборку exe. Однако может быть, что нужный нам класс находится в другой сборке dll. Для этого после полного имени класса через запятую указывается имя сборки:
Type type = Type.GetType("Reflection.Person, MyLibrary", false, false);
Теперь, получив информацию о типе, можно приступать к его исследованию, чем мы и займемся в следующей части.
Итого
Рефлексия C# — это процесс выявления типов (объектов типа Type
) во время выполнения приложения (run-time). С помощью классов, содержащихся в пространстве имен System.Reflection
мы можем получать информацию о сборке, используемых классах, свойствах классов и прочую информацию для использования в нашем же приложении. Так, например, используя класс AssemblyName
можно довольно легко получить информацию о версии сборки и, при необходимости сравнить эту версию с другой.
Для получения информации о типе можно использовать три варианта работы: используя ключевое слово typeof
, используя метод GetType()
у Object
и, используя статический метод Type.GetType()
.