Опубликован: 11.05.2007 | Уровень: специалист | Доступ: платный
Лекция 4:

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

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

4.4. Классы сериализации SoapFormatter и BinaryFormatter

Класс сериализации System.Runtime.Serialization.Formatters.Soap.SoapFormatter используется исключительно в среде .NET Remoting, а класс System.Runtime.Serialization.Formatters.Binary.BinaryFormatter может также использоваться в среде MSMQ вместо XMLSerializer. Оба класса форматирования по приведенной классификации являются универсальными. Класс форматирования BinaryFormatter реализует двоичный закрытый метод сериализации, класс SoapFormatter – текстовый и открытый, основанный на спецификации кодирования SOAP-RPC (пространство имен http://schemas.xmlsoap.org/soap/encoding/).

При разработке .NET Framework 2.0 разработчики по некоторым данным собирались придать классу SoapFormatter статус устаревшего. Класс SoapFormatter не поддерживает одно из важных нововведений – параметризированные типы данных ( generic types ).

Оба указанных класса в простейшем случае при сериализации сохраняют все поля класса (но не его свойства), вне зависимости от их видимости. Поля, имеющие атрибут System.NonSerializeAttribute, игнорируются. Класс должен иметь атрибут System.SerializableAttribute. В ходе сериализации класса форматирования используют методы класса System.Runtime.Serialization.FormatterServices. Сериализуемый класс должен содержать конструктор без параметров, который вызывается при создании нового объекта в ходе десериализации.

Если же обрабатываемый класс реализует интерфейс ISerializable, то он сериализуется вызовом метода GetObjectData(SerializationInfo info, StreamingContext context) этого интерфейса, внутри которого обычно так же вызываются методы FormatterServices. Десериализация таких классов осуществляется вызовом конструктора ISerializable(SerializationInfo info, StreamingContext context), заполняющего поля объекта значениями из info.

О завершении своей десериализации объект может получить уведомление, реализовав интерфейс System.Runtime.Serialization.IDeserializationCallback с единственным методом OnDeserialization.

Полученный таким образом на первом шаге сериализации объект класса SerializationInfо содержит имена и значения сериализуемых полей. Рассматриваемые классы форматирования, реализующие интерфейс IFormatter, преобразуют эти имена в некоторый вид, передаваемый между доменами приложения через потоки ввода-вывода.

Рассмотрим пример создания класса с интерфейсом ISerializable и собственным механизмом сериализации.

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
using System.Reflection;

[Serializable]
public class Person : ISerializable
{           
    public String name;

    public Person()
    {
    }

Метод GetObjectData используется на первом шаге сериализации класса. В ходе его работы в объект класса SerializationInfo добавляется информация о полях класса, подлежащих сериализации. Для получения метаданных о полях класса используется статический метод GetSerializableMembers класса FormatterServices.

public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        Type thisType = this.GetType();
        MemberInfo[] serializableMembers = 
            FormatterServices.GetSerializableMembers(thisType, context);
        foreach (MemberInfo serializableMember in serializableMembers)
        {            
            // Не обрабатывать поля с аттрибутом NonSerializedAttribute
            if (!(Attribute.IsDefined(serializableMember, 
                        typeof(NonSerializedAttribute))))
            {                    
                info.AddValue(serializableMember.Name, 
                        ((FieldInfo)serializableMember).GetValue(this));
            }
        }
    }

Для проведения десериализации класс содержит конструктор специального вида, заполняющий поля класса значениями из объекта класса SerializationInfo.

protected Person(SerializationInfo info, 
                        StreamingContext context)
    {
        Type thisType = this.GetType();
        MemberInfo[] serializableMembers = 
                FormatterServices.GetSerializableMembers(thisType, context);
        foreach (MemberInfo serializableMember in serializableMembers) 
        {
            FieldInfo fieldInformation = (FieldInfo)serializableMember;
            if (!(Attribute.IsDefined(serializableMember, 
                                        typeof(NonSerializedAttribute))))
            {                    
                fieldInformation.SetValue(this, 
                            info.GetValue(fieldInformation.Name, 
                            fieldInformation.FieldType));
            }
        }
    }
} // Person

Ниже приведен пример использования созданного класса Person.

public class SampleApp
{
    public static void Main()
    {
        using (Stream stream = new MemoryStream())
        {
            IFormatter formatter = new BinaryFormatter();

            Person person = new Person();
            person.name = "Иван";
            Console.WriteLine("Сохранено: {0}", person.name);
            formatter.Serialize(stream, person);

            stream.Position = 0;
            Person personRestored = (Person) formatter.Deserialize(stream);
            Console.WriteLine("Восстановлено: {0}", personRestored.name);
       }
    }
}

Классы форматирования имеют механизм, позволяющий изменить процедуры сериализации и десериализации для объектов некоторого класса и его потомков. Это необходимо, в частности, при использовании удаленных объектов, которые маршализируются по ссылке и не пересекают границы домена приложения. Такие объекты находятся на сервере, а на стороне клиента для их использования должен быть создан некоторый посредник, реализующий весь интерфейс удаленного объекта, включая доступ к его полям и свойствам. Для реализации маршализации по ссылке к объекту форматирования через поле SurrogateSelector можно присоединить класс, реализующий интерфейс System.Runtime.Serialization.ISurrogateSelector. Он должен связывать тип удаленного объекта со специальной процедурой его сериализации и десериализации. Использование этого механизма в .NET Remoting приводит к тому, что наследники класса MarshalByRefObject не покидают своего домена приложения. При использовании же BinaryFormatter в среде MSMQ наследники MarshalByRefObject сериализуется обычным образом, поскольку использующий объекты форматирования класс BinarryMessageFormatter не создает связанный с типом MarshalByRefObject объект класса SurrogateSelector.

Использование класса BinaryFormatter является наиболее эффективным и универсальным, но и самым закрытым способом сериализации. Этот класс позволяет передавать между доменами приложения произвольный граф объектов, но при его использовании распределенная система теряет свойство открытости. В случае применения этого класса взаимодействующие компоненты могут быть созданы только на платформе CLI, причем обоим сторонам необходимо иметь сборку с сериализуемым типом. При использовании в качестве параметров типов из стандартной библиотеки или использующих их классов желательно, чтобы обе стороны были реализованы на одной версии CLI. Поэтому для передачи сложных типов лучше всего использовать XML. Однако, стандартный класс System.Xml.XmlDocument не может быть сериализован классами BinaryFormatter и SoapFormatter, поскольку данный класс по неясным причинам не имеет атрибута Serializable. Для сериализации объектов класса XmlDocument проще всего преобразовать его в строку, и затем сериализовать ее. Можно так же создать наследника XmlDocument, который будет реализовывать интерфейс ISerializable.

Ниже приводится пример вспомогательного класса с двумя статическими методами, преобразующими объект класса XmlDocument в строку и наоборот. Поскольку метод XmlDocument.ToString() против ожиданий не возвращает текст XML-документа и у него нет метода, обратного LoadXml, то следует использовать класс StringWriter.

// SevaXmlUtils.cs
using System;
using System.IO;
using System.Xml;

namespace Seva.Xml
{
    public static class XmlUtils
    {
        public static String XmlToString(XmlDocument xml)
        {
            StringWriter xmlLine = new StringWriter();
            xml.Save(xmlLine);
            return xmlLine.ToString();
        }
        
        public static XmlDocument XmlFromString(String xmlLine)
        {
            XmlDocument xml = new XmlDocument();
            xml.LoadXml(xmlLine);
            return xml;
        } 
    }
}

4.5. Выводы по использованию классов сериализации

Все классы сериализации библиотеки .NET Framework имеют свои особенности и ограничения, что может вызвать значительные изменения в программном коде при переходе с одной промежуточной среды на другую. Один из способов борьбы с этой проблемой состоит в отказе от сериализации нетривиальных классов (содержащих что-либо, кроме примитивных типов-значений и строк), и особенно сложных списочных структур. Вместо них, вероятно, следует использовать наборы данных (класс System.Data.Dataset ) или документы XML (класс System.Xml.XMLDocument ). Хотя такой способ может являться не совсем удобным для разработчика, он дает залог создания независимого от класса форматирования программного кода.

< Лекция 3 || Лекция 4: 1234 || Лекция 5 >
Екатерина 2
Екатерина 2
Россия, г. Москва
pankaj kowale
pankaj kowale
Индия, bandra