NET Reflection представляет собой классический пример некоторой низкоуровневой библиотеки, которая может быть использована при решении прикладных задач. Что же это такое?Рефлексия (ударение на последнем "и", синоним слова интроспекция), или, по-английски, reflection - система, предоставляющая выполняемому коду информацию о нем самом. Звучит немного запутанно, и, как всегда, намного проще понять суть на примере.
Допустим, у вас есть класс MyCar, предоставляющий метод Start(). Его стандартный сценарий использования тривиален: public class MyCar { public void Start() { Console.WriteLine("Started!"); } } // Main method MyCar car = new MyCar(); car.Start();
Теперь давайте заставим наше маленькое приложение заняться интроспекцией, т.е. получить информацию о классе MyCar. Вставим такой вызов: Type myCarType = typeof(MyCar);
В переменную myCarType возвращается ссылка на метаданные, описывающие класс MyCar. С её помощью мы можем получить всю доступную информацию о классе, его полях, свойствах, методах, событиях и т.д. Мы не будем вдаваться в возможности, предоставляемые классом Type, для этого существует MSDN, RSDN и множество других ресурсов. Например, на сайте RSDN.ru есть очень хороший материал, посвященный данной теме (https://www.rsdn.ru/article/dotnet/refl.xml). Для нас первостепенное значение имеет то, что мы можем эту информацию получить. Среда .NET позволяет нам получить ссылку на описание типа по его полному имени: Type myCarType = Type.GetType("Serge.ReflectionSample.MyCar", false, true);
Что мы можем сделать в первую очередь, имея эту ссылку? Конечно же, создать экземпляр класса, или, по-простому, объект. Для этого нам надо получить информацию о конструкторе. // удостоверяемся, что тип был найден средой выполнения if(myCarType != null) { // для простоты получаем ссылку на конструктор без параметров ConstructorInfo ci = myCarType.GetConstructor(new Type[]{}); // вызываем конструктор, используя Reflection. конструктор // возвращает ссылку на созданный объект object myCarReflected = ci.Invoke(new object[]{}); }
Теперь у нас есть объект класса MyCar, созданный при помощи рефлексии. Что дальше?
Что это нам дает?Конечно, имея ссылку типа object, не очень много можно сделать с таким объектом, но того, что есть в среде .NET, вполне достаточно. Что нам мешает привести myCarReflected к типу MyCar, и пользоваться методами класса напрямую? Конечно же, ничего. Или есть другой вариант. Допустим, MyCar реализует интерфейс IVehicle, а для нашей программы не важно, какого именно класса объект мы создали, главное, что он поддерживает необходимый нам интерфейс. Поэтому через него мы можем обратиться к членам класса. Давайте подытожим, что у нас есть к этому моменту: - Мы можем получить информацию о любом типе (классе) по его полному имени
- Мы можем получить информацию о конструкторе, создающем экземпляр класса
- Мы можем создать объект, используя полученную информацию
- Мы можем привести объект к нужному нам типу
Постановка задачиВеликое множество программистов сталкивалось с необходимостью использовать оператор switch для того, чтобы определить, объект какого класса создать. Автору, как ведущему программисту в компании, где он работает, не раз приходилось видеть switch-блоки, не вмещающиеся на один экран.В одном из проектов автора была необходимость загружать XML файл в память и превращать его в дерево объектов, где каждому типу XML-узла создавался объект соответствующего класса. Необходимо было предоставить возможность добавлять в систему поддержку новых типов узлов без модификации ядра системы. В данной статье мы рассмотрим упрощенную реализацию задачи, где мы не будем выносить реализацию классов в отдельные сборки и оставим за кадром вопросы, связанные с XML атрибутами.Наверное, проницательный читатель уже понял, к чему идет речь. Если нет, то читайте дальше. Если да, то все равно читайте, может быть, встретите что-нибудь новое и полезное и для себя.
Реализация на C#Код, приведенный ниже, достаточно подробно закомментирован, поэтому читайте: using System; using System.Collections; using System.Reflection; using System.Xml; namespace ReflectionDemo { /// <summary> /// Абстрактный базовый класс для всех типов узлов /// </summary> public abstract class ElementBase { /// <summary> /// Конструктор по умолчанию. Ничего полезного не делает :) /// </summary> public ElementBase() { } /// <summary> /// Возвращает имя объекта /// </summary> public abstract string Name { get; } } /// <summary> /// Данный класс соответствует узлу image /// </summary> public class ImageElement : ElementBase { public override string Name { get { return "image"; } } } /// <summary> /// Данный класс соответствует узлу text /// </summary> public class TextElement : ElementBase { public override string Name { get { return "text"; } } } /// <summary> /// Класс, отвечающий за разбор XML файла и создание списка узлов /// </summary> public class XmlParserService { /// <summary> /// Загружает XML файл и создает список объектов по XML узлам /// </summary> /// <param name="fileName">путь к файлу</param> /// <returns>ArrayList объектов, либо null в случае исключения</returns> public static ArrayList Parse(string fileName) { XmlDocument xmlDoc = new XmlDocument(); XmlNode rootNode = null; ArrayList elements = new ArrayList(); // загружаем файл try { xmlDoc.Load(fileName); } catch(Exception ex) { Console.WriteLine("Error loading XML: " + ex.ToString()); return null; } // XML файл всегда содержит корневой элемент rootNode = xmlDoc.ChildNodes[0]; foreach(XmlNode nd in rootNode.ChildNodes) { // получаем ссылку на нужный нам тип Type nodeType = GetElementType(nd); ElementBase newElement = null; try { // здесь мы используем другой, более простой способ создания объектов // по типу, потому что нам не надо находить специфический конструктор. // мы пользуемся конструктором по умолчанию newElement = (ElementBase) Activator.CreateInstance(nodeType, false); } catch(InvalidCastException) { Console.WriteLine("Implementation class for node " + nd.Name + " does not inherit from ElementBase. Node is being skipped"); } catch(Exception ex) { Console.WriteLine("Generic exception while creating implementation class: " + ex.ToString()); } elements.Add(newElement); } return elements; } /// <summary> /// Возвращает ссылку на тип, описыващий нужный нам класс /// </summary> /// <param name="sourceNode">Исходный узел. Его имя используется для определения /// имени нужно нам класса. </param> /// <returns>System.Type в случае успеха, null в случае неудачи</returns> private static Type GetElementType(XmlNode sourceNode) { return Type.GetType("ReflectionDemo." + sourceNode.Name + "Element", false, true); } } /// <summary> /// Класс приложения /// </summary> public class MyApp { public static void Main() { ArrayList elements = XmlParserService.Parse("test.xml"); if(elements != null) { foreach (ElementBase el in elements) Console.WriteLine("Object " + el.GetType().FullName + ", internal name " + el.Name); } Console.ReadLine(); } } }
XML файл для тестирования (test.xml). Должен быть помещен в одну папку с приложением.<document> <document> <image /> <text /> <image /> </document>
|