Приветствую всех, сегодня поговорим о рефлексии, для чего она нужна, ее применения. Тема эта очень интересная, и ее методы использования часто приходится применять в больших проектах.
Рефлексия (отражение) — это процесс, во время которого программа может отслеживать и модифицировать собственную структуру и поведение во время выполнения, это своего рода процесс обнаружения типов во время выполнения.
Рефлексия позволяет: перечислять члены типа, создавать новые экземпляры объекта, запускать на выполнение члены объекта, извлекать информацию о типе, извлекать информацию о сборке, исследовать пользовательские атрибуты, примененные к типу, создавать и компилировать новые сборки.
Метаданные описывают все классы и члены классов, определённые в сборке, а также классы и члены классов, которые текущая сборка вызывает из другой сборки.
Манифест сборки— коллекция данных, с описанием того, как элементы любой сборки (статической или динамической) связаны друг с другом. Манифест сборки содержит все
метаданные, необходимые для задания требований сборки к версиям и удостоверения безопасности, а также все метаданные, необходимые для определения области действия сборки и разрешения ссылок на ресурсы и классы. Манифест сборки может храниться в PE-файле (EXE или DLL) с кодом MSIL или же в отдельном PE-файле, содержащем только данные манифеста.
Модули — это контейнеры типов, расположенные внутри сборки. Модуль может быть контейнером в простой, или многофайловой сборке. Несколько модулей в одной сборке применяются в редких случаях, когда нужно добавить код на разных языках в одну сборку или обеспечить поддержку выборочной загрузки модулей.
Парадигма программирования, положенная в основу отражения, называется рефлексивным программированием. Это один из видов метапрограммирования.
System.Reflection — Пространство имен содержит типы, предназначенные для извлечения сведений о сборках, модулях, членах, параметрах и других объектах в управляемом коде путем обработки их метаданных. Эти типы также можно использовать для работы с экземплярами загруженных типов, например для подключения событий или вызова методов.
Класс Type.
Type является корневым классом для функциональных возможностей рефлексии и основным способом доступа к метаданным. С помощью членов класса Type можно
получить сведения об объявленных в типе элементах: конструкторах, методах, полях, свойствах и событиях класса, а также о модуле и сборке, в которых развернут данный класс.
Способы получения экземпляра:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class MyClass { } class Program { static void Main() { var my = new MyClass(); Type type; // Способы получения экземрляра класса Type. // 1. type = my.GetType(); Console.WriteLine("1й способ: " + type); // 2. type = Type.GetType("TypeTest.MyClass"); // Полное квалифицированое имя типа в строковом представлении. Console.WriteLine("2й способ: " + type); // 3. type = typeof(MyClass); Console.WriteLine("3й способ: " + type); // Delay. Console.ReadKey(); } |
Таким образом мы получим ссылку на объект Type, содержащий информацию о целевом типе .
В типе System. Туре находятся перегруженных версий статического метода GetType. Принимают они в качестве аргумента String. Аргументом которым является строка должна иметь полное имя типа, а также включать его пространство имен. Если строка содержит просто имя типа, метод проверяет, определен ли тип с указанным именем в вызывающей сборке. Если это так, возвращается ссылка на соответствующий объект RuпtimeType.
При помощи членов класса Type можно получить сведения об объявленных в типе элементах: конструкторах, методах, полях, свойствах и событиях класса, а также о модуле и сборке, в которых развернут данный класс.
Экземпляр класса Туре можно получить несколькими способами. Единственное, что нельзя делать — так это напрямую создавать объект Туре с помощью ключевого слова
new, потому что класс Туре является абстрактным.
Получаем разную информацию о классе с помощью методов класса Type:
1 2 3 4 5 6 7 |
Type t = cl.GetType(); Console.WriteLine("Полное Имя: {0}", t.FullName); Console.WriteLine("Базовый класс: {0}", t.BaseType); Console.WriteLine("Абстрактный: {0}", t.IsAbstract); Console.WriteLine("Это COM объект: {0}", t.IsCOMObject); Console.WriteLine("Запрещено наследование: {0}", t.IsSealed); Console.WriteLine("Это class: {0}", t.IsClass); |
Получаем информацию об именах всех методов:
1 2 3 4 5 |
Type t = cl.GetType(); MethodInfo[] mi = t.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); |
Получаем информацию об именах полей класса.
1 2 3 4 5 |
Type t = cl.GetType(); FieldInfo[] fi = t.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); |
Получаем список всех свойств класса:
1 2 |
Type t = cl.GetType(); PropertyInfo[] pi = t.GetProperties(); |
Получаем список всех интерфейсов классов:
1 2 3 |
Type t = cl.GetType(); Type[] it = t.GetInterfaces(); |
Получаем информацию обо всех конструкторах:
1 2 |
Type t = cl.GetType(); ConstructorInfo[] ci = t.GetConstructors(); |
Получаем значение из полей и вносим в них запись
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
class Program { class Test { public Test() { privateField = 7; } int privateField; public double publicField; public override string ToString() { return string.Format( "privateField: {0}, publicField: {1}", privateField, publicField ); } } static void Main() { var test = new Test() { publicField = 10.8 }; var privateField = typeof(Test).GetField("privateField", BindingFlags.NonPublic | BindingFlags.Instance); var publicField = typeof(Test).GetField("publicField", BindingFlags.Public | BindingFlags.Instance); Console.WriteLine( "Reflection, get fields values\r\n\tprivateField: {0}\r\n\tpublicField: {1}", (int)privateField.GetValue(test), (double)publicField.GetValue(test) ); privateField.SetValue(test, 35); publicField.SetValue(test, double.MaxValue); Console.WriteLine( "Reflection, fields values after 'set' method\r\n\tprivateField: {0}\r\n\tpublicField: {1}", (int)privateField.GetValue(test), (double)publicField.GetValue(test) ); Console.ReadLine(); } } } |
Класс Assembly.
Класс Assembly представляет собой сборку, которая является модулем с возможностью многократного использования, поддержкой версий и встроенным механизмом описания
общеязыковой исполняющей среды.
Статические методы класса Assemdly
GetAssembly -возвращает объект Assembly
GetCallingAssembly-возвращает Assembly с кодом, вызываемым текущим методом.
GetEntryAssembly-возвращает Assembly с кодом, запустившим процесс.
GetExecutingAssembly-возвращает Assembly с кодом, исполняемым в данный момент.
Load-загружает Assembly из текущей директории исполняемого файла.
LoadFile-загружает Assembly из заданного каталога.
LoadFrom-загружает Assembly в текущий AppDomain из заданного каталога.
ReflectionOnlyLoad-загружает Assembly только для анализа, запуск сборки невозможен.
ReflectionOnlyLoadFrom-загружает Assembly из заданного каталога только для анализа сборки, ее запуск невозможен.
Методы LoadFile и LoadFrom предоставляются для редко используемых скриптов, в которых сборка должна определяться по пути.
Чтобы получить объект Assembly для выполняемой в текущий момент сборки, следует воспользоваться методом GetExecutingAssembly.
Метод GetName возвращает объект AssemblyName, который обеспечивает доступ к отображаемому имени сборки.
Метод GetCustomAttributes используется для вывода атрибутов, примененных для сборки.
Метод GetFiles обеспечивает доступ к файлам в манифесте сборки.
При помощи класса Assembly — можно динамически загружать сборки, обращаться к членам класса в процессе выполнения (ПОЗДНЕЕ СВЯЗЫВАНИЕ), а так же получать информацию о самой сборке.
Метод GetManifestResourceNames предоставляет имена всех ресурсов в манифесте сборки.
Метод GetTypes выводит все типы, содержащиеся в сборке.
Метод GetExportedTypes выводит типы, которые видимы вызывающим объектам, находящимся вне сборки.
Метод GetType может использоваться для поиска конкретного типа в сборке.
Метод CreateInstance может использоваться для поиска и создания экземпляров ряда типов в сборке.’
Перечисление BindingFlags управляет извлечением членов типа с помощью методов GetMembers и других методов, специфичных для членов типа.
Перечисление BindingFlags поддерживает флаги, то есть, принимает несколько значений.
Поздним связыванием (late binding) называется технология, которая позволяет создавать экземпляр определенного типа и вызывать его члены во время выполнения без кодирования факта его существования жестким образом на этапе компиляции. При создании приложения, в котором предусмотрено позднее связывание с типом из какой то внешней сборки, добавлять ссылку на эту сборку нет никакой причины, и потому в манифесте вызывающего кода она непосредственно не указывается.
Assembly.Load() — метод для загрузки сборки.
1 |
Assembly assembly=Assembly.Load("Mylib"); |
Метод для получения информации о всех типах в сборке.
1 |
Type[] types = assembly.GetTypes(); |
Метод для получения информации о членах класса.
1 2 |
Type type = assembly.GetType("MyLib.dog"); MemberInfo[] members = type.GetMembers(); |
Получаем информацию о параметрах для метода TurboBoost();
1 2 3 4 5 6 7 8 9 10 |
Type type = assembly.GetType("MyLib.dog"); MethodInfo method = type.GetMethod("Barking"); ParameterInfo[] parameters = method.GetParameters(); // Выводим некоторые характеристики каждого из параметров. foreach (ParameterInfo parameter in parameters) { Console.WriteLine("Имя параметра: {0}", parameter.Name); Console.WriteLine("Позиция в методе: {0}", parameter.Position); Console.WriteLine("Тип параметра: {0}", parameter.ParameterType); } |
Класс Activator
Класс System.Activator (определенный в сборке mscorlib.dll) играет ключевую роль в процессе позднего связывания в .NET. Его метод Createlnstance, позволят создавать экземпляр подлежащего позднему связыванию типа. Этот метод имеет несколько перегруженных версий и потому обеспечивает довольно высокую гибкость. В самой простой версии Createlnstance принимает действительный объект Туре, описывающий сущность, которая должна размещаться в памяти на лету.
Динамическая генерация кода предназначена для динамической загрузки и исполнения сборки без предварительного обращения к ней.
Динамический код создается, только если загружается код, на который приложение раньше не ссылалось.
Для обеспечения динамической генерации кода, необходимо получить информацию о типах и запросить объект ConstructorInfo для конструирования нового типа. После
получения ConstructorInfo для создания требуемого объекта достаточно вызвать конструктор.
Класс Activator содержит методы для локального создания типов объектов.
Метод CreateInstance() создает экземпляр типа, определенного в сборке путем вызова конструктора, который наилучшим образом соответствует заданным аргументам.
Позднее связывание — это технология которая позволяет обнаруживать типы, определять их имена и члены непосредственно в процессе выполнения.
Раннее связывание — все указанные выше операции выполняются во время компиляции.
Пример позднего связываения, в данном примере мы выбираем исполняемы фаил, указываем метод и работаем с ним, передавая параметры и принимая значения вычисляемые методом.
Пример исполняемого файла, с которым мы применим позднее связывание благодаря рефлексии(отражение).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
namespace ConsoleApp40 { class MyClass { public int MyMethod(int i) { return i =i * i; } static void Main(string[] args) { } } } |
1 2 3 4 5 6 7 8 |
Assembly assembly = Assembly.LoadFrom(@"E:\ConsoleApp40.exe"); Type t = assembly.GetType("ConsoleApp40.MyClass"); object[] ps = { 25 }; var instance = Activator.CreateInstance(t); MethodInfo inf = t.GetMethod("MyMethod"); object[] fd = { 3 }; var r= inf.Invoke(instance, fd); Console.WriteLine(r); |
При помощи класса Activator производится позднее связывание. Метод CreateInstance() — предназначен для создания экземпляра типа во время выполнения.
1 2 |
Type t = assembly.GetType("ConsoleApp40.MyClass"); var instance = Activator.CreateInstance(t); |
Получаем экземпляр класса MethodInfo для метода MyMethod().
1 |
MethodInfo inf = t.GetMethod("MyMethod"); |
Массив параметров для метода
1 |
object[] ps = { 25 }; |
Вызов метода MyMethod.
Первый параметр — ссылка на экземпляр для которого будет вызван метод MyMethod
Второй параметр — массив аргументов метода MyMethod (в данном случае передаем массив объектов хранящий значение 3)
1 |
var r= inf.Invoke(instance, ps); |
Еще один пример, содержащий два варианта вызова метода и его применения.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
namespace Reflection { class Class1 { public int AddNumb(int numb1, int numb2) { int ans = numb1 + numb2; return ans; } [STAThread] static void Main(string[] args) { Type type1 = typeof(Class1); //Create an instance of the type object obj = Activator.CreateInstance(type1); object[] mParam = new object[] { 5, 10 }; //invoke AddMethod, passing in two parameters int res = (int)type1.InvokeMember("AddNumb", BindingFlags.InvokeMethod, null, obj, mParam); Console.Write("Result: {0} \n", res); var rr = (Class1)obj; int tt = rr.AddNumb(1, 2); Console.Write("Result: {0} \n", tt); Console.ReadKey(); } } } |
Пример того как можно использовать позднее связывание, а так же использовать методы из других сборок.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
namespace ConsoleApp40 { class MyClass { public int MyMethod(int i) { return i = i * i; } } class Program { static void Main(string[] args) { Assembly assembly = Assembly.Load("ConsoleApp40"); dynamic instance = Activator.CreateInstance(assembly.GetType("ConsoleApp40.MyClass")); Console.WriteLine(" " + instance.MyMethod(5)); } } } |
По аналогии мы можем использовать исполняемый фаил exe либо dll, достаточно изменить.
1 |
Assembly assembly = Assembly.LoadFrom("С:\\ConsoleApp40.exe"); |
Класс Module
Класс Module можно использовать для извлечения или поиска типов в заданном модуле. Для сборок, изначально написанных на языке с поддержкой модулей, данный класс также поддерживает методы GetField, GetFields, GetMethod и GetMethods. К модулям данного типа поля и методы можно подключать непосредственно.
Класс Propertylnfo
Класс Propertylnfo поддерживает получение и установку отдельных свойств. В данном случае метод GetValue класса Propertylnfo вызывается для получения значения свойства Count.
Класс MemberInfo
Класс MemberInfo предназначен для исполнения произвольного кода над заданным экземпляром типа также для исполнения статического кода, связанного с определенным типом.
Генерация кода во время выполнения — техника, которая позволяет определить информацию о сборках и типах, или даже создать собственную сборку (хранимую в памяти, на диске) непосредственно в момент выполнения.
Пример: Обращаемся к полям с помощью отражения C#
Пример dll которая находится в папке с программой.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
using System.Linq; using System.Text; using System.Threading.Tasks; internal class falldamage { // Token: 0x040016E2 RID: 5858 public static float min_vel = 24f; // Token: 0x040016E3 RID: 5859 public static float max_vel = 38f; // Token: 0x040016E4 RID: 5860 public static bool enabled = true; public static float injury_length = 40f; } |
Основная программа в которой мы применяем отражение для изменения полей:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
using System.Reflection; namespace ConsoleApp13 { class Program { static void Main(string[] args) { Assembly a = Assembly.Load(@"Libra"); Type t = a.GetType("falldamage"); FieldInfo field = t.GetField("enabled", BindingFlags.Public | BindingFlags.Static); field.SetValue(a, false); Console.WriteLine(field.GetValue(a)); Console.ReadKey(); } } } |
Таким образом изменив значение переменной, мы вводим новый результат в консоль.
Пример показывающий информацию о текущей сборке, все класс и методы.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public static void Reflection() { Type[] typelist = Assembly.GetExecutingAssembly().GetTypes(); foreach (Type cl in typelist) { Console.WriteLine(""); Console.WriteLine(cl.Name); Console.WriteLine("**************************************************************************"); MethodInfo[] mi = cl.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); foreach(var tt in mi) { Console.WriteLine(tt.Name); } Console.WriteLine(""); } } |
Пример показывающий информацию о всех сборках, подключенных dll.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public static void Search() { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); string text=""; foreach (var w in assemblies) { foreach (var i in w.GetReferencedAssemblies()) { text += i.FullName+"\n"; Console.WriteLine(i.FullName); } } File.WriteAllText("D:\\test.txt", text); } |
Альтернативный вариант но он не работает в Unity
1 2 3 4 5 6 7 8 9 10 11 12 |
public static void Main(string[] args) { Assembly a = Assembly.GetEntryAssembly(); foreach (var i in a.GetReferencedAssemblies()) { Console.WriteLine(i.FullName); } Console.ReadLine(); } |
Создание объекта с помощью рефлексии.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
class MyClass { public int Property { get; set; } private bool Field; public int Method(int argument) { return Property + argument; } } class Program { public static void Main() { var type = typeof(MyClass); ConstructorInfo ctor = type.GetConstructor(new Type[] { }); var result = ctor.Invoke(new object[] { }); // делает то же, что // object result = new MyClass(); var propertyInfo = type.GetProperty("Property"); propertyInfo.SetValue(result, 5); var variable1 = (int)type.GetMethod("Method").Invoke(result, new object[] { 3 }); // делает то же, что // result.Property = 5; var variable1 = result.Method(3); } } |
Рефлексия для свойств, методов и полей
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
class MyClass { public int Property { get; set; } private bool Field; public int Method(int argument) { return Property + argument; } } class Program { public static void Main() { var type = typeof(MyClass); ConstructorInfo ctor = type.GetConstructor(new Type[] { }); var result = ctor.Invoke(new object[] { }); // делает то же, что // object result = new MyClass(); var propertyInfo = type.GetProperty("Property"); propertyInfo.SetValue(result, 5); var variable1 = (int)type.GetMethod("Method").Invoke(result, new object[] { 3 }); // делает то же, что // result.Property = 5; var variable1 = result.Method(3); var field = type.GetField("Field", BindingFlags.NonPublic | BindingFlags.Instance); field.SetValue(result, true); // делает то же, что // result.Field = true, несмотря на то, что это поле private } } |