Сериализация объектов. Способы сериализации в .NET Framework
В качестве примера рассмотрим пример общего класса, реализующий интерфейс IXMLSerializable для сериализации коллекции пар из ключа и значения какого-либо фиксированного типа. Класс содержит два объекта сериализации – один для обработки строк, второй для типа значений.
using System.Xml; using System.Collections; using System.Xml.Serialization; public class DictionaryCollection<TValue> : DictionaryBase, IXmlSerializable { private XmlSerializer valueSerializer; private XmlSerializer keySerializer; public DictionaryCollection() { keySerializer = new XmlSerializer(typeof(String)); valueSerializer = new XmlSerializer(typeof(TValue)); }
Класс имеет индексируемое свойство, отвечающее за получение значения по его ключу, и метод добавления ключа и значения в коллекцию.
public TValue this[string key] { get { return (TValue)this.Dictionary[key]; } set { this.Dictionary[key] = value; } } public void Add(String key, TValue value) { this.Dictionary.Add(key, value); }
Для сериализации классом XMLSerializer класс имеет два метода – WriteXml и ReadXML.
public void WriteXml(System.Xml.XmlWriter writer) { foreach (String key in this.Dictionary.Keys) { keySerializer.Serialize(writer, key); valueSerializer.Serialize(writer, this[key]); } } public void ReadXml(System.Xml.XmlReader reader) { reader.Read(); while (reader.NodeType != XmlNodeType.EndElement) { String key = (String) keySerializer.Deserialize(reader); TValue value = (TValue) valueSerializer.Deserialize(reader); reader.MoveToContent(); Add(key, value); } }
Метод GetSchema необходим для генерации XSD-файла для данного класса. Поскольку создание такой схемы для класса общего вида невозможно, метод не реализован.
public System.Xml.Schema.XmlSchema GetSchema() { return null; } } // DictionaryCollection<TValue>
Ниже показан фрагмент кода, использующего данный класс для записи пар строка-число в файл.
DictionaryCollection<Int32> dictionary = new DictionaryCollection<Int32>(); XmlSerializer serializer = new XmlSerializer(typeof(DictionaryCollection<Int32>)); dictionary.Add("key1", 10); dictionary.Add("key2", 20); using (StreamWriter writer = new StreamWriter("sample.xml")) { XmlTextWriter xmlWriter = new XmlTextWriter(writer); xmlWriter.Formatting = Formatting.Indented; serializer.Serialize(xmlWriter, dictionary); }
Атрибуты XmlAttributeAttribute и XmlElementAttribute обычно применяют к скалярным полям класса. Два их опционных параметра – название атрибута или элемента XML с результатом сериализации, а также тип объекта. XmlElementAttribute может быть применен только к примитивным типам и строкам. Если тип объекта, хранящимся в поле, совпадает с указанным в типе или в атрибуте поля, то в XML-файле не будет использоваться атрибут типа xsi:type для указания типа объекта.
Атрибуты XmlArrayAttribute, XmlArrayItemAttribute используются для контейнерных классов. К ним относятся массивы, коллекции (например, ArrayList ) и коллекции общего вида (например, List<T> ). В этом случае атрибут XmlArray аналогичен атрибуту XmlAttribute для скалярных классов, а XmlArrayItem указывает все возможные типы элементов массива или списка и соответствующие им имена элементов XML. Для корректной обработки контейнера в атрибутах XmlArrayItem должны быть указаны все типы объектов, которые могут храниться в контейнере. Если в контейнере хранится только тип, указанный явным образом при его объявлении, то данный атрибут не обязателен. Таким образом, если в списке List<Person> persons хранятся только объекты типа Person, то атрибут XmlArrayItem не обязателен. Следующий пример служит для иллюстрации применения указанных атрибутов для сериализации.
// Файл figures.cs using System; using System.IO; using System.Xml.Serialization; using System.Collections.Generic; public abstract class GeomFigure { }
Абстрактный класс фигуры имеет двух наследников, представляющих точку и прямую на плоскости. Конструктор по умолчанию необходим для десериализации объектов.
public class GeomPoint: GeomFigure { private double xField, yField; [XmlAttribute("X")] public double X { get {return xField;} set {xField = value;} } [XmlAttribute("Y")] public double Y { get {return yField;} set {yField = value;} } public GeomPoint() { } public GeomPoint(double x, double y) { this.X = x; this.Y = y; } public override string ToString() { return String.Format("({0}, {1})", X, Y); } } public class GeomLine: GeomFigure { private GeomPoint aField, bField; public GeomPoint A { get {return aField;} set {aField = value;} } public GeomPoint B { get {return bField;} set {bField = value;} } public GeomLine() { } public GeomLine(GeomPoint a, GeomPoint b) { this.A = a; this.B = b; } public override string ToString() { return String.Format("{0} {1}", A, B); } }Листинг 4.1.
В списке фигур используются атрибуты XmlArrayItem для описания всех возможных типов фигур.
public class GeomFigures { private List<GeomFigure> figuresField; [XmlArrayItem("Point", typeof(GeomPoint)), XmlArrayItem("Line", typeof(GeomLine))] public List<GeomFigure> Figures { get { return figuresField;} } public GeomFigures() { figuresField = new List<GeomFigure>(); } } public class App { public static void Main() { GeomFigures figures = new GeomFigures(); figures.Figures.Add(new GeomPoint(2, -1)); figures.Figures.Add(new GeomLine(new GeomPoint(-1, -1), new GeomPoint(2, 2))); XmlSerializer serializer = new XmlSerializer(typeof(GeomFigures)); using (StreamWriter writer = new StreamWriter("figures.xml")) { serializer.Serialize(writer, figures); }; } }Листинг 4.2.
В результате выполнения этого примера будет создан следующий XML-файл.
<?xml version="1.0" encoding="utf-8"?> <GeomFigures xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Figures> <Point X="2" Y="-1" /> <Line> <A X="-1" Y="-1" /> <B X="2" Y="2" /> </Line> </Figures> </GeomFigures>
Важное применение атрибутов заключается в том, что они позволяют описать соответствие класса XSD-схеме получаемого в ходе сериализации документа. В состав .NET Framework входит утилита xsd.exe, позволяющая выполнять три основные задачи:
- создание частичного (partial) описания класса на С# по схеме XSD;
- создание схемы XSD по классу С#;
- создание схемы XSD по образцу XML-файла (правильность зависит от полноты образца).
Если для указанного выше файла выполнить следующую команду, то будет создан файл schema0.xsd со схемой XML.
xsd.exe figures.exe /type:GeomFigures
Сама схема будет иметь следующий вид.
<?xml version="1.0" encoding="utf-8"?> <xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="GeomFigures" nillable="true" type="GeomFigures" /> <xs:complexType name="GeomFigures"> <xs:sequence> <xs:element minOccurs="0" maxOccurs="1" name="Figures" type="ArrayOfChoice1" /> </xs:sequence> </xs:complexType> <xs:complexType name="ArrayOfChoice1"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element minOccurs="1" maxOccurs="1" name="Point" nillable="true" type="GeomPoint" /> <xs:element minOccurs="1" maxOccurs="1" name="Line" nillable="true" type="GeomLine" /> </xs:choice> </xs:complexType> <xs:complexType name="GeomPoint"> <xs:complexContent mixed="false"> <xs:extension base="GeomFigure"> <xs:attribute name="X" type="xs:double" use="required" /> <xs:attribute name="Y" type="xs:double" use="required" /> </xs:extension> </xs:complexContent> </xs:complexType> <xs:complexType name="GeomFigure" abstract="true" /> <xs:complexType name="GeomLine"> <xs:complexContent mixed="false"> <xs:extension base="GeomFigure"> <xs:sequence> <xs:element minOccurs="0" maxOccurs="1" name="A" type="GeomPoint" /> <xs:element minOccurs="0" maxOccurs="1" name="B" type="GeomPoint" /> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> </xs:schema>Листинг 4.3.
Следующая команда создаст по XML-схеме файл на языке C#, который при необходимости можно использовать для десериализации созданного в примере XML-файла вместо оригинального файла примера.
xsd.exe schema0.xsd /classes
Таким образом, при использовании для обмена данными между компонентами класса XmlSerializer можно как создать схему по сериализуемому классу и представить ее в качестве спецификации передаваемых данных, так и сгенерировать на языке C# код описания публичных свойств класса из XSD-схемы. На рисунке 4.4 показана схема применения XML-сериализатора в распределенных системах. В зависимости от применения класса XMLSerializer схема XML может передаваться как отдельный документ, специфицирующий формат обмена сообщениями между компонентами, или входить в описание интерфейса программной компоненты на языке WSDL.
Особенностью класса XMLSerializer является то, что в ходе работы он создает сборки с кодом сериализации для каждого обрабатываемого класса при вызове его конструктора. Например, в описанном ранее примере в памяти будет создана сборка с именем GeomFigures.XmlSerializers, что приводит к определенной однократной задержке.
Если такая задержка нежелательна (например, программа не совершает повторяющейся сериализации одного и того же класса, но желает при этом осуществлять операции максимально быстро), то при помощи утилиты sgen.exe можно заранее создать такие сборки и затем подключить их к проекту.
Резюмируя краткое описание XMLSerializer, следует отметить, что несмотря на отдельные сложности его применения к некоторым классам, его использование позволяет создать открытое взаимодействия удаленных компонент со спецификацией сериализуемых классов в виде схемы XSD. При использовании XMLSerializer можно также рекомендовать вместо сложных структур данных использовать классы XmlDocument или Dataset, особенно если такие структуры включают неподдерживаемые XML-сериализацией классы. Сериализуемый тип может быть классом общего вида, но невозможность создания для таких классов схемы XML-документа ограничивают их применение. Особенностью класса XmlSerializer является жесткая привязка к десериализуемому типу данных, обычно передаваемому ему в качестве аргумента конструктора.