Xml сериализация в .Net Framework 2.0
Страница 4. Xml Сериализация и наследование


 

Xml Сериализация и наследование

У Xml сериализации непростые отношения с объектным наследованием. Рассмотрим пример. Объявим класс ChildClass – наследник DataClass, а в самом DataClass объявим поле Child:
public class DataClass
{
...
[XmlElement]
public DataClass Child;
...
}

public class ChildClass : DataClass
{
public string ParentName;
}
А теперь попробуем воспользоваться полиморфизмом, и присвоим DataClass.Child объект типа ChildClass
DataClass obj = new DataClass();
obj.Child = new ChildClass();
string xml = XmlUtility.Obj2XmlStr(obj, DataClass.XmlNamespace);
Console.WriteLine(xml);
Результат будет плачевный. XmlSerializer не приветствует наши опыты с полиморфизмом и отказывается сериализовать наши объекты, выдавая при этом исключение:

UnitTest.XmlUtilityTest.SerializeTest : System.InvalidOperationException : There was an error generating the XML document.
----> System.InvalidOperationException : The type UnitTest.ChildClass was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.

В тексте сообщения об ошибке содержатся довольно четкие инструкции о том, как исправить ситуацию. Необходимо использовать атрибут XmlInclude с указанием конкретного типа - наследника. Им мы должны пометить базовый класс. Вот таким образом:
[XmlRoot("Data", Namespace=DataClass.XmlNamespace )]
[XmlInclude(typeof(ChildClass))]
public class DataClass
{
public const string XmlNamespace = "urn:MyDataClass";
public DataClass(){}

[XmlElement]
public DataClass Child;
...
}
[XmlType(Namespace=DataClass.XmlNamespace)]
public class ChildClass : DataClass
{
public string ParentName;
}

Причем при появлении новых наследников, мы должны добавлять новые XmlInclude над базовым классом. Если же исходный код базового класса нам не доступен, то мы попадаем в довольно сложную ситуацию. Предположим мы не можем изменять описание базового класса DataClass, потому что у нас нет доступа к его исходному коду. У нас объявлен еще один его наследник CrandChildClass:DataClass, и мы присваиваем полю DataClass.Child экземпляр этого нового типа. Сериализация в этом случае не возможна. Но выход, все-таки есть, хотя и довольно запутанный. Специально для этого случая существует тип XmlAttributeOverrides и конструктор в XmlSerializer, принимающий этот тип. Суть его использования состоит в том, что с его помощью мы в runtime можем переопределить атрибуты, управляющие xml сериализацией, заданные в исходном коде.

Вот пример:

DataClass obj = new DataClass();
// присваиваем значение наследника для которого не был определен XmlInclude

obj.Child = new GrandChildClass();
// переопределяем атрибуты сериализации

XmlAttributes attrs = new XmlAttributes();
XmlElementAttribute attr = new XmlElementAttribute();
attr.ElementName = "GrandChildClass";
attr.Type = typeof(GrandChildClass);
attrs.XmlElements.Add(attr);
XmlAttributeOverrides attrOverrides = new XmlAttributeOverrides();
attrOverrides.Add(typeof(DataClass), "Child", attrs);
// используем специальный конструктор

XmlSerializer sr = new XmlSerializer(typeof(DataClass), attrOverrides);
StringBuilder sb = new StringBuilder();
StringWriter w = new StringWriter(sb, System.Globalization.CultureInfo.InvariantCulture);
sr.Serialize(w,obj); // успех

string xml = sb.ToString();
Console.WriteLine(xml);

Видите, сколько кода пришлось написать, только для того чтобы сделать возможным сериализацию вот этой строки: obj.Child = new GrandChildClass(); Причем надо учитывать, что это всего лишь пример. В этом коде мы не сможем сериализовать obj если присвоим obj.Child значение любого другого типа кроме GrandChildClass. Для этого нам надо будет добавить в XmlAttributeOverrides переопределения атрибутов “Child” для всех остальных типов, которые предполагается использовать.

 
Следующая статья »