1. Введение
Во многих старых учебниках и примерах по C# можно встретить сериализацию с помощью класса BinaryFormatter. Когда-то это был стандарт: удобно, быстро, забот меньше. Пример из старых времён выглядел примерно так:
// Опасный старый код! Не делайте так в новых проектах!
var bf = new BinaryFormatter();
using (var stream = File.Open("player.dat", FileMode.Create))
{
bf.Serialize(stream, playerObject);
}
Но в современном .NET 9 вы уже не сможете даже скомпилировать такой код: BinaryFormatter удалён из платформы.
Путь от героя к изгою
Изначально BinaryFormatter задумывался как универсальный механизм сериализации и десериализации .NET‑объектов, умеющий сохранять сложные графы объектов «как есть». Со временем вскрылись серьёзные проблемы безопасности и совместимости, и теперь он остался в прошлом — примерно как кассетные магнитофоны в эпоху стриминга.
2. Главные причины отказа от BinaryFormatter
Гигантская дыра в безопасности
BinaryFormatter умеет не только сериализовать данные, но и восстанавливать типы из входного потока. Если бездумно десериализовать непроверенные данные (например, полученные из сети), злоумышленник может внедрить полезную нагрузку, приводящую к удалённому выполнению кода (RCE). Именно поэтому Microsoft много лет подряд предупреждала: «Не используйте BinaryFormatter». Начиная с .NET 5 он был помечен как устаревший и опасный ([Obsolete]), а в .NET 9 — удалён.
Зависимость от внутренней структуры классов
Формат BinaryFormatter тянет за собой детали реализации типов: поля, упаковку, версии. Малейшие изменения в классах (добавили/удалили поле) ломают совместимость с уже сохранёнными файлами. Это делает такой формат непригодным для долгого хранения и межверсионного обмена.
Сложности в кроссплатформенности
Данные, сохранённые BinaryFormatter, привязаны к внутреннему представлению объектов .NET. Прочитать их на другой платформе/языке или даже иной версии .NET часто невозможно.
3. Как обнаружить «старый код» и что делать?
Признаки использования BinaryFormatter выглядят так:
using System.Runtime.Serialization.Formatters.Binary; // <-- подозрительно!
BinaryFormatter bf = new BinaryFormatter(); // <-- опасно!
bf.Serialize(...);
bf.Deserialize(...);
В современных версиях .NET этот код не компилируется: инструменты выдадут ошибку "Тип или имя не найдено". В легаси‑проектах на .NET Framework — как минимум строгие предупреждения.
Если вы столкнулись с таким кодом, заменяйте BinaryFormatter на современные, безопасные альтернативы.
Какие форматы и классы использовать сегодня?
Для JSON
- System.Text.Json — быстрый, безопасный, встроенный в .NET. Официальная документация
- Newtonsoft.Json — популярная сторонняя библиотека для сложных кейсов.
Для XML
- XmlSerializer — сериализация в XML. Документация
Для бинарных форматов
- System.Formats.Cbor (CBOR)
- Protobuf.Net (Protocol Buffers), MessagePack for C#
Для специфичных задач подбирайте специализированные форматы и библиотеки — «универсального» бинарного сериализатора в стиле BinaryFormatter больше нет, и это хорошо.
4. Переписываем код с BinaryFormatter
Для чего нельзя использовать BinaryFormatter
Нельзя:
- Сохранять пользовательские настройки и важные данные.
- Передавать данные по сети (клиент ↔ сервер).
- Десериализовать данные из непроверенных источников.
Вместо этого:
- Для повседневных нужд — System.Text.Json.
- Для конфигураций — JSON, XML и т. п.
- Для межъязыкового обмена — форматы с кроссплатформенной совместимостью (JSON, XML, Protobuf, MessagePack).
Какой код теперь писать?
Сериализация в JSON (рекомендуемый способ)
using System.Text.Json;
// Ваш класс:
public class Player
{
public string Name { get; set; } = "";
public int Health { get; set; }
public bool IsAlive { get; set; }
}
// Объект для сериализации
var player = new Player { Name = "Aragorn", Health = 100, IsAlive = true };
// Преобразуем объект в строку JSON
string json = JsonSerializer.Serialize(player);
// Сохраняем в файл — безопасно!
File.WriteAllText("player.json", json);
// Для восстановления объекта из файла:
string loadedJson = File.ReadAllText("player.json");
Player loadedPlayer = JsonSerializer.Deserialize<Player>(loadedJson)!;
Сериализация в XML (если нужен строгий формат)
using System.Xml.Serialization;
// Класс должен иметь публичный конструктор без параметров
var player = new Player { Name = "Aragorn", Health = 100, IsAlive = true };
var serializer = new XmlSerializer(typeof(Player));
using (var fs = File.Create("player.xml"))
{
serializer.Serialize(fs, player);
}
Визуальная схема «старое vs новое»
| Задача | Старый способ (BinaryFormatter) | Современный способ |
|---|---|---|
| Сериализация в файл | |
|
| Десериализация из файла | |
|
| Безопасность | Уязвим для атак (RCE) | Безопасные парсеры и строгие модели |
| Кроссплатформенность | Нет | Да (JSON/XML/Protobuf) |
| Скорость | Быстро, но опасно | Быстро и безопасно |
5. Типичные ошибки при работе с XmlSerializer
Ошибка №1: приватные свойства или поля. XmlSerializer сериализует только public свойства; даже protected приведёт к ошибке.
Ошибка №2: отсутствие конструктора по умолчанию. Нужен публичный конструктор без параметров, иначе сериализация/десериализация не сработает.
Ошибка №3: несоответствие между типами данных. XmlSerializer плохо работает с Dictionary, interface и delegate — используйте обёртки или другие форматы.
Ошибка №4: циклические ссылки (circular reference). XmlSerializer не поддерживает графы вида A → B → A; для таких случаев чаще берут JSON с подходящими настройками.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ