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。
- 配置文件 — 用 JSON、XML 等可读格式。
- 跨语言交换 — 用跨平台兼容的格式(JSON、XML、Protobuf、MessagePack)。
现在应该写什么样的代码?
序列化到 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) | 现代方法 |
|---|---|---|
| 序列化到文件 | |
|
| 从文件反序列化 | |
|
| 安全性 | 易受攻击 (RCE) | 安全的解析器和严格的模型 |
| 跨平台性 | 否 | 是 (JSON/XML/Protobuf) |
| 速度 | 快,但不安全 | 快且安全 |
5. 使用 XmlSerializer 时的常见错误
错误 №1:私有属性或字段。XmlSerializer 只序列化 public 属性;就算是 protected 也会出问题。
错误 №2:缺少默认构造函数。需要公共的无参数构造函数,否则序列化/反序列化无法工作。
错误 №3:数据类型不匹配。XmlSerializer 对 Dictionary、interface 和 delegate 支持不好 — 使用包装类或换其它格式。
错误 №4:循环引用(circular reference)。XmlSerializer 不支持 A → B → A 这种对象图;遇到这种情况通常会选择带合适设置的 JSON。
GO TO FULL VERSION