Serialization & Deserialization in .net and it’s internals

Introduction:

Before going in detail, let’s discuss

what is Serialization & Deserialization in .net?

Serialization is a process of converting an object into stream of bytes. Whereas deserialization is other way around. i.e converting stream of bytes into objects.

Here are some examples where we see the need of Serialization

– A set of objects to be sent over a network on to the other machine. Ex: WCF and remoting.

– You can save application state in the local machine and then restore when required.

– Serialization is mainly required for cloning of data (Deep cloning)

Formatters:

The namespace for the serialization is System.Runtime.Serialization.

.Net supports 2 types of formatters

– Soap formatter (System.Runtime.Serialization.Formatter.Soap)

– Binary formatter (System.Runtime.Serialization.Formatter.Binary)

You can use XmlSerializer and DataContractSerializer for serialization and Deserialization of xml.

Quick Start:

Let’s start with an example using memory stream.
var namesDictionary = new Dictionary<int, string>() { { 1, "Alex" }, { 2, "Stephan" }, { 3, "Thomas" } };
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, namesDictionary);
stream.Position = 0;
namesDictionary = null;
var result=(Dictionary<int, string>)formatter.Deserialize(stream);
foreach (var item in result.Values)
{
Console.WriteLine(item);
}
}

The code looks easy. Isn’t it?

Memory stream is found in System.IO namespace. Memory stream represents in-memory stream of data.

You can even serialize the data in the file.  You have to use FileStream instead of MemoryStream.

File Stream represents a file in the computer. File stream is used to read from, write to, open and from close files using FileMode enumeration.


var namesDictionary = new Dictionary<int, string>() { { 1, "Alex" }, { 2, "Stephan" }, { 3, "Thomas" } };

using (FileStream stream = new FileStream(@"C:\Sample\sample.txt", FileMode.OpenOrCreate))

{

BinaryFormatter formatter = new BinaryFormatter();

formatter.Serialize(stream, namesDictionary);

stream.Position = 0;

namesDictionary = null;

var result = (Dictionary<int, string>)formatter.Deserialize(stream);

foreach (var item in result.Values)

{

Console.WriteLine(item);

}

}

SerializationOutput.JPG

When you open sample.txt file, you can see assembly’s file name, version number, culture and public key token information. While Deserializing, the formatter(in our case Binary formatter) first grabs the assembly information i.e assembly name, version number, culture and public key token and it ensures the assembly is loaded using Assembly.Load method.
If the assembly information doesn’t match then SerializationException will be thrown.

Note: Serialize method internally uses reflection in order to identify the object’s data type.

Usage of Serializable attributes:

Let’s take another example. In this example, I created a class called Addition.


public class Addition
{
private int _value1;
private int _value2;

public int sum;

public Addition(int value1,int value2)
{
_value1 = value1;
_value2 = value2;
sum = _value1 + _value2;
}
}

In the Main method, we will use the same code as we used in the quick start example

try
{
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, new Addition(1, 2));
stream.Position = 0;
Addition addition = (Addition)formatter.Deserialize(stream);
Console.WriteLine(addition.sum);
}
}
catch (SerializationException ex)
{
Console.WriteLine(ex.ToString());
}

After running this code,serialization exception is thrown saying Addition class has to be marked with Serializable attribute.
After changing code

[Serializable]
public class Addition
{
private int _value1;
private int _value2;

[NonSerialized]
public int sum;

public Addition(int value1,int value2)
{
_value1 = value1;
_value2 = value2;
sum = _value1 + _value2;
}
}

After applying serializable attribute, all the fields in the class are serialized. In the addition class example, I don’t want to serialize sum field as the value will change if the value1 and value2 are change and is easily calculated.

After running, the sum value is 0 because we marked sum as Non serializable attribute. So what to do next?
For these type of issues, Microsoft has come up with 4 different attributes;OnSerializing, OnSerialized, OnDeserializing and OnDeserialized. Execution flow will happen in the same order I mentioned before i.e OnSerializing, OnSerialized, OnDeserializing and OnDeserialized

After applying OnDeserialized attribute code

[Serializable]
public class Addition
{
private int _value1;
private int _value2;

[NonSerialized]
public int sum;

public Addition(int value1,int value2)
{
_value1 = value1;
_value2 = value2;
sum = _value1 + _value2;
}

[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
sum = _value1 + _value2;
}
}

After running you can see the value as 3 in the output window.
Note: You can use OptionalField attribute instead of NonSerialized attribute for the sum field. After applying OptionalField attribute, OnDeserialized method is no more required.

ISerializable Interface:

Now the question is why ISerializable is required, When we have OnSerializing, OnSerialized,OnDerializing, OnDeserialized and OptionalField?
ISerializable interface has many advantages
– Total control over all the attributes
– ISerializable interface will help in the improving the application performance. With the previous approach, internally we were using reflection.

[Serializable]
public class Employee : ISerializable
{
public int Id { get; set; }

public string Name { get; set; }

public Employee()
{

}

[SecurityPermission(SecurityAction.Demand,SerializationFormatter =true)]
private Employee(SerializationInfo info,StreamingContext context)
{
Id = info.GetInt32("Id");
Name = info.GetString("Name");
}

[SecurityPermission(SecurityAction.Demand,SerializationFormatter =true)]
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Id", Id);
info.AddValue("Name", Name);
}
}

ISerializable interface has GetObjectData method, which takes serializationInfo and StreamingContext as a parameter.
In short, GetObjectData method is used for serialization while private constructor is used for deserialization.
AddValue in the GetObjectData method is used to add serialization information for the type. While Deserializing, we are using GetInt32 and GetString to get the stream of objects.
Note:
1) GetObjectData method and private constructor (Deserialization) are intended to be used by the formatter and there are chances of data manipulation. So it’s always recommended to use SecurityPermission attribute.
2) While deserializing, you can even use GetValue(“name”,Type) instead of GetInt32, GetString etc.

ISerializationSurrogate:

If the class is not marked with Serialization attribute then ISerializationSurrogate comes handy.
Serialization surrogate has some advantages:
– ISerializationSurrogate is used when type is not originally designed to be serialized
– It’s useful to map version of type to a different version of a type.


public class Employee
{
public int Id { get; set; }

public string Name { get; set; }
}

public class EmployeeSurrogate : ISerializationSurrogate
{
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
Employee emp = (Employee)obj;
info.AddValue("Id", emp.Id);
info.AddValue("Name", emp.Name);
}

public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
Employee emp = (Employee)obj;
emp.Id = info.GetInt32("Id");
emp.Name = info.GetString("Name");
return emp;
}
}

static void Main(string[] args)
{
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
SurrogateSelector selector = new SurrogateSelector();
selector.AddSurrogate(typeof(Employee), new StreamingContext(
StreamingContextStates.All), new EmployeeSurrogate());
formatter.SurrogateSelector = selector;
formatter.Serialize(stream, new Employee { Id = 1, Name = "abc" });
stream.Position = 0;
var result = (Employee)formatter.Deserialize(stream);
Console.WriteLine(result.Name);
}
Console.ReadLine();
}

Here, GetObjectData method is used for serialization and SetObjectData is used for
Deserialization.

StreamingContext:

Streaming context is the struct which describes source or destination of serialized stream. The state property in the StreamingContext hold value from the
StreamingContextState enumeration that indicates destination of object data during
Serialization and source of data during Deserialization.

StreamingContextState enumeration looks like this

[Serializable, Flags]
[System.Runtime.InteropServices.ComVisible(true)]
public enum StreamingContextStates {
CrossProcess=0x01,
CrossMachine=0x02,
File =0x04,
Persistence =0x08,
Remoting =0x10,
Other =0x20,
Clone =0x40,
CrossAppDomain =0x80,
All =0xFF,
}
}

By default, streamingContextState is set to All.

We will see how to create Deep cloning. I created an extension method for Deep cloning

public static class SerilizationExtension
{
public static T DeepClone(this object obj)
{
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Context = new StreamingContext(StreamingContextStates.Clone);
formatter.Serialize(stream, obj);
stream.Position = 0;
return (T)formatter.Deserialize(stream);
}
}
}

Now, we will see how to use this extension method

[Serializable]
public class Employee : ISerializable
{
public int Id { get; set; }

public string Name { get; set; }

public Employee()
{

}

[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
private Employee(SerializationInfo info, StreamingContext context)
{
Id = info.GetInt32("Id");
Name = info.GetString("Name");
}

[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Id", Id);
info.AddValue("Name", Name);
}
}

class Program
{
static void Main(string[] args)
{
Employee employee = new Employee { Id = 1, Name = "abc" };
var result=employee.DeepClone();

Console.WriteLine(result.Id);
Console.WriteLine(result.Name);
Console.ReadLine();
}
}

Hope this article helped you!!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s