Click here to Skip to main content
15,308,666 members
Articles / Programming Languages / C#
Article
Posted 27 Dec 2015

Tagged as

Stats

16.5K views
103 downloads
6 bookmarked

Serialization & Deserialization in .net and it's internals

Rate me:
Please Sign up or sign in to vote.
3.68/5 (7 votes)
27 Dec 2015CPOL4 min read
This article is about serialization and deserialization in c#

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. 

C#
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);
                }
            }
</int,></int,>

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. 

C#
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);
                }
            }</int,></int,>

Output

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. 

C#
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

C#
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

C#
[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

C#
[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. 
C#
[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.
C#
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

C#
[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

C#
public static class SerilizationExtension
    {
        public static T DeepClone<t>(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);
            }
        }
    }
</t>

Now, we will see how to use this extension method

C#
[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<employee>();
            
            Console.WriteLine(result.Id);
            Console.WriteLine(result.Name);
            Console.ReadLine();
        }
    }
</employee>

Hope this article helped you!!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Anup Raghavendra Hosur
Software Developer (Senior)
India India
No Biography provided

Comments and Discussions

 
QuestionOptionalField is optional Pin
Qwertie28-May-19 12:35
MemberQwertie28-May-19 12:35 
Strangely, OptionalFieldAttribute usually has no effect because of secret undocumented behavior in BinaryFormatter. By default, it allows deserialization of a binary blob with missing required fields. By default, the missing field will get a value of default(T) (where T is the type of the field).

This weirdness is described here. I have verified that if you do this:
binaryFormatter.AssemblyFormat = FormatterAssemblyStyle.Full;

It causes an undocumented side effect of paying attention to OptionalField. However, in practice you probably should not use FormatterAssemblyStyle.Full because it will break deserialization if the assembly version number changes, or if your assembly is signed and you make any change whatsoever.
QuestionMay be you better read J. Richther and .Net Performance Optimization books Pin
Alexey KK28-Dec-15 11:10
professionalAlexey KK28-Dec-15 11:10 
QuestionSome problems in the Introduction Pin
Paulo Zemek27-Dec-15 11:04
MemberPaulo Zemek27-Dec-15 11:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.