奇怪的反序列化错误,子对象没有完全反序列化。

3

我刚刚在二进制序列化中发现了一个奇怪的行为:当我反序列化类中的字典并立即尝试向其中添加内容时,由于它没有完全初始化,我会收到一个错误:

[Serializable]
class Foo : ISerializable
{
    public Dictionary<int, string> Dict { get; private set; }

    public Foo()
    {
        Dict = new Dictionary<int, string>();
    }

    public Foo(SerializationInfo info, StreamingContext context)
    {
        Dict = (Dictionary<int, string>)info.GetValue("Dict", typeof(Dictionary<int, string>));
        Dict.Add(99, "test"); // Error here
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("Dict", Dict);
    }

}

在我向字典添加数据的这行代码中,我遇到了NullReferenceException异常,但是Dict属性不为null:它已经被实例化,但没有初始化(所有字段都为0或null)。我怀疑它只是通过FormatterServices.GetUninitializedObject创建,但尚未实际反序列化。
我理解此时,也许它不应该完全初始化。因此,我尝试了另一种方法,通过实现IDeserializationCallback接口。MSDN上说:
“作为支持对象图反序列化完成后调用的方法的一部分来实现当前接口。 如果一个对象需要在其子对象上执行代码,它可以延迟此操作,实现IDeserializationCallback,并仅在回调此接口时执行代码。”
所以这似乎正是我需要的,我期望在调用OnDeserialization时我的字典已经完全初始化... 但我得到了相同的错误!
[Serializable]
class Foo : IDeserializationCallback
{
    public Dictionary<int, string> Dict { get; private set; }

    public Foo()
    {
        Dict = new Dictionary<int, string>();
    }

    public void OnDeserialization(object sender)
    {
        Dict.Add(99, "test"); // Error here
    }
}

既然IDeserializationCallback是设计用来在子对象上执行代码的,我希望在此时子对象已经完全初始化。请注意,如果我手动在字典上调用OnDeserialize,它可以正常工作,但我不认为我应该这样做...

这种行为正常吗?有人能解释一下这里发生了什么吗?


我已经检查过了,[OnDeserialized] 属性也没有帮助。此外,我已经检查了 .NET 源代码,并根据它们的说法,在调用 [OnDeserialized]IDeserializationCallback.OnDeserialization 之一时应该初始化所有内容。 - Alexander Yezutov
当整个对象图被反序列化时,将调用OnDeserialization方法。我怀疑真正的问题在于序列化代码中,从这里看不出来。 - Hans Passant
2个回答

1
如果您在反序列化处理程序中添加Dict.OnDeserialization(sender),那么一切都会变得很好。
所以,这样做是有效的:
    [Serializable]
    class Foo : IDeserializationCallback
    {
        public Dictionary<int, string> Dict { get; private set; }

        public Foo()
        {
            Dict = new Dictionary<int, string>();
        }

        public void OnDeserialization(object sender)
        {
            // The dictionary is initialized with values in next line
            Dict.OnDeserialization(sender);
            Dict.Add(99, "test");
        }
    }

从源代码来看,字典在整个对象图已经反序列化之后才对其内部进行后处理,通过OnDeserialization。这个钩子在对象中不是确定性地被调用,因此如果您的Foo需要调用字典的'OnDeserializing'方法,则必须手动调用该字典的方法。根据源代码注释,它可以被多次调用而没有问题。(第一次之后的调用将导致无操作。)请注意,出于同样的原因,您应该在Foo的OnDeserialization方法中放置一个检查以防止多次调用。 - Adam Klein

0

你需要在 OnDeserialization 处理程序中实例化你的 Dictionary<>。

public void OnDeserialization(object sender)
{
    Dict = new Dictionary<int, string>();
    Dict.Add(99, "test"); // Error here
}

如果我这样做,它就不会是我序列化的字典了... 不管怎样,我认为你误解了我的问题:我不是在问如何解决这个问题,我已经有解决方法了。我的问题是为什么它会像这样行为,因为文档建议了不同的行为。 - Thomas Levesque

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接