Introduction
Serialization has always been a tricky topic for programmers. Using C++ and MFC, I managed to develop a method of working that minimized the amount of effort needed. When I switched to C# and .NET programming, I noticed things got much worse instead of better. Microsoft promotes using automatic serialization, but if you want to do more fancy things like using versioning, you cannot use this technology. Custom serialization is a reasonable alternative. It took me quite some time to understand custom serialization with enough detail to be useful for my purpose. In the first part of this article, I covered the basics. In this part, I cover advanced topics, like serializing arrays, derived objects and versioning.
Using the Code
I provide one single solution space, with two sample projects. Serialization1
demonstrates the basics for custom serialization. The project Serialization2
is a more extensive example. For Serialization1
, the code is explained in the previous part of this article. Serialization2
will be covered in this article. Both projects use a single file named program.cs for all classes. I used a simple console application to keep things as simple as possible.
Both articles are included in the demo project.
Unlike the first article, I will not explain the code sequentially. I will cover four topics, which are mixed through the code sample:
- Using versioning
- Serialization and inheritance
- Serialization of an array of objects
- Serialization mixed types objects
Using Versioning
If you write an application and you want to add new functions later, you should make sure the new version is compatible with older versions. If you add a new object, during deserialization you should create a default value for this object when the application is stored using an older version.
In MFC, Microsoft supported a method called Versionable Schema to support versioning. This had lots of limitations, one of them that it does not support inheritance. I present a way of working for versioning that overcomes these limitations.
To each class, you should add a constant named Version (or what you like). This constant holds the actual version of the class. A derived class has its own Version constant. If you add new member variables to a class, you should increase the version number in this constant.
During serialization, you must serialize the class version number as well. The identifier must contain the class name in this case, because custom serialization does not really support inheritance. If you would use Version constants in the base class and in the derived class, the Soap Formatter will complain that it cannot serialize the same object twice.
Serialization goes like this:
[Serializable]
public class ObjectList:ISerializable
{
private const Int32 Version=1;
....
public virtual void GetObjectData(SerializationInfo info,
StreamingContext context)
{
info.AddValue("ObjectListVersion",Version);
....
}
}
During deserialization, in the deserialization constructor, you must first read the Version. Then, using a switch
statement you can handle different versions of this object.
protected ObjectList(SerializationInfo info,StreamingContext context)
{
Int32 Version=info.GetInt32("ObjectListVersion");
...
switch(Version)
{
case 1:
{
......
break;
}
default:
{
break;
}
}
}
Initially it gives some extra work, but as you see it is not difficult. The point is you must do it from the very beginning.
Serialization and Inheritance
The SOAP formatter does not really understand inheritance, it merely flattens your object structure. This is a problem, if you use a variable in a derived object with the same name it has in the base object. This is completely legal (the Version constant discussed before is a perfect example). So you must take care to provide enough context manually during serialization. If you forget, an exception will be raised.
During serialization, you must serialize the base object like this:
public override void GetObjectData(SerializationInfo info,
StreamingContext context)
{
base.GetObjectData(info,context);
info.AddValue("DerivedVersion",version);
....
}
All you need to do is make sure serialization identifiers are unique and you should call the method GetObjectData
for the base object. Do not forget to set the override modifier!
Deserialization is even more simple. In the special deserialization constructor, you just invoke the constructor for the base class first:
protected DerivedObject(SerializationInfo info,StreamingContext context):
base(info,context)
{
Int32 StoredVersion;
StoredVersion=info.GetInt32("DerivedVersion");
switch(StoredVersion)
{
case 1:
{
.....
break;
}
default:
{
break;
}
}
}
Serializing an Array of Objects
The SOAP formatter does not support serialization of generic types. You will have to do this manually. A problem is that you must provide a unique identification for each object.
I will show how to serialize a generic list of the type List<MyObject>
. The trick has two parts. You must tell the serializer how many objects you want to serialize and you must give each object a unique name. So, we obtain the number of objects to store and serialize this number. When retrieving, we first retrieve the number of objects and then the objects. For naming the objects, you can use a string
and append a sequence number to it.
This is the result:
public virtual void GetObjectData(SerializationInfo info,
StreamingContext context)
{
info.AddValue("ObjectListVersion",Version);
Int32 i=0;
Int32 ListLength=theList.Count;
info.AddValue("ListLength",ListLength);
for(i=0;i<ListLength;i++)
{
info.AddValue("Object"+i.ToString(),theList[i]);
}
}
Deserialization goes like this:
protected ObjectList(SerializationInfo info,StreamingContext context)
{
Type t_MyObject=Type.GetType("Serialization.MyObject");
Int32 Listlength=0;
Int32 Version=info.GetInt32("ObjectListVersion");
theList=new List<myobject __designer:dtid="562949953421364" />();
String str;
switch(Version)
{
case 1:
{
Listlength=info.GetInt32("ListLength");
for(Int32 i=0;i<Listlength;i++)
{
MyObject object1=(MyObject)
info.GetValue("Object"+i.ToString(),t_MyObject);
theList.Add(object1);
break;
}
break;
}
default:
{
break;
}
}
}
Note that you should add the object to the ObjectList
explicitly.
Serializing Mixed Object Types
By now, you may have guessed that you can serialize mixed object types in an array. You should serialize the object type. While deserializing, you must deserialize the object type and construct a switch
statement to deserialize the correct type.
In the code below, this is demonstrated:
public virtual void GetObjectData(SerializationInfo info,
StreamingContext context)
{
...
for(i=0;i<ListLength;i++)
{
Type t=theList[i].GetType();
switch(t.ToString())
{
case "Serialization.MyObject":
{
String str=t.ToString()+i.ToString();
info.AddValue("Type"+i.ToString(),t.ToString());
info.AddValue("Object"+i.ToString(),theList[i]);
break;
}
case "Serialization.DerivedObject":
{
String str=t.ToString()+i.ToString();
info.AddValue("Type"+i.ToString(),t.ToString());
info.AddValue("Object"+i.ToString(),theList[i]);
break;
}
default:
{
System.Console.WriteLine("Serialization error in ObjectList,
type "+t.ToString()+" unknown");
break;
}
}
}
}
Deserialization
protected ObjectList(SerializationInfo info,StreamingContext context)
{
Type t_MyObject=Type.GetType("Serialization.MyObject");
Type t_DerivedObject=Type.GetType("Serialization.DerivedObject");
Int32 Listlength=0;
Int32 Version=info.GetInt32("ObjectListVersion");
theList=new List<MyObject>();
String str;
switch(Version)
{
case 1:
{
TestString=(String)info.GetString("Test");
Listlength=info.GetInt32("ListLength");
for(Int32 i=0;i$lt;Listlength;i++)
{
str=info.GetString("Type"+i.ToString());
switch(str)
{
case "Serialization.MyObject":
{
MyObject object1=(MyObject)
info.GetValue("Object"+i.ToString(),t_MyObject);
theList.Add(object1);
break;
}
case "Serialization.DerivedObject":
{
DerivedObject object2=
(DerivedObject)
info.GetValue("Object"+i.ToString(),
t_DerivedObject);
theList.Add(object2);
break;
}
default:
{
break;
}
}
}
break;
}
default:
{
break;
}
}
}
XML File
I show the essential part of the XML file here without further comments. You may notice a minor issue in this file....
<SOAP-ENV:Envelope xmlns:xsi=...>
<SOAP-ENV:Body>
<a1:ObjectList id="ref-1" xmlns:a1=....>
<ObjectListVersion>1</ObjectListVersion>
<Test id="ref-3">Test string</Test>
<ListLength>3</ListLength>
<Type0 id="ref-4">Serialization.MyObject</Type0>
<Object0 href="#ref-5"/>
<Type1 id="ref-6">Serialization.DerivedObject</Type1>
<Object1 href="#ref-7"/>
<Type2 href="#ref-4"/>
<Object2 href="#ref-8"/>
</a1:ObjectList>
<a1:MyObject id="ref-5" xmlns:a1=....>
<Version>1</Version>
<Idx>1</Idx>
</a1:MyObject>
<a1:DerivedObject id="ref-7" xmlns:a1=....>
<Version>1</Version>
<Idx>2</Idx>
<DerivedVersion>1</DerivedVersion>
<Derived>5</Derived>
</a1:DerivedObject>
<a1:MyObject id="ref-8" xmlns:a1=....">
<Version>1</Version>
<Idx>3</Idx>
</a1:MyObject>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Conclusion
I believe these samples provide you with enough basic information to develop your own applications. I did not try to make foolproof code. The code provided is just meant to explain the principles. I leave it to you to add sufficient exception handling and error processing to create robust code.
I hope you will appreciate this contribution to The Code Project, which really is the very best site for code development.
History
- 18th August, 2007: First release