Рефлексия C# (или «отражение» в терминах Microsoft) — это, в первую очередь, процесс выявления типов (объектов типа Type) во время выполнения приложения. Любое наше приложение C# состоит из объектов, реализующих те или иные классы и интерфейсы, а также из методов, свойств объектов и других элементов. Рефлексия же (отражение) позволяет определить все эти элементы прямо во время выполнения приложение и, в случае необходимости, производить манипуляции с выявленными объектами.
Когда удобно использовать рефлексию (отражение в C#)
Рефлексию удобно использовать в следующих ситуациях:
- При необходимости доступа к атрибутам в метаданных программы.
- Для проверки и создания экземпляров типов в сборке.
- Для создания типов во время выполнения.
- Для выполнения позднего связывания, которое обеспечивает доступ к методам в типах, созданных во время выполнения.
Основные классы
Основные методы и классы рефлексии сосредоточены в пространстве имен System.Reflection. В этом пространстве имен можно выделить следующие основные классы:
Assembly— класс, представляющий сборку и позволяющий манипулировать этой сборкойAssemblyName— класс, хранящий информацию о сборкеMemberInfo— базовый абстрактный класс, определяющий общий функционал для классовEventInfo,FieldInfo,MethodInfoиPropertyInfoEventInfo— класс, хранящий информацию о событии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().