Прикладное применение рефлексии в .NET

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, а для нашей программы не важно, какого именно класса объект мы создали, главное, что он поддерживает необходимый нам интерфейс. Поэтому через него мы можем обратиться к членам класса.

Давайте подытожим, что у нас есть к этому моменту:

  1. Мы можем получить информацию о любом типе (классе) по его полному имени
  2. Мы можем получить информацию о конструкторе, создающем экземпляр класса
  3. Мы можем создать объект, используя полученную информацию
  4. Мы можем привести объект к нужному нам типу

Постановка задачи

Великое множество программистов сталкивалось с необходимостью использовать оператор 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>

 
« Предыдущая статья   Следующая статья »