Serialization



Дата10.04.2018
Размер150.75 Kb.
#66637

Serialization


Георги Иванов
Сериализацията процеса на преобразуването на състоянието на обект във форма която може да бъде записана някъде или транспортирана. Обратния процес на сериализацията е десериализацията (deserialization), която ковертира даден поток в обект. Заедно, тези процеси позволяват данни да бъдат лесни записвани и предавани. Работата с потоци ни позволява да сериализираме (или представим във вид на поток от данни) различни типове от данни. Въпреки всичко, това което не сме взели в предвид е работата с може би най-важниа тип, обекта (System.Object). как ние записваме обект във поток и как го възстановяваме после обратно, така че той да бъде в същото състояние като преди?

Един начин да направим това е да предоставим явно Save и Load методи, които ще вземат параметър от тип Stream, в който можем да записваме или четем явно всяко едно поле (field) на класа. Няма нищо лошо с тази техника, но когато работим с голяма брой от класове, скоро става доста трудно да вкараме такава логика за всеки един от тях. Защо да не можем да напраим общ механизъм за записване и четене на всеки един обект? В края на краищата, метадатата съдържа цялата информация за всички полета на класа. Би било много лесно да обходим всички тези полета и да прочетем или запишем техните стойности. .NET предлага точно такъв универсален механизъм за сериализация.



Защо ви бихте използвали сериализация? Двете най-важни причини са: да запазите състоянието на даден обект в storage medium, така че точно копие на обекта може да бъде пресъзданено по-късно. Друга причина да използвате сериализацията е да “изпратите” обекта по стойност от един домейн към друг. Например, сериализацията се използва да се запипе т.нар. Session state в ASP.NET и да запишат обектите в clipboard в Windows Forms. То се използва също от Remoting-а за да предаваме обекти “по стойност” от един домейн на програма в друг.

  • Постояннен склад (Persistent Storage) – често се налага да запазим стойностите на някои полета от класа на твърдия диск, и да използваме тази информация по-късно. Върпеки че това лесно може да се направи без да се разчита на сериализацията, този подход е често трудоемък и предразполага към допускане на много грешки (error prone) и става прогресивно по сложен когато се налага да сериализираме ерархия от обекти. Представете си че изграждате бизнес приложение съдържащо хиляди обекти и точно тогава се налга да пишете код който записва и чете полетата и properties на класа върху/от диска за всеки един тип. Сериализацията дава удобен механизъм за реализирането на тази функционалност с най-минимални усилия. CLR се грижи за това как обектите са подредени в паметта и предоставя механизъм за автоматина сериализация кат оизползва REFLECTION. Когато обектът е сериализиран, името на класа, асемблито, и всички data members на обекта (инстанцията на класа) са записани в storage. Но обектите често пазят референции към други обекти в членове променливи. Когато този клас е сериализиран, serialization engine се грижи да сериализира първо всички обекти които той реферира. Също така се грижи всеки един обект да се сериализира точно един път. Начинът на сериализация, която предоставя .NET Framework коректно обработва графи от обекти и т.нар. кръгови референции (circular references) автоматично. Единственото изискване наложено на обектите от графите е че всички трябва да бъдат отбелязани с Serializable. Ако това не е налице, едно изкючние ще бъде хвърлено когато сериализатора се опита да да сериализира точно този обект. Когато този обект е десериализиран, той е пресъздаден и всички стойности на дата членовете са автоматично пресъздадени.

  • Предаване по стойност (Marshal by Value) – всеки обект е валиден само в домейна в който е бил създаден. Всеки опит този обект да бъде предаден като параметър или стойност на връзане (return value) ще бъде неуспешен, ако обектът не наследява MarshalByRefObject или не е маркиран като Serializable. Ако класът е маркиран като Serializable, обектът ще бъде автоматично сериализиран и транспортиран от един домейн в друг, и след това десериализиран за да пресъздаде абсолютно точно обектът от първия домейн. Този процес обикновено се нарична предаване по стойност (Marshal By Value). Когато обект наследява MarshalByRefObject, референция към самия обект ще бъде предадена от един домейн към друг, а не самия обект или негово копие. Когато този обект е използван за remoting, formatter-а отговорен за сериализацията, конфигуриран с surrogate selector (SurrogateSelector), взема контрола и заменя всички обекти, които наследяват MarshalByRefObject с прокси. Ако не се използва този SurrogateSelector, архитектурата на сериализацията следва следните правила:



  1. Проверява се дали formatter-а е surrogate selector – ако той е такъв, се проверява дали за дадения тип има дефиниран surrogate selector. Този surrogate selector има знание за самия тип и ISerializable.GetObjectData() е извикан за този surrogate selector .

  2. Ако няма surrogate selector който да обработва типа, който искаме да сериализираме, се проверява дали този тип/клас е маркиран като Serializable. Ако това не е налице, изключение от тип SerializationException се хвърля.

  3. Aко типът е маркиран като Serializable, се проверява дали тои имплементира интерфейса ISerializable. Ако това е налице, се вика GetObjectData за този обект.

  4. Ако типът/класът не имплементира ISerializable, тогава се използва стандартния начин за сериализация, а именно се сериализират всички полета, които не са маркирани с атрибута NonSerialized.

[Определение] Процеса процеса на записване на сътоянието на обекта в поток е програмна задача наречена сериализация.


Remoting infrastructure и .NET Web Services зависят много от сериализацията. Например, сериализиранят вид на един обект може да бъде пренесен на друг хост, където обекта може да бъде реконструиран. Като даваме на обекта ефективен начин за серилизане, обекта просто може да бъде сериализиран в поток от байтове в паметта и след това пренесен на отдалечена машина.

В .NET типът който се нуждае от сериализация трябва явно да покаже това чрез употребата на атрибута System.SerializableAttribute. Например:


[Serializable]

public class Foo

{

// int field

private int m_nIntValue = 1;
// double field

private double m_dDoubleValue = 0.0f;
// string field

private String m_strStringValue = "ToRiN is the best";
// non serialized field

[NonSerialized]

private int m_nNonSerializedField = 77;

public Foo()

{



}

}

Ако се обитваме да сериализираме клас, който не е отбелязан с атрибута Serializable системата пуска SerializationException. Същото изключение се хвърля когато ако някой от обектите в сериализационния граф не е маркиран с този атрибут също.

По подразбиране, всички членове-полета дефинирани за този клас стават сериализируеми. За да изпуснем определено поле, можем да приложим за него атрибута System.NonSerialized.

.NET framework предлага два основни начина за сериализация:



  • Бинарната сериализация запазва цялостта на дадения тип (type fidelity), което е ценно и важно при запазване на цялостното състояние на обекта между различни извиквания от проложението. Например, вие можете да поделяте (share) обекти между рализчни проложения като ги сериализирате в clipboard-а. Вие можете също да ги сериализирате в поток, на диск, паметта, в локалната мрежа и т.н. Remoting-a използва сериализацията за да предава обекти “по стойност” (by value) от един компютър или домейн към друг.

  • XML сериализацията сериализира само public пропертитата и полета на даден тип и не запазва цялостта на дадения тип (type fidelity). Това е удобно когато искате да предавате или използвате данни без да се ограничават приложенията които използват тези данни. Приложенията не се грижат за persistant състоянието на обектите между тях. Тъй като XML е отворен стандарт, той е добър избор за поделяне на данни през Internet. XML се наложи също и поради присъствието на SOAP (също отворен стандарт).


Собствена сериализация (Custom Serialization)

Често обектите се нуждаят точно да контолират начина по който ще бъдат сериализирани. Например един обект може да иска да запази своето състояние в по компатен (компресиран) вид, особено ако данните се пишат във файл. Също, един обект може да не иска да сериализира някои данни от себе си, особено ако после тези данни могат да бъдат използвани от много други компютри.

За да предостави собствена сериализация, даден тип/клас трябва да имплементира стандартен интерфейс System.Runtime.Serialization.ISerializable. Ето и неговата дефиниция:

public interface ISerializable

{

void GetObjectData( SerializationInfo info, StreamingContext context );



}

Имплементирайки ISerializable, ние трябва да предоставим реализация на GetObjectData и на специален конструктор, който ще бъде използван когато обектът е десериализиран.


[Serializable]

public class CustomSerialization : System.Runtime.Serialization.ISerializable

{

public int m_nIntValue = 0;



public String m_strStringValue = "[Default]";

public double m_dDoubleValue = 0.0;


public CustomSerialization( int nValue, String strValue, double dValue )

{

m_nIntValue = nValue;



m_strStringValue = strValue;

m_dDoubleValue = dValue;

}
protected CustomSerialization(SerializationInfo info, StreamingContext context)

{

m_nIntValue = info.GetInt32( "Integer Field" );



m_strStringValue = info.GetString( "String Field" );

m_dDoubleValue = info.GetDouble( "Double Field" );

}
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)

{

info.AddValue( "Integer Field", m_nIntValue );



info.AddValue( "String Field", m_strStringValue );

info.AddValue( "Double Field", m_dDoubleValue );

}

}
Когато сериализацията е в процес на изпълнение, CLR проверява дали обектът имплементира ISerializable, в който случай извиква GetObjectData директно за този обект. Обектът може да инжектира собсвени данни в SerializationInfo, с помощта на метода му AddValue.



ВАЖНО е да се отбележи че като имплементираме ISerializable интерфейса, това не означава че не трябва да маркираме нашия клас като Serializable. Без този атрибут сложен за нашия клас, CLR дори не счита че инстанциите от този клас трябва дори да бъдат считани за сериализация! Също така важно е да се отбележи че вие се нуждаете да имплементирате както GetObjectData, така и специалниа конструктор. Компилатора ще ви предупреди че GetObjectData липсва, но тъй като е невъзможно да задължите имплементирането на конструктор, нокакви предупреждения (warnings) няма да бъдат дадени ако този специален конструктор липсва. Ако това стане, ще бъде хвърлено изключение в момента на десериализацията, която от своя страна търси този конструктор. Текущия дизайн на CLR се опитва да се заобиколи някои потенциални проблеми свързани със сигурността и версиите. Например, SetObjectData е public метод, тъй като е дефиниран в интерфейс. Това от своя страна дава възможност на потребителите да викат многократно този метод. Затова програмистите трябва да пишат код който да ги защитава от такива бъгове, който биха били използани от хакерите.

По време на десериализацията, обект SerializationInfo се подава на класа, чрез конструктора предоставен точно за тази цел. Всякакви ограничение за видимостта (visibility constraints) се игнорират, когато обекта се десериализира, така че вие можете да дефинирате класа като public, protected, internal, or private. Добра идея е да направите специалниа конструктор protected, освен ако класа ви не е забранен за наследяване (sealed), в който случай този конструктор трябва да бъде маркиран като private. За да възстановите състоянието на сериализирания обект, просто извлечете стойностите на полетата от SerializationInfo като използвате имената им по време на сериализацията. Ако някой базов клас имплементира ISerializable, вие трябва да извикате базовия конструктор, за да позволите на базовия обект да възстанови своите полета. Ето и пример:


[Serializable]

public class DerivedCustomSerializatoin : CustomSerialization

{

public new int m_nIntValue = 0;



public new String m_strStringValue = "[Default]";

public new double m_dDoubleValue = 0.0;

[NonSerialized] public int m_nNonSerializableField = 88;
public DerivedCustomSerializatoin( int nValue, String strValue, double dValue )

:base( nValue, strValue, dValue )

{

m_nIntValue = nValue;



m_strStringValue = strValue;

m_dDoubleValue = dValue;

}
protected DerivedCustomSerializatoin(SerializationInfo info, StreamingContext context)

:base(info, context)

{

m_nIntValue = info.GetInt32( "DerivedInt" );



m_dDoubleValue = info.GetDouble( "DerivedDouble" );

m_strStringValue = info.GetString( "DerivedString" );

}
public override void GetObjectData(SerializationInfo info, StreamingContext context ) {

base.GetObjectData( info, context );


info.AddValue( "DerivedInt", m_nIntValue );

info.AddValue( "DerivedDouble", m_dDoubleValue );

info.AddValue( "DerivedString", m_strStringValue );

}
public Boolean IsEqualWithBaseClass()

{

Boolean bIntValue = ( m_nIntValue == base.m_nIntValue ) ? true : false;



Boolean bStringValue=(m_strStringValue == base.m_strStringValue)?true:false;

Boolean bDoubleValue = ( m_dDoubleValue == base.m_dDoubleValue )?true:false;

return ( bIntValue && bStringValue && bDoubleValue ) ? true : false;

}

}


Не забравяйте да десериализирате базовия клас от специалниа десериализиращ конструктор. Ако това стане, съответния конструктор на базови клас няма да бъде извикан никога и обектът няма да бъде в същото състояние в което е бил сериализиран.

Обекти се констуират от вътре навън (обекти които са агрегирани във външен клас), и за такива ако извикаме някой метод, по време на десериализацията, това може да доведе до странични ефекти (side effects), тъй като методите могат да използват референции, които все още не са били десериализирани (в момента на извикване на метода). Ако класа, който се сериализира импплементира IDesirializationCallback, метода OnDeserialization ще бъде автоматично извикан когато целия граф с обекти бъде десериализиран. В този момент, всички агрегирани обекти са били изцяло реконструирани. Хеш таблица е типичен пример за клас, който е трудно да се десериализира без да се използва такъв notification като OnDeserialization . лесно е да извлечем двойка от ключ/стойност по време на десериализация, но вмъкването на тези обекти обратно в хеш таблицата може да доведе до проблеми, защото няма никаква гаранция че обектите в самата таблица са били десериализирани. Извикването на методи на хеш таблицата в този момент е нежелателно.




Formatters


Сериализацията може да бъде стартирана от проложението или от runtime. Приложението стартира сериалзиацията, например, за да запази състоянието на обект на хард диска във вид на файл, когато приложението спира (exit-ва се). Runtime стартира сериализацията когато, например, когато обект се подава на друга application domain, вероятно на друга машина дори. Форматът на данните които се сериализират зависи от причината и къде ще се използва сериализирания вид на обекта. Така например, ако обекта ще бъде предаден на друга машина по HTTP, записването на данните в съвместим със SOAP протокола XML формат има смисъл. Обаче, ако обекта ще бъде сериализиран във файл или ще се праща по ТСР, записването на данните в бинарен вид е по-ефективен. Точно заради това логично е да отделим логиката за сериализацията и формата на сериализираните данни.

В .NET формата на изходния сериализиран поток, е контролиран от това което се нарича Formatter, или клас който имплементира стандартен интерфейс IFormatter. Ето и някои от неговите методи:




Public Properties

Binder


Gets or sets the SerializationBinder that performs type lookups during deserialization.

Context

Gets or sets the StreamingContext used for serialization and deserialization.

SurrogateSelector

Gets or sets the SurrogateSelector used by the current formatter




Public Methods

Deserialize


Deserializes the data on the provided stream and reconstitutes the graph of objects.

Serialize

Serializes an object, or graph of objects with the given root to the provided stream.

Метода Serialize сериализира обекта (и всички други които са вътре в него) в поток от данни. Метода Deserialize от друга страна чете данните от потока и пресъзвдава състоянието на обекта. Пропертито Context дава възможност на инициатора на сериализацията да добави допълнителна информация за обекта, който ще се сериализира.



BinaryFormatter class


BCL предостава formatter наречен BinaryFormatter за да сериализира обект във бинарен формат. Резултатния поток е много компактен и може да бъде анализиран бързо (paresed quickly). Ето и пример:

public static void UseBinaryStream()

{

Foo myObj = new Foo();

myObj.DoubleValue = 77.77;

myObj.intValue = 77;

myObj.NonSerializedValue = 88;

myObj.StringValue = "7777.7777";
// open a file for binary write

using( FileStream fsw = File.OpenWrite( "Output.bin" ) )

{

BinaryFormatter bf = new BinaryFormatter();

bf.Serialize( fsw, myObj );

fsw.Close();

}
// open the file for reading and recreate Foo

Foo myObjRecreated = null;

using( FileStream fsr = File.OpenRead( "Output.bin" ) )

{

BinaryFormatter bf = new BinaryFormatter();

myObjRecreated = (Foo) bf.Deserialize( fsr );

}

}


  • сериализацията на обект не е ограничена само върху public членове полета (member fields). За да запишете състоянито на обекта с голяма точност, всички членове полета, включително и тези които са private, трябва да бъдат сериализирани.

  • Туй като логиката за сериализацията е независима от потока който се използва, лесно можем да променим предшестващия пример да използва MemoryStream вместо FileStream



SoapFormatter class


Има още един полезен клас в BCL наречен SoapFormatter. Този клас генерира SOAP базиран XML съвместим изход (output). Ако променим в предния пример BinaryFormatter с SoapFormatter, бихме получили XML базиран изход.






77

77.77

7777.7777







XML Serializer


В ерата на комуникациите, XML стана стандартен формат за обмяна на информация между приложенията. BinaryFormatter предоставя компактен формат, но той работи само межу .NET приложения. Ако сериализираме данни в XML формат, ние създаваме данни които могат да бъдат четени от всяка платформа и от всеки. Когато създаваме бизнес приложения, доста често ние пишем или четем от XML документи. Формата на един XML документ, обикновено се придържа стриктно към дадена схема наречена XML Schema Definition (XSD).

.NET предоставя един клас, XmlSerializer (System.Xml.Serialization namespace) който ви позволява да контролирате начина на сериализация на обекти в XML формат и след това, обектът да бъде пресъздаден от данни в XML вид. XmlSerializer принадлежи на XML class library, а не на BCL.

public static void UseXMLSerialization()

{

BookInfo book = new BookInfo();



book.Author = "George Ivanov";

book.Title = "Sexual Revolution";

book.Price = 77.88f;

book.ISBN = "345234523452345";


// open a file for XML output

using( FileStream fsWriter = File.Open("output00.xml",

FileMode.OpenOrCreate, FileAccess.Write, FileShare.None) )

{

try



{

System.Xml.Serialization.XmlSerializer xs =

new System.Xml.Serialization.XmlSerializer( typeof(BookInfo) );
xs.Serialize( fsWriter, book );

}

catch(Exception ex)



{

Console.WriteLine( ex.Message );

Console.WriteLine( ex.Source );

System.Diagnostics.Trace.WriteLine( ex.HelpLink );

}
fsWriter.Close();

}
// open the output file to recreate object

BookInfo recreatedBook = null;

using ( FileStream fsReader = File.Open("output00.xml",

FileMode.Open, FileAccess.Read, FileShare.None) )

{

System.Xml.Serialization.XmlSerializer xs =



new System.Xml.Serialization.XmlSerializer( typeof(BookInfo) );
recreatedBook = xs.Deserialize( fsReader ) as BookInfo;

}

if (recreatedBook != null &&



recreatedBook.Author == book.Author &&

recreatedBook.ISBN == book.ISBN &&

recreatedBook.Title == book.Title &&

recreatedBook.Price == book.Price)

Console.WriteLine( "The two objects contain identical information" );

else


Console.WriteLine( "The two obejcts differs!" );

}
За да промените изхода от XML сериализацията, вие можете да използвате атрибути за XML сериализации приложени за вашите полета.




Attribute

Description
XmlRoot

Да инентифицирате клас или структура като root node. Обикновено се използва за да се сложи различно име на root node от името на класа

XmlElement

Публичното property или поле трябва да бъде сериализирано като XML елемент. Обикновено се използва за да преименува поле с друго име

XmlAttribute

Публичното property или поле трябва да бъде сериализирано като XML атрибут.

XmlArray

Публично поле трябва да бъде сериализирано като масив. Използва се когато масив от обекти трябва да бъдат сериализирани.

XmlArrayItem

Да се идентифицира тип който може да се сериализира в масив.

XmlIgnore

Не сериализрай публичното property или поле.

[XmlRoot(ElementName="book")]

public class BookInfo

{

protected String m_ISDN = null;



protected String m_Author = null;

protected String m_Title = null;


[XmlIgnore]

public float Price = 0.0f;


public BookInfo()

{

}


[XmlAttribute(AttributeName = "ISBN")]

public String ISBN

{

get


{

return m_ISDN;

}

set


{

m_ISDN = value;

}

}
[XmlAttribute(AttributeName = "Author")]



public String Author

{

get



{

return m_Author;

}

set


{

m_Author = value;

}

}
[XmlAttribute(AttributeName = "Title")]



public String Title

{

get



{

return m_Title;

}

set


{

m_Title = value;

}

}

}



Serializor vs Formatter

Въпреки че SoapFormatter може също да бъде използван за да сериализира обект в XML, той и XML сериализатора решават 2 различни проблема. Formatter се иползва за да се сериализира обект до своята най-точна прецизност. От друга страна, XML сериализатора се използва да обработва XML документи които обикновено се подчиняват на определени XSD схеми. Той не е асоцииран в никакъв аспект с архитекурата за сериализацията, както са formatter-ите и се контролира от различен набор от атрибути, които се използват от formatter-ите.

Възможността да създадем XML документи който спазват дадени схеми е много силна но въпреки всичко това създава някои ограничения. Едно такова ограничение е че само public полетата или пропертита могат да бъдат сериализирани. Друго ограничение е че ако обектния граф съдържа циклични референции (circular references) тогава обектът не може да бъде сериализиран.



Тъй като XSD схемите са често използвани за да определят форматът на един XML файл, .NET SDK предлага един tool наречен XML Schema Definition Tool (xsd.exe) която ви позволява да генерирате силно типизирани С# класове, базирани на съществуваща XSD схема. Например, ако предположим че схемата за класа BookInfo е дифнирана в Bookinfo.xsd командата генерира изходен файл, съдържащ съответния С# клас :
xsd.exe Bookinfo.xsd /c
възможно е също да генерирате XSD схема по иходния XML файл, генериран за даден обект. Това може да стане чрез командата:
xsd.exe BookInstance.xml





Каталог: dotnet -> 2003 -> lectures
lectures -> Програмиране за платформата. Net взаимодействие между managed и unmanaged код Стоян Йорданов
lectures -> Потоци и файлове Георги Иванов
lectures -> Програмиране за платформата. Net работа с xml стоян Йорданов Какво е xml?
lectures -> Решение на проблема оператора delete
lectures -> Програмиране за платформата. Net web Services Стоян Йорданов Преглед
lectures -> Clr, създаване и инсталиране на. Net компоненти, типове данни Стоян Йорданов Common Language Runtime (clr)
lectures -> Решение на проблема оператора delete
lectures -> Програмиране за платформата. Net работа с xml стоян Йорданов Какво е xml?


Сподели с приятели:




©obuch.info 2024
отнасят до администрацията

    Начална страница