Serialization And Deserialization in .NET

Introduction

 
Before going into detail, let's discuss.
 

What is Serialization & Deserialization in .NET

 
Serialization is a process of converting an object into a stream of bytes. Whereas deserialization is another way around i.e converting a stream of bytes into objects.
 
Here are some examples where we see the need for Serialization:
  • A set of objects to be sent over a network onto the other machine. Ex: WCF and remoting.
  • You can save the application state in the local machine and then restore it when required.
  • Serialization is mainly required for the cloning of data (Deep cloning).

Formatters

 
The namespace for the serialization is System.Runtime.Serialization.
  • .NET supports 2 types of formats.
  • 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 of using a memory stream.
    1. varnamesDictionary = newDictionary < intstring > ()     
    2. {    
    3.     {    
    4.         1,"Alex"    
    5.     },     
    6.     {    
    7.         2,"Stephan"    
    8.     },     
    9.     {    
    10.         3,"Thomas"    
    11.     }    
    12. };    
    13. using(MemoryStream stream = newMemoryStream())    
    14. {    
    15.     BinaryFormatter formatter = newBinaryFormatter();    
    16.     formatter.Serialize(stream, namesDictionary);    
    17.     stream.Position = 0;    
    18.     namesDictionary = null;    
    19.     var result = (Dictionary < intstring > ) formatter.Deserialize(stream);    
    20.     foreach(var item in result.Values)    
    21.     {    
    22.         Console.WriteLine(item);    
    23.     }    
    24. }   
      The code looks easy. Isn’t it?
       
      Memory stream is found in System.IO namespace. Memory stream represents an in-memory stream of data.
       
      You can even serialize the data in the file. You have to use FileStream instead of MemoryStream.
       
      FileStream represents a file in the computer. File stream is used to read from, write to, open and close files using FileMode enumeration.
      1. var namesDictionary = newDictionary < int,  
      2.     string > ()   
      3.     {  
      4.         {  
      5.             1,"Alex"  
      6.         },   
      7.         {  
      8.             2,"Stephan"  
      9.         },   
      10.         {  
      11.             3,"Thomas"  
      12.         }  
      13.     };  
      14. using(FileStream stream = newFileStream(@ "C:\Sample\sample.txt", FileMode.OpenOrCreate))  
      15. {  
      16.     BinaryFormatter formatter = newBinaryFormatter();  
      17.     formatter.Serialize(stream, namesDictionary);  
      18.     stream.Position = 0;  
      19.     namesDictionary = null;  
      20.     var result = (Dictionary < intstring > ) formatter.Deserialize(stream);  
      21.     foreach(var item in result.Values)  
      22.     {  
      23.         Console.WriteLine(item);  
      24.     }  
      25. }  
      CODE
       
      When you open the sample.txt file, you can see the 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.
      1. public classAddition        
      2. {        
      3.     private int _value1;        
      4.     private int _value2;        
      5.         
      6.     public int sum;        
      7.         
      8.     public Addition(int value1, int value2)        
      9.     {        
      10.         _value1 = value1;        
      11.         _value2 = value2;        
      12.         sum = _value1 + _value2;        
      13.     }        
      14. }     
      In the Main method, we will use the same code as we used in the quick start example.
      1. try {  
      2.     using(MemoryStream stream = newMemoryStream())  
      3.     {  
      4.         BinaryFormatter formatter = newBinaryFormatter();  
      5.         formatter.Serialize(stream, newAddition(1, 2));  
      6.         stream.Position = 0;  
      7.         Addition addition = (Addition) formatter.Deserialize(stream);  
      8.         Console.WriteLine(addition.sum);  
      9.     }  
      10. catch (SerializationException ex)  
      11. {  
      12.     Console.WriteLine(ex.ToString());  
      13. }  
      After running this code, a serialization exception is thrown saying the Additional class has to be marked with the Serializable attribute.
       
      After changing code
        1. [Serializable]    
        2. public class Addition    
        3. {    
        4.     privateint _value1;    
        5.     privateint _value2;    
        6.     
        7.     [NonSerialized]    
        8.     public int sum;    
        9.     
        10.     public Addition(int value1, int value2)    
        11.     {    
        12.         _value1 = value1;    
        13.         _value2 = value2;    
        14.         sum = _value1 + _value2;    
        15.     }    
        16. }   
          After applying the Serializable attribute, all the fields in the class are serialized. In the addition class example, I don’t want to serialize the sum field as the value will change if the value1 and value2 are changed and are easily calculated.
           
          After running, the sum value is 0 because we marked the sum as a Non-serializable attribute. So what to do next?
           
          For these types 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 the OnDeserialized attribute code
            1. [Serializable]    
            2. public class Addition     
            3. {    
            4.     private int _value1;    
            5.     private int _value2;    
            6.     
            7.     [NonSerialized]    
            8.     public int sum;    
            9.     
            10.     public Addition(int value1, int value2)     
            11.     {    
            12.         _value1 = value1;    
            13.         _value2 = value2;    
            14.         sum = _value1 + _value2;    
            15.     }    
            16.     
            17.     [OnDeserialized]    
            18.     private void OnDeserialized(StreamingContext context)     
            19.     {    
            20.         sum = _value1 + _value2;    
            21.     }    
            22. }   
              After running you can see the value as 3 in the output window.
               
              Note: You can use the OptionalField attribute instead of the Non-Serialized attribute for the sum field. After applying the OptionalField attribute, the 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 improving the application performance. With the previous approach, internally we were using reflection.
                1. [Serializable]    
                2. public class Employee: ISerializable    
                3. {    
                4.     public int Id    
                5.     {    
                6.         get;    
                7.         set;    
                8.     }    
                9.     
                10.     public string Name     
                11.     {    
                12.         get;    
                13.         set;    
                14.     }    
                15.     
                16.     public Employee()    
                17.     {    
                18.     
                19.     }    
                20.     
                21.     [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]    
                22.     private Employee(SerializationInfo info, StreamingContext context)    
                23.     {    
                24.         Id = info.GetInt32("Id");    
                25.         Name = info.GetString("Name");    
                26.     }    
                27.     
                28.     [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]    
                29.     public void GetObjectData(SerializationInfo info, StreamingContext context)     
                30.     {    
                31.         info.AddValue("Id", Id);          
                32.         info.AddValue("Name", Name);    
                33.     } -    
                34. }   
                  ISerializable interface has GetObjectData method, which takes serializationInfo and StreamingContext as a parameter.
                   
                  In short, the GetObjectData method is used for serialization while the 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 the 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 in handy. Serialization surrogate has some advantages:
                  • ISerializationSurrogate is used when the type is not originally designed to be serialized.
                  • It’s useful to map a version of a type to a different version of a type.
                    1. public classEmployee     
                    2. {    
                    3.     public int Id     
                    4.     {    
                    5.         get;    
                    6.         set;    
                    7.     }    
                    8.     
                    9.     public string Name     
                    10.     {    
                    11.         get;    
                    12.         set;    
                    13.     }    
                    14. }    
                    15.     
                    16. public class EmployeeSurrogate: ISerializationSurrogate     
                    17. {    
                    18.     public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)    
                    19.     {    
                    20.         Employee emp = (Employee) obj;    
                    21.         info.AddValue("Id", emp.Id);    
                    22.         info.AddValue("Name", emp.Name);    
                    23.     }    
                    24.     
                    25.     public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)     
                    26.     {    
                    27.         Employee emp = (Employee) obj;    
                    28.         emp.Id = info.GetInt32("Id");    
                    29.         emp.Name = info.GetString("Name");    
                    30.         return emp;    
                    31.     }    
                    32. }    
                    33.     
                    34. static void Main(string[] args)     
                    35. {    
                    36.     using(MemoryStream stream = newMemoryStream())    
                    37.     {    
                    38.         BinaryFormatter formatter = newBinaryFormatter();    
                    39.         SurrogateSelector selector = newSurrogateSelector();    
                    40.         selector.AddSurrogate(typeof(Employee), newStreamingContext(    
                    41.             StreamingContextStates.All), newEmployeeSurrogate());    
                    42.         formatter.SurrogateSelector = selector;    
                    43.         formatter.Serialize(stream, newEmployee    
                    44.         {    
                    45.             Id = 1, Name = "abc"    
                    46.         });    
                    47.         stream.Position = 0;    
                    48.         var result = (Employee) formatter.Deserialize(stream);    
                    49.         Console.WriteLine(result.Name);    
                    50.     }    
                    51.     Console.ReadLine();    
                    52. }   
                      Here, the GetObjectData method is used for serialization, and SetObjectData is used for Deserialization.
                       
                      StreamingContext
                       
                      Streaming context is the struct that describes the source or destination of the serialized stream. The state property in the StreamingContext holds a value from the StreamingContextState enumeration that indicates the destination of object data during Serialization and source of data during Deserialization.
                       
                      StreamingContextState enumeration looks like the following,
                        1. [Serializable, Flags]  
                        2. [System.Runtime.InteropServices.ComVisible(true)]  
                        3. public enumStreamingContextStates  
                        4. {  
                        5.     CrossProcess = 0x01,  
                        6.         CrossMachine = 0x02,  
                        7.         File = 0x04,  
                        8.         Persistence = 0x08,  
                        9.         Remoting = 0x10,  
                        10.         Other = 0x20,  
                        11.         Clone = 0x40,  
                        12.         CrossAppDomain = 0x80,  
                        13.         All = 0xFF,  
                        14. }  

                          By default, streamingContextState is set to All.
                           
                          We will see how to create deep cloning. I created an extension method for deep cloning.
                            1. public static class SerilizationExtension    
                            2. {    
                            3.     public staticT DeepClone < T > (thisobject obj)    
                            4.     {    
                            5.         using(MemoryStream stream = newMemoryStream())    
                            6.         {    
                            7.             BinaryFormatter formatter = newBinaryFormatter();    
                            8.             formatter.Context = newStreamingContext(StreamingContextStates.Clone);    
                            9.             formatter.Serialize(stream, obj);    
                            10.             stream.Position = 0;    
                            11.             return (T) formatter.Deserialize(stream);    
                            12.         }    
                            13.     }    
                            14. }  
                            Now, we will see how to use this extension method,
                              1. [Serializable]    
                              2. public class Employee: ISerializable    
                              3. {    
                              4.     public int Id    
                              5.     {    
                              6.         get;    
                              7.         set;    
                              8.     }    
                              9.     
                              10.     public string Name    
                              11.     {    
                              12.         get;    
                              13.         set;    
                              14.     }    
                              15.     
                              16.     public Employee()     
                              17.     {    
                              18.     
                              19.     }    
                              20.     
                              21.     [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]    
                              22.     private Employee(SerializationInfo info, StreamingContext context)    
                              23.     {    
                              24.         Id = info.GetInt32("Id");    
                              25.         Name = info.GetString("Name");    
                              26.     }    
                              27.     
                              28.     [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]    
                              29.     public void GetObjectData(SerializationInfo info, StreamingContext context)    
                              30.     {    
                              31.         info.AddValue("Id", Id);    
                              32.         info.AddValue("Name", Name);    
                              33.     }    
                              34. }    
                              35.     
                              36. class Program     
                              37. {    
                              38.     static void Main(string[] args)    
                              39.     {    
                              40.         Employee employee = newEmployee    
                              41.         {    
                              42.             Id = 1, Name = "abc"    
                              43.         };    
                              44.         var result = employee.DeepClone < Employee > ();    
                              45.     
                              46.         Console.WriteLine(result.Id);    
                              47.         Console.WriteLine(result.Name);    
                              48.         Console.ReadLine();    
                              49.     }    
                              50. }   
                                Hope this article helped you. 


                                Similar Articles