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

如何为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区分不同实例:

  1. 实现序列化代理类
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;
    }
}
  1. 使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 06:57:04