Сериализация (Serialization): сохранение и восстановление объектов Сериализация - преобразование объекта или графа объектов в линейную последовательность байт для сохранения на некотором носителе или передачи по каналу связи для последующего восстановления. Десериализация – восстановление объекта. Классы BinaryFormatter и SOAPFormatter Сериализацию обеспечивают методы интерфейса IFormatter, реализованные в классах-форматтерах: System.Object System.Runtime.Serialization.Formatters.Binary.BinaryFormatter System.Runtime.Serialization.Formatters.Soap.SoapFormatter BinaryFormatter сохраняет объект в двоичном формате. При использовании двоичного формата обеспечивается высокая производительность. SoapFormatter сохраняет объект в XML-документе, оформленном по стандарту SOAP (Simple Object Access Protocol). При использовании SOAP формата обеспечивается межплатформенность. Интерфейс System.Runtime.Serialization.IFormatter : public interface IFormatter { void Serialize( Stream serializationStream, object graph ); object Deserialize( Stream serializationStream ); … } Сериализация : общая схема Сериализация: • cоздаем или получаем поток для сохранения объектов; • создаем форматтер; • вызываем метод Serialize для сериализации объектов в поток; • не забыть закрыть поток! Abc a = new Abc (3, “abc”); Abc [] arr = new Abc[2] {new Abc(2, “efg”), new Abc(3, “xyz”)} ; FileStream sW = File.Create("Data.bin"); BinaryFormatter binF = new BinaryFormatter(); binF.Serialize(sW, a); binF.Serialize(sW, arr); sW.Close(); Сериализация : общая схема -2 Сериализовать можно только объекты классов (структур, перечислений, делегатов), имеющих атрибут [Serializable]. Если производный класс имеет атрибут [Serializable], базовый класс также должен иметь этот атрибут. Все типы в графе объекта также должны иметь атрибут [Serializable]. Часть полей можно обозначить как [NonSerialized] (атрибут можно прикрепить только к полю). [Serializable] class DataObject { public int value; [NonSerialized] public string name; // Поле name не будет сохранено … // и восстановлено } Восстановление объектов : общая схема Десериализация: • cоздаем или получаем поток для восстановления объектов; • создаем форматтер; • вызываем метод Deserialize для восстановления объектов из потока; • не забыть закрыть поток! FileStream sR = File.OpenRead("Data.bin"); BinaryFormatter binR = new BinaryFormatter(); Abc da = (Abc) binR.Deserialize(sR); Abc[] darr = (Abc[]) binR.Deserialize(sR); sR.Close(); Нестандартная(custom) сериализация Можно внести изменения в стандартный процесс сериализации. Для этого необходимо • реализовать в классе, объекты которого сериализуются, интерфейс ISerializable; public interface ISerializable { void GetObjectData (SerializationInfo info, StreamingContext context); } • определить специальный конструктор T(SerializationInfo info, StreamingContext context); Структура SerializationInfo содержит информацию об объекте, в том числе имя типа и сборки. StreamingContext содержит информацию об источнике/приемнике Нестандартная(custom) сериализация - 2 Некоторые методы структуры SerializationInfo: public public public public void void void void AddValue( AddValue( AddValue( AddValue( string string string string name, name, name, name, object value ); double value ); int value ); object value, Type type ); public public public public double GetDouble( string name ); int GetInt32( string name ); string GetString( string name ); object GetValue( string name, Type type ); Нестандартная(custom) сериализация - 3 Метод GetObjectData явно сериализует данные объекта: public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("nt", nt); info.AddValue("r", lst); info.AddValue("name", name); } Конструктор вызывается при восстановлении объекта: public CustomLAbc (SerializationInfo info, StreamingContext context) { nt = info.GetInt32("nt"); name = info.GetString ("name"); lst = (ArrayList) info.GetValue("r", typeof(ArrayList)); rData(); } Интерфейс IDeserializationCallback Интерфейс IDeserializationCallback поддерживается всеми версиями .NET Framework (1.0 и выше). public interface IDeserializationCallback { void OnDeserialization(object sender);} Если тип реализует интерфейс IDeserializationCallback, метод OnDeserialization будет вызван после десериализации объекта. В этом методе можно выполнить необходимую инициализацию, например, присвоить значения полям объекта с атрибутом [NonSerialized] (по умолчанию при десериализации объекта всем полям с атрибутом [NonSerialized] присваиваются значения по умолчанию в соответствии с типом). События в BinaryFormatter Класс BinaryFormatter поддерживает четыре события, связанных с сериализацией. Класс SOAPFormatter события не поддерживает. Схемы из статьи Juval Lowy “Format Your Way to Success with the .NET Framework Versions 1.1 and 2.0”- MSDN Magazine, October 2004. События в BinaryFormatter Событие Serializing Serialized Deserializing Deserialized Вызов перед сериализацией после сериализации перед десериализацией после десериализации Атрибут [OnSerializing] [OnSerialized] [OnDeserializing] [OnDeserialized] Методы, которые работают как обработчики событий, должны иметь атрибут и сигнатуру void <Method Name>(StreamingContext context); Атрибуты [OnSerializ…] не наследуются. Атрибуты [OnSerializi…] можно одновременно прикрепить к нескольким методам класса. К одному и тому же методу можно одновременно прикрепить и атрибут [OnSerializing] и атрибут [OnSerialized], но нет простого способа выяснить, для какого события был вызван метод. Сериализация и версии сборки Для каждого сериализуемого объекта кроме его состояния (значения сериализуемых полей) сохраняется полное имя сборки и информация о версии сборки. При десериализации объекта загружается сборка и метаданные типа. Если сборка, в которой находится сериализуемый тип, не имеет строгого имени (strong name), форматеры полностью игнорируют информацию о версии. Для сборок со строгим именем при сериализации и десериализации версии сборок с типами должны быть согласованы. Сериализация и версии сборки -2 В классе BinaryFormatter определено свойство AssemblyFormat , которое дает возможность использовать только дружественное имя сборки (friendly name) без информации о версии и открытом ключе (public key token), даже если сборка имеет строгое имя: public FormatterAssemblyStyle AssemblyFormat { get; set; } Перечисление FormatterAssemblyStyle: public enum FormatterAssemblyStyle { Full, Simple } Если значение свойства равно FormatterAssemblyStyle.Full, при десериализации выполняется проверка версии сборки. Если значение равно FormatterAssemblyStyle.Simple, при десериализации проверка версии сборки не выполняется. Version Tolerant Serialization (VTS)) В .NET Framework 1.x между метаданными типа, которые используются при сериализации и десериализации, должно быть полное соответствие. В .NET Framework 2.0 в класс BinaryFormatter внесены изменения, которые допускают небольшие отличия в описании типа при сериализации и десериализации. Это позволяет сохранить совместимость, если через некоторое время сериализуемые типы немного модифицируются. VTS: Толерантность по отношению к внешним или неожидаемым данным В приведенном ниже примере • при сериализации данных была использована новая версия сборки с типом Address; • при десериализации данных используется старая версия сборки с типом Address; BinaryFormatter при десериализации пропустит поле CountryField и не бросит исключение. // Старая версия типа [Serializable] public class Address { string Street; string City; } // Новая версия типа [Serializable] public class Address { string Street; string City; string CountryField; } VTS: Толерантность по отношению к пропущенным данным В приведенном ниже примере • при сериализации данных была использована старая версия сборки с типом Address; • при десериализации данных используется новая версия сборки с типом Address; BinaryFormatter при десериализации присвоит полю CountryField значение по умолчанию и не бросит исключение. // Новая версия типа // Старая версия типа [Serializable] public class Address { string Street; string City; } [Serializable] public class Address { string Street; string City; [OptionalField] string CountryField; }