1. Giới thiệu
Chúng ta đã quen với việc tạo đối tượng trong chương trình. Nhớ là sắp tới chúng ta sẽ bắt đầu học sâu về class và object, nhưng ngay bây giờ ta đã hiểu rằng biến, list, thậm chí string — không phải chỉ đơn giản, mà là những "thực thể" trong chương trình. Ví dụ, ta có thể tạo biến int age = 30; hoặc string name = "Vasya";. Nhưng nếu ta cần lưu thông tin về một user hoàn chỉnh vào file, người đó có tên, tuổi, địa chỉ, danh sách sách yêu thích và nhiều thứ khác thì sao?
Hãy tưởng tượng: bạn viết game. Bạn có một object Player với hàng tá thuộc tính: health, level, inventory (danh sách item), tọa độ trên bản đồ... Người chơi chơi, lên cấp, tìm ra artifact ngon. Rồi họ quyết định thoát game. Chuyện gì xảy ra? Tất cả dữ liệu về hành trình của họ, lưu trong RAM, biến mất! Buồn thật. Để tránh điều đó, ta phải lưu trạng thái của object Player vào file, và khi người chơi quay lại, tải nó lên lại.
Đây chính là lúc serialization xuất hiện. Nó nối cầu giữa các object "sống" trong bộ nhớ và dữ liệu "chết nhưng bền" trên đĩa.
2. Quá trình serialization (từ object đến file)
Hãy phân tích cách "phép màu" biến object thành byte để ghi vào file diễn ra như thế nào.
Giả sử ta có class Book (Sách):
// Đây là "bản vẽ" hoặc "khuôn" để tạo các đối tượng sách
public class Book
{
// Thuộc tính của sách
public string Title { get; set; } // Tên sách
public string Author { get; set; } // Tác giả
public int Year { get; set; } // Năm xuất bản
// Constructor - phương thức đặc biệt để tạo Book mới
public Book(string title, string author, int year)
{
Title = title;
Author = author;
Year = year;
}
// Phương thức để in thông tin sách (không bắt buộc cho serialization nhưng hữu ích)
public void DisplayInfo()
{
Console.WriteLine($"Tên: {Title}, Tác giả: {Author}, Năm: {Year}");
}
}
Bước 1: Tạo đối tượng để serialize.
Trước hết ta cần đối tượng muốn lưu. Ví dụ, ta tạo instance của Book:
Book myFavoriteBook = new Book("Đi nhờ xe qua Dải Ngân Hà", "Douglas Adams", 1979);
Object myFavoriteBook này đang nằm trong bộ nhớ RAM.
Bước 2: Chọn công cụ (serializer).
Không thể cứ "bắt" đối tượng copy nguyên sang đĩa. Máy tính không hiểu object trực tiếp trong file — nó cần byte. Cần một công cụ đặc biệt — serializer. Nhiệm vụ của nó là bóc tách object (các thuộc tính: Title, Author, Year) và biến những phần đó thành chuỗi byte hoặc text (ví dụ JSON hoặc XML).
Hôm nay ta không đi sâu vào triển khai cụ thể — chỉ nghĩ đơn giản đó là một "hộp chuyển đổi".
Bước 3: Chuyển object thành luồng dữ liệu.
Serializer nhận object myFavoriteBook, nhìn vào các thuộc tính (Title, Author, Year) và chuyển từng cái sang định dạng có thể ghi được. Tất cả byte (hoặc ký tự text) được gom lại thành một luồng dữ liệu — một "dải băng" dài.
Bước 4: Ghi luồng vào file.
Khi đã có "dải băng byte" này, ta dùng các thứ quen thuộc như FileStream và có thể StreamWriter (nếu dùng định dạng text như JSON) hoặc chỉ FileStream (cho dữ liệu nhị phân) để ghi luồng này xuống đĩa.
3. Quá trình deserialization (từ file về object)
Bước 1: Đọc luồng dữ liệu từ file.
Ta lại dùng FileStream và nếu cần StreamReader (nếu là định dạng text) để đọc nội dung file. Dữ liệu đến dưới dạng "dải băng" byte hoặc text.
Bước 2: Chọn công cụ (deserializer).
Cần công cụ ngược lại — deserializer. Nó phải biết làm thế nào để diễn giải byte/text nhận được và tái tạo cấu trúc object. Quan trọng: để deserialization đúng, thường dùng cùng loại serializer (và thường cùng thư viện) đã dùng khi serialization, nếu không "bộ lắp ráp" sẽ không hiểu hướng dẫn.
Bước 3: Chuyển luồng ngược lại thành object.
Deserializer đọc dữ liệu, hiểu chỗ nào là Title, chỗ nào là Author, tiếp đến là Year, và dựa vào đó tạo một object Book mới trong bộ nhớ, gán giá trị cho các thuộc tính.
Bước 4: Nhận object hoàn chỉnh.
Vuala! Ta lại có object Book đầy đủ trong RAM để tiếp tục làm việc.
Ví dụ: Lưu "Siêu-Sách" của chúng ta bằng tay (để hiểu khái niệm)
Tạm thời không dùng thư viện chuyên dụng: làm serialization và deserialization thủ công đơn giản bằng StreamWriter và StreamReader. Cách này giúp hiểu nguyên lý.
Object Book của chúng ta:
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($"Tên: \"{Title}\", Tác giả: {Author}, Năm: {Year}");
}
}
Serialization thủ công: phương thức SaveBookToTextFile
Tạo phương thức lưu các thuộc tính sách vào file text, mỗi thuộc tính trên một dòng.
void SaveBookToTextFile(Book book, string filePath)
{
using var writer = new StreamWriter(filePath);
writer.WriteLine(book.Title);
writer.WriteLine(book.Author);
writer.WriteLine(book.Year);
}
Chuyện gì xảy ra? Mở StreamWriter và lần lượt ghi Title, Author, Year — đó là sơ đồ serialization đơn giản của chúng ta.
Nếu chạy code, nội dung file sẽ như sau:
Đi nhờ xe qua Dải Ngân Hà
Douglas Adams
1979
Deserialization thủ công: phương thức LoadBookFromTextFile
Viết phương thức đọc dữ liệu và ghép lại object Book mới.
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);
}
Sử dụng những phương thức này trong Main:
//tạo object
var myBook = new Book("Đi nhờ xe qua Dải Ngân Hà", "Douglas Adams", 1979);
string filePath = "my_favorite_book.txt";
//lưu vào file
SaveBookToTextFile(myBook, filePath);
//đọc từ file
Book loadedBook = LoadBookFromTextFile(filePath);
Trong LoadBookFromTextFile có gì? Mở StreamReader và đọc các dòng cùng thứ tự: trước là title, sau là author, rồi year và parse bằng int.Parse. Sau đó tạo instance mới của Book.
Trong thực tế nên thêm kiểm tra (File.Exists) và bắt lỗi bằng try-catch, nhưng ở đây ta tập trung vào ý tưởng cơ bản.
Tại sao serialization "thủ công" tệ (và vì sao cần thư viện)?
- Nhiều code viết tay. Mỗi thuộc tính phải ghi rồi đọc. Nếu có nhiều object và field — code phình to.
- Dễ vỡ khi thay đổi. Thêm thuộc tính mới Pages (int)? Phải chỉnh cả ghi lẫn đọc, và giữ thứ tự thật chặt.
- Cấu trúc phức tạp. Collection lồng nhau và object lồng nhau (ví dụ List<Chapter>) sẽ biến code thành "mì ý" (spaghetti).
- Định dạng và hiệu năng. Text thì đơn giản nhưng không luôn tối ưu và an toàn; với dữ liệu nhị phân phải thao tác byte thủ công, dùng BinaryWriter/BinaryReader v.v.
- Không có metadata. File của ta không "biết" dòng đầu là Title còn dòng thứ ba là Year. Serializer chuyên dụng có thể lưu metadata và chịu được thay đổi mô hình hơn.
Đó là lý do trong dự án thực tế dùng thư viện serializer có sẵn, họ tự động bóc tách/ghép object, làm việc với JSON, XML và định dạng nhị phân, và bền hơn khi model thay đổi. Vấn đề này sẽ được nói ở bài tiếp theo!
Ứng dụng thực tế: serialization để làm gì?
- Lưu và tải dữ liệu. Cài đặt, trạng thái game, cấu hình.
- Truyền dữ liệu qua mạng. Trao đổi object phức tạp giữa service (thường bằng JSON).
- Cache. Tái sử dụng nhanh dữ liệu đã lấy trước đó.
- Ghi log object phức tạp. Tiện cho debug và audit.
- Sao chép sâu (deep copy). Serialization + deserialization như cách clone graph object.
Serialization — một trong những viên gạch nền tảng của phần mềm hiện đại: từ lưu tiến độ game đến làm việc với web service — nó có mặt khắp nơi!
GO TO FULL VERSION