Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Serialization in C# .NET I - Custom Serialization

0.00/5 (No votes)
15 Jul 2010 2  
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:

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:

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:

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.

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:

<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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here