Protobuf-net 3.2.52中实现WPlane实例跨类引用反序列化复用的技术问询
我完全理解你现在的困境:序列化时B、C、D三个类的_Plane字段明明指向同一个WPlane实例,但反序列化后每个类都拿到了独立的WPlane对象,而且protobuf-net 3.x版本里原先的AsReference属性已经被移除,找不到直接替代的配置项。下面我会结合protobuf-net 3.2.52的特性,给你一步步的解决方案,同时还会指出你当前代码里容易被忽略的反序列化漏洞:
第一步:先修复WPlane类的反序列化逻辑(关键前提)
我注意到你当前的WPlane类只实现了序列化前的字节数组转换,但没有处理反序列化后的对象恢复逻辑!如果不补充这部分代码,即使反序列化得到了WPlane实例,它的Plane属性会是null,完全无法使用。请给WPlane类添加反序列化后处理方法:
[ProtoContract(SkipConstructor = true, ReferenceTracked = true)] public class WPlane { private bool _IsSerialized; public WPlane(Plane plane) { _IsSerialized = false; Plane = plane; } [ProtoMember(2)] private byte[] _Plane_BA; public Plane Plane { get; set; } [ProtoBeforeSerialization] private void OnSerializing() { if (!_IsSerialized) { _Plane_BA = SerializationHelper.EyeshotObjectSerialize(Plane); _IsSerialized = true; } } // 新增反序列化后处理方法 [ProtoAfterDeserialization] private void OnDeserialized() { if (_Plane_BA != null) { Plane = SerializationHelper.EyeshotObjectDeserialize(_Plane_BA); _IsSerialized = true; // 标记为已序列化,避免后续重复处理 } } }
第二步:启用WPlane类型的引用追踪(核心解决方案)
在protobuf-net 3.x中,AsReference被替换为引用追踪配置,有两种方式实现你的需求:
方案1:针对WPlane类型精准启用引用追踪(推荐)
给WPlane类的[ProtoContract]属性添加ReferenceTracked = true(已经在上面的代码示例中加上了),这样只有WPlane类型会被protobuf-net追踪实例引用,序列化时会记录实例的唯一标识,反序列化时自动复用同一个实例。
然后在序列化/反序列化时,需要使用SerializerOptions启用显式引用追踪:
// 序列化 var options = new SerializerOptions { ReferencePreservation = ReferencePreservationOptions.Explicit }; byte[] serializedData = Serializer.Serialize(options, yourRootObject); // yourRootObject是包含B、C、D的根对象 // 反序列化 var deserializedObject = Serializer.Deserialize<YourRootType>(options, serializedData);
这个方案的优势是精准控制,不会影响其他类型的序列化行为,避免不必要的体积开销。
方案2:全局启用所有类型的引用追踪
如果你希望所有支持的类型都启用引用追踪,可以直接设置ReferencePreservationOptions.All:
var options = new SerializerOptions { ReferencePreservation = ReferencePreservationOptions.All }; // 序列化反序列化代码同上
优点是配置简单,无需修改类的属性;缺点是会对所有类型启用引用追踪,可能增加序列化后的字节体积,或者引入潜在的循环引用问题(如果你的对象结构中有循环引用的话)。
第三步:验证效果
完成上述修改后,你可以在反序列化后通过object.ReferenceEquals方法验证:
// 假设deserializedObject包含B、C、D实例 bool areSameInstance = object.ReferenceEquals(deserializedObject.B._Plane, deserializedObject.C._Plane); // 预期返回true,说明两个_WPlane指向同一个实例
额外注意事项
- 确保你的根对象(包含B、C、D的对象)也正确标记了
[ProtoContract],否则protobuf-net无法正确序列化整个对象树; SkipConstructor = true的配置是正确的,因为你的类都有带参数的构造函数,protobuf-net会跳过构造函数直接赋值私有字段;- 如果WPlane实例在序列化前被修改过,记得重置
_IsSerialized为false,否则下次序列化时会使用旧的_Plane_BA数据。
内容来源于stack exchange




