二十四、运行时序列化(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 处理外部数据
  • 使用 DataContractSerializerSystem.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/)


🎯 面试题

  1. 哪些条件下 .NET 类型可以被 BinaryFormatter 正确序列化?

    ✅ 答案:需加 [Serializable],且字段不能是未标记的非序列化类型。

  2. 如何跳过某个字段不被序列化?

    ✅ 答案:加 [NonSerialized] 特性,仅适用于 BinaryFormatter。

  3. 在 Unity 中,JsonUtility 能否序列化 Dictionary?

    ❌ 答案:不能,JsonUtility 不支持复杂对象。应使用 Newtonsoft.Json。

  4. 为什么 BinaryFormatter 不适合暴露在网络层?

    ✅ 答案:反序列化是反射 + 动态创建,容易被攻击构造恶意类型实例,存在 RCE(远程代码执行) 风险。

  5. 如何为序列化对象添加额外行为(如加密/转换)?

    ✅ 答案:

    • 实现 ISerializable
    • 使用 OnSerializing/OnDeserialized 特性进行状态转换
posted @ 2025-08-26 10:08  世纪末の魔术师  阅读(17)  评论(0)    收藏  举报