二进制序列化后静态实例引用失效引发KeyNotFoundException问题求助
首先,咱们得搞清楚为什么会出现这个问题:BinaryFormatter的序列化逻辑是把对象的完整状态保存下来,反序列化时会创建一个全新的实例——哪怕原对象是静态成员也不例外。静态成员的“静态”只是说这个成员属于类本身,全局只有一个实例,但序列化机制并不会识别这一点,它只关心当前要序列化的对象的内容,反序列化时就老老实实根据序列化的数据造新对象,所以你反序列化得到的sword和原静态的sword完全是两个不同的实例,字典自然找不到Key。
至于你说的单例模式不可行,这很正常——单例是让整个Weapon类只有一个实例,而你需要的是sword和axe两个不同的实例,单例显然和这个需求冲突,所以这条路走不通。
那怎么让反序列化后的实例和原静态实例“认亲”呢?这里有几个靠谱的方案:
方案一:实现IObjectReference接口(推荐)
这个接口的作用就是让你在反序列化时,指定返回已有的对象(比如你的静态实例),而不是用新创建的临时实例。
修改你的Weapon类,实现IObjectReference:
using System.Runtime.Serialization; [Serializable] public class Weapon : IObjectReference { public string name; public Weapon(string name) { this.name = name; } // 反序列化时会调用这个方法,返回实际要使用的对象 public object GetRealObject(StreamingContext context) { // 根据name匹配对应的静态实例 return name switch { "Sword" => Program.sword, "Axe" => Program.axe, _ => this // 如果是未知的武器名称,返回当前实例(也可以抛出异常) }; } }
原理很简单:当BinaryFormatter反序列化出Weapon的临时实例后,会调用GetRealObject方法,用你返回的静态实例替换掉这个临时实例。这样字典里的Key就和原静态实例是同一个了,Des[sword]自然不会抛出KeyNotFoundException。
方案二:重写Equals和GetHashCode(值相等替代实例相等)
如果你不需要严格的实例同一,只需要字典能通过武器名称识别Key,那可以让Weapon类重写这两个方法,基于name来判断相等性:
[Serializable] public class Weapon { public string name; public Weapon(string name) { this.name = name; } public override bool Equals(object obj) { return obj is Weapon weapon && name == weapon.name; } public override int GetHashCode() { return HashCode.Combine(name); } }
这样哪怕是不同实例,只要name相同,字典就会认为是同一个Key。不过要注意:如果有两个Weapon实例的name相同,会导致字典冲突,所以这个方案只适用于name是唯一标识的场景。
方案三:手动替换字典Key(繁琐但直接)
反序列化完成后,遍历字典的键值对,把每个Key替换成对应的静态实例,重新构建字典:
// 反序列化后执行这段代码 var newDes = new Dictionary<Weapon, int>(); foreach (var kvp in Des) { Weapon realWeapon = kvp.Key.name switch { "Sword" => Program.sword, "Axe" => Program.axe, _ => kvp.Key }; newDes[realWeapon] = kvp.Value; } Des = newDes;
这个方法比较直观,但代码量更大,不如前两种优雅,适合临时解决问题。
最后再澄清一下你对静态的误解:静态成员只是类级别的唯一实例,但它本质上还是一个普通的对象,序列化机制不会因为它是静态就特殊处理——序列化的是对象的状态,不是对象的“身份”(比如它是不是静态成员),所以反序列化必然会生成新实例,这是正常的逻辑,你并没有误解静态的核心,只是没搞清楚序列化的行为而已。
内容的提问来源于stack exchange,提问作者user2998964

