Опубликован: 11.05.2007 | Доступ: свободный | Студентов: 1706 / 243 | Оценка: 4.36 / 4.25 | Длительность: 16:06:00
Лекция 4:

Сериализация объектов. Способы сериализации в .NET Framework

< Лекция 3 || Лекция 4: 1234 || Лекция 5 >

В качестве примера рассмотрим пример общего класса, реализующий интерфейс 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.

Использование схем XML для сериализации объектов

Рис. 4.4. Использование схем XML для сериализации объектов

Особенностью класса XMLSerializer является то, что в ходе работы он создает сборки с кодом сериализации для каждого обрабатываемого класса при вызове его конструктора. Например, в описанном ранее примере в памяти будет создана сборка с именем GeomFigures.XmlSerializers, что приводит к определенной однократной задержке.

Если такая задержка нежелательна (например, программа не совершает повторяющейся сериализации одного и того же класса, но желает при этом осуществлять операции максимально быстро), то при помощи утилиты sgen.exe можно заранее создать такие сборки и затем подключить их к проекту.

Резюмируя краткое описание XMLSerializer, следует отметить, что несмотря на отдельные сложности его применения к некоторым классам, его использование позволяет создать открытое взаимодействия удаленных компонент со спецификацией сериализуемых классов в виде схемы XSD. При использовании XMLSerializer можно также рекомендовать вместо сложных структур данных использовать классы XmlDocument или Dataset, особенно если такие структуры включают неподдерживаемые XML-сериализацией классы. Сериализуемый тип может быть классом общего вида, но невозможность создания для таких классов схемы XML-документа ограничивают их применение. Особенностью класса XmlSerializer является жесткая привязка к десериализуемому типу данных, обычно передаваемому ему в качестве аргумента конструктора.

< Лекция 3 || Лекция 4: 1234 || Лекция 5 >