Resolving Circular References for Objects Implementing ISerializable

为君一笑 提交于 2019-12-05 22:02:36
Wesley Hill

This situation is the reason for the FormatterServices.GetUninitializedObject method. The general idea is that if you have objects A and B which reference each other in their SerializationInfo, you can deserialize them as follows:

(For the purposes of this explanation, (SI,SC) refers to a type's deserialization constructor, i.e. the one which takes a SerializationInfo and a StreamingContext.)

  1. Pick one object to deserialize first. It shouldn't matter which you pick, as long as you don't pick one which is a value-type. Lets say you pick A.
  2. Call GetUninitializedObject to allocate (but not initialize) an instance of A's type, because you're not yet ready to call its (SI,SC) constructor.
  3. Build B in the usual way, i.e. create a SerializationInfo object (which will include the reference to the now half-deserialized A) and pass it to B's (SI,SC) constructor.
  4. Now you have all the dependencies you need to initialize your allocated A object. Create it's SerializationInfo object and call A's (SI,SC) constructor. You can call a constructor on an existing instance via reflection.

The GetUninitializedObject method is pure CLR magic - it creates an instance without ever calling a constructor to initialize that instance. It basically sets all fields to zero/null.

This is the reason you are cautioned not to use any of the members of a child object in a (SI,SC) constructor - a child object may be allocated but not yet initialized at that point. It is also the reason for the IDeserializationCallback interface, which gives you a chance to use your child objects after all object initialization is guaranteed to be done and before the deserialized object graph is returned.

The ObjectManager class can do all of this (and other types of fix-ups) for you. However, I've always found it to be quite under-documented given the complexity of deserialization, so I never spent the time to try figure out how to use it properly. It uses some more magic to do step 4 using some internal-to-the-CLR reflection optimized to call the (SI,SC) constructor quicker (I've timed it at about twice as fast as the public way).

Finally, there are object graphs involving cycles which are impossible to deserialize. One example is when you have a cycle of two IObjectReference instances (I've tested BinaryFormatter on this and it throws an exception). Another I suspect is if you have a cycle involving nothing but boxed value-types.

You need to detect that you have used the same object more than once in your object graph, tag each object in the output, and when you come to occurance #2 or higher, you need to output a "reference" to an existing tag instead of the object once more.

Pseudo-code for serialization:

for each object
    if object seen before
        output tag created for object with a special note as "tag-reference"
    else
        create, store, and output tag for object
        output tag and object

Pseudo-code for deserialization:

while more data
    if reference-tag to existing object
        get object from storage keyed by the tag
    else
        construct instance to deserialize into
        store object in storage keyed by deserialized tag
        deserialize object

It is important that you do the last steps there in the order they're specified, so that you can correct handle this case:

SomeObject obj = new SomeObject();
obj.ReferenceToSomeObject = obj;    <-- reference to itself

ie. you cannot store the object into your tag-storage after you've completely deserialized it, since you might need a reference to it in the storage while you are deserializing it.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!