До момента разгледахме класическата сериализация и десериализация на обекти. Нека сега се запознаем с още една възможност за съхраняване и възстановяване състоянието на обекти, която .NET Framwork предоставя на програмиста – XML сериализацията.
Какво е XML сериализация?
XML сериализация представлява записването на публичните полета на обект в XML формат с цел съхранение или пренасяне. Тя е част от вградената поддръжка на XML в .NET Framework. Обратният процес на XML сериализацията е XML десериализацията.
XML сериализацията създава някои ограничения, които трябва да имаме предвид. При нея се сериализират само публичните полета и не се запазва целостта на типа. XML сериализацията не може да се справи с циклично свързани графи от обекти. Могат да се сериализират всякакви обекти, но класът трябва да има конструктор без параметри.
Всъщност XML сериализацията не е сериализация в истинския смисъл на това понятие, защото не съхранява и възстановява пълното състояние на обектите, а само части от него.
XML сериализация – пример
В следващия пример ще илюстрираме как един клас може да сериализира данните си чрез XML сериализация:
public class Student
{
public string mName;
public int mAge;
public void SerializeToXml(Stream aStream)
{
XmlSerializer xmlSerializer =
new XmlSerializer(typeof(Student));
xmlSerializer.Serialize(aStream, this);
}
public static Student DeserializeFromXml(Stream aStream)
{
XmlSerializer xmlSerializer =
new XmlSerializer(typeof(Student));
Student st = (Student) xmlSerializer.Deserialize(aStream);
return st;
}
}
| Как работи примерът?
Класът Student има две публични полета – mName и mAge. Те трябва да са публични, за да могат да се запазят при XML сериализацията.
Реализирали сме метод SerializeToXml(…), който сериализира данните на класа в XML формат в подадения му като параметър поток. За целта създаваме обект от класа XmlSerializer и извикваме метода му Serialize(…), който сериализира инстанцията на класа в потока.
Методът DeserializeFromXml(…) служи за десериализиране на данните от подадения му като параметър поток. За целта създаваме обект от класа XmlSerializer и извикваме метода му Deserialize(…), който десериализира данните от потока и връща десериализирания обект.
Проста XML сериализация – пример
Ще представим още един по-подробен пример, илюстриращ възможностите на .NET Framework за сериализация на обекти в XML формат чрез класа XmlSerializer:
using System;
using System.IO;
using System.Xml.Serialization;
public class Student
{
private string mName;
private int mAge;
public string Name
{
get { return mName; }
set { mName = value; }
}
public int Age
{
get { return mAge; }
set { mAge = value; }
}
public override string ToString()
{
string result =
String.Format("(Name: {0}, Age: {1})", Name, Age);
return result;
}
}
class XmlSerializationDemo
{
static void Main()
{
Student student = new Student();
student.Name = "Дядо Мраз";
student.Age = 99;
Console.WriteLine("Original = {0}", student);
// Serialize student object to "student.xml" file
XmlSerializer xmlSerializer =
new XmlSerializer(typeof(Student));
FileStream outputStream = File.OpenWrite("student.xml");
using (outputStream)
{
xmlSerializer.Serialize(outputStream, student);
}
Console.WriteLine("Student serialized.");
// Deserialize student object from "student.xml" file
FileStream inputStream = File.OpenRead("student.xml");
using (inputStream)
{
Student deserializedStudent =
(Student) xmlSerializer.Deserialize(inputStream);
Console.WriteLine("Student deserialized.");
Console.WriteLine("Deserialized = {0}",
deserializedStudent);
}
}
}
| Как работи примерът?
В примера сме дефинирали класа Student, който има две публични свойства, които ще бъдат сериализирани. В класа е предефиниран методът ToString(), който връща символен низ, описващ съдържанието на обект от този тип. Този метод ще използваме за визуализация на Student обекти.
Във функцията Main() на класа XmlSerializationDemo създаваме обект от класа Student, инициализираме го и отпечатваме съдържанието му в конзолата. След това създаваме обект от класа XmlSerializer и използваме метода му Serialize(…), за да сериализираме инстанцията на класа Student във файла student.xml. Накрая, използвайки метода Deserialize(…) на класа XmlSerializer, извършваме десериализацията от XML документ към инстанция на Student и отпечатваме съдържанието на тази инстанция в конзолата. Ето какъв е резултатът след изпълнението на примера:
Както виждаме, информацията е възстановена коректно. Оригиналният обект и обектът, получен след десериализацията, са еднакви. Ето как изглежда и съдържанието на файла student.xml, в който е записан сериализираният обект:
Виждаме, че в XML файла са записани всички публични членове на сериализирания Student обект.
Контролиране на изходния XML
Ако е нужно, можем да контролираме изходния XML, генериран от класа XmlSerializer. Това става чрез атрибути, които прилагаме към класа или към неговите полета. Ето кратък пример:
using System.Xml.Serialization;
public class OptionalOrder
{
[XmlElement(ElementName = "Tax_Rate")]
public decimal TaxRate;
[XmlAttribute]
public string FirstOrder;
[XmlIgnoreAttribute]
public bool FirstOrderSpecified;
[XmlArrayAttribute("Items")]
[XmlArrayItem("MemberName")]
public OrderedItem[] OrderedItems;
[XmlElement]
public Employee[] Employees;
}
|
В примера сме дефинирали класа OptionalOrder. Към полетата му сме приложили атрибути, чрез които указваме как да се запишат в XML – чрез XML елементи, чрез XML атрибути и др.
Чрез атрибутът XmlElement указваме, че полето, към което е приложен, трябва да се сериализира като XML елемент. Чрез него можем да контролираме характеристиките на XML елемента, като най-често го използваме за указване на името на елемента.
Атрибутът XmlAttribute указва, че полето, към което е приложен, трябва да се сериализира като XML атрибут. По подразбиране XmlSerializer сериализира публичните полета като XML елементи.
Атрибутът XmlIgnoreAttribute указва, че полето не трябва да бъде сериализирано.
Атрибутът XmlArrayAttribute указва, че полето, към което е приложен, трябва да бъде сериализирано като масив. Чрез този атрибут може да укажем и името на генерирания XML елемент.
Атрибутът XmlArrayItem обикновено се използва заедно с атрибута XmlArrayAttribute и идентифицира тип, който може да се сериализира в масив. Чрез този атрибут също може да укажем името на генерирания XML елемент (както сме направили в нашия пример).
Контрол на XML сериализацията – пример
Ще представим още един, по-обширен, пример как чрез атрибути може да се контролира процесът на XML сериализацията:
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Xml.Serialization;
[XmlRoot("animal")]
public class Animal
{
[XmlArray("eyes")]
[XmlArrayItem("eye")]
public Eye[] Eyes;
[XmlElement("claws")]
public Claw[] Claws;
[XmlIgnore]
public string SomeMember = "Some member";
public Animal Friend;
}
public class Eye
{
[XmlAttribute("vision")]
public double Vision;
public Eye()
{
}
public Eye(double aVision)
{
Vision = aVision;
}
}
public class Claw
{
[XmlElement(ElementName="claw")]
public string Description;
public Claw()
{
}
public Claw(string aDescription)
{
Description = aDescription;
}
}
public class ControllingSerializationDemo
{
public static void SerializeAnimalToXml(Animal aAnimal,
string aFileName)
{
XmlSerializer xmlSerializer =
new XmlSerializer(typeof(Animal));
TextWriter writer = new StreamWriter(aFileName);
using (writer)
{
xmlSerializer.Serialize(writer, aAnimal);
}
}
public static Animal DeserializeAnimalFromXml(
string aFileName)
{
TextReader reader = new StreamReader(aFileName);
using (reader)
{
XmlSerializer xmlSer = new XmlSerializer(typeof(Animal));
object deserializedAnimal = xmlSer.Deserialize(reader);
return (Animal) deserializedAnimal;
}
}
public static void Main()
{
Animal animal1 = new Animal();
animal1.Eyes = new Eye[] {new Eye(1.05), new Eye(0.85)};
animal1.Claws = new Claw[] {
new Claw("Left claw"),
new Claw("Right claw")};
Animal animal2 = new Animal();
animal2.Eyes = new Eye[] {new Eye(1.00), new Eye(1.00)};
animal2.Claws = new Claw[] {new Claw("Beautiful claw")};
animal1.Friend = animal2;
// animal2.Friend = animal1;
SerializeAnimalToXml(animal1, "animal.xml");
Console.WriteLine("Animal serialized.");
Animal deserializedAnimal =
DeserializeAnimalFromXml("animal.xml");
Console.WriteLine("Animal deserialized.");
}
}
| Как работи примерът?
Класът Animal съдържа няколко полета, за които сме указали чрез атрибутите XmlArray, XmlArrayItem, XmlElement и XmlIgnore как трябва да се запишат в изходния XML.
Класовете Eye и Claw, които се използват от класа Animal също ползват атрибути, за да опишат как да се запишат в изходния XML.
В класа ControllingSerializationDemo са реализирани два метода – SerializeAnimalToXml и DeserializeAnimalFromXml, които съответно сериализират и десериализират Animal обекти.
Във метода Main() създаваме две инстанции на класа Animal, задаваме стойности на публичните им членове и правим едната инстанция член на другата. След това извършваме сериализация във файла animal.xml и десериализираме този файл, за да получим обратно записаната в него Animal инстанция. След като изпълним примера получаваме следният резултат:
Обектът бива сериализиран и след това обратно десериализиран. На картинката по-долу виждаме как изглежда и файлът animal.xml, получен при сериализацията на обекта.
Забелязва се, че полето SomeMember не е било сериализирано, понеже е маркирано с атрибута XmlIgnore. Имената на елементите са такива, каквито сме указали чрез атрибутите, които сме приложили към полетата.
Ако в горния пример премахнем коментара от реда "animal2.Friend = animal1" и така направим двете инстанции на класа Animal циклично свързани една с друга и изпълним след това примера, ще получим изключение. Това се случва, защото XML сериализацията не може да сериализира циклични структури.
Външен контрол на XML сериализацията
В .NET Framework е предвиден механизъм, който ни позволява да контролираме XML сериализацията извън обекта, т.е. без да указваме това в изходния код на класа. Този механизъм се използва, когато нямаме достъп до изходния код на класа или когато искаме да създадем един набор от сериализируеми класове, но да сериализираме обектите по различен начин в зависимост от това къде се използват.
Външният контрол на сериализацията прилича много на контрола на сериализацията с атрибути. Функционалността е същата като при нея, дори класовете са същите, само механизмът за добавяне е различен.
Външният контрол на сериализацията се извършва чрез класовете XmlAttributesOverrides и XmlAttributes. Чрез тях, за всеки член на даден клас, се задава колекция XmlAttributes, описваща формата на изходния XML. За целта се създава XmlAttributesOverrides обект, който по-късно се подава на конструктора на XmlSerializer. Резултатният XmlSerializer обект използва информацията, която се съдържаща в XmlAttributesOverrides, за да определи как да извърши сериализацията. XmlAttributesOverrides обекта съдържа колекция от типове, за които ще бъде предефинирана автоматичната сериализация, както и XmlAttributes обект, асоцииран с всеки един от тях. XmlAttributes обектът съдържа избран набор от атрибути, указващи как да бъдат сериализирани всяко едно поле, свойство или клас.
Нека разгледаме следващия фрагмент код, илюстриращ как става това:
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
XmlAttributes attribs = new XmlAttributes();
attribs.XmlElements.Add(new XmlElementAttribute("PersonName"));
overrides.Add(typeof(Person), "Name", attribs);
XmlSerializer xmlSerializer =
new XmlSerializer(typeof(Person), overrides);
...
|
В примера указваме на XML сериализацията, че полето (или свойството) Name на класа Person трябва да се запише в XML таг с име PersonName.
Първо създаваме XmlAttributesOverrides обект. След това създаваме XmlAttributes обект и към колекцията му XmlElements добавяме нов XmlElementAttribute. После, използвайки метода Add(…), добавяме XmlAttributes обекта към XmlAttributesOverrides обекта. Като параметри на метода подаваме и типа, за който предефинираме сериализацията, както и името на полето, чиято сериализация предефинираме. Накрая подаваме XmlAttributesOverrides обекта на конструктора на XmlSerializer.
Външен контрол на сериализацията – пример
Ще представим един пример, илюстриращ, как можем да контролираме формата на изходния XML документ при XML сериализация по недекларативен път (без да се променя сорс кода на класа, който се сериализира):
using System;
using System.IO;
using System.Xml.Serialization;
public class Person
{
public string Name;
public int Age;
public string[] Friends;
}
class OverridingXmlSerializationDemo
{
static void Main()
{
Person person = new Person();
person.Name = "Бай Мангал";
person.Age = 82;
person.Friends = new string[] {"Дядо Мраз", "Баба Яга"};
XmlAttributeOverrides overrides =
new XmlAttributeOverrides();
XmlAttributes nameAttributes = new XmlAttributes();
XmlElementAttribute nameElement =
new XmlElementAttribute("PersonName");
nameAttributes.XmlElements.Add(nameElement);
overrides.Add(typeof(Person), "Name", nameAttributes);
XmlAttributes friendsAttributes = new XmlAttributes();
XmlArrayAttribute friendsArray =
new XmlArrayAttribute("PersonFriends");
friendsAttributes.XmlArray = friendsArray;
XmlArrayItemAttribute friendsArrayItem =
new XmlArrayItemAttribute();
friendsArrayItem.ElementName = "FriendName";
friendsAttributes.XmlArrayItems.Add(friendsArrayItem);
overrides.Add(typeof(Person), "Friends",
friendsAttributes);
TextWriter writer = new StreamWriter("person.xml");
using (writer)
{
XmlSerializer xmlSer = new XmlSerializer(typeof(Person),
overrides);
xmlSer.Serialize(writer, person);
}
Console.WriteLine("Person instance serialized.");
}
}
| Как работи примерът?
Дефинирали сме клас Person с няколко полета. В началото на функцията Main() създаваме инстанция на класа Person и инициализираме нейните полетата. След това на полето Name от класа Person съпоставяме колекция от XML атрибути, които указват, че това поле трябва да се форматира като XML елемент с име PersonName. После на полето Friends от класа Person (което представлява масив от низове) съпоставяме колекция от XML атрибути, които указват, че това поле трябва да се форматира като XML елемент с име PersonFriends, което съдържа в себе си за всеки елемент от масива по един XML елемент с име FriendName. Накрая сериализираме обекта във файла person.xml.
Ето как изглежда и файлът person.xml, получен при сериализацията:
Виждаме, че полетата са сериализирани по начина, който сме указали чрез атрибутите, които сме приложили към тях.
Приложение: FormatterServices
Ще разгледаме съвсем накратко, без да даваме пример, средствата за реализация на собствени форматери в .NET Framework. Едно от тези средства е класът FormatterServices. Той предоставя основната функционалност, която трябва да притежава форматера – извличане на сериализируемите членове на обект, определяне на техните типове и извличане на стойностите им. Този клас не може да бъде наследяван.
Методи за сериализация public static MemberInfo[] GetSerializableMembers(Type)
Методът приема като параметър типа на класа, който ще бъде сериализиран, и връща като резултат масив от MemberInfo обекти, съдържащи информация за сериализируемите членове на класа.
public static Object[] GetObjectData(Object, MemberInfo[])
Методът приема като параметри обект, който ще бъде сериализиран и масив с членовете, които трябва бъдат извлечени от обекта. За всеки от тях се извлича стойността, асоциирана с него в сериализирания обект и тези стойности се връщат като масив от обекти. Дължината му е същата, като дължината на масива с членовете, извличани от обекта.
Методи за десериализация public static Type GetTypeFromAssembly(Assembly, String)
Методът намира типа на определен обект в дадено асембли. Той приема като параметри асемблито и името на обекта, който ще се търси, и връща като резултат типа на този обект.
public static Object GetUninitializedObject(Type)
Методът приема като параметър тип на обект и връща като резултат нова инстанция на обект от дадения тип.
public static Object GetObjectMembers(Object, MemberInfo[], Object[])
Методът попълва със стойности полетата на обект, като тези стойности се вземат от масив с обекти. За целта като параметри му се подават обекта, чиито полета ще се запълват, масив от MemberInfo обекти, описващ кои полета да се запълват и масив с обекти, от който ще се вземат стойностите за полета. Като резултат се връща обекта с попълнени полета.
Упражнения -
Да се дефинира клас Graph, който описва насочен граф (представен като масив от върхове). Да се дефинира клас Node, който описва един връх от графа. Класът Node трябва да съдържа информационна част (текстово поле) и масив от наследници (инстанции на същия клас Node). Да се Реализира функционалност, която сериализира и десериализира инстанции на класа Graph.
-
Опитайте се да сериализирате бинарно инстанция на класа System. Collections.Hashtable. Опитайте след това да сериализирате хеш-таблица с XML сериализация. Какви проблеми възникват? Можете ли да обясните защо XML сериализацията не работи? Предложете алтернативно решение.
-
Дефинирайте класове Country и Town, които съдържат информация за държави и градове. Може да считате, че в една държава има много градове. Реализирайте бинарна и XML сериализация и десериализация за тези класове. Реализирайте TCP сървър, който по име на държава връща информация за държавата заедно с всички градове в нея (във вид на бинарно сериализиран Country обект). Реализирайте Windows Forms клиентско приложение за TCP сървъра, което позволява да се извлича и визуализира информация за държавите. Клиентът и сървърът трябва да поддържат два режима на работа – с бинарна сериализация и с XML сериализация.
-
Обяснете защо SoapFormatter може да сериализира цикличен граф от обекти, а XML сериализацията не може. Упътване: създайте цикличен граф от обекти, сериализайте го по двата начина и сравнете изходните XML файлове.
Използвана литература -
Михаил Стойнов, Сериализация на данни – http://www.nakov.com/ dotnet/lectures/Lecture-19-Serialization-v1.0.ppt
-
MSDN Library – http://msdn.microsoft.com
-
Object Serialization in the .NET Framework
-
System.Runtime.Serialization Namespace
-
System.Runtime.Serialization.Formatters Namespace
-
System.Runtime.Serialization.Formatters.Binary Namespace
-
System.Runtime.Serialization.Formatters.Soap Namespace
-
System.Xml.Serialization Namespace
-
XML and SOAP Serialization
-
XmlSerializer Class
-
Controlling XML Serialization Using Attributes
-
Overriding XML Serialization
-
Attributes That Control Encoded SOAP Serialization
-
Attributes That Control XML Serialization
-
The XML Schema Definition Tool and XML Serialization
-
Generating SOAP Messages With XML Serialization
-
FormatterServices Class
-
Vyacheslav Biktagirov, .NET Serialization – http://www.csharphelp.com /archives/archive38.html
-
Mickey Williams, CodeGuru: .NET Serialization - http://www.codeguru. com/columns/DotNet/article.php/c6595/
Сподели с приятели: |