C#如何检测BinaryFormatter反序列化得到的对象是否存在指定字段
C# BinaryFormatter 序列化结构变更兼容方案
你反序列化得到的对象是当前代码中定义的新版本SaveData类实例,所有新增字段本身都是存在的,只是旧存档中没有对应序列化数据时会被赋值为对应类型的默认值(bool为false、值类型为0、引用类型为null),你需要解决的是区分「默认值是用户主动设置」还是「旧存档缺失字段」,以及处理复杂结构变更的问题,可通过以下几种方案实现:
方案1:使用官方序列化兼容特性(最简便)
利用.NET 自带的序列化特性配合版本号实现升级,无需修改读写逻辑,所有兼容处理内聚到数据类中:
- 引入命名空间
System.Runtime.Serialization - 给
SaveData添加版本号字段,所有新增字段标记[OptionalField]特性并指定新增的版本号 - 添加
[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
相关产品推荐
相关产品推荐

