二十四、运行时序列化(Runtime Serialization)
第24章:运行时序列化(Runtime Serialization)
在 .NET 世界中,“序列化”是一种常见且强大的机制,它允许你将对象的状态转化为可传输或可存储的格式,再在需要时恢复为原对象。
✳️ 序列化的基本概念
- 序列化(Serialization):将对象的字段状态转换为一组字节,以便存储或传输。
- 反序列化(Deserialization):将字节流恢复为原始对象。
- 应用场景:
- 网络传输(WCF / Web API)
- 跨 AppDomain 数据传输
- 数据持久化(文件、数据库)
- 缓存机制(如 Redis 缓存对象)
✳️ 序列化类型支持
CLR 默认支持的序列化方式:
- BinaryFormatter(Binary 序列化):适合 .NET 内部使用;速度快但不可跨平台。
- SoapFormatter(已过时):基于 XML,适合跨平台。
- XmlSerializer:适合 Web / 配置场景,但需要 public 类型 + 无参构造函数。
- DataContractSerializer(WCF 默认): 更现代,更灵活,可控制字段与名称。
✅ 控制序列化的方式
使用特性(Attribute)控制哪些字段会被序列化:
| 特性 | 含义 |
|---|---|
[Serializable] |
表示该类可序列化 |
[NonSerialized] |
忽略此字段(BinaryFormatter) |
[DataContract] |
声明型序列化(用于 WCF) |
[DataMember] |
标记参与序列化的字段或属性 |
还可以实现以下接口:
[Serializable]
class MyData : ISerializable
{
public int Age;
public string Name;
// 自定义序列化过程
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Age", Age);
info.AddValue("Name", Name.ToUpper()); // 举例:自定义序列化逻辑
}
// 自定义反序列化构造函数
protected MyData(SerializationInfo info, StreamingContext context)
{
Age = info.GetInt32("Age");
Name = info.GetString("Name");
}
}
🔄 对象图与引用处理
.NET 会自动处理:
- 循环引用
- 多次引用同一对象
这是因为 CLR 在序列化过程中构建了一个 对象图(Object Graph),通过 ObjectIDGenerator 来跟踪已经处理的对象。
🔐 安全性注意
- 不要将序列化对象暴露在不可信通道中(可能被构造恶意数据攻击)
- 避免使用 BinaryFormatter 和 SoapFormatter 处理外部数据
- 使用
DataContractSerializer或System.Text.Json
🌈 Unity 场景下的序列化建议
Unity 中使用的是自己的序列化系统(Unity Serialization),但在以下场景中 CLR 的序列化依然适用:
- 离线数据缓存(二进制或 JSON)
- 保存关卡状态至本地磁盘
- 与 Web API 通信时对象转换
示例代码(Unity 序列化)
[System.Serializable]
public class PlayerData
{
public int Level;
public float Health;
public Vector3 Position;
}
// 存档写入
void Save(PlayerData data)
{
var path = Application.persistentDataPath + "/save.json";
var json = JsonUtility.ToJson(data);
File.WriteAllText(path, json);
}
// 读取存档
PlayerData Load()
{
var path = Application.persistentDataPath + "/save.json";
if (File.Exists(path))
{
var json = File.ReadAllText(path);
return JsonUtility.FromJson<PlayerData>(json);
}
return null;
}
🧠 高级技巧:OnSerializing/OnDeserialized 特性
你可以使用如下特性,在序列化前后加入自定义行为:
[OnSerializing]
void OnSerializingMethod(StreamingContext context)
{
// 序列化前执行
}
[OnDeserialized]
void OnDeserializedMethod(StreamingContext context)
{
// 反序列化后执行
}
这类似 Unity 的 OnEnable() / OnDisable() 钩子。
注意 ⚠
JsonUtility不支持ISerializable接口和[OnDeserialized]之类的钩子方法,如果你想用这些机制,请使用 .NET 自带的序列化器(如BinaryFormatter/DataContractSerializer/System.Text.Json)或切换到 Newtonsoft.Json。
using System;
using System.Runtime.Serialization;
using UnityEngine;
public class TestSerializable : MonoBehaviour
{
public MyData myData;
[ContextMenu("TestMyData")]
void TestMyData()
{ myData = new MyData(10, "Hello World");
//---JsonUtility只支持字段的直接序列化和反序列化,不支持 C# 的高级序列化机制
//---不支持ISerializable接口的序列化和反序列化
//---不支持[OnDeserialized]、[OnSerializing] 等序列化钩子
string json = JsonUtility.ToJson(myData);
Debug.Log(json);
MyData data = JsonUtility.FromJson<MyData>(json);
Debug.Log(data.x + " " + data.y);
//---BinaryFormatter支持ISerializable接口的序列化和反序列化
//---支持[OnDeserialized]、[OnSerializing] 等序列化钩子
using var stream = new System.IO.MemoryStream();
var formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
formatter.Serialize(stream, myData);
stream.Position = 0;
data = (MyData)formatter.Deserialize(stream);
Debug.Log(data.x + " " + data.y);
}
}
[Serializable]
public class MyData : ISerializable
{
public int x;
public string y;
public MyData(int x, string y)
{ this.x = x;
this.y = y;
} public void GetObjectData(SerializationInfo info, StreamingContext context)
{ info.AddValue("x",x);
info.AddValue("y",y.ToLower());
}
protected MyData(SerializationInfo info, StreamingContext context)
{ x = info.GetInt32("x");
y = info.GetString("y").ToLower();
}
[OnDeserializing]
void OnSerializing(StreamingContext context)
{ Debug.Log("OnSerializing");
} [OnDeserialized]
void OnDeserialized(StreamingContext context)
{ x+=10;
y = y.ToUpper();
Debug.Log("OnDeserialized");
}}
Unity中推荐方案:手动调用自定义钩子
[Serializable]
public class MyData
{
public int x;
public string y;
// 自定义钩子方法
public void OnBeforeSerialize()
{
y = y.ToLower(); // 👈 在序列化前修改
Debug.Log("OnBeforeSerialize");
}
public void OnAfterDeserialize()
{
Debug.Log("OnAfterDeserialize");
}
}
void TestMyData()
{
myData = new MyData { x = 10, y = "HELLO World" };
myData.OnBeforeSerialize(); // 👈 手动调用
string json = JsonUtility.ToJson(myData);
Debug.Log(json);
MyData data = JsonUtility.FromJson<MyData>(json);
data.OnAfterDeserialize(); // 👈 手动调用
}
📚 小结
| 要点 | 内容 |
|---|---|
| 默认机制 | [Serializable] + BinaryFormatter |
| 高级定制 | 实现 ISerializable 或使用 DataContract |
| 安全推荐 | 尽量使用 DataContractSerializer 或 JSON |
| Unity 场景 | 用 JsonUtility 或 Newtonsoft.Json |
| 钩子支持 | 使用 OnSerializing/Deserialized |
LitJson(https://www.cnblogs.com/Firepad-magic/p/5532650.html)
Protocol Buffers(https://protobuf.com.cn/overview/)
🎯 面试题
-
哪些条件下 .NET 类型可以被 BinaryFormatter 正确序列化?
✅ 答案:需加
[Serializable],且字段不能是未标记的非序列化类型。 -
如何跳过某个字段不被序列化?
✅ 答案:加
[NonSerialized]特性,仅适用于 BinaryFormatter。 -
在 Unity 中,
JsonUtility能否序列化 Dictionary?❌ 答案:不能,
JsonUtility不支持复杂对象。应使用 Newtonsoft.Json。 -
为什么 BinaryFormatter 不适合暴露在网络层?
✅ 答案:反序列化是反射 + 动态创建,容易被攻击构造恶意类型实例,存在 RCE(远程代码执行) 风险。
-
如何为序列化对象添加额外行为(如加密/转换)?
✅ 答案:
- 实现
ISerializable - 使用
OnSerializing/OnDeserialized特性进行状态转换
- 实现
作者:世纪末的魔术师
出处:https://www.cnblogs.com/Firepad-magic/
Unity最受欢迎插件推荐:点击查看
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

浙公网安备 33010602011771号