Опис завдання

Припустимо, у нас є ієрархія класів предметної області для
подання інформації про фізичних і юридичних осіб.
Відповідна діаграма класів приведено на Рис. 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
може мати безліч варіантів практичного застосування. У наступній
статті буде розглянуто один нестандартний варіант використання
поліморфної десеріалізациі в задачі конвертації проксі-класів для
веб-сервісів.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*