Introduction
Using serialization objects are persisted which allows us to store them easily in files or databases. E.g.: Storing configuration settings of an application, storing custom settings of a user for an application, saving the state of an application. Since we have persisted forms of objects, we can transfer them between applications. Deserialization is the opposite of serialization, which creates the object from its persisted form.
Objects are serialized into streams during serialization. FileStream
can be stored to persist the objects in files and MemoryStream
can be used to store the serialized form of the object in memory.
.NET supports XML, SOAP and Binary serializations.
XML Serialization
Creating the Object
Let’s create our class for the serialization sample. Exam
class contains a header and a set of questions:
public class Exam{
public Exam(){
header=new Header();
}
private Header header;
private Question[] questions;
public Header Header{
get{return header;}
set{header=value;}
}
public Question[] Questions{
get{return questions ;}
set{questions=value;}
}
}
public class Header{
public Header(){}
private string title;
public string Title{
get{return title;}
set{title=value;}
}
}
public class Question{
public Question(){}
private int id;
private string[] items;
public int ID{
get{return id;}
set{id=value;}
}
public string[] Items{
get{return items;}
set{items=value;}
}
}
And we initialize the object:
Exam exam=new Exam();
exam.Header=new Header();
exam.Header.Title="Exam title";
exam.Questions=new Question[1];
exam.Questions[0]=new Question();
exam.Questions[0].ID=1;
exam.Questions[0].Title=
"What is your favourite serialization method?";
exam.Questions[0].Items=new string[2];
exam.Questions[0].Items[0]="Xml";
exam.Questions[0].Items[1]="Soap";
Serializing the Object into a File using XML Serialization
public static void ToXml(Object objToXml,
string filePath,bool includeNameSpace) {
StreamWriter stWriter=null;
XmlSerializer xmlSerializer;
try {
xmlSerializer = new XmlSerializer(objToXml.GetType());
stWriter = new StreamWriter(filePath);
if (!includeNameSpace){
System.Xml.Serialization.XmlSerializerNamespaces xs=
new XmlSerializerNamespaces();
xs.Add("","");
xmlSerializer.Serialize(stWriter, objToXml,xs);
}
else{
xmlSerializer.Serialize(stWriter, objToXml);
}
}
catch(Exception exception){
throw exception;
}
finally {
if(stWriter!=null) stWriter.Close();
}
}
Our method takes the file path to serialize the object. We pass the object to serialize and the method creates a file serializing the object. And the file content would be:
="1.0"="utf-8"
<Exam>
<Header>
<Title>Exam title</Title>
</Header>
<Questions>
<Question>
<ID>1</ID>
<Title>What is your favourite serialization method?</Title>
<Items>
<string>Xml</string>
<string>Soap</string>
</Items>
</Question>
</Questions>
</Exam>
DeSerializing the Object from the XML File
The following method can be used to deserialize an object from a file:
public static object XmlToFromFile(string filePath,
Type type) {
XmlSerializer xmlSerializer;
FileStream fileStream=null;
try {
xmlSerializer = new XmlSerializer(type);
fileStream = new FileStream(filePath,
FileMode.Open,FileAccess.Read);
object objectFromXml=
xmlSerializer.Deserialize(fileStream );
return objectFromXml;
}
catch(Exception Ex) {
throw Ex;
}
finally {
if(fileStream!=null) fileStream.Close();
}
}
Serializing the Object into a String in Memory using XML Serialization
Sometimes, we need to store the serialized form of an object in memory. We can use MemoryStream
to store the serialized form of an object in memory.
public static string ToXml(Object objToXml,
bool includeNameSpace) {
StreamWriter stWriter=null;
XmlSerializer xmlSerializer;
string buffer;
try {
xmlSerializer =
new XmlSerializer(objToXml.GetType());
MemoryStream memStream = new MemoryStream();
stWriter = new StreamWriter(memStream);
if (!includeNameSpace){
System.Xml.Serialization.XmlSerializerNamespaces xs=
new XmlSerializerNamespaces();
xs.Add("","");
xmlSerializer.Serialize(stWriter, objToXml,xs);
}
else{
xmlSerializer.Serialize(stWriter, objToXml);
}
buffer = Encoding.ASCII.GetString(memStream.GetBuffer());
}
catch(Exception Ex){
throw Ex;
}
finally {
if(stWriter!=null) stWriter.Close();
}
return buffer;
}
DeSerializing the Object from an XML String in Memory
Of course, you will need a deserializer to convert in memory string
s to objects!
public static object XmlTo(string xmlString,Type type) {
XmlSerializer xmlSerializer;
MemoryStream memStream=null;
try {
xmlSerializer = new XmlSerializer(type);
byte[] bytes=new byte[xmlString.Length];
Encoding.ASCII.GetBytes(xmlString,0,xmlString.Length,bytes,0);
memStream = new MemoryStream(bytes);
object objectFromXml= xmlSerializer.Deserialize(memStream);
return objectFromXml;
}
catch(Exception Ex) {
throw Ex;
}
finally {
if(memStream!=null) memStream.Close();
}
}
Changing the Class
Adding New Properties
Let’s add a description
property to the header
class. The header
class is shown below:
public class Header{
public Header(){}
private string title;
private string description;
public string Title{
get{return title;}
set{title=value;}
}
public string Description{
get{return description;}
set{description=value;}
}
}
We can still deserialize the objects without getting any errors. Values that do not exist in the serialized form are set to initial values. In this case, description
property is set to null
.
Removing a Property
Now, we want to remove the description
property from the Header
class. We want to deserialize an object from a file having the description
property in the header
section. Do we get any errors? We can deserialize successfully without any error! Removing or adding new properties does not cause any problem with XML serialization.
Controlling XML Serialization using Attributes
Serialization can be controlled using attributes. A property can be marked to be excluded from serialization or it can be serialized as an XML attribute or XML element. SOAP/Binary serialization attributes differ from XML serialization attributes. The sample uses some of the attributes to explain how to control serialization.
Changing the Text of a Property while Serializing
Suppose that you want to see “QuestionTitle
” in the file that the object is serialized to instead of “Title
” tag inside the question tags. But you want to refer to the same property in the code. To do that, we should set the new value using the XML attribute as shown below:
[System.Xml.Serialization.XmlElementAttribute("QuestionTitle")]
public string Title{
get{return title;}
set{title=value;}
}
If you apply this attribute to an array property, it would cause the outer tag to disappear.
[System.Xml.Serialization.XmlElementAttribute("Item")]
public string[] Items{
get{return items;}
set{items=value;}
}
Final output would be:
="1.0"="utf-8"
<Exam>
<Header>
<Title>Exam title</Title>
<Description>Exam description</Description>
</Header>
<Questions>
<Question>
<ID>1</ID>
<QuestionTitle>What is your favourite serialization method?
</QuestionTitle>
<Item>Xml</Item>
<Item>Soap</Item>
</Question>
</Questions>
</Exam>
Adding an ArrayList to a Class
Let’s change the Exam
class so that we can use ArrayList
for questions instead of an array.
public class Exam{
public Exam(){
header=new Header();
questions=new ArrayList();
}
private Header header;
private ArrayList questions;
public Header Header{
get{return header;}
set{header=value;}
}
public ArrayList Questions{
get{return questions ;}
set{questions=value;}
}
}
When we run the sample, we get InvalidOperationException
with “The type SerializationSamples.Question
was not expected. Use the XmlInclude
or SoapInclude
attribute to specify types that are not known statically.” inner exception.
We can specify the type in the ArrayList
using XmlArrayItem
attribute. It allows us to be able to use ArrayList
s while serializing the classes.
[XmlArrayItem(typeof(Question))]
public ArrayList Questions{
get{return questions ;}
set{questions=value;}
}
Now the serialization works as it was, generating the same output to the file.
Customizing the Serialization
Sometimes, we need to control XML serialization. In this case, we can override serialization to change its default behaviour. We create our own StreamWriter
and use this class while serializing. For example, let’s remove the XML tag from our XML output. So we will not see “<?xml version="1.0" encoding="utf-8"?>
” line in the output anymore.
Example: Removing XML Version Line
To control everything during serialization, ISerializable
interface must be implemented instead of using .NET serialization.
Let’s write the class. It is called SpecialXmlWriter
and it is inherited from XmlTextWriter
. Its constructor takes a parameter to include the XML version line and WriteStartDocument
is overridden:
public class SpecialXmlWriter:XmlTextWriter
{
bool m_includeStartDocument=true;
public SpecialXmlWriter(TextWriter tw,
bool includeStartDocument):base(tw) {
m_includeStartDocument=includeStartDocument;
}
public SpecialXmlWriter(Stream sw,Encoding encoding,
bool includeStartDocument):base(sw,null) {
m_includeStartDocument=includeStartDocument;
}
public SpecialXmlWriter(string filePath,Encoding encoding,
bool includeStartDocument):base(filePath,null) {
m_includeStartDocument=includeStartDocument;
}
public override void WriteStartDocument() {
if (m_includeStartDocument) {
base.WriteStartDocument();
}
}
Now let’s write another overloaded method for serialization. In this method, we used SpecialXmlWriter
class instead of StreamWriter
and passed the includeStartDocument
parameter to the SpecialXmlWriter
’s constructor.
public static void ToXml(Object objToXml,
string filePath,
bool includeNameSpace,
bool includeStartDocument) {
SpecialXmlWriter stWriter=null;
XmlSerializer xmlSerializer;
try {
xmlSerializer =
new XmlSerializer(objToXml.GetType());
stWriter = new SpecialXmlWriter(filePath,null,
includeStartDocument);
System.Xml.Serialization.XmlSerializerNamespaces xs=
new XmlSerializerNamespaces();
xs.Add("","");
xmlSerializer.Serialize(stWriter, objToXml,xs);
}
catch(Exception Ex) {
throw Ex;
}
finally {
if(stWriter!=null) stWriter.Close();
}
}
SOAP Serialization
- During SOAP and binary serializatons,
private
fields, type information including assembly, namespace, public key token information are also serialized. - Deserialization gives the same object as it was before serialization.
- Serialization can be controlled to exclude a field using [
NonSerialized
] attribute. - [
Serializable
] attribute is used to mark a class as serializable. It does not have to be put when XML serialization is used. When a class is marked with this attribute, runtime does all the work. However, it must be placed for each class that is to be serialized. Classes do not inherit this attribute from their base classes. To serialize a class, all the members of the class have to be serializable, otherwise it cannot be serialized. - To use SOAP and binary serialization, classes do not need to have a parameterless constructor. Because of performance issues, the constructor of the class is not called during deserialization.
IDictionary
objects which cannot be serialized using XML serialization can be serialized using SOAP/Binary serialization.
- Binary Serialization:
- Compact serialization
- Performs better
- Creates byte stream
- SOAP Serialization
- Creates SOAP messages
- Use if the serialization and deserialization platforms are not .NET
- Use if the message is sent through a firewall
- Easy debugging
Both SOAP and binary formatters inherit from IFormatter
interface. The SOAP examples in this article can easily be changed to binary serialization samples by just changing the creation of formatter as shown below:
Formatter=new BinaryFormatter();
Serializing the Object into a File using SOAP Serialization
public static string ToSoap(Object objToSoap,
string filePath) {
IFormatter formatter;
FileStream fileStream=null;
string strObject="";
try{
fileStream = new FileStream(filePath,
FileMode.Create,FileAccess.Write);
formatter = new SoapFormatter();
formatter.Serialize(fileStream, objToSoap);
}
catch(Exception exception){
throw exception;
}
finally{
if (fileStream!=null) fileStream.Close();
}
return strObject;
}
Our method takes the file path and the object to serialize, and the file is created with the content shown below:
<SOAP-ENV:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<a1:Exam id="ref-1"
xmlns:a1="http://schemas.microsoft.com/clr/nsassem/
SerializationSamples.SOAP/SerializationSamples%2C%20Version%3D1.0.0.0
%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<header href="#ref-3"/>
<questions href="#ref-4"/>
</a1:Exam>
<a1:Header id="ref-3"
xmlns:a1="http://schemas.microsoft.com/clr/nsassem/
SerializationSamples.SOAP/SerializationSamples%2C%20Version
%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<title id="ref-5">Exam title</title>
<description id="ref-6">Exam description</description>
</a1:Header>
<a2:ArrayList id="ref-4"
xmlns:a2="http://schemas.microsoft.com/clr/ns/System.Collections">
<_items href="#ref-7"/>
<_size>1</_size>
<_version>1</_version>
</a2:ArrayList>
<SOAP-ENC:Array id="ref-7" SOAP-ENC:arrayType="xsd:anyType[16]">
<item href="#ref-8"/>
</SOAP-ENC:Array>
<a4:Question id="ref-8"
xmlns:a4="http://schemas.microsoft.com/clr/nsassem/
SerializationSamples/SerializationSamples%2C%20Version
%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<id>1</id>
<title id="ref-9">What is your favourite serialization method?</title>
<items href="#ref-10"/>
</a4:Question>
<SOAP-ENC:Array id="ref-10" SOAP-ENC:arrayType="xsd:string[2]">
<item id="ref-11">Xml</item>
<item id="ref-12">Soap</item>
</SOAP-ENC:Array>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
DeSerializing the Object from the SOAP File
The following method can be used to deserialize an object from a SOAP file:
public static object SoapToFromFile(string filePath) {
IFormatter formatter;
FileStream fileStream=null;
Object objectFromSoap=null;
try{
fileStream = new FileStream(filePath,
FileMode.Open,FileAccess.Read);
formatter = new SoapFormatter();
objectFromSoap=formatter.Deserialize(fileStream);
}
catch(Exception exception){
throw exception;
}
finally{
if (fileStream!=null) fileStream.Close();
}
return objectFromSoap;
}
Serializing the Object into a String in Memory using SOAP Serialization
public static string ToSoap(Object objToSoap) {
IFormatter formatter;
MemoryStream memStream=null;
string strObject="";
try{
memStream = new MemoryStream();
formatter = new SoapFormatter();
formatter.Serialize(memStream, objToSoap);
strObject =
Encoding.ASCII.GetString(memStream.GetBuffer());
int index=strObject.IndexOf("\0");
if (index>0){
strObject=strObject.Substring(0,index);
}
}
catch(Exception exception){
throw exception;
}
finally{
if (memStream!=null) memStream.Close();
}
return strObject;
}
DeSerializing the Object from a SOAP String in Memory
public static object SoapTo(string soapString) {
IFormatter formatter;
MemoryStream memStream=null;
Object objectFromSoap=null;
try{
byte[] bytes=new byte[soapString.Length];
Encoding.ASCII.GetBytes( soapString, 0,
soapString.Length, bytes, 0);
memStream = new MemoryStream(bytes);
formatter = new SoapFormatter();
objectFromSoap=
formatter.Deserialize(memStream);
}
catch(Exception exception){
throw exception;
}
finally{
if (memStream!=null) memStream.Close();
}
return objectFromSoap;
}
Making Changes in the Class before Deserializing
Adding a new property or changing the namespace does not effect the XML serialization. But if SOAP/Binary serialization is used, you should be very careful. SOAP/Binary serialization directly deals with members and also serializes the type information. In .NET, type information also includes the assembly name and namespace. Changing one of these may impact the application. Adding a new member or updating the members may cause problems. It may not be possible to deserialize the object back.
Adding a Property
Let’s add a new property with the type of string
to our Exam
class and try to deserialize the object from a file that is serialized with the previous version of the class. We have added Author
property. Our Exam
class becomes as follows:
[Serializable]
public class Exam{
public Exam(){
header=new Header();
questions=new ArrayList();
}
private Header header;
private ArrayList questions;
private string author;
public Header Header{
get{return header;}
set{header=value;}
}
public ArrayList Questions{
get{return questions ;}
set{questions=value;}
}
public string Author{
get{return author ;}
set{author=value;}
}
}
When we run the application, we get the following exception "An unhandled exception of type 'System.Runtime.Serialization.SerializationException' occurred in SerializationSamples.exe
".
Additional information: "Wrong number of Members. Object SerializationSamples.SOAP.Exam has 3 members, number of members deserialized is 2
".
SOAP serialization serializes all members of the class and looks for all of them while serializing.
To overcome this problem, let’s start from the beginning. We remove the Author
property to start from scratch. We add a Hashtable
named “properties
” for future enhancements to our class regardless of whether we use it or not. Currently we are not using this Hashtable
but it will be serialized as an empty Hashtable
. In future, if we want to add a new property, we will be storing and getting them from the hashtable
. Since the Hashtable
is already serialized, newly added properties can successfully be deserialized and serialized again. If the property is not stored inside the Hashtable
, it can be set to a default value.
Our class becomes as follows:
[Serializable]
public class Exam{
public Exam(){
header=new Header();
questions=new ArrayList();
}
private Header header;
private ArrayList questions;
private Hashtable properties;
public Header Header{
get{return header;}
set{header=value;}
}
public ArrayList Questions{
get{return questions ;}
set{questions=value;}
}
}
Now, add the Hashtable
to the Exam
class, initialize it and serialize the class, so that we can test the next step to understand if we can still add a new property.
public Exam(){
header=new Header();
questions=new ArrayList();
properties=new Hashtable();
}
private Hashtable properties;
Let’s add the Author
property and try to deserialize the object from an existing file. Author
property uses the Hashtable
to store and get its value. It does not use a class member, otherwise deserialization would break.
public string Author{
get{
if (properties[PROPERTYNAME.AUTHOR]==null){
return "";
}
else{
return (string)properties[PROPERTYNAME.AUTHOR];
}
}
set{
properties[PROPERTYNAME.AUTHOR]=value;
}
}
And the PROPERTYNAME
enumerations:
public enum PROPERTYNAME{
AUTHOR=0
}
While adding a new property to the class, we add a new enum
to the PROPERTYNAME
enumeration and use this value as a key to get and store values inside the Hashtable
.
Changing the Type Information
What happens if we want to make a change in the type information of our class. Type information is also serialized while the class is serialized enabling the class to be deserialized using the type information. Type information consists of namespace, class name, assembly name, culture information, assembly version, and public key token. As long as your deserialized class and the class that is serialized reside in the same assembly, it does not cause any problem. But if the serializer is in a separate assembly, .NET cannot find your class’ type hence cannot deserialize it.
If you change the assembly name of your class then you get the following error that tells you .NET cannot find the associated assembly "An unhandled exception of type 'System.Runtime.Serialization.SerializationException' occurred in serializationsamplesa.dll
".
Additional information: "Parse Error, no assembly associated with XML key a1:http://schemas.microsoft.com/clr/nsassem/SerializationSamples.SOAP/ SerializationSamples%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull Exam
".
If you change the namespace of your class, then you will get the following error that tells you .NET cannot find the associated type "An unhandled exception of type 'System.Runtime.Serialization.SerializationException' occurred in serializationsamples.dll
".
Additional information: "Parse Error, no type associated with Xml key a1 SerializationSamples.SOAP.Exam SerializationSamples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
".
If it is very likely to make changes it might be a good idea to implement ISerializable
interface instead of using the Serializable
attribute. If the class implements ISerializable
interface, all the serialization must be coded manually.
In this case, we can write our own class’s binder and tell the deserialization to use our binder while looking for our classes’ types. For each class that is serialized or type information recorded in the SOAP message, we need to return its type information so that the correct type can be loaded. In the last else
statement in the code below, we return the default coming type information. In our case, they are Object
, ArrayList
and Hashtable
types since these are the system classes that are used by our class.
public class ExamBinder:
System.Runtime.Serialization.SerializationBinder {
public override Type BindToType(string assemblyName,
string typeName) {
string[] typeInfo=typeName.Split('.');
string className=typeInfo[typeInfo.Length -1];
if (className.Equals("Exam")){
return typeof (Exam);
}
else if (className.Equals("Header")){
return typeof (Header);
}
if (className.Equals("Question")){
return typeof (Question);
}
else{
return Type.GetType(string.Format( "{0}, {1}",
typeName, assemblyName));
}
}
}
Now let’s create another overloaded method for deserialization. It takes a binder parameter and passes this parameter to the formatter’s binder
property.
public static object SoapToFromFile(string filePath,
SerializationBinder binder) {
IFormatter formatter;
FileStream fileStream=null;
Object objectFromSoap=null;
try{
fileStream = new FileStream(filePath,
FileMode.Open, FileAccess.Read);
formatter = new SoapFormatter();
formatter.Binder=binder;
objectFromSoap=formatter.Deserialize(fileStream);
}
catch(Exception exception){
throw exception;
}
finally{
if (fileStream!=null) fileStream.Close();
}
return objectFromSoap;
}
It can be called as shown below:
System.Runtime.Serialization.SerializationBinder binder=
new ExamBinder();
Exam soapExamFromBinder =
XmlUtil.SoapToFromFile(txtFilePath.Text,binder) as Exam;