Прикладное применение рефлексии в .NET
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 есть очень хороший материал, посвященный данной теме (http://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>