CodeGym /課程 /C# SELF /聊聊 BinaryFormatter

聊聊 BinaryFormatter

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

1. 介紹

在很多舊的 C# 教材和範例中,你會看到用 BinaryFormatter 做序列化。它曾經是標準:方便、速度快、麻煩少。舊時的範例大概長這樣:

// 危險的舊寫法!別在新專案這樣做!
var bf = new BinaryFormatter();
using (var stream = File.Open("player.dat", FileMode.Create))
{
    bf.Serialize(stream, playerObject);
}

但在現代的 .NET 9 你已經連編譯這段程式碼都做不到:BinaryFormatter 已經從平台中移除。

從英雄到被淘汰

最初 BinaryFormatter 是設計成通用的 .NET 物件序列化/反序列化機制,可以把複雜的物件圖「如實」保存。隨著時間推移,暴露出嚴重的安全與相容性問題,現在它已成為過去式 — 有點像串流時代的錄音帶機,被淘汰是必然的。

2. 拒用 BinaryFormatter 的主要原因

超大的安全漏洞

BinaryFormatter 不只序列化資料,還會從輸入流還原型別。如果不加檢查地反序列化不受信任的資料(例如從網路收到的),攻擊者可以植入載荷,導致遠端執行程式碼(RCE)。這就是為什麼 Microsoft 多年來一直警告:「不要使用 BinaryFormatter。從 .NET 5 開始它被標註為過時且危險([Obsolete]),到 .NET 9 則被移除。

依賴類別的內部結構

BinaryFormatter 的格式會帶入型別的實作細節:欄位、封裝與版本。類別一旦稍微改動(新增或移除欄位),就可能破壞與既有檔案的相容性。這讓它不適合用於長期儲存或跨版本交換資料。

跨平台相容性的困難

BinaryFormatter 存的資料綁定在 .NET 物件的內部表示上。要在其他平台/語言,甚至不同版本的 .NET 上讀取這些資料通常是不可能的。

3. 如何發現「舊程式碼」以及該怎麼做?

以下是常見使用 BinaryFormatter 的徵兆:

using System.Runtime.Serialization.Formatters.Binary; // <-- 可疑!
BinaryFormatter bf = new BinaryFormatter();           // <-- 危險!
bf.Serialize(...);
bf.Deserialize(...);

在現代的 .NET 版本中這段程式碼無法編譯:工具會報錯像是 "類型或名稱未找到"。在遺留的 .NET Framework 專案中通常至少會有嚴重的警告。

如果你發現這類程式碼,就把 BinaryFormatter 換成現代且安全的替代方案。

今天該用哪些格式和類別?

對於 JSON

  • System.Text.Json — 快、較安全,且內建在 .NET 裡。官方文件
  • Newtonsoft.Json — 廣泛使用的第三方函式庫,適合較複雜的情境。

對於 XML

  • XmlSerializer — 用於 XML 序列化。文件

對於二進位格式

  • System.Formats.Cbor (CBOR)
  • Protobuf.Net (Protocol Buffers), MessagePack for C#

針對特殊需求選擇專門的格式與函式庫 — 已經沒有像 BinaryFormatter 那種「萬用」的二進位序列化器,這其實是件好事。

4. 把使用 BinaryFormatter 的程式碼改寫

哪些情況不能用 BinaryFormatter

別用於:

  • 儲存使用者設定和重要資料。
  • 透過網路傳輸資料(Client ↔ Server)。
  • 反序列化來自不受信任來源的資料。

建議改用:

  • 日常需求 — System.Text.Json
  • 設定檔 — JSONXML 等。
  • 跨語言交換 — 使用跨平台相容的格式(JSONXMLProtobufMessagePack)。

現在該寫什麼程式碼?

序列化成 JSON(推薦做法)

using System.Text.Json;
// 你的類別:
public class Player
{
    public string Name { get; set; } = "";
    public int Health { get; set; }
    public bool IsAlive { get; set; }
}

// 要序列化的物件
var player = new Player { Name = "Aragorn", Health = 100, IsAlive = true };

// 把物件轉成 JSON 字串
string json = JsonSerializer.Serialize(player);

// 寫入檔案 — 安全!
File.WriteAllText("player.json", json);

// 從檔案還原物件:
string loadedJson = File.ReadAllText("player.json");
Player loadedPlayer = JsonSerializer.Deserialize<Player>(loadedJson)!;

序列化成 XML(需要嚴格格式時)

類別必須有 public 的無參數建構子

using System.Xml.Serialization;
// 範例:
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);
}

視覺化比較「舊的 vs 新的」

任務 舊方法 (BinaryFormatter) 現代方法
序列化到檔案
bf.Serialize(stream, obj)
JsonSerializer.Serialize(...)
XmlSerializer.Serialize(...)
從檔案反序列化
bf.Deserialize(stream)
JsonSerializer.Deserialize<T>(...)
XmlSerializer.Deserialize(...)
安全性 易遭攻擊 (RCE) 安全的解析器與嚴格的模型
跨平台性 是 (JSON/XML/Protobuf)
速度 快,但危險 快速且安全

5. 使用 XmlSerializer 時常見的錯誤

錯誤 №1:private 屬性或欄位。XmlSerializer 只會序列化 public 屬性;甚至 protected 也會導致錯誤。

錯誤 №2:缺少預設建構子。需要 public 的無參數建構子,否則序列化/反序列化會失敗。

錯誤 №3:資料型別不匹配。XmlSerializerDictionaryinterfacedelegate 支援不佳 — 建議使用包裝物件或改用其他格式。

錯誤 №4:循環參考 (circular reference)。XmlSerializer 不支援 A → B → A 這種物件圖;對這類情況通常會選擇用 JSON 並搭配適當設定。

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