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 的格式会带上类型的实现细节:字段、boxing 行为、版本等。类上做一点小改动(加/删字段)就会破坏和已保存文件的兼容性。这让这种格式不适合长期存储或跨版本交换。

跨平台兼容性问题

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

别用在:

  • 保存用户设置或重要数据。
  • 在网络中传输数据(客户端 ↔ 服务器)。
  • 反序列化来自不可信来源的数据。

改用:

  • 日常需求 — 用 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(需要严格格式时)

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:私有属性或字段。XmlSerializer 只序列化 public 属性;就算是 protected 也会出问题。

错误 №2:缺少默认构造函数。需要公共的无参数构造函数,否则序列化/反序列化无法工作。

错误 №3:数据类型不匹配。XmlSerializerDictionaryinterfacedelegate 支持不好 — 使用包装类或换其它格式。

错误 №4:循环引用(circular reference)。XmlSerializer 不支持 A → B → A 这种对象图;遇到这种情况通常会选择带合适设置的 JSON

2
任务
C# SELF, 第 44 级, 课程 1
已锁定
比较简单对象的 JSON 和 XML 序列化
比较简单对象的 JSON 和 XML 序列化
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION