Сериализация объектов

42

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

Во многих случаях сохранение данных приложения с использованием служб сериализации выливается в код меньшего объема, чем применение классов для чтения/записи из пространства имен System.IO.

Например, предположим, что создано настольное приложение с графическим интерфейсом, в котором необходимо предоставить конечным пользователям возможность сохранения их предпочтений (цвета окон, размер шрифта и т.п.). Для этого можно определить класс по имени UserPrefs и инкапсулировать в нем примерно два десятка полей данных. В случае применения типа System.IO.BinaryWriter придется вручную сохранять каждое поле объекта UserPrefs.

Аналогично, когда вам понадобится загрузить данные из файла обратно в память, придется использовать SystemIO.BinaryReader и, опять-таки, вручную читать каждое значение, чтобы реконструировать новый объект UserPrefs.

Сэкономить значительное время можно, снабдив класс UserPrefs атрибутом [Serializable]:

[Serializable]
public class UserPrefs
{
   public string WindowColor;
   public int FontSize;
}

После этого полное состояние объекта может быть сохранено с помощью всего нескольких строк кода. Пока не погружаясь в детали, взгляните на следующий метод Main():


static void Main(string[] args)
{
   UserPrefs userData = new UserPrefs();
   userData.WindowColor = "Yellow";
   userData.FontSize = 50;
   
   // BinaryFormatter сохраняет данные в двоичном формате. Чтобы получить доступ к BinaryFormatter, понадобится
   // импортировать System.Runtime.Serialization.Formatters.Binary
   BinaryFormatter binFormat = new BinaryFormatter();
   // Сохранить объект в локальном файле.
   using(Stream fStream = new FileStream("user.dat",
      FileMode.Create, FileAccess.Write, FileShare.None))
   {
      binFormat.Serialize(fStream, userData);
   }
}

Хотя сохранять объекты с помощью механизма сериализации объектов .NET довольно просто, процесс, происходящий при этом "за кулисами", достаточно сложен. Например, когда объект сохраняется в потоке, все ассоциированные с ним данные (т.е. данные базового класса и содержащиеся в нем объекты) также автоматически сериализуются. Поэтому, при попытке сериализовать производный класс в игру вступают также все данные по цепочке наследования. И как будет показано, набор взаимосвязанных объектов, участвующих в этом, представляется графом объектов.

Службы сериализации .NET также позволяют сохранять граф объектов в различных форматах. В предыдущем примере кода применялся тип BinaryFormatter, поэтому состояние объекта UserPrefs сохраняется в компактном двоичном формате. Граф объектов можно также сохранить в формате SOAP или XML, используя другие типы форматеров. Эти форматы полезны, когда необходимо гарантировать возможность передачи хранимых объектов между разными операционными системами, языками и архитектурами.

В WCF предлагается слегка отличающийся механизм для сериализации объектов в и из операций службы WCF в нем используются атрибуты [DataContract] и [DataMember].

И, наконец, имейте в виду, что граф объектов может быть сохранен в любом типе, унаследованном от System.IO.Stream. В предыдущем примере объект UserPrefs был сохранен в локальном файле через тип FileStream. Однако если вместо этого понадобится сохранить объект в определенной области памяти, можно применить тип MemoryStream. Главное, чтобы последовательность данных корректно представляла состояние объектов в графе.

Роль графов объектов

Как упоминалось ранее, когда объект сериализуется, среда CLR учитывает все связанные объекты, чтобы гарантировать корректное сохранение данных. Этот набор связанных объектов называется графом объектов. Графы объектов представляют простой способ документирования набора отношений между объектами, и эти отношения не обязательно отображаются на классические отношения ООП (вроде отношений "является" и "имеет"), хотя достаточно хорошо моделируют эту парадигму.

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

В качестве простого примера предположим, что создан набор классов, моделирующих автомобили. Существует базовый класс по имени Car, который "имеет" класс Radio. Другой класс по имени JamesBondCar расширяет базовый тип Car. На рисунке показан возможный граф объектов, который моделирует эти отношения:

Граф объектов

При чтении графов объектов для описания соединяющих стрелок можно использовать выражение "зависит от" или "ссылается на". Таким образом, на рисунке видно, что класс Car ссылается на класс Radio (учитывая отношение "имеет"), JamesBondCar ссылается на Car (учитывая отношение "имеет"), как и на Radio (поскольку наследует эту защищенную переменную-член).

Конечно, среда CLR не рисует картинок в памяти для представления графа взаимосвязанных объектов. Вместо этого отношение, документированное в предыдущей диаграмме, представлено математической формулой, которая выглядит примерно так:

[Car 3, ref 2], [Radio 2], [JamesBondCar 1, ref 3, ref 2]

Если вы проанализируете эту формулу, то опять увидите, что объект 3 (Car) имеет зависимость от объекта 2 (Radio). Объект 2 (Radio) — это "одинокий волк", которому не нужен никто. И, наконец, объект 1 (JamesBondCar) имеет зависимость как от объекта 3, так и от объекта 2. В любом случае, при сериализации или десериализации JamesBondCar граф объектов гарантирует, что типы Radio и Car также будут участвовать в процессе.

Изящество процесса сериализации состоит в том, что граф, представляющий отношения между объектами, устанавливается автоматически, "за кулисами". Как будет показано далее, если необходимо вмешаться в конструирование графа объектов, это можно сделать посредством настройки процесса сериализации через атрибуты и интерфейсы.

Пройди тесты
Лучший чат для C# программистов