using System.Runtime.Serialization.Formatters.Soap;
…
static void Main(string[] args) {
…
// Сохранение объекта в файл CarData.soap в формате SOAP.
SoapFormatter soapFormat = new SoapFormatter();
fStream = new FileStream("CarData.soap", FileMode.Create, FileAccess.Write, FileShare.None);
soapFormat.Serialize(fStream, jbc);
fStream.Close();
Console.ReadLine();
}
Как и ранее, здесь просто используются Serialize() и Deserialize() для перемещения объектного графа в поток и восстановления его из потока. Если открыть полученный файл *.soap, вы увидите в нем элементы XML, представляющие значения JamesBondCar и взаимосвязи между объектами графа (с помощью лексем #ref). Рассмотрите следующий фрагмент XML-кода, соответствующий конечному результату (для краткости здесь опущены указания на пространства имен XML).
‹SOAP-ENV:Envelope xmlns:xsi="…"›
‹SOAP-ENV:Body›
‹a1:JamesBondCar id="ref-1" xmlns:a1="…"›
‹canFly›true‹/canFly›
‹canSubmerge›false‹/canSubmerge›
‹theRadio href="#ref-3"/›
‹isHatchBack›false‹/isHatchBack›
‹/a1:JamesBondCar›
‹a1:Radio id="ref-3" xmlns:a1="…"›
‹hasTweeters›true‹/hasTweeters›
‹hasSubWoofers›false‹/hasSubWoofers›
‹stationPresets href="ref-4"/›
‹/a1:Radio›
‹SOAP-ENC:Array id="ref-4" SOAP-ENC:arrayType="xsd:dooble[3]"›
‹item›89.3‹/item›
‹item›105.1‹/item›
‹item›97.1‹/item›
‹/SOAP-ENC:Array›
‹/SOAP-ENV:Body›
‹/SOAP-ENV:Envelope›
Сериализация объектов с помощью XmlSerializer
Вдобавок к SOAP и двоичному формату, компоновочный блок System.Xml.dll предлагает третий формат, обеспечиваемый типом System.Xml.Serialization. XmlSerializer который может использоваться для сохранения состояния данного объекта в виде "чистого" XML в противоположность данным XML, упакованным в сообщении SOAP. Работа с этим типом немного отличается от работы с типами SoapFormatter и BinaryFormatter. Рассмотрим следующий программный код.
using Sуstem.Xml.Serialization;
…
static void Main(string[] args) {
…
// Сохранение объекта в файл CarData.xml в формате XML.
XmlSerializer xmlFormat = new XmlSerializer(typeof(JamesBondCar), new Type[] { typeof(Radio), typeof(Car) });
fStream = new FileStream("CarData.xml", FileMode.Create, FileAccess.Write, FileShare.None);
xmlFormat.Serialize(fStream, jbc);
fStream.Close();
…
}
Здесь главным отличием является то, что тип XmlSerializer требует указания информации о типе соответствующего элемента объектного графа. Обратите внимание на то, что первый аргумент конструктора XmlSerializer определяет корневой элемент XML-файла, а второй аргумент является массивом типов System.Type, содержащих метаданные подчиненных элементов. Если заглянуть в сгенерированный файл CarData.xml, вы увидите следующий XML-код (здесь он приводится в сокращенном виде).
‹?xml version="1.0" encoding="utf-8"?›
‹JamesBondCar xmlns:xsi="…"›
‹theRadio›
‹hasTweeters›true‹/hasTweeters›
‹hasSubWoofers›false‹/hasSubwoofers›
‹stationPresets›
‹double›89.3‹/double›
‹double›105.1‹/double›
‹double›97.1‹/double›
‹/stationPresets›
‹/theRadio›
‹isHatchBack›false‹/isHatchBack›
‹canFly›true‹/canFly›
‹canSubmerge›false‹/canSubmerge›
‹/JamesBondCar›
Замечание. Для XmlSerializer требуется, чтобы все типы в объектном графе, предназначенные для сериализации, поддерживали конструктор, заданный по умолчанию (так что не забудьте добавить его, если вы определили пользовательские конструкторы). Если это условие не будет выполнено, в среде выполнения будет сгенерировано исключение InvalidOperationException.
Контроль генерируемых XML-данных
Если у вас есть опыт использования XML-технологий, вы должны хорошо знать о том, что в документе XML очень важно гарантировать соответствие элементов набору правил, обеспечивающих "допустимость" данных. Следует понимать, что "допустимость" XML-документа не связана напрямую с синтаксической правильностью его XML-элементов (например, с требованием о том, что все открываемые элементы должны иметь закрывающие их дескрипторы). Скорее, допустимость документов связана с правилами форматирования (например, поле X должно быть атрибутом и не вложенным элементом), которые обычно задаются XML-схемой или DTD-файлом (файл определения типа документа),
По умолчанию все поля данных типа [Serializable] форматируются, как элементы, а не как XML-атрибуты. Для контроля того, как XmlSerializer компонует генерируемый XML-документ, следует указать для типов [Serializable] дополнительные атрибуты из пространства имен System.Xml.Serialization. В табл. 17.1 представлены некоторые из атрибутов, влияющих на кодирование XML-данных, передаваемых в поток.
Таблица 17.1. Атрибуты пространства имен System.Xml.Serialization, связанные с сериализацией объектов
Атрибут Описание XmlAttributeAttribute Член будет сохранен в виде XML-атрибута XmlElementAttribute Поле или свойство будут сохранены в виде XML-элемента XmlEnumAttribute Имя элемента перечня XmlRootAttribute Атрибут, контролирующий формат корневого элемента (пространство имен и имя элемента) XmlTextAttribute Свойство или поле должно сохраняться в виде XML-текста XmlTypeAtttribute Имя и пространство имен XML-типа
Для примера давайте сначала выясним, как поля данных JamesBondCar сохраняются в XML-документе в настоящий момент.
‹?xml version="1.0" encodings="utf-8"?›
‹JamesBondCar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd=http://www.w3.org/2001/XMLSchema›
…
‹canFly›true‹/canFly›
‹canSubmerge›false‹/canSubmerge›
‹/JamesBondCar›
Если вы хотите указать пользовательское пространство имен XML, соответствующее JamesBondCar, и кодировать значения canFly и canSubmerge в виде XML-атрибутов, это можно сделать с помощью изменения определения JamesBondCar в C# следующим образом.
[Serializablе, XmlRoot(Namespace = "http://www.intertechtraining.com")]
public class JamesBondCar: Car {
…
[XmlAttribute]
public bool canFly;
[XmlAttribute]
public bool canSubmerge;
}
Это должно дать в результате следующий XML-документ (обратите внимание на открывающий элемент ‹JamesBondCar›).
‹?xml version="1.0" encodin="utf-8"?›
‹JamesBondCar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" canFly="true" canSubmerge="false"
xmlns="http://www.intertechtraining.com"›
…
‹/JamesBondCar›"
Конечно, есть множество других атрибутов, которые вы можете использовать для управления процессом генерирования XML-документа с помощью XmlSerializer. Чтобы ознакомиться со всеми опциями, выполните поиск информации о пространстве имен System.Xml.Serialization в документации .NET Framework 2.0 SDK
Сохранение коллекций объектов
Теперь вы знаете, как сохранить в потоке отдельный объект, и давайте выясним, как сохранить множество объектов. Заметим, что метод Serialize() интерфейса IFormatter не позволяет указать произвольное число объектов (а только один System.Object). Аналогично, возвращаемым значением Deserialize() тоже является один System.Object.
public interface IFormatter {
…
object Deserialize(System.IO.Stream serializationStream);
void Serialize(System.IO.Stream serializationStream, object graph);
}
Напомним, что System.Object фактически представляет весь объектный граф. Поэтому при передаче объекта, обозначенного атрибутом [Serializable] и содержащего другие объекты [Serializable], будет сохранен сразу весь набор объектов. Большинство типов, находящихся в рамках пространства имен System.Collections и System.Collections.Generic, уже обозначены атрибутом [Serializable]. Таким образом, чтобы сохранить набор объектов, просто добавьте этот набор в контейнер (например, в ArrayList или List‹›) и выполните сериализацию полученного объекта в подходящий поток.
Предположим, что в класс JamesBondCar был добавлен конструктор с двумя аргументами, чтобы можно было установить некоторые начальные данные состояния (обратите внимание на то, что конструктор, заданный по умолчанию, был возвращен на место в соответствии с требованиями XmlSerializer),
[Serializable,
XmlRoot(Namespace = "http://www.intartechtraining.com")]
public class JamesBondCar: Car {
public JamesBondCar(bool skyWorthy, bool seaWorthy) {