CodeGym /Kursy /C# SELF /Ciag działań i przykłady: serializacja obiektów

Ciag działań i przykłady: serializacja obiektów

C# SELF
Poziom 43 , Lekcja 1
Dostępny

1. Wprowadzenie

Już jesteśmy przyzwyczajeni do tworzenia obiektów w naszych programach. Pamiętasz, zaraz zaczniemy zgłębiać klasy i obiekty bardziej szczegółowo, ale już teraz rozumiemy, że zmienne, listy, nawet proste stringi – to nie jest nic przypadkowego, to pewne "byty" w naszym programie. Na przykład możemy stworzyć zmienną int age = 30; albo string name = "Wasia";. Ale co jeśli trzeba zapisać do pliku informacje o całym użytkowniku, który ma imię, wiek, adres, listę ulubionych książek i wiele więcej?

Wyobraź sobie: piszesz grę. Masz obiekt Player z masą cech: zdrowie, poziom, inwentarz (lista przedmiotów), współrzędne na mapie i tak dalej. Gracz gra, rozwija się, znajduje fajne artefakty. I nagle postanawia wyjść z gry. Co się stanie? Wszystkie dane o jego przygodach, które były w pamięci, znikną! Smutek! Żeby do tego nie dopuścić, trzeba zapisać stan obiektu Player do pliku, a kiedy gracz wróci, załadować go z powrotem.

I tu wchodzi na scenę serializacja. Ona zajmuje się właśnie budowaniem mostu między "żywymi" obiektami w pamięci a "martwymi", ale trwałymi danymi na dysku.

2. Proces serializacji (od obiektu do pliku)

Rozłóżmy na czynniki pierwsze, jak przebiega ta "magia" przemiany obiektu w bajty, które można zapisać do pliku.

Wyobraź sobie, że mamy taką klasę Book (Książka):


// To nasz "szkic" albo "plan" do tworzenia obiektów-książek
public class Book
{
    // Właściwości książki
    public string Title { get; set; } // Tytuł książki
    public string Author { get; set; } // Autor
    public int Year { get; set; } // Rok wydania

    // Konstruktor - specjalna metoda do tworzenia nowych obiektów Book
    public Book(string title, string author, int year)
    {
        Title = title;
        Author = author;
        Year = year;
    }

    // Metoda do wygodnego wyświetlania informacji o książce (na razie niekonieczna do serializacji, ale przydatna)
    public void DisplayInfo()
    {
        Console.WriteLine($"Tytuł: {Title}, Autor: {Author}, Rok: {Year}");
    }
}

Krok 1: Utworzenie obiektu do serializacji.

Najpierw oczywiście potrzebujemy obiektu, który chcemy zapisać. Na przykład utworzyliśmy egzemplarz Book:

Book myFavoriteBook = new Book("Autostopem po Galaktyce", "Douglas Adams", 1979);

Ten obiekt myFavoriteBook teraz znajduje się w pamięci operacyjnej.

Krok 2: Wybór narzędzia (serializatora).

Nie możemy po prostu wziąć i "skopiować" obiektu na dysk. Komputer nie rozumie obiektów bezpośrednio w plikach — potrzebne są bajty. Potrzebne jest specjalne narzędzie — serializator. Jego zadanie to rozebrać nasz obiekt na części składowe (jego właściwości: Title, Author, Year) i zamienić te części w sekwencję bajtów lub w tekst (na przykład JSON albo XML).

Dziś nie wchodzimy głęboko w konkretne implementacje — traktuj to jak specjalne "pudełko-przekształcacz".

Krok 3: Przekształcenie obiektu w strumień danych.

Serializator dostaje nasz obiekt myFavoriteBook, patrzy na jego właściwości (Title, Author, Year) i zamienia każdą z nich na format, który można zapisać. Wszystkie te bajty (lub znaki tekstowe) są składane w jeden strumień danych — długą "taśmę" informacji.

Krok 4: Zapis strumienia do pliku.

Teraz, kiedy mamy tę "taśmę bajtów", używamy naszych starych znajomych FileStream i ewentualnie StreamWriter (jeśli wybrano format tekstowy, np. JSON) albo po prostu FileStream (dla czysto binarnych danych), żeby zapisać ten strumień na dysk.

3. Proces deserializacji (od pliku do obiektu)

Krok 1: Odczyt strumienia danych z pliku.

Znów używamy FileStream i w razie potrzeby StreamReader (jeśli to format tekstowy), żeby przeczytać zawartość pliku. Dane przychodzą jako "taśma" bajtów albo tekstu.

Krok 2: Wybór narzędzia (deserializatora).

Potrzebne jest narzędzie odwrotne — deserializator. Musi wiedzieć, jak interpretować otrzymane bajty/tekst i poprawnie odtworzyć strukturę obiektu. Bardzo ważne: do deserializacji używa się zwykle tego samego typu serializatora (i zazwyczaj tej samej biblioteki), co do serializacji, inaczej twój "konstruktor" nie zrozumie instrukcji składania.

Krok 3: Przekształcenie strumienia z powrotem w obiekt.

Deserializator czyta dane, rozumie, gdzie jest Title, potem Author, później Year, i na tej podstawie tworzy nowy obiekt Book w pamięci, wypełniając jego właściwości.

Krok 4: Otrzymanie gotowego obiektu.

Wuala! Mamy z powrotem pełnoprawny obiekt Book w pamięci operacyjnej, z którym można pracować.

Przykład: Zapisujemy naszą "Super-Książkę" „ręcznie” (dla zrozumienia koncepcji)

Na razie nie będziemy używać wyspecjalizowanych bibliotek: zrobimy najprostsze „ręczne” serializowanie i deserializowanie za pomocą StreamWriter i StreamReader. To pomoże zrozumieć zasadę.

Nasz obiekt 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($"Tytuł: \"{Title}\", Autor: {Author}, Rok: {Year}");
    }
}

Ręczna serializacja: metoda SaveBookToTextFile

Stwórzmy metodę, która zapisze właściwości książki do pliku tekstowego, po jednej właściwości na linię.

void SaveBookToTextFile(Book book, string filePath)
{
    using var writer = new StreamWriter(filePath);
    writer.WriteLine(book.Title);
    writer.WriteLine(book.Author);
    writer.WriteLine(book.Year);
}

Co się dzieje? Otwieramy StreamWriter i kolejno zapisujemy Title, Author, Year — to nasz najprostszy schemat serializacji.

Jeśli uruchomisz kod, zawartość pliku będzie taka:


Autostopem po Galaktyce
Douglas Adams
1979

Ręczna deserializacja: metoda LoadBookFromTextFile

Napiszmy metodę, która odczyta dane i złoży nowy obiekt 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);
}

I używamy tych metod w Main:

//tworzymy obiekt
var myBook = new Book("Autostopem po Galaktyce", "Douglas Adams", 1979);
string filePath = "my_favorite_book.txt";

//zapisujemy go do pliku
SaveBookToTextFile(myBook, filePath);

//czytamy z pliku
Book loadedBook = LoadBookFromTextFile(filePath);

Co się dzieje w LoadBookFromTextFile? Otwieramy StreamReader i w tej samej kolejności czytamy linie: najpierw tytuł, potem autora, potem rok i konwertujemy go przez int.Parse. Potem tworzymy nową instancję Book.

W praktyce warto dodawać sprawdzenia (File.Exists) i obsługę błędów przez try-catch, ale tu skupiamy się na samej idei.

Dlaczego „ręczna” serializacja jest zła (i po co biblioteki)?

  • Dużo ręcznego kodu. Każda właściwość musi być zapisana i potem odczytana. Jeśli obiektów i pól jest dużo — kod się rozrasta.
  • Wrażliwość na zmiany. Dodano nowe pole Pages (int)? Trzeba zmieniać zapis i odczyt i pilnować porządku.
  • Złożone struktury. Zagnieżdżone kolekcje i obiekty (np. List<Chapter>) zamienią kod w "spaghetti".
  • Formaty i wydajność. Format tekstowy jest prosty, ale nie zawsze kompaktowy i bezpieczny; dla binarnych danych trzeba ręcznie pracować z bajtami, BinaryWriter/BinaryReader itd.
  • Brak metadanych. Nasz plik nie "wie", że pierwsza linia to Title, a trzecia to Year. Specjalistyczne serializatory mogą zapisywać metadane i być bardziej odporne na zmiany modeli.

Dlatego w realnych projektach używa się gotowych bibliotek-serializatorów, które potrafią automatycznie rozbierać/składać obiekty, pracować z JSON, XML i formatami binarnymi oraz bezpiecznie przenosić zmiany modeli. O tym — w następnej lekcji!

Praktyczne zastosowanie: po co serializacja?

  • Zapisywanie i ładowanie danych. Ustawienia, stany gier, konfiguracje.
  • Przesyłanie danych po sieci. Wymiana złożonych obiektów między serwisami (często w JSON).
  • Cache'owanie. Szybkie ponowne wykorzystanie wcześniej pobranych danych.
  • Logowanie złożonych obiektów. Przydatne do debugowania i audytu.
  • Głębokie kopiowanie obiektów. Serializacja + deserializacja jako sposób klonowania grafu obiektów.

Serializacja — jeden z fundamentów współczesnego oprogramowania: od zapisu postępu w grze po pracę z web‑service'ami — jest wszędzie!

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION