如何为BinaryFormatter指定自定义相等比较器保证OnDeserialized调用次数匹配
问题根因
BinaryFormatter序列化遍历对象图时,默认会调用对象的Equals和GetHashCode方法判定是否为同一实例,而非使用引用相等判断。你重写了类的等值逻辑后,业务值相等的不同实例会被判定为同一个对象,仅执行一次序列化流程,最终导致OnSerialized和OnDeserialized调用次数不匹配。
可行解决方案
方案1:添加不参与等值比较的唯一实例字段(推荐,改动最小)
在你的可序列化类中新增一个私有字段,仅用于BinaryFormatter做实例区分,不要加入现有Equals和GetHashCode的计算逻辑即可:
[Serializable] public class YourUnmanagedHandleClass : IEquatable<YourUnmanagedHandleClass> { // 新增唯一实例标识,不参与业务等值比较 private readonly Guid _instanceUniqueId = Guid.NewGuid(); // 你原有业务字段、Equals、GetHashCode、引用计数逻辑全部保持不变 }
该方案完全不影响原有业务层面的等值判断,两个业务值相等的不同实例会因为_instanceUniqueId不同被BinaryFormatter识别为不同对象,保证两个回调的调用次数一一对应。
方案2:自定义序列化代理(无需修改原有类代码)
如果不能修改原有类的结构,可以实现ISerializationSurrogate自定义序列化逻辑,额外写入实例唯一标识让BinaryFormatter区分不同实例:
- 实现序列化代理类
public class YourClassSerializationSurrogate : ISerializationSurrogate { public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { var instance = (YourUnmanagedHandleClass)obj; // 写入原有所有需要序列化的业务字段 info.AddValue("HandleValue", instance.Handle); // 额外写入实例的引用哈希作为唯一标识,不需要对应类的实际字段 info.AddValue("InstanceId", RuntimeHelpers.GetHashCode(instance)); // 其余业务字段依次添加 } public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) { var instance = (YourUnmanagedHandleClass)obj; // 读取业务字段赋值即可,InstanceId仅用于序列化时区分实例,反序列化不需要处理 instance.Handle = info.GetIntPtr("HandleValue"); // 其余业务字段依次赋值 return instance; } }
- 使用BinaryFormatter时指定代理
var formatter = new BinaryFormatter(); var selector = new SurrogateSelector(); selector.AddSurrogate(typeof(YourUnmanagedHandleClass), formatter.Context, new YourClassSerializationSurrogate()); formatter.SurrogateSelector = selector;
方案3:反序列化端去重(兼容现有序列化逻辑)
如果无法修改序列化侧的逻辑,可以在反序列化侧做实例去重,避免重复实例导致的引用计数错误:
[Serializable] public class YourUnmanagedHandleClass : IEquatable<YourUnmanagedHandleClass> { // 存储已反序列化的实例,不会影响实例的垃圾回收 private static readonly ConditionalWeakTable<YourUnmanagedHandleClass, object> _deserializedCache = new(); private static readonly object _cacheLock = new(); [OnDeserialized] private void OnDeserializedHandler(StreamingContext context) { lock (_cacheLock) { if (_deserializedCache.TryGetValue(this, out _)) { // 重复实例,主动释放非托管资源,修正引用计数 ReleaseUnmanagedHandle(); return; } _deserializedCache.Add(this, null); // 原有反序列化逻辑 } } }
注意事项
BinaryFormatter已被微软标记为不安全的序列化组件,.NET 5+版本默认禁用,仅建议在完全可控的内部应用场景下使用,对外公开的接口不要使用BinaryFormatter做序列化处理。
内容的提问来源于stack exchange,提问作者LOST
相关产品推荐
相关产品推荐

