1. Введение
Мы уже привыкли создавать объекты в наших программах. Помните, мы вот-вот начнём изучать классы и объекты глубже, но уже сейчас мы понимаем, что переменные, списки, даже простые строки – это не просто так, это уже некие "сущности" в нашей программе. Например, мы можем создать переменную int age = 30; или string name = "Вася";. Но что, если нам нужно сохранить в файл информацию о целом пользователе, у которого есть имя, возраст, адрес, список любимых книг и многое другое?
Представьте: вы пишете игру. У вас есть объект Player с кучей характеристик: здоровье, уровень, инвентарь (список предметов), координаты на карте и так далее. Игрок играет, прокачивается, находит крутые артефакты. И тут он решает выйти из игры. Что произойдёт? Все данные о его приключениях, которые хранились в памяти, исчезнут! Печаль! Чтобы этого не произошло, нам нужно сохранить состояние объекта Player в файл, а когда игрок вернётся, загрузить его обратно.
И вот тут на сцену выходит сериализация. Она как раз и занимается тем, что строит мост между "живыми" объектами в памяти и "мёртвыми", но стойкими данными на диске.
2. Процесс сериализации (от объекта к файлу)
Давайте разберем, как происходит эта "магия" превращения объекта в байты, которые можно записать в файл.
Представьте, что у нас есть такой класс Book (Книга):
// Это наш "чертеж" или "план" для создания объектов-книг
public class Book
{
// Свойства книги
public string Title { get; set; } // Название книги
public string Author { get; set; } // Автор
public int Year { get; set; } // Год издания
// Конструктор - специальный метод для создания новых объектов Book
public Book(string title, string author, int year)
{
Title = title;
Author = author;
Year = year;
}
// Метод для удобного вывода информации о книге (пока не обязателен для сериализации, но полезен)
public void DisplayInfo()
{
Console.WriteLine($"Название: {Title}, Автор: {Author}, Год: {Year}");
}
}
Шаг 1: Создание объекта для сериализации.
Сначала, конечно, нам нужен объект, который мы хотим сохранить. Например, мы создали экземпляр Book:
Book myFavoriteBook = new Book("Автостопом по Галактике", "Дуглас Адамс", 1979);
Этот объект myFavoriteBook сейчас находится в оперативной памяти.
Шаг 2: Выбор инструмента (сериализатора).
Мы не можем просто взять и "скопировать" объект на диск. Компьютер не понимает объекты напрямую в файлах — ему нужны байты. Нужен специальный инструмент — сериализатор. Его задача — разобрать наш объект на составные части (его свойства: Title, Author, Year) и превратить эти части в последовательность байтов или в текстовую строку (например, в JSON или XML).
Мы не углубляемся сегодня в конкретные реализации — просто держим в голове, что это специальная "коробка-преобразователь".
Шаг 3: Преобразование объекта в поток данных.
Сериализатор получает наш объект myFavoriteBook, смотрит на его свойства (Title, Author, Year) и преобразует каждое из них в формат, который можно записать. Все эти байты (или текстовые символы) собираются в единый поток данных — длинную "ленту" информации.
Шаг 4: Запись потока в файл.
Теперь, когда у нас есть эта "лента байтов", мы используем наши старые знакомые FileStream и, возможно, StreamWriter (если выбран текстовый формат, такой как JSON) или просто FileStream (для чисто бинарных данных), чтобы записать этот поток на диск.
3. Процесс десериализации (от файла к объекту)
Шаг 1: Чтение потока данных из файла.
Мы снова используем FileStream и при необходимости StreamReader (если это текстовый формат), чтобы прочитать содержимое файла. Данные приходят в виде "ленты" байтов или текста.
Шаг 2: Выбор инструмента (десериализатора).
Нужен обратный инструмент — десериализатор. Он должен знать, как интерпретировать полученные байты/текст и правильно воссоздать структуру объекта. Очень важно: для десериализации используют тот же тип сериализатора (и обычно ту же библиотеку), что и для сериализации, иначе ваш "конструктор" не поймёт инструкцию по сборке.
Шаг 3: Преобразование потока обратно в объект.
Десериализатор читает данные, понимает, где находится Title, затем Author, потом Year, и на основе этого создает новый объект Book в памяти, заполняя его свойства.
Шаг 4: Получение готового объекта.
Вуаля! У нас снова есть полноценный объект Book в оперативной памяти, с которым можно работать.
Пример: Сохраняем нашу "Супер-Книгу" «вручную» (для понимания концепции)
Пока не будем использовать специализированные библиотеки: сделаем простейшую «ручную» сериализацию и десериализацию с помощью StreamWriter и StreamReader. Это поможет понять принцип.
Наш объект Book:
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
public int Year { get; set; }
public Book(string title, string author, int year)
{
Title = title;
Author = author;
Year = year;
}
public void DisplayInfo()
{
Console.WriteLine($"Название: \"{Title}\", Автор: {Author}, Год: {Year}");
}
}
Ручная сериализация: метод SaveBookToTextFile
Создадим метод, который сохранит свойства книги в текстовый файл, по одному свойству на строку.
void SaveBookToTextFile(Book book, string filePath)
{
using var writer = new StreamWriter(filePath);
writer.WriteLine(book.Title);
writer.WriteLine(book.Author);
writer.WriteLine(book.Year);
}
Что происходит? Открываем StreamWriter и последовательно записываем Title, Author, Year — это наша простейшая схема сериализации.
Если запустить код, содержимое файла будет таким:
Автостопом по Галактике
Дуглас Адамс
1979
Ручная десериализация: метод LoadBookFromTextFile
Напишем метод, который прочитает данные и соберёт новый объект Book.
Book LoadBookFromTextFile(string filePath)
{
using var reader = new StreamReader(filePath);
string title = reader.ReadLine();
string author = reader.ReadLine();
int year = int.Parse(reader.ReadLine());
return new Book(title, author, year);
}
И используем эти методы в Main:
//создаем объект
var myBook = new Book("Автостопом по Галактике", "Дуглас Адамс", 1979);
string filePath = "my_favorite_book.txt";
//сохраняем его в файл
SaveBookToTextFile(myBook, filePath);
//читаем из файла
Book loadedBook = LoadBookFromTextFile(filePath);
Что происходит в LoadBookFromTextFile? Мы открываем StreamReader и в том же порядке читаем строки: сначала заголовок, затем автора, затем год и преобразуем его через int.Parse. После этого создаём новый экземпляр Book.
На практике стоит добавлять проверки (File.Exists) и обработку ошибок через try-catch, но здесь мы фокусируемся на самой идее.
Почему «ручная» сериализация — это плохо (и почему нужны библиотеки)?
- Много ручного кода. Каждое свойство нужно записать и затем прочитать. Если объектов и полей много — код разрастается.
- Хрупкость к изменениям. Добавили новое свойство Pages (int)? Придётся менять и запись, и чтение, и строго следить за порядком.
- Сложные структуры. Вложенные коллекции и объекты (например, List<Chapter>) превратят код в «спагетти».
- Форматы и эффективность. Текстовый формат прост, но не всегда компактен и безопасен; для бинарных данных придётся вручную работать с байтами, BinaryWriter/BinaryReader и т.п.
- Нет метаданных. Наш файл не «знает», что первая строка — это Title, а третья — Year. Специализированные сериализаторы могут сохранять метаданные и быть устойчивее к изменениям моделей.
Именно поэтому в реальных проектах используют готовые библиотеки-сериализаторы, которые умеют автоматически разбирать/собирать объекты, работать с JSON, XML и бинарными форматами и устойчиво переносить изменения моделей. Об этом — в следующей лекции!
Практическое применение: зачем нужна сериализация?
- Сохранение и загрузка данных. Настройки, состояния игр, конфигурации.
- Передача данных по сети. Обмен сложными объектами между сервисами (часто в JSON).
- Кэширование. Быстрое повторное использование ранее полученных данных.
- Логирование сложных объектов. Удобно для отладки и аудита.
- Глубокое копирование объектов. Сериализация + десериализация как способ клонирования графа объектов.
Сериализация — один из краеугольных камней современного ПО: от сохранения прогресса в игре до работы с веб‑сервисами — она везде!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ