protobuf-net继承层级变更后反序列化异常问题求助
首先,咱们得揪出这个问题的核心:子类型ID的重复使用加上继承层级的变更,导致protobuf-net在反序列化时对类型的映射逻辑混乱了。
先回顾你版本1的模型配置:
FooA的子类型里,201对应FooBFooB的子类型里,201对应FooCFooC的子类型里,201对应FooD
这种在嵌套继承层级里重复使用同一个子类型ID的做法,在原继承结构下是没问题的——因为protobuf-net的子类型ID是相对于父类型的局部标识。但当你把版本2的继承结构改成FooC : FooA1、FooA1不再继承FooA之后,问题就爆发了:
旧数据里的FooC实例,序列化时的类型标识路径是:FooA → FooB(201) → FooC(201)。而你新版本的模型里,FooA1的子类型201对应FooC,FooC的子类型201又对应FooD。当反序列化旧数据时,protobuf-net解析到FooB(201)之后,又遇到下一个201标识,这时候它会去当前类型(新版本里,它错误地把路径关联到FooC的子类型)找201对应的类型,也就是FooD,所以最终实例化了FooD,而D字段在旧数据里不存在,自然就是默认值0。
修复方案
1. 紧急修复现有旧数据的反序列化问题
你可以通过自定义类型解析逻辑来干预protobuf-net的类型映射,把旧的类型路径强制映射到新的FooC:
Model.ResolveType += (sender, args) => { // 检查旧类型路径:FooB的子类型201(对应旧版本的FooC) if (args.DeclaringType == typeof(FooB) && args.Key == 201) { // 强制返回新的FooC类型,阻止后续错误的子类型解析 return typeof(FooC); } // 其他情况保持默认解析逻辑 return null; };
这个事件会在protobuf-net尝试根据父类型和子类型ID查找目标类型时触发,我们可以在这里拦截旧的路径,直接返回正确的新类型。
2. 修正模型配置,避免未来再出现类似问题
不要在不同父类型的子类型中重复使用同一个ID:哪怕是嵌套层级里,也尽量给每个子类型分配全局唯一的ID。比如把版本2的模型改成:
Model = RuntimeTypeModel.Create(); Model.Add(typeof(FooA), false) .AddSubType(201, typeof(FooB)) .AddSubType(202, typeof(FooA1)) .Add(1, "A") .Add(2, "B"); Model[typeof(FooB)] // 给旧FooC保留兼容ID,映射到新的FooC .AddSubType(201, typeof(FooC)) .Add(1, "C"); Model[typeof(FooA1)] // 用新的唯一ID,避免和旧路径冲突 .AddSubType(203, typeof(FooC)) .Add(1, "A") .Add(2, "B"); Model[typeof(FooC)] // 同样使用唯一ID .AddSubType(204, typeof(FooD)); Model[typeof(FooD)] .Add(1, "D");这样旧数据里的
FooB→201路径会被映射到新的FooC,而新数据里FooA1→203才是新的FooC,不会和FooC→204→FooD混淆。如果必须保留原ID,拆分兼容逻辑:比如给旧的继承路径单独做兼容处理,确保旧数据的类型标识能正确匹配到新类型,而不会触发后续的子类型解析。
总结
这个问题本质是protobuf-net的子类型ID局部性,在继承层级变更后,重复ID导致了类型解析的歧义。通过自定义类型解析事件可以快速修复旧数据的反序列化问题,而规范子类型ID的分配方式则能避免未来再踩同样的坑。
内容的提问来源于stack exchange,提问作者ilCosmico

