1. Giới thiệu
Hãy tưởng tượng đối tượng trong chương trình của bạn — giống như một bộ LEGO phức tạp đẹp mắt. Nó gồm nhiều chi tiết, mỗi chi tiết ở đúng vị trí, và tất cả tạo thành một tổng thể. Khi bộ LEGO đứng trên bàn bạn (trong bộ nhớ máy tính), mọi thứ đều ổn. Nhưng nếu bạn cần đem nó đến nhà bạn bè (gửi qua mạng), hoặc cất vào hộp cho lần sau (lưu vào ổ đĩa)? Bạn không thể cứ thế bỏ nó vào hộp phẳng! Nó sẽ bị phá hủy!
Chính việc tháo gỡ đối tượng một cách cẩn thận thành các phần để lưu trữ hoặc truyền đi — đó là việc mà serialization đảm nhiệm. Đây là quá trình biến đổi đối tượng "sống" trong bộ nhớ thành một dãy byte (hoặc biểu diễn dạng văn bản), mà bạn có thể lưu vào file, gửi qua mạng hoặc đặt vào clipboard. Về cơ bản, giống như tháo bộ LEGO ra, bỏ các chi tiết vào túi, dán nhãn và đóng gói vào hộp để vận chuyển hoặc lưu trữ.
flowchart LR
ObjectInMemory(Đối tượng trong bộ nhớ)
Serialize[SERIALIZATION]
BytesOrText[Byte / File văn bản / JSON / XML]
Deserialize[DESERIALIZATION]
ObjectAgain(Đối tượng lại trong bộ nhớ)
ObjectInMemory -->|serialize| Serialize
Serialize --> BytesOrText
BytesOrText -->|deserialize| Deserialize
Deserialize --> ObjectAgain
Và dĩ nhiên, nếu ta đóng gói, ta phải biết cách mở gói! Quá trình ngược lại gọi là deserialization. Là khi bạn mở hộp chứa các túi chi tiết LEGO, đổ chúng ra, rồi theo hướng dẫn (hoặc nhớ cách lắp) phục hồi đối tượng trong bộ nhớ chính xác như trạng thái trước khi đóng gói. Thế đó, phép thuật!
Thuật ngữ "serialization" theo nghĩa đen là "biểu diễn theo chuỗi/tuần tự". Ta lấy cấu trúc phức tạp trong bộ nhớ (đối tượng có thể tham chiếu đến đối tượng khác, collection, v.v. tạo thành một "đồ thị đối tượng") và biến nó thành dạng tuyến tính, tuần tự, dễ ghi hoặc truyền.
2. Tại sao không thể chỉ dùng File.WriteAllText?
Câu hỏi hay! Nếu mọi dữ liệu chỉ là chuỗi hoặc số đơn giản thì không cần serialization. Nhưng trong thực tế ứng dụng làm việc với mô hình phức tạp: khách hàng, đơn hàng, sản phẩm, sinh viên, nhân vật, level. Tất cả đều là đối tượng trong bộ nhớ. Và đây là lý do serialization là bạn tốt nhất của bạn:
Lưu trạng thái ứng dụng
Hãy tưởng tượng bạn viết một trình soạn thảo văn bản. Người dùng gõ chữ, đổi font, chèn hình. Tất cả dữ liệu này (văn bản, cài đặt, vị trí con trỏ) — về cơ bản là các đối tượng trong chương trình. Khi người dùng nhấn "Lưu", bạn muốn lần chạy tiếp theo chương trình hiện đúng như khi họ rời đi. Serialization cho phép "đông lạnh" các đối tượng cần thiết và ghi chúng ra đĩa, rồi "rã đông" khi tải lại. Giống như chức năng "save game" trong game. Bạn đâu muốn chơi lại toàn bộ level nếu mất điện đúng không?
Trao đổi dữ liệu giữa ứng dụng
Chương trình C# của bạn có thể giao tiếp với web server viết bằng Python. Hoặc app di động (ví dụ trên Xamarin hoặc MAUI) giao tiếp với server trên .NET. Những chương trình đó không chia sẻ bộ nhớ. Để trao đổi dữ liệu cần định dạng chung, dễ hiểu. Serialization biến đối tượng thành định dạng chung (ví dụ JSON hoặc XML), có thể gửi qua mạng. Bên nhận sẽ deserialization thành đối tượng của họ, hiểu được bởi ngôn ngữ của họ. Nếu không có serialization, bạn phải "mở gói" từng mảnh dữ liệu bằng tay và lắp lại — lâu, dễ sai!
Tưởng tượng gửi bưu kiện sang nước ngoài. Bạn không thể gửi đồ nguyên trạng; phải đóng gói vào container (serialization) theo chuẩn quốc tế. Bên nhận mở container và lấy đồ ra (deserialization).
Cấu hình ứng dụng
Ứng dụng thường có nhiều cài đặt: kích thước cửa sổ, đường dẫn file, tài liệu mở gần đây, theme. Lưu từng cài đặt thành biến riêng rồi ghi thủ công vào file (ví dụ .ini hoặc .txt) là bất tiện. Tốt hơn định nghĩa lớp NastroykiPrilozheniya, mỗi cài đặt là một property, rồi serialize toàn bộ đối tượng cài đặt vào file. Khi chạy lại — deserialization. Ngon lành!
Cache dữ liệu
Đôi khi lấy dữ liệu (ví dụ từ DB hoặc server từ xa) mất thời gian. Để không gọi lại mỗi lần, ta có thể lấy một lần, serialize và lưu vào cache (disk hoặc storage chuyên dụng). Lần sau kiểm tra cache, nếu có thì deserialization. Tăng tốc app và giảm tải cho nguồn dữ liệu bên ngoài.
3. Ý tưởng chính: lưu "trạng thái"
Khái niệm quan trọng nhất trong serialization là lưu trạng thái. Đối tượng về cơ bản là tập các trường và giá trị của chúng tại một thời điểm. Serialization "chụp ảnh" trạng thái đó. Khi deserialization, ta không chỉ tạo một đối tượng rỗng, mà tái tạo nó với mọi giá trị trường như lúc serialize.
Đây không chỉ là copy dữ liệu. Là sao chép sâu cấu trúc và giá trị, bao gồm các tham chiếu tới đối tượng khác, nếu serializer hỗ trợ. Một số serializer còn lưu thông tin kiểu (type), cho phép deserialization trở lại lớp đúng, ngay cả khi lúc deserialize ta không biết chính xác type trước.
Ví dụ cổ điển: serialization thú cưng
Tiếp tục ví dụ app "Bách khoa thú cưng". Giả sử ta có lớp như sau:
public class Pet
{
public string Name { get; set; }
public string Type { get; set; } // Naprimer: "Kot", "Sobaka"
public int Age { get; set; }
}
Chúng ta muốn:
- Điền một collection những đối tượng như vậy trong bộ nhớ,
- Lưu nó vào file,
- Và sau đó phục hồi (ví dụ sau khi khởi động lại chương trình).
Sơ đồ nhìn chung như sau:
Kolekciya List<Pet>
↓ serializaciya
Fayl (JSON, XML, byte)
↓ deserializaciya
Kolekciya List<Pet> (lai v pamyati!)
4. Những định dạng serialization phổ biến
Như trong vận chuyển có nhiều loại thùng (thùng giấy, thùng gỗ, container kim loại), trong lập trình cũng có các định dạng serialization khác nhau. Mỗi định dạng có đặc điểm, lợi và hại riêng.
Chúng ta chưa đi sâu, nhưng nhớ vài tên để hiểu các bài sau sẽ nói về gì:
- JSON (JavaScript Object Notation): Hiện nay có lẽ là định dạng phổ biến nhất. Là văn bản, khá đọc được bởi con người và được dùng rộng rãi để trao đổi dữ liệu trên web. Trông như các cặp "khóa-giá trị" trong dấu ngoặc nhọn. Về bản chất là text có cấu trúc dễ parse cho chương trình.
- XML (Extensible Markup Language): Cũ hơn nhưng vẫn phổ biến. Là định dạng text dựa trên tag, giống HTML, có cấu trúc chặt chẽ. Thường dùng cho file cấu hình và trao đổi dữ liệu trong hệ thống doanh nghiệp. Cũng đọc được nhưng thường "dài dòng".
- Định dạng nhị phân: Lưu dữ liệu không phải dạng text mà trực tiếp bằng byte như trong bộ nhớ. Thường gọn hơn và nhanh hơn khi serialize/deserialze, nhưng không đọc được bằng mắt. Dùng khi cần hiệu năng tối đa hoặc kích thước nhỏ, ví dụ lưu dữ liệu game hoặc truyền lượng lớn giữa các thành phần hệ thống nội bộ.
Chọn định dạng phụ thuộc vào nhiệm vụ: có cần người đọc dữ liệu dễ dàng không? Tốc độ và kích thước có quan trọng không? Dữ liệu sẽ truyền tới nền tảng nào? Để trao đổi giữa hệ thống khác nhau thì JSON và XML thường là lựa chọn do phổ quát. Để lưu nội bộ trong cùng chương trình, định dạng nhị phân có thể nhanh hơn và tiết kiệm.
Như bạn thấy, serialization là công cụ mạnh mẽ nằm ở nền tảng nhiều ứng dụng hiện đại. Nó cho phép chương trình "nhớ" dữ liệu giữa các lần chạy, giao tiếp với nhau và quản lý thông tin phức tạp hiệu quả. Ở các bài sau chúng ta sẽ không chỉ nói "phép thuật" mà sẽ tự thực hành với thư viện .NET! Chuẩn bị nhé, sẽ rất thú vị!
GO TO FULL VERSION