1. Wprowadzenie
W wielu starych podręcznikach i przykładach po C# można spotkać serializację za pomocą klasy BinaryFormatter. Kiedyś to był standard: wygodnie, szybko, mniej zmartwień. Przykład ze starych czasów wyglądał mniej więcej tak:
// Niebezpieczny stary kod! Nie rób tak w nowych projektach!
var bf = new BinaryFormatter();
using (var stream = File.Open("player.dat", FileMode.Create))
{
bf.Serialize(stream, playerObject);
}
Ale w nowoczesnym .NET 9 nie będziesz już nawet w stanie skompilować takiego kodu: BinaryFormatter został usunięty z platformy.
Droga od bohatera do wykluczonego
Początkowo BinaryFormatter był pomyślany jako uniwersalny mechanizm serializacji i deserializacji obiektów .NET, potrafiący zapisywać skomplikowane grafy obiektów „tak jak są”. Z czasem wyszły na jaw poważne problemy z bezpieczeństwem i kompatybilnością, i teraz pozostał w przeszłości — mniej więcej jak magnetofony kasetowe w erze streamingu.
2. Główne powody rezygnacji z BinaryFormatter
Ogromna dziura w bezpieczeństwie
BinaryFormatter potrafi nie tylko serializować dane, ale i odtwarzać typy z wejściowego strumienia. Jeśli bezmyślnie deserializujesz niezaufane dane (np. otrzymane z sieci), atakujący może wstrzyknąć ładunek, prowadzący do zdalnego wykonania kodu (RCE). Dlatego Microsoft przez wiele lat ostrzegał: „Nie używaj BinaryFormatter”. Od .NET 5 był oznaczony jako przestarzały i niebezpieczny ([Obsolete]), a w .NET 9 — usunięty.
Zależność od wewnętrznej struktury klas
Format BinaryFormatter przenosił szczegóły implementacji typów: pola, pakowanie, wersje. Najmniejsza zmiana w klasach (dodanie/usunięcie pola) łamała kompatybilność z już zapisanymi plikami. To czyniło ten format nieodpowiednim do długotrwałego przechowywania i wymiany między wersjami.
Trudności z cross‑platform
Dane zapisane przez BinaryFormatter są powiązane z wewnętrzną reprezentacją obiektów .NET. Odczytanie ich na innej platformie/języku albo nawet innej wersji .NET często jest niemożliwe.
3. Jak wykryć „stary kod” i co robić?
Znaki użycia BinaryFormatter wyglądają tak:
using System.Runtime.Serialization.Formatters.Binary; // <-- podejrzane!
BinaryFormatter bf = new BinaryFormatter(); // <-- niebezpieczne!
bf.Serialize(...);
bf.Deserialize(...);
W nowoczesnych wersjach .NET taki kod się nie skompiluje: narzędzia zwrócą błąd "Typ lub nazwa nie znaleziona". W legacy‑projektach na .NET Framework — przynajmniej mocne ostrzeżenia.
Jeśli natrafisz na taki kod, zamień BinaryFormatter na współczesne, bezpieczne alternatywy.
Jakie formaty i klasy używać dziś?
Dla JSON
- System.Text.Json — szybki, bezpieczny, wbudowany w .NET. Oficjalna dokumentacja
- Newtonsoft.Json — popularna biblioteka zewnętrzna dla bardziej złożonych przypadków.
Dla XML
- XmlSerializer — serializacja do XML. Dokumentacja
Dla formatów binarnych
- System.Formats.Cbor (CBOR)
- Protobuf.Net (Protocol Buffers), MessagePack for C#
Dla specyficznych zadań dobieraj specjalizowane formaty i biblioteki — „uniwersalnego” binarnego serializatora w stylu BinaryFormatter już nie ma, i dobrze.
4. Przepisywanie kodu z BinaryFormatter
Do czego nie używać BinaryFormatter
Nie wolno:
- Zapisywać ustawień użytkownika i ważnych danych.
- Przesyłać danych po sieci (klient ↔ server).
- Deserializować danych z niezaufanych źródeł.
Zamiast tego:
- Do codziennych potrzeb — System.Text.Json.
- Dla konfiguracji — JSON, XML itd.
- Dla wymiany między językami — formaty z cross‑platform kompatybilnością (JSON, XML, Protobuf, MessagePack).
Jaki kod teraz pisać?
Serializacja do JSON (zalecany sposób)
using System.Text.Json;
// Twój klasa:
public class Player
{
public string Name { get; set; } = "";
public int Health { get; set; }
public bool IsAlive { get; set; }
}
// Obiekt do serializacji
var player = new Player { Name = "Aragorn", Health = 100, IsAlive = true };
// Zamieniamy obiekt na string JSON
string json = JsonSerializer.Serialize(player);
// Zapis do pliku — bezpiecznie!
File.WriteAllText("player.json", json);
// Do odtworzenia obiektu z pliku:
string loadedJson = File.ReadAllText("player.json");
Player loadedPlayer = JsonSerializer.Deserialize<Player>(loadedJson)!;
Serializacja do XML (jeśli potrzebny ścisły format)
using System.Xml.Serialization;
// Klasa musi mieć publiczny konstruktor bez parametrów
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);
}
Wizualny schemat „stare vs nowe”
| Zadanie | Stary sposób (BinaryFormatter) | Współczesny sposób |
|---|---|---|
| Serializacja do pliku | |
|
| Deserializacja z pliku | |
|
| Bezpieczeństwo | Wrażliwe na ataki (RCE) | Bezpieczne parsery i ścisłe modele |
| Cross‑platform | Nie | Tak (JSON/XML/Protobuf) |
| Prędkość | Szybko, ale niebezpiecznie | Szybko i bezpiecznie |
5. Typowe błędy przy pracy z XmlSerializer
Błąd nr 1: prywatne właściwości lub pola. XmlSerializer serializuje tylko public właściwości; nawet protected spowoduje błąd.
Błąd nr 2: brak konstruktora domyślnego. Potrzebny publiczny konstruktor bez parametrów, inaczej serializacja/deserializacja nie zadziała.
Błąd nr 3: niezgodność typów danych. XmlSerializer słabo radzi sobie z Dictionary, interface i delegate — użyj wrapperów lub innych formatów.
Błąd nr 4: cykliczne odniesienia (circular reference). XmlSerializer nie obsługuje grafów typu A → B → A; dla takich przypadków częściej używa się JSON z odpowiednimi ustawieniami.
GO TO FULL VERSION