Сериализация объектов. Способы сериализации в .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 является жесткая привязка к десериализуемому типу данных, обычно передаваемому ему в качестве аргумента конструктора.
