CodeGym /課程 /C# SELF /操作順序與範例:物件序列化

操作順序與範例:物件序列化

C# SELF
等級 43, 課堂 1
開放

1. 介紹

我們已經習慣在程式裡建立物件。記得嗎,我們馬上就要更深入學習 class 和 object,但現在就能理解,變數、list、甚至簡單的字串不是隨便的東西,它們在程式裡是「實體」。例如,我們可以建立一個變數 int age = 30; 或 string name = "瓦西亞";。 但如果我們要把一個完整使用者的資訊儲存到檔案,這個使用者有名字、年齡、位址、最愛書籍的清單等等,該怎麼辦?

想像你在寫一個遊戲。你有一個 Player 物件,裡面很多屬性:health、level、inventory(道具清單)、在地圖上的座標等等。玩家玩、升等、撿到神器。然後他決定離開遊戲。會發生什麼?所有存在記憶體中的資料會消失!好可惜!為了避免這種情況,我們得把 Player 的狀態儲存到檔案,等玩家回來時再把它載回來。

這時候就輪到序列化上場。序列化就是在記憶體中的「活」物件和硬碟上「死但持久」的資料之間搭橋。

2. 序列化過程(從物件到檔案)

我們來拆解這個把物件變成可寫入檔案的位元組序列的「魔術」。

假設有這樣一個 class Book(書):


// 這是我們的「藍圖」或「模板」,用來建立 Book 物件
public class Book
{
    // 書的屬性
    public string Title { get; set; } // 書名
    public string Author { get; set; } // 作者
    public int Year { get; set; } // 出版年

    // 建構子 - 用來建立新的 Book 物件
    public Book(string title, string author, int year)
    {
        Title = title;
        Author = author;
        Year = year;
    }

    // 方便輸出書的資訊的方法(對序列化不是必需,但很實用)
    public void DisplayInfo()
    {
        Console.WriteLine($"名稱: {Title}, 作者: {Author}, 年份: {Year}");
    }
}

步驟 1:建立要序列化的物件。

當然,首先我們需要一個要儲存的物件。例如,我們建立了一個 Book 的實例:

Book myFavoriteBook = new Book("搭便車環遊銀河系", "道格拉斯 亞當斯", 1979);

這個物件 myFavoriteBook 現在在記憶體中。

步驟 2:選擇工具(序列化器)。

我們不能直接把物件「複製」到磁碟。電腦檔案系統只能處理位元組。需要一個特殊工具——序列化器,它的工作是把物件分解成各個部分(它的屬性: Title, Author, Year),並把這些部分轉成位元組序列或文字(例如 JSON 或 XML)。

今天我們不會深入具體實作細節,只把它想像成一個專門的「轉換盒子」。

步驟 3:把物件轉成資料流。

序列化器拿到 our myFavoriteBook,檢視它的屬性(Title, Author, Year)並把每個屬性轉成可寫入的格式。所有這些位元組(或文字)會被串成單一的資料流 —— 一條長長的資訊「帶」。

步驟 4:把資料流寫入檔案。

當我們有了這條「位元組帶」,我們會用熟悉的 FileStream,如果是文字格式(像 JSON)則可能搭配 StreamWriter,或者純二進位資料就用 FileStream 直接寫入。這樣資料就保存在磁碟上了。

3. 反序列化過程(從檔案到物件)

步驟 1:從檔案讀取資料流。

我們再次使用 FileStream,如果需要讀文字則用 StreamReader,把檔案內容讀出。資料會以位元組或文字的「帶」回來。

步驟 2:選擇工具(反序列化器)。

需要一個反向工具——deserializer。它要知道如何解讀拿到的位元組/文字並正確重建物件結構。非常重要的是:反序列化通常要用與序列化時相同的 serializer/同一個函式庫,否則你的「組裝說明」可能會被誤解。

步驟 3:把資料流轉回物件。

deserializer 會讀資料,找出哪裡是 Title,哪裡是 Author,哪裡是 Year,然後在記憶體中新建一個 Book 並填入這些屬性。

步驟 4:得到可用的物件。

噠啦!我們又有一個完整的 Book 物件在記憶體中,可以繼續使用。

範例:用「手動」方式儲存我們的「超級書」(幫助理解概念)

先不使用現成的函式庫:用最簡單的「手動」序列化/反序列化,透過 StreamWriter 與 StreamReader。這有助於理解原理。

我們的 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($"名稱: \"{Title}\", 作者: {Author}, 年份: {Year}");
    }
}

手動序列化:方法 SaveBookToTextFile

建立一個方法,把書的屬性逐行寫入文字檔。

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

發生了什麼?我們開啟 StreamWriter,然後依序寫入 Title、Author、Year —— 這就是最簡單的序列化格式。

執行後檔案內容會是這樣:


搭便車環遊銀河系
道格拉斯 亞當斯
1979

手動反序列化:方法 LoadBookFromTextFile

寫一個方法,讀取資料並組成新的 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);
}

在 Main 中使用這些方法:

// 建立物件
var myBook = new Book("搭便車環遊銀河系", "道格拉斯 亞當斯", 1979);
string filePath = "my_favorite_book.txt";

// 把它存到檔案
SaveBookToTextFile(myBook, filePath);

// 從檔案讀回來
Book loadedBook = LoadBookFromTextFile(filePath);

在 LoadBookFromTextFile 中發生了什麼?我們開啟 StreamReader,以同樣的順序讀取行:先標題、再作者、然後把年份用 int.Parse 轉換。最後建立一個新的 Book 實例。

實務上應該加入檢查(例如 File.Exists)和錯誤處理(try-catch),但這裡我們專注概念。

為什麼「手動」序列化不太好(以及為何要用函式庫)?

  • 大量手動程式碼。每個屬性都要手動寫入與讀取。物件和欄位多了程式會爆炸式成長。
  • 對變更很脆弱。新增屬性 Pages(int)?你得同時改寫寫入與讀取邏輯,而且要確保順序一致。
  • 複雜結構難處理。巢狀集合或物件(例如 List<Chapter>)會把程式變成「義大利麵」狀態。
  • 格式與效能。文字格式雖然簡單,但不一定緊湊或安全;二進位資料你要自己處理位元組,可能要用 BinaryWriter/BinaryReader 等工具。
  • 缺乏metadata。我們的檔案不會「知道」第一行是 Title、第三行是 Year。專業的序列化器可以儲存 metadata,對模型變更更有韌性。

所以在真實專案中通常會用現成的 序列化函式庫,它們能自動拆解/重建物件、支援 JSON、XML、二進位格式,並且對模型變更更穩健。下一堂課會談到這些內容!

實務應用:為什麼需要序列化?

  • 儲存與載入資料。設定、遊戲存檔、組態檔。
  • 透過網路傳輸資料。不同服務之間交換複雜物件(通常用 JSON)。
  • 快取。快速重用先前取得的資料。
  • 紀錄複雜物件。方便除錯與稽核。
  • 深拷貝物件。序列化 + 反序列化可以用來克隆物件圖。

序列化是現代軟體的基石之一:從遊戲存檔到 web services,它無處不在!

2
任務
C# SELF, 等級 43, 課堂 1
上鎖
建立一個簡單的儲存系統
建立一個簡單的儲存系統
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION