Вы находитесь на странице: 1из 13

Сериализация

Введение в сериализацию объектов


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

Атрибут Serializable

Чтобы объект определенного класса можно было сериализовать, надо этот класс
пометить атрибутом Serializable:

1 [Serializable]
2 class Person
3 {
4     public string Name { get; set; }
5     public int Year { get; set; }
6
7     public Person(string name, int year)
8     {
9         Name = name;
10         Year = year;
11     }
12 }

При отстутствии данного атрибута объект Person не сможет быть сериализован, и при
попытке сериализации будет выброшено исключение SerializationException.

Если мы не хотим, чтобы какой-то член класса сериализовался, то мы его помечаем


атрибутом NonSerialized:

1 [Serializable]
2 class Person
3 {
4     public string Name { get; set; }
5     public int Year { get; set; }
6
7     [NonSerialized]
8     public string AccNumber { get; set; }
9
10     public Person(string name, int year)
11     {
12         Name = name;
13         Year = year;
14     }
15 }
1
При наследовании подобного класса, следует учитывать, что атрибут Serializable
автоматически не наследуется. И если мы хотим, чтобы производный класс также мог
бы быть сериализован, то опять же мы применяем к нему атрибут:

1 [Serializable]
2 class Worker : Person

Формат сериализации

Хотя сериализация представляет собой преобразование объекта в некоторый набор


байтов, но в действительности только бинарным форматом она не ограничивается.
Итак, в .NET можно использовать следующие форматы:

 бинарный
 SOAP
 xml
 JSON

Для каждого формата предусмотрен свой класс: для сериализации в бинарный формат
- класс BinaryFormatter, для формата SOAP - класс SoapFormatter, для xml
- XmlSerializer, для json - DataContractJsonSerializer.

Для классов BinaryFormatter и SoapFormatter сам функционал сериализации определен


в интерфейсе IFormatter:

1 public interface IFormatter


2 {
3     SerializationBinder Binder { get; set;}
4     StreamingContext Context { get; set;}
5     ISurrogateSelector SurrogateSelector  { get; set;}
6     object Deserialize (Stream serializationStream);
7     void Serialize (Stream serializationStream, object graph);
8 }

Хотя классы BinaryFormatter и SoapFormatter по-разному реализуют данный интерфейс,


но общий функционал будет тот же: для сериализации будет использоваться
метод Serialize, который в качестве параметров принимает поток, куда помещает
сериализованные данные (например, бинарный файл), и объект, который надо
сериализовать. А для десериализации будет применяться метод Deserialize, который в
качестве параметра принимает поток с сериализованными данными.

Класс XmlSerializer не реализует интерфейс IFormatter и по функциональности в целом


несколько отличается от BinaryFormatter и SoapFormatter, но и он также предоставляет
для сериализации метод Serialize, а для десериализации Deserialize. И в этом плане
очень легко при необходимости перейти от одного способа сериализации к другому.

Бинарная сериализация. BinaryFormatter


Для бинарной сериализации применяется класс BinaryFormatter:

1 using System;
2 using System.IO;
2
using System.Runtime.Serialization.Formatters.Binary;
3
4 namespace Serialization
5 {
6     [Serializable]
7     class Person
8     {
9         public string Name { get; set; }
10         public int Age { get; set; }
11
12         public Person(string name, int age)
13         {
14             Name = name;
15             Age = age;
16         }
17     }
18
    class Program
19
    {
20
        static void Main(string[] args)
21
        {
22
            // объект для сериализации
23
            Person person = new Person("Tom", 29);
24
            Console.WriteLine("Объект создан");
25
26
            // создаем объект BinaryFormatter
27             BinaryFormatter formatter = new BinaryFormatter();
28             // получаем поток, куда будем записывать сериализованный объект
29             using (FileStream fs = new FileStream("people.dat",
30 FileMode.OpenOrCreate))
31             {
32                 formatter.Serialize(fs, person);
33
34                 Console.WriteLine("Объект сериализован");
35             }
36
37             // десериализация из файла people.dat
38             using (FileStream fs = new FileStream("people.dat",
39 FileMode.OpenOrCreate))
40             {
41                 Person newPerson = (Person)formatter.Deserialize(fs);
42
43                 Console.WriteLine("Объект десериализован");
44                 Console.WriteLine("Имя: {0} --- Возраст: {1}", newPerson.Name,
45 newPerson.Age);
46             }
47
48             Console.ReadLine();
49         }
50     }
}

Так как класс BinaryFormatter определен в пространстве


имен System.Runtime.Serialization.Formatters.Binary, то в самом начале
подключаем его.

3
У нас есть простенький класс Person, который объявлен с атрибутом Serilizable.
Благодаря этому его объекты будут доступны для сериализации.

Далее создаем объект BinaryFormatter: BinaryFormatter formatter = new


BinaryFormatter();

Затем последовательно выполняем сериализацию и десериализацию. Для обоих


операций нам нужен поток, в который либо сохранять, либо из которого считывать
данные. Данный поток представляет объект FileStream, который записывает нужный
нам объект Person в файл people.dat.

Сериализация одним методом formatter.Serialize(fs, person) добавляет все данные


об объекте Person в файлpeople.dat.

При десериализации нам нужно еще преобразовать объект, возвращаемый функцией


Deserialize, к типу Person:(Person)formatter.Deserialize(fs).

Как вы видите, сериализация значительно упрощает процесс сохранения объектов в


бинарную форму по сравнению, например, с использованием связки классов
BinaryWriter/BinaryReader.

Хотя мы взяли лишь один объект Person, но равным образом мы можем использовать и
массив подобных объектов, список или иную коллекцию, к которой применяется
атрибут Serializable. Посмотрим на примере массива:

1 Person person1 = new Person("Tom", 29);


2 Person person2 = new Person("Bill", 25);
3 // массив для сериализации
4 Person[] people = new Person[] { person1, person2 };
5
6 BinaryFormatter formatter = new BinaryFormatter();
7
8 using (FileStream fs = new FileStream("people.dat", FileMode.OpenOrCr
9 {
10     // сериализуем весь массив people
11     formatter.Serialize(fs, people);
12
13     Console.WriteLine("Объект сериализован");
14 }
15
16 // десериализация
17 using (FileStream fs = new FileStream("people.dat", FileMode.OpenOrCr
18 {
19     Person[] deserilizePeople = (Person[])formatter.Deserialize(fs);
20
21     foreach (Person p in deserilizePeople)
22     {
23         Console.WriteLine("Имя: {0} --- Возраст: {1}", p.Name, p.Age
24     }
25 }

Сериализация в формат SOAP. SoapFormatter

4
Протокол SOAP (Simple Object Access Protocol) представляет простой протокол для
обмена данными между различными платформами. При такой сериализации данные
упакуются в конверт SOAP, данные в котором имеют вид xml-подобного документа.
Посмотрим на примере.

Прежде чем использовать класс SoapFormatter, нам надо добавить в проект


сборкуSystem.Runtime.Serialization.Formatters.Soap.dll. После этого нам станет
доступным функциональность SoapFormatter:

1 using System;
2 using System.IO;
3 using System.Runtime.Serialization.Formatters.Soap;
4
5 namespace Serialization
6 {
7     [Serializable]
8     class Person
9     {
10         public string Name { get; set; }
11         public int Age { get; set; }
12
13         public Person(string name, int age)
14         {
15             Name = name;
16             Age = age;
17         }
    }
18
    class Program
19
    {
20
        static void Main(string[] args)
21
        {
22
            Person person = new Person("Tom", 29);
23
            Person person2 = new Person("Bill", 25);
24
            Person[] people = new Person[] { person, person2 };
25
26
            // создаем объект SoapFormatter
27             SoapFormatter formatter = new SoapFormatter();
28             // получаем поток, куда будем записывать сериализованный объект
29             using (FileStream fs = new FileStream("people.soap", FileMode.OpenOrCr
30             {
31                 formatter.Serialize(fs, people);
32
33                 Console.WriteLine("Объект сериализован");
34             }
35
36             // десериализация
37             using (FileStream fs = new FileStream("people.soap", FileMode.OpenOrCr
38             {
39                 Person[] newPeople = (Person[])formatter.Deserialize(fs);
40
41                 Console.WriteLine("Объект десериализован");
42                 foreach (Person p in newPeople)
43                 {
44                     Console.WriteLine("Имя: {0} --- Возраст: {1}", p.Name, p.Age)
45                 }
46             }

5
47
            Console.ReadLine();
48
        }
49
    }
50 }
51

Принцип использования SoapFormatter похож на рассмотренную в прошлой теме


бинарную сериализацию. Здесь также создается поток, записывающий данные в файл
people.soap. Для сериализации используется методformatter.Serialize(fs, people),
использующий поток и объект для сериализации.

При десериализации считываем ранее сохраненные объекты и преобразуем их к


нужному нам объекту в методе Deserialize:Person[] newPeople =
(Person[])formatter.Deserialize(fs)

После сериализации все данные будут сохранены в файл people.soap, который будет


иметь следующее содержание:

<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-
ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-
ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<SOAP-ENC:Array SOAP-ENC:arrayType="a1:Person[2]"
xmlns:a1="http://schemas.microsoft.com/clr/nsassem/Serialization/Serializatio
n%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<item href="#ref-3"/>
<item href="#ref-4"/>
</SOAP-ENC:Array>
<a1:Person id="ref-3"
xmlns:a1="http://schemas.microsoft.com/clr/nsassem/Serialization/Serializatio
n%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<_x003C_Name_x003E_k__BackingField
id="ref-5">Tom</_x003C_Name_x003E_k__BackingField>
<_x003C_Age_x003E_k__BackingField>29</_x003C_Age_x003E_k__BackingField>
</a1:Person>
<a1:Person id="ref-4"
xmlns:a1="http://schemas.microsoft.com/clr/nsassem/Serialization/Serializatio
n%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<_x003C_Name_x003E_k__BackingField
id="ref-6">Bill</_x003C_Name_x003E_k__BackingField>
<_x003C_Age_x003E_k__BackingField>25</_x003C_Age_x003E_k__BackingField>
</a1:Person>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Сериализация в XML. XmlSerializer

6
Для сериализации объектов в файлы xml используется класс XmlSerializer. Он стоит
несколько особняком от других ранее рассмотренных классов сериализаций, поэтому
работа с ним будет немного отличаться.

Во-первых, XmlSerializer предполагает некоторые ограничения. Например, класс,


подлежащий сериализации, должен иметь стандартный конструктор без параметров.
Также сериализации подлежат только открытые члены. Если в классе есть поля или
свойства с модификатором private, то при сериализации они будут игнорироваться.

Во-вторых, XmlSerializer требует указания типа:

1 using System;
2 using System.IO;
3 using System.Xml.Serialization;
4
5 namespace Serialization
6 {
7     // класс и его члены объявлены как public
8     [Serializable]
9     public class Person
10     {
11         public string Name { get; set; }
12         public int Age { get; set; }
13
14         // стандартный конструктор без параметров
15         public Person()
16         { }
17
18         public Person(string name, int age)
        {
19
            Name = name;
20
            Age = age;
21
        }
22
    }
23
    class Program
24
    {
25
        static void Main(string[] args)
26
        {
27             // объект для сериализации
28             Person person = new Person("Tom", 29);
29             Console.WriteLine("Объект создан");
30
31             // передаем в конструктор тип класса
32             XmlSerializer formatter = new XmlSerializer(typeof(Person));
33
34             // получаем поток, куда будем записывать сериализованный объект
35             using (FileStream fs = new FileStream("persons.xml",
36 FileMode.OpenOrCreate))
37             {
38                formatter.Serialize(fs, person);
39
40                 Console.WriteLine("Объект сериализован");
41             }
42
43             // десериализация
44             using (FileStream fs = new FileStream("persons.xml",
7
FileMode.OpenOrCreate))
45             {
46                 Person newPerson = (Person)formatter.Deserialize(fs);
47
48                 Console.WriteLine("Объект десериализован");
49                 Console.WriteLine("Имя: {0} --- Возраст: {1}",
50 newPerson.Name, newPerson.Age);
51             }
52
53             Console.ReadLine();
54         }
55     }
}

Итак, класс Person общедоступный и имеет общедоступные свойства, поэтому он может


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

И, как и с другими классами-сериализаторами, метод Serialize добавляет данные в


файл persons.xml. А метод Deserialize извлекает их оттуда.

Если мы откроем файл persons.xml, то увидим содержание нашего объекта:

<?xml version="1.0"?>
1
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
3
  <Name>Tom</Name>
4   <Age>29</Age>
5 </Person>

Равным образом мы можем сериализовать массив или коллекцию объектов, но главное


требование состоит в том, чтобы в них был определен стандартный конструктор:

1 Person person1 = new Person("Tom", 29);


2 Person person2 = new Person("Bill", 25);
3 Person[] people = new Person[] { person1, person2 };
4
5 XmlSerializer formatter = new XmlSerializer(typeof(Person[]));
6
7 using (FileStream fs = new FileStream("people.xml", FileMode.OpenOrCreate))
8 {
9     formatter.Serialize(fs, people);
10 }
11
12 using (FileStream fs = new FileStream("people.xml", FileMode.OpenOrCreate))
13 {
14     Person[] newpeople = (Person[])formatter.Deserialize(fs);
15
16     foreach (Person p in newpeople)
17     {
18         Console.WriteLine("Имя: {0} --- Возраст: {1}", p.Name, p.Age);
19     }
20 }

8
Но это был простой объект. Однако с более сложными по составу объектами работать
так же просто. Например:

1 using System;
2 using System.IO;
3 using System.Xml.Serialization;
4
5 namespace Serialization
6 {
7     [Serializable]
8     public class Person
9     {
10         public string Name { get; set; }
11         public int Age { get; set; }
12         public Company Company { get; set; }
13
14         public Person()
15         { }
16
17         public Person(string name, int age, Company comp)
18         {
            Name = name;
19
            Age = age;
20
            Company = comp;
21
        }
22
    }
23
24
    [Serializable]
25
    public class Company
26
    {
27
        public string Name { get; set; }
28
29         // стандартный конструктор без параметров
30         public Company() { }
31
32         public Company(string name)
33         {
34             Name = name;
35         }
36     }
37     class Program
38     {
39         static void Main(string[] args)
40         {
41             Person person1 = new Person("Tom", 29, new Company("Microsoft"));
42             Person person2 = new Person("Bill", 25, new Company("Apple"));
43             Person[] people = new Person[] { person1, person2 };
44
45             XmlSerializer formatter = new XmlSerializer(typeof(Person[]));
46
47             using (FileStream fs = new FileStream("people.xml",
48 FileMode.OpenOrCreate))
49             {
50                formatter.Serialize(fs, people);
51             }
52
53             using (FileStream fs = new FileStream("people.xml",
9
FileMode.OpenOrCreate))
54             {
55                 Person[] newpeople = (Person[])formatter.Deserialize(fs);
56
57                 foreach (Person p in newpeople)
58                 {
59                     Console.WriteLine("Имя: {0} --- Возраст: {1} ---
60 Компания: {2}", p.Name, p.Age, p.Company.Name);
61                 }
62             }
63             Console.ReadLine();
64         }
65     }
}

Класс Person содержит свойство Company, которое будет хранить объект класса
Company. Члены класса Company объявляются с модификатором public, кроме того
также присутствует стандартный конструктор без параметров. В итоге после
сериализации мы получим следующий xml-документ:

1 <?xml version="1.0"?>
<ArrayOfPerson xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
3
  <Person>
4
    <Name>Tom</Name>
5
    <Age>29</Age>
6
    <Company>
7
      <Name>Microsoft</Name>
8
    </Company>
9
  </Person>
10   <Person>
11     <Name>Bill</Name>
12     <Age>25</Age>
13     <Company>
14       <Name>Apple</Name>
15     </Company>
16   </Person>
17 </ArrayOfPerson>

Сериализация в JSON.
DataContractJsonSerializer
Для сериализации объектов в формат JSON в пространстве
System.Runtime.Serialization.Json определен классDataContractJsonSerializer. Чтобы
задействовать этот класс, в проект надо добавить
сборкуSystem.Runtime.Serialization.dll. Для записи объектов в json-файл в этом классе
имеется метод WriteObject(), а для чтения ранее сериализованных объектов -
метод ReadObject(). Рассмотрим их применение.

1 using System;
2 using System.IO;
3 using System.Runtime.Serialization.Json;
4 using System.Runtime.Serialization;
5
namespace Serialization
10
6 {
7     [DataContract]
8     public class Person
9     {
10         [DataMember]
11         public string Name { get; set; }
12         [DataMember]
13         public int Age { get; set; }
14
15         public Person(string name, int year)
16         {
17             Name = name;
18             Age = year;
19         }
20     }
21     class Program
22     {
23         static void Main(string[] args)
24         {
25             // объект для сериализации
26             Person person1 = new Person("Tom", 29);
27             Person person2 = new Person("Bill", 25);
28             Person[] people = new Person[] { person1, person2 };
29
30             DataContractJsonSerializer jsonFormatter = new
31 DataContractJsonSerializer(typeof(Person[]));
32
33             using (FileStream fs = new FileStream("people.json", FileMode.OpenOrCr
34             {
35                 jsonFormatter.WriteObject(fs, people);
36             }
37
38             using (FileStream fs = new FileStream("people.json", FileMode.OpenOrCr
39             {
40                 Person[] newpeople = (Person[])jsonFormatter.ReadObject(fs);
41
42                 foreach (Person p in newpeople)
43                 {
44                     Console.WriteLine("Имя: {0} --- Возраст: {1}", p.Name, p.Age)
45                 }
            }
46
47
            Console.ReadLine();
48
        }
49
    }
50
}
51

Чтобы пометить класс как сериализуемый, к нему применяется атрибут DataContract, а


ко всем его сериализуемым свойствам - атрибут DataMember.

Метод WriteObject() принимает два параметра: файловый поток FileStream и объект,


который надо сериализовать - в данном случае массив объектов Person. А
метод ReadObject() принимает в качестве параметра файловый поток, который
представляет файл в формате json.

11
Если мы откроем файл people.json, то увидим содержание нашего объекта:

1 [{
2     "Age":29,"Name":"Tom"
3 },{
4     "Age":25,"Name":"Bill"
5 }]

Подобным образом можно сериализовать/десериализовать более сложные объекты:

1 using System;
2 using System.IO;
3 using System.Runtime.Serialization.Json;
4 using System.Runtime.Serialization;
5
6 namespace Serialization
7 {
8     [DataContract]
9     public class Person
10     {
11         [DataMember]
12         public string Name { get; set; }
13         [DataMember]
14         public int Age { get; set; }
15         [DataMember]
16         public Company Company { get; set; }
17
        public Person()
18
        { }
19
20
        public Person(string name, int age, Company comp)
21
        {
22
            Name = name;
23
            Age = age;
24
            Company = comp;
25
        }
26
    }
27
28     public class Company
29     {
30         public string Name { get; set; }
31
32         public Company() { }
33
34         public Company(string name)
35         {
36             Name = name;
37         }
38     }
39     class Program
40     {
41         static void Main(string[] args)
42         {
43             Person person1 = new Person("Tom", 29, new Company("Microsoft"));
44             Person person2 = new Person("Bill", 25, new Company("Apple"));
45             Person[] people = new Person[] { person1, person2 };
46
12
            DataContractJsonSerializer jsonFormatter = new
47 DataContractJsonSerializer(typeof(Person[]));
48
            using (FileStream fs = new FileStream("people.json",
49
FileMode.OpenOrCreate))
50
            {
51
                jsonFormatter.WriteObject(fs, people);
52
            }
53
54
            using (FileStream fs = new FileStream("people.json",
55
FileMode.OpenOrCreate))
56
            {
57
                Person[] newpeople = (Person[])jsonFormatter.ReadObject(fs);
58
59                 foreach (Person p in newpeople)
60                 {
61                     Console.WriteLine("Имя: {0} --- Возраст: {1} ---
62 Компания: {2}", p.Name, p.Age, p.Company.Name);
63                 }
64             }
65             Console.ReadLine();
66         }
67     }
}

В итоге программа создаст следующий json-файл:

1 [{
2     "Age":29,
3     "Company":{"Name":"Microsoft"},
4     "Name":"Tom"
5 },{
6     "Age":25,
7     "Company":{"Name":"Apple"},
8     "Name":"Bill"
9 }]

13

Вам также может понравиться