You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#如何检测BinaryFormatter反序列化得到的对象是否存在指定字段

C# BinaryFormatter 序列化结构变更兼容方案

你反序列化得到的对象是当前代码中定义的新版本SaveData类实例,所有新增字段本身都是存在的,只是旧存档中没有对应序列化数据时会被赋值为对应类型的默认值(bool为false、值类型为0、引用类型为null),你需要解决的是区分「默认值是用户主动设置」还是「旧存档缺失字段」,以及处理复杂结构变更的问题,可通过以下几种方案实现:

方案1:使用官方序列化兼容特性(最简便)

利用.NET 自带的序列化特性配合版本号实现升级,无需修改读写逻辑,所有兼容处理内聚到数据类中:

  1. 引入命名空间System.Runtime.Serialization
  2. 给SaveData添加版本号字段,所有新增字段标记[OptionalField]特性并指定新增的版本号
  3. 添加[OnDeserialized]标记的反序列化回调方法,在回调中根据版本号补全旧版本缺失字段的默认值

示例代码:

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;

[Serializable]
public class SaveData
{
    // 版本号,每次结构变更+1,初始旧版本无此字段时反序列化默认值为0
    [OptionalField(VersionAdded = 1)]
    public int saveVersion = 3; // 当前最新版本为3

    // 初始版本就存在的字段
    public bool isVibrationOn;

    // 版本2新增字段
    [OptionalField(VersionAdded = 2)]
    public bool isMusicOn;

    // 版本3新增复杂类型字段
    [OptionalField(VersionAdded = 3)]
    public List<string> unlockedLevels;

    // 反序列化完成后自动执行
    [OnDeserialized]
    private void OnDeserialized(StreamingContext context)
    {
        // 低于版本2的存档补全音乐开关默认值
        if (saveVersion < 2)
        {
            isMusicOn = true; // 自定义默认值,避免用类型默认的false
        }

        // 低于版本3的存档补全解锁关卡初始值
        if (saveVersion < 3)
        {
            unlockedLevels = new List<string> { "level_1" };
        }

        // 升级为最新版本号,下次保存就是新版本结构
        saveVersion = 3;
    }
}

方案2:实现ISerializable接口自定义序列化逻辑(灵活性最高)

如果需要处理字段改名、删除旧字段、嵌套结构大改等复杂场景,可以实现ISerializable接口完全控制序列化和反序列化过程:
示例代码:

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;

[Serializable]
public class SaveData : ISerializable
{
    public int saveVersion = 3;
    public bool isVibrationOn;
    public bool isMusicOn;
    public List<string> unlockedLevels;

    // 反序列化时调用的构造函数
    protected SaveData(SerializationInfo info, StreamingContext context)
    {
        // 读取版本号,旧版本不存在则默认为1
        saveVersion = info.TryGetValue("saveVersion", out int ver) ? ver : 1;

        // 读取初始版本就有的字段
        isVibrationOn = info.GetBoolean("isVibrationOn");

        // 读取版本2新增字段,不存在则给默认值
        if (saveVersion >= 2 && info.TryGetValue("isMusicOn", out bool musicOn))
        {
            isMusicOn = musicOn;
        }
        else
        {
            isMusicOn = true;
        }

        // 读取版本3新增字段,不存在则给默认值
        if (saveVersion >= 3 && info.TryGetValue("unlockedLevels", out List<string> levels))
        {
            unlockedLevels = levels;
        }
        else
        {
            unlockedLevels = new List<string> { "level_1" };
        }
    }

    // 序列化时调用的方法
    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("saveVersion", saveVersion);
        info.AddValue("isVibrationOn", isVibrationOn);
        info.AddValue("isMusicOn", isMusicOn);
        info.AddValue("unlockedLevels", unlockedLevels);
    }
}

注意事项

  • 不要删除已经发布过的字段,否则旧存档反序列化会直接报错,废弃字段可以标记[Obsolete]保留,或者在自定义序列化逻辑中跳过读取
  • 嵌套对象的兼容处理和根类一致,给嵌套类也添加[Serializable]特性,用同样的版本控制逻辑处理即可
  • 微软官方已标记BinaryFormatter为不安全类型,有远程代码执行风险,有条件的话更推荐用Json.NET等序列化库存储为明文Json,兼容性和调试便捷性更高

内容的提问来源于stack exchange,提问作者gadzet gadzitekovic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 22:54:03