Исходники.Ру - Программирование
Исходники
Статьи
Книги и учебники
Скрипты
Новости RSS
Магазин программиста

Главная » Статьи по программированию » .NET - Все статьи »

Обсудить на форуме Обсудить на форуме

Полиморфная сериализация в XML
В статье рассматривается методика полиморфной сериализации/десериализации объектов в XML. Приложение к статье - исходные тексты класса, реализующего данную методику.
В статье рассматривается методика полиморфной сериализации/десериализации объектов в XML. Приложение к статье - исходные тексты класса, реализующего данную методику.

Описание задачи

Предположим, у нас есть иерархия классов предметной области для представления информации о физических и юридических лицах. Соответствующая диаграмма классов приведена на Рис. 1.

Рис. 1 Диаграмма классов физических/юридических лиц

Есть абстрактный класс BaseBody и два наследника - PrivateBody (физическое лицо), CorporateBody (юридическое лицо). Все лица обладают целочисленными уникальными идентификаторами (атрибут Id на диаграмме), могут иметь несколько телефонов и несколько адресов. Физические лица дополнительно характеризуются фамилией, именем, отчеством, датой рождения. Юридические лица - названием, ИНН, БИК.

Объекты классов PrivateBody и CorporateBody сериализуются в XML, сериализованное представление может размещаться в базе данных, файлах на диске - хранилище данных значения не имеет. Приложение, предназначенное для учета клиентов, должно восстанавливать объекты соответствующих классов из XML.

Задача XML-сериализации/десериализации объектов в .Net решается с помощью класса XmlSerializer. В простейшем случае, когда нам априори известен класс восстанавливаемого из потока объекта:

	XmlSerializer deserializer = new XmlSerializer(typeof(PrivateBody));
	StringReader reader = new StringReader(xmlContent);
	PrivateBody body = (PrivateBody) deserializer.Deserialize(reader);  

Желательно, чтобы процесс восстановления объекта был полиморфным, то есть мы получаем поток, о котором известно, что он содержит сериализованное XML-представление одного из наследников базового класса BaseBody, и нам необходимо восстановить объект соответствующего класса. Стандартными средствами XmlSerializer добиться полиморфной десериализации невозможно, так как в конструкторе XmlSerializer необходимо указывать конкретный класс объектов, которые будут восстанавливаться из потока, при этом если в потоке окажется наследник заданного класса, то XmlSerializer не сможет выполнить восстановление, и вызов завершится исключительной ситуацией. Таким образом, следующий код не пригоден для использования:

	XmlSerializer deserializer = new XmlSerializer(typeof(BaseBody));
	StringReader reader = new StringReader(xmlContent);
	BaseBody body = (BaseBody) deserializer.Deserialize(reader);

Методика решения

Для решения задачи полиморфной десериализации объектов из XML разработаем свой вспомогательный класс, назовем его PolymorphousXmlSerializer. Нам понадобятся два статических метода.

 

Метод Serialize является простой оберткой вызова XmlSerializer.

	/// <summary>
	/// Сериализация объекта в поток
	/// </summary>
	/// <param name="writer">Поток для сериализации</param>
	/// <param name="obj">Объект</param>
	public static void Serialize(TextWriter writer, object obj)
	{
		XmlSerializer serializer = new XmlSerializer(obj.GetType());
		serializer.Serialize(writer, obj);    
	}

	/// <summary>
	/// Десериализация объекта из потока
	/// </summary>
	/// <param name="reader">Поток для десериализации</param>
	/// <param name="baseType">Базовый класс объекта</param>
	/// <param name="useDerivatives">Использовать ли наследников</param>
	/// <returns>Объект</returns>
	public static object Deserialize(TextReader reader, Type baseType, 
bool useDerivatives)

Реализация метода Deserialize немного сложнее, хотя в конечном счете все сводится к вызову того же XmlSerializer. Параметр useDerivatives определяет, следует ли использовать полиморфный механизм десериализации. Если useDerivatives = false, то вызов метода Deserialize полностью эквивалентен стандартному механизму работы XmlSerializer.

Значительно интереснее вызов метода с useDerivatives = true. В этом случае мы предполагаем, что в качестве baseType нам передали базовый класс, а поток может содержать сериализованное представление любого наследника baseType. Информацию о том, какой из наследников baseType сериализован в потоке, можно получить из названия корневого тега XML - оно соответствует имени класса либо значению XmlTypeAttribute.TypeName, если для класса задан указанный атрибут сериализации. Зная базовый тип и название корневого тега XML, мы можем определить требуемый класс наследника, затем создать XmlSerializer с передачей в качестве параметра конструктору класса наследника и вызвать стандартный метод десериализации. С учетом сказанного код метода Deserialize будет иметь следующий вид:

	public static object Deserialize(TextReader reader, Type baseType,
 bool useDerivatives)
	{
		Type serializerType = null;
		string xmlContent = reader.ReadToEnd();

		if (useDerivatives)
		{
			Match rootMatch = RootElementRegex.Match(xmlContent);
			string rootName = rootMatch.Groups["1"].Value;

			serializerType = GetSerializerType(baseType, rootName);
		} 

		if (serializerType == null)
		{
			serializerType = baseType;
		}

		XmlSerializer deserializer = new XmlSerializer(serializerType);
		return deserializer.Deserialize(new StringReader(xmlContent));   
	}

Для определения названия корневого тега в XML используется регулярное выражение:

	// Регулярное выражение для поиска названия root-элемента в XML файле
	protected static readonly Regex RootElementRegex = 
new Regex("<\\s*(\\w+)", RegexOptions.Compiled);

Класс наследника определяется с помощью вспомогательного метода GetSerializerType.

	/// <summary>
	/// Возвращает тип, подходящий для сериализации объектов baseType 
	/// с root-элементом rootName
	/// </summary>
	/// <param name="baseType">Базовый тип</param>
	/// <param name="rootName">root-элемент сериализованного в XML
/// объекта
/// </param>
	/// <returns></returns>
	public static Type GetSerializerType(Type baseType, string rootName)

В методе GetSerializerType нам необходимо найти всех наследников от baseType, а затем выбрать того наследника, сериализованное XML-представление которого будет иметь корневой элемент rootName. С помощью Reflection мы можем найти всех наследников baseType в текущем домене приложения (AppDomain). Для увеличения скорости работы метода поиск наследников базового класса будем проводить только при первом вызове, а затем кэшировать информацию о типах в обычной хэш-таблице.

Для упрощения структуры кэша и механизма поиска мы будем искать наследников не от baseType, а от класса, являющегося вершиной иерархии (после Object), в которой находится и baseType. Выбор наследника, соответствующего rootName, будет осуществляться по имени класса либо по значению атрибута XmlTypeAttribute.TypeName. Код метода GetSerializerType выглядит следующим образом:

	public static Type GetSerializerType(Type baseType, string rootName)
	{
		//Определяем базовый тип - вершину иерархии
		Type theMostBaseType = baseType;
		while (!theMostBaseType.BaseType.Equals(typeof(object)))
		{
			theMostBaseType = theMostBaseType.BaseType;
		}

		Guid baseTypeGuid = theMostBaseType.GUID;
		if (BaseTypeDerivatives.Contains(baseTypeGuid))
		{
			return (Type) ((Hashtable) 
BaseTypeDerivatives[baseTypeGuid])[rootName];
		}
		else
		{
			AppDomain currentDomain = AppDomain.CurrentDomain;
			Assembly[] assemblies = currentDomain.GetAssemblies();

			Hashtable typeTable = new Hashtable(); 
			TypeFilter derivativesFilter = 
new TypeFilter(FilterDerivatives);

			foreach (Assembly assembly in assemblies)
			{
				Module[] modules = assembly.GetModules(false);
				foreach (Module m in modules)
				{
					Type[] types = m.FindTypes(derivativesFilter,
theMostBaseType);
					foreach (Type type in types)
					{
						XmlTypeAttribute typeAttribute = 
(XmlTypeAttribute)
 Attribute.GetCustomAttribute(type,
 typeof(XmlTypeAttribute));
						if ((typeAttribute != null) && 
(typeAttribute.TypeName != null) &&
(typeAttribute.TypeName != ""))
     typeTable.Add(typeAttribute.TypeName,
 type);
						else
							typeTable.Add(type.Name, type);
					}
				}
			}
			BaseTypeDerivatives[baseTypeGuid] = typeTable;
			return (Type) typeTable[rootName];
		}
}

В качестве кэша информации о типах используется следующая хэш-таблица:

	
	// Кэш для информации о типах и их наследниках
	protected static Hashtable BaseTypeDerivatives =
Hashtable.Synchronized(new Hashtable());

Дополнительно к классу PolymorphousXmlSerializer добавлен метод для сброса кэша:

	/// <summary>
	/// Сброс кэша с информацией о типах и их наследниках
	/// </summary>
	public static void ResetTypeCache()
	{
		BaseTypeDerivatives.Clear();
}

В процессе поиска наследников используется callback-функция следующего вида:

	/// <summary>
	/// Делегат для фильтрации наследников заданного класса
	/// </summary>
	/// <param name="type">Проверяемый тип</param>
	/// <param name="baseType">Критерий фильтрации</param>
	/// <returns></returns>
	protected static bool FilterDerivatives(Type type, object baseType)
	{
		return type.IsSubclassOf((Type) baseType);
	}

Вот собственно и весь код, остается только добавить перегруженный метод Deserialize, который будет автоматически использовать полиморфный механизм десериализации.

	/// <summary>
	/// Десериализация объекта из потока
	/// </summary>
	/// <param name="reader">Поток</param>
	/// <param name="baseType">Базовый класс объекта</param>
	/// <returns>Объект</returns>
	public static object Deserialize(TextReader reader, Type baseType)
	{
		return Deserialize(reader, baseType, true);
	}

Теперь полиморфная десериализация объектов физических и юридических лиц будет иметь вид:

	StringReader reader = new StringReader(xmlContent);
	BaseBody body = (BaseBody)PolymorphousXmlSerializer.Deserialize(reader,
typeof(BaseBody));

Заключение

Рассмотренная методика полиморфной десериализации объектов из XML может иметь множество вариантов практического применения. В следующей статье будет рассмотрен один нестандартный вариант использования полиморфной десериализации в задаче конвертирования прокси-классов для веб-сервисов.


Может пригодится:


Автор: Иванов Роман Сергеевич
Прочитано: 4075
Рейтинг:
Оценить: 1 2 3 4 5

Комментарии: (0)

Добавить комментарий
Ваше имя*:
Ваш email:
URL Вашего сайта:
Ваш комментарий*:
Код безопастности*:

Рассылка новостей
Рейтинги
© 2007, Программирование Исходники.Ру