Click here to Skip to main content
15,868,016 members
Articles / Programming Languages / C# 3.5

Serialization in C# .NET I - Custom Serialization

Rate me:
Please Sign up or sign in to vote.
3.41/5 (8 votes)
15 Jul 2010CPOL4 min read 77.7K   652   10   13
Explains serialization, the need for custom serialization, and how to implement custom serialization in your code.

Abstract

Serialization in C# .NET plays a key role in various functions, such as remoting. Developers may often need to perform custom serialization in order to have complete control over the serialization and deserialization processes. The standard .NET serialization processes will therefore not be enough to provide the developer with control over these processes. In this series of articles, Anupam Banerji explains serialization, the need for custom serialization, and how to implement custom serialization in your code.

Introduction

Serialization of objects is a new feature in the .NET Framework. Prior to serialization, the only way to store class objects was to write the objects into a stream object. This has two consequences. First, code has to be written to store each object. If the property is a user-defined type, or an object containing several other objects, then this task becomes very complicated very quickly. Second, if changes are made to class objects, then the code to store them must be changed too. This results in a doubling of effort for each change.

Serialization was introduced to provide the developer with a simple, efficient and consistent way to store class objects. There are very few requirements to implementing standard serialization. The standard .NET serialization model also includes serialization events in order to recalculate values when stored objects are retrieved.

Standard Serialization

Standard Serialization is implemented in classes through a series of attributes. To implement serialization of a class, add the [Serializable] attribute above the class declaration. To exclude any calculated field, tag it with the [NonSerialized] attribute.

To recalculate objects when the object is deserialized, the developer must implement the IDeserializationCallback interface.

To serialize a class, the developer has a choice between a BinaryFormatter object and a SoapFormatter object. The BinaryFormatter serialization object should be used when serialization and deserialization occurs between two .NET assemblies. The SoapFormatter object should be used when serialization and deserialization occurs between a .NET assembly and Simple Object Access Protocol (SOAP) compliant executable. SOAP formatting will be discussed in another article.

Custom Serialization

Custom serialization is implemented through the ISerializable interface. The interface implements the GetObjectData() method and an overloaded constructor used for deserialization. The GetObjectData() method is implemented as follows:

C#
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
    // Implemented code
}

The method takes two arguments, one is the SerializationInfo object which implements the IFormatterConverter interface. We will use it in an example below. The StreamingContext object contains information about the purpose of the serialized object. For example, a StreamingContext of the Remoting type is set when the serialized object graph is sent to a remote or unknown location.

The overloaded constructor has two arguments; the SerializationInfo object and the StreamingContext object.

The BinaryFormatter serialization object fires four events that the developer may implement: OnSerializing, OnSerialized, OnDeserializing and OnDeserialized. The events are implemented as attributes in the class implementing the ISerializable interface. The methods marked as serialization events must have a StreamingContext argument, or else a runtime exception occurs. The methods must also be marked as void.

If both interfaces are implemented, the OnDeserialization() method in the IDe-serializationCallback interface is called after the OnDeserialized event, as the example output below shows.

A Quick Example: A Custom Serialization Class

We implement both serialization interfaces in our class declaration below:

C#
using System.Runtime.Serialization;

[Serializable]
class TestClass : ISerializable, IDeserializationCallback  
{
    public string Name
    {
        get;
        private set;
    }

    public int ToSquare
    {
        get;
        private set;
    }

    [NonSerialized]
    public int Squared;

    public TestClass(string name, int toSquare)
    {
        Name = name;
        ToSquare = toSquare;
        ComputeSquare();
    }

    public TestClass(SerializationInfo     info, StreamingContext context)
    {
        // Deserialization Constructor 

        Name = info.GetString("Name");
        ToSquare = info.GetInt32("ToSquare");
        Console.WriteLine("Deserializing constructor");
        ComputeSquare();
    }

    private void ComputeSquare()
    {
        Squared = ToSquare * ToSquare;
    }

    [OnSerializing]
    private void OnSerializing(StreamingContext context)
    {
        Console.WriteLine("OnSerializing fired.");
    }

    [OnSerialized]
    private void OnSerialized(StreamingContext context)
    {
        Console.WriteLine("OnSerialized fired.");
    }

    [OnDeserializing]
    private void OnDeserializing(StreamingContext context)
    {
        Console.WriteLine("OnDeserializing fired.");
    }

    [OnDeserialized]
    private void OnDeserialized(StreamingContext context)
    {
        Console.WriteLine("OnDeserialized fired.");
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        Console.WriteLine("Serializing...");
        info.AddValue("Name", Name);
        info.AddValue("ToSquare", ToSquare);
    }
    
    void IDeserializationCallback.OnDeserialization(object sender)
    {
         Console.WriteLine("IDeserializationCallback.OnDeserialization method."); 
            ComputeSquare();
    }
}

The Squared field is not serialized, but recalculated when the TestClass object is deserialized. The four events that are fired during (de)serialization illustrate the potential use of the events to the developer. The methods in this example write the progress of the (de)serialization process to the console.

We write the calling function to (de)serialize as shown:

C#
TestClass tc = new TestClass("Test", 3);
FileStream fs = new FileStream("Serialized.txt", FileMode.Create);
BinaryFormatter bf = new BinaryFormatter();

bf.Serialize(fs, tc);
fs.Close();
tc = null;

fs = new FileStream("Serialized.txt", FileMode.Open);

tc = (TestClass)bf.Deserialize(fs);

Console.WriteLine("Squared = " + tc.Squared);

The serialized output is written to the Serialized.txt file. This is a binary file containing the state of the TestClass instance. The console output after the assembly is executed is:

OnSerializing fired.
Serializing...
OnSerialized fired.
OnDeserializing fired.
Deserializing constructor
OnDeserialized fired.
IDeserializationCallback.OnDeserialization method called.

The OnDeserialization() method is called last, not between the OnDeserializing and OnDeserialized events.

We can implement a SOAP formatter instead of a binary formatter by replacing the instance of BinaryFormatter with an instance of SoapFormatter. To instance the SoapFormatter, make sure that the System.Runtime.Serialization.Formatters.Soap namespace is referenced. The .NET Framework 3.5 documentation states that the SoapFormatter class is obsolete. However, developers will come across implementations of this class, especially in assemblies requiring portable object graphs.

C#
TestClass tc = new TestClass("Test", 3);
FileStream fs = new FileStream("Serialized.txt", FileMode.Create);
SoapFormatter sf = new SoapFormatter();

sf.Serialize(fs, tc);
fs.Close();
tc = null;

fs = new FileStream("Serialized.txt", FileMode.Open);
tc = (TestClass)sf.Deserialize(fs);
Console.WriteLine("Squared = " + tc.Squared);

The SoapFormatter object also supports firing the four serialization events, and the console output is identical to the output when the BinaryFormatter object is implemented.

The SoapFormatter creates a serialized object in a human readable format. The Serialization.txt file contains the following XML output:

XML
<SOAP-ENV:Envelope >

<SOAP-ENV:Body>

<a1:TestClass id="ref-1" >

<Name id="ref-3">Test</Name>

<ToSquare>3</ToSquare>

</a1:TestClass>

</SOAP-ENV:Body>

</SOAP-ENV:Envelope>

The items in the serialized object are marked in blue. An XML reader in another language or platform can easily read and rebuild the TestClass instance. The XML output can be controlled. I will address this in a later article.

Conclusion

Custom serialization in .NET allows the developer complete control of the (de)serialization process. The sequence of events and the implemented interfaces and attributes should be understood before designing a serializable class. The serialization formatter choice will dictate class design. A design that incorporates serialization processes for a wide range of applications should be implemented if there are undecided issues of deployment and integration.

This is the first article in this series.

To download this technical article in PDF format, visit the Coactum Solutions website at http://www.coactumsolutions.com.

History

  • 15th July, 2010: Initial post

License

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


Written By
Australia Australia
Anupam Banerji has a wealth of experience writing, evaluating and validating systems for the cement, pharmaceuticals, energy derivatives and banking and finance industries.

Additionally, he’s designed and written software solutions in VB/VBA, SQL and C# .NET, and has worked as a Quantitative Analyst in banking. He is a Microsoft Certified Technology Specialist in WPF 3.5 and has written and deployed commercial solutions in the .NET 3.5 and 4.0 object models. He has also written applications in MVVM and Prism.

Comments and Discussions

 
Question[My vote of 2] not great Pin
BillW337-Sep-12 3:28
professionalBillW337-Sep-12 3:28 
QuestionBut How Can we Save MouseEventArgs? Pin
mschotamaster24-Dec-11 5:40
mschotamaster24-Dec-11 5:40 
GeneralMy vote of 2 Pin
jc mahne18-Aug-10 3:46
jc mahne18-Aug-10 3:46 
GeneralMy vote of 2 Pin
User 661920715-Jul-10 19:47
User 661920715-Jul-10 19:47 
GeneralMy vote of 2 Pin
voloda215-Jul-10 7:43
voloda215-Jul-10 7:43 
GeneralRe: My vote of 2 Pin
Anupam Banerji15-Jul-10 15:42
Anupam Banerji15-Jul-10 15:42 
GeneralMy vote of 2 Pin
sam.hill15-Jul-10 5:28
sam.hill15-Jul-10 5:28 
GeneralRe: My vote of 2 Pin
Anupam Banerji15-Jul-10 15:48
Anupam Banerji15-Jul-10 15:48 
GeneralRe: My vote of 2 Pin
sam.hill15-Jul-10 17:52
sam.hill15-Jul-10 17:52 
GeneralRe: My vote of 2 Pin
Anupam Banerji15-Jul-10 18:03
Anupam Banerji15-Jul-10 18:03 
GeneralNot complete Pin
#realJSOP15-Jul-10 0:14
mve#realJSOP15-Jul-10 0:14 
GeneralRe: Not complete Pin
Anupam Banerji15-Jul-10 0:17
Anupam Banerji15-Jul-10 0:17 
GeneralRe: Not complete Pin
Anupam Banerji15-Jul-10 1:05
Anupam Banerji15-Jul-10 1:05 

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.