Jump to content
  • entries
    146
  • comments
    436
  • views
    198708

On .Net Serialization, Part 9

Sign in to follow this  
Washu

532 views

At this point we have enough done to write begin writing up the code required to serialize an object graph. Looking over our NetworkFormatter class however, we note that it really isn't in the domain of responsibility for it to write the objects out to the stream. So we're going to introduce a new class, classed ObjectWriter, to deal with the actual serialization of the objects. The actual serialization process is a recursive process which involves fetching each field of the object, and then calling a write function on it. This will then repeat for each field within the object that is the field data. Of course, as you can guess, if we have any circular dependancies, then we will end up with a recursive loop that will end in a stack overflow. While it is true that we could write this as an iterative process, the code to do it is not nearly as pretty nor as easily maintained as a recursive process, so recursion it is.

internal class ObjectWriter {
public ObjectWriter(AssemblyTypeInformation typeInformationStore) {
this.typeInformationStore = typeInformationStore;
}
public void Serialize(BinaryWriter serializationStream, object graph) {
Type graphType = graph.GetType();
WriteObject(serializationStream, graphType, graph);
}
The first part is fairly straight forward, the constructor takes the AssemblyTypeInformation, which we have built up over the last few entries, and stores it in a field. The Serialize() method takes the binary writer we created in the Serialize() method of the NetworkFormatter class. It then gets the type of the object, and passes all three on to the WriteObject() method, which is the real workhorse of this class.

private void WriteObject(BinaryWriter serializationStream, Type graphType, object graph) {
MethodInfo serializationMethod = typeof(BinaryWriter).GetMethod(
"Write", new Type[] { graphType });
if (serializationMethod != null) {
WritePrimitive(serializationStream, graph, serializationMethod);
} else {
WriteSerializableObject(serializationStream, graphType, graph);
}
}
As you can see, this is a real workhorse method. Does a lot of work, eh? Quite simply it just checks if the BinaryWriter class has a method for writing out the current type. If it does, then it writes out the object using that writer, which is done by calling the WritePrimitive() method. If the type is not writable by the BinaryFormatter class then we assume that it is another serializable object, and call the WriteSerializableObject method. The reflection done in the initial part could be done using another lookup table, like those we built in the AssemblyTypeInformation class.

private void WriteSerializableObject(BinaryWriter serializationStream, Type graphType, object graph) {
int id = typeInformationStore.GetIdFromType(graphType);

serializationStream.Write(id);
WriteFields(serializationStream, graphType, graph);
}
Another hardcore method, this one simply writes out the object type id, and then it calls the WriteFields() method. One thing you should be getting from all of this is that small methods which express their intention clearly is a good coding style. Large methods with lots of conditionals makes for hard to read code. Code that is hard to read is hard to maintain, and generally hard to debug.

private void WriteFields(BinaryWriter serializationStream, Type graphType, object graph) {
foreach (FieldInfo field in typeInformationStore.GetFieldsFromType(graphType)) {
WriteObject(serializationStream, field.FieldType, field.GetValue(graph));
}
}
Another facinating method. This one loops through all of the serializable fields from the object, obtained by calling the GetFieldsFromType() method. It then simply calls the WriteObject() method. The type and value of the field (obtained by calling on the object FieldInfo.GetValue(object) method). Obviously this recursive loop will only stop when it encounters an object with only primitive types.

private static void WritePrimitive(BinaryWriter serializationStream, object graph, MethodInfo serializationMethod) {
serializationMethod.Invoke(serializationStream, new object[] { graph });
}
Perhaps the simplest method of the lot, it simply invokes the method we obtained in the WriteObject() method, passing the BinaryWriter instance, and the object to write. If you are wondering why I wrapped up such a small piece of code, the answer is simple, it kept the code in the WriteObject() method at the same level (for the most part), and easy to read.

Now that the ObjectWriter class is out of the way, we can simply plug it into the NetworkSerializer in place of our current calls to get the ID and to write said ID out. However, before we do that, we should consider one more thing. We wish to ensure that the stream always starts with an object ID, else we will not be able to know what the stream contains, and hence how to deserialize it. Hence we cannot simply allow a primitive to be passed so the Serialize() method of the NetworkFormatter class. So, we should add a new method to the AssemblyTypeInformation class, say AssertIsAssemblyType(), which we will call in the Serialize() method of the ObjectWriter class. This will throw a SerializationException if the type is not contained within the type map we built. This is, of course, left as an exercise to the reader, but it should be fairly simple to do. If you are wondering why we don't just call the GetIdFromType() method, since it will do exactly that, the simple answer is: That is not the purpose for which it was written, and we wish to keep the code as readable as possible. Having an oddball call to a "Get" function will appear out of place, and will confuse the general observer. Best to write a specialized method for this purpose, even if it is written in terms of the GetIdFromType() method.
Sign in to follow this  


0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!