I'm currently implementing persistence in my side project and I wanted to use Json as it's supported by our likely caching technologies (MongoDB or Redis) and it doesn't have the verbosity of Xml. I've previously serialized to binary and didn't want the headaches of dealing with upgrades to objects. It's just easier dealing with text based serialization as the serialized objects can be modified without having to go down the whole SerializationSurrogate side of things. That will hopefully make product upgrades go smoother.
Having chosen Newtonsoft's Json library, I did a proof of concept program to see whether it would be up for the job. I don't want to serialize everthing in my objects, on deserialization I can get a fair amount of the components from the cache rather than having to serialize them. All I need to serialize is the unique identifier that allows me to find them again. That said, I didn't want to have to create classes to deal with each object's deserialization, I wanted each object to be able to know how to unpack itself.
Therefore, it was time to get to grips with how the OnDeserializing and OnDeserialized attributes work. I created the following classes:
[Serializable] public class BaseClass { public string StringProp { get; set; } [OnDeserialized] private void OnDeserializedMethod(StreamingContext context) { Console.WriteLine("BaseClass.OnDeserialized ({0}) - {1}", GetHashCode(), StringProp); } [OnDeserializing] private void OnDeserializingMethod(StreamingContext context) { Console.WriteLine("BaseClass.OnDeserializing ({0}) - {1}", GetHashCode(), StringProp); } } [Serializable] public class Subclass : BaseClass { public double DoubleProp { get; set; } [OnDeserialized] private void OnDeserialized(StreamingContext context) { Console.WriteLine("Subclass.OnDeserialized ({0}) - {1}", GetHashCode(), DoubleProp); } [OnDeserializing] private void OnDeserializing(StreamingContext context) { Console.WriteLine("Subclass.OnDeserializing ({0}) - {1}", GetHashCode(), DoubleProp); } } [Serializable] public class TestContainer { public IEnumerable<BaseClass> WrappedObjects { get; set; } [OnDeserialized] private void OnDeserializedMethod(StreamingContext context) { Console.WriteLine("TestContainer.OnDeserialized ({0})", GetHashCode()); } [OnDeserializing] private void OnDeserializing(StreamingContext context) { Console.WriteLine("TestContainer.OnDeserializing ({0})", GetHashCode()); } }
Serialization was handled like so:
var first = new BaseClass { StringProp = "First", IntProp = 1 }; var second = new BaseClass { StringProp = "Second", IntProp = 2 }; var third = new Subclass { StringProp = "Third", IntProp = 3, DoubleProp = 3.0 }; var container = new TestContainer { WrappedObjects = new BaseClass[] { first, second, third } }; var serialized = JsonConvert.SerializeObject(container); var deserialized = JsonConvert.DeserializeObject<TestContainer>(serialized);
Which resulted in the following output:
TestContainer.OnDeserializing (38104455)
BaseClass.OnDeserializing (578700) -
BaseClass.OnDeserialized (578700) - First
BaseClass.OnDeserializing (21411931) -
BaseClass.OnDeserialized (21411931) - Second
BaseClass.OnDeserializing (54043951) -
BaseClass.OnDeserialized (54043951) - Third
TestContainer.OnDeserialized (38104455)
We can see that the container and contained elements are deserializing as expected, however the third element isn't going into the subclass' OnDeserialized method, it only executes the base class' method. This isn't what we wanted. This is due to the fact that the Json doesn't include type information in the serialized object by default. We need to add a JsonSerializationSettings instance and specify that type info should be included:
var first = new BaseClass { StringProp = "First", IntProp = 1 }; var second = new BaseClass { StringProp = "Second", IntProp = 2 }; var third = new Subclass { StringProp = "Third", IntProp = 3, DoubleProp = 3.0 }; var container = new TestContainer { WrappedObjects = new BaseClass[] { first, second, third } }; var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto }; var serialized = JsonConvert.SerializeObject(container, settings); var deserialized = JsonConvert.DeserializeObject<TestContainer>(serialized, settings);
This gives the following program output:
TestContainer.OnDeserializing (13476220)
BaseClass.OnDeserializing (15654122) -
BaseClass.OnDeserialized (15654122) - First
BaseClass.OnDeserializing (37839230) -
BaseClass.OnDeserialized (37839230) - Second
BaseClass.OnDeserializing (7904578) -
Subclass.OnDeserializing (7904578) - 0
BaseClass.OnDeserialized (7904578) - Third
Subclass.OnDeserialized (7904578) - 3
TestContainer.OnDeserialized (13476220)
That's more like it! There are a couple things we need to understand here:
- The OnDeserialized and OnDeserializing methods are private in the BaseClass and Subclass definitions. If you try to change the methods to "protected virtual" in the base class and override them in the subclass you will get a runtime error. There's no need to do that though as they are firing in the expected order of Container class, Base class, Subclass. Just like how an instances of objects in an inheritance tree are constructed, you can rely on the fact that the base class methods will be called before subclass methods.
- The member variables are deserialized by the framework sometime between the OnDeserializing and OnDeserialized method calls. This is handy if you want to pass state from the container class to the subclasses. That's not relevant to this example but I do need that functionality for my side project.
No comments:
Post a Comment