Страница 4 из 5 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();
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” для всех остальных типов, которые предполагается использовать. |