Application Development: Advanced serialization in .NET

The .NET Framework uses several built-in mechanisms to achieve serialization; however, those default techniques may not be enough. This tutorial explains how advanced custom serialization is implemented in .NET.

This article deals with some of the advanced concepts associated with serialization in .NET. It is recommended that the reader be familiar with the basic concepts discussed in the previous article on serialization. This article deals with custom serialization and the implementation of ISerializable. The cloning of objects and serialization aspects to be considered in implementing .NET Remoting are also discussed.

Implementing the ISerializable interface
In certain cases, the default serialization techniques provided by .NET might not suffice. In such scenarios, it is possible to implement custom serialization by implementing the ISerializable interface. By implementing the ISerializable interface, an object can control its own serialization and deserialization process.

In order to implement the ISerializable interface, the implementing class needs to have the GetObjectData method and a special constructor, which is used by the common language runtime during deserialization.

Let's take a look at the code sample in Listing A to see how ISerializable is implemented for a class. The listing shows a sample class, TestClass, which implements the ISerializable interface.

The GetObjectData method takes two parameters. It is the responsibility of the developer to store the fields that need to be persisted into the SerializationInfo class; they are then passed as a parameter. The SerializationInfo class provides an AddValue method, which can be used to store name-value pairs.

In the listing, the values in the fields are stored in the SerializationInfo class after giving specific names to each field. These names are later used to retrieve information at the time of deserialization.

Another important thing to note while implementing ISerializable is that it is not possible to force a check for the private constructor at compilation, and, so, even if the private constructor was missed, the code would compile but throw an error at runtime.

Let's take a look at the other requirement, the private constructor. The constructor needs to be private for security reasons. This constructor takes the same parameters as the GetObjectData method. In this, too, the developer is responsible for extracting the values back onto the variables.

The StreamingContext object, which forms the second parameter, is a struct and is helpful when using Serialization surrogates discussed later in this article.

Cloning objects: The ICloneable interface
The ICloneable interface is implemented by objects that support cloning. The ICloneable interface exposes one method, Clone. The .NET Framework provides the MemberwiseClone method on all objects. This, however, uses the XML Serialization and can, therefore, clone only the public fields of the object, a technique referred to as Shallow Serialization. In scenarios where this will not suffice, the object would need to implement the ICloneable interface and provide an implementation for the Clone method.

Within the Clone method, binary or SOAP serialization techniques can be used to create a clone of the object.

Let's take a look at the sample code in Listing B, which demonstrates the implementation of ICloneable using Binary Serialization. We will consider the same sample from before and add the Clone method to the same class.

Focus on the implantation of the Clone method in the listing. The binary formatter has been used to serialize the fields of the object TestClass into a MemoryStream. Then, we seek to the beginning of this stream and pass it to the deserialize method of the formatter. Once the deserialization is done, we have an exact clone of the object. This cloned object is returned back to the caller.

Serialization surrogates
A serialization surrogate is a class which takes over the steps necessary to serialize and deserialize an object (see Figure A). This serialization surrogate would need to implement the ISerializationSurrogate interface.

It is also possible to override how a type serializes itself by specifying a serialization surrogate. This technique is popularly used to override how the .NET DataSet object serializes itself. This is important because a DataSet is XML data, and it gives a lot of scope for developers to implement more efficient custom techniques for compressing and storing data.

The Serialization surrogates allow us to serialize even classes that are not marked as Serializable at design time. Let's consider a Class, TestClass2, shown in Listing C, which neither implements ISerializable nor is it marked as Serializable at design time.

Listing D shows the Surrogate class for TestClass2.

The GetObjectData method is the same as was seen in the ISerializable interface. We just cast the object back to the original class, TestClass2. The fields of the object are then loaded into the SerializationInfo object passed as a parameter.

The other method is SetObjectData. In this method, we perform the same cast in the first step and then extract the values from the SerializationInfo object, set it back to the properties of the class, and return it back.

Figure A

Listing E shows the code for a Main method which would actually run through the steps in making use of a serialization surrogate.

Here is a walk-through of the steps mentioned in Listing E.
  • STEP 1: An instance of the class TestClass2 is created and the values to its property are assigned.
  • STEP 2: New instances of a MemoryStream, a formatter, the SurrogateSelector, and the SerializationSurrogate, which we just now implemented, are created.
  • STEP 3: The AddSurrogate method is called on the Surrogate Selector class.
  • STEP 4: The SurrogateSelector property of the formatter object is set to the instance of SurrogateSelector on which the AddSurrogate method was called.
  • STEP 5: The Serialize method is called on the formatter. This step completes the serialization process.
  • STEP 6: This step demonstrates deserialization of the memory stream back to TestClass2.

Serialization in .NET Remoting
Serialization finds its use in another important technology which is part of the .NET Framework: .NET Remoting. .NET Remoting also makes heavy use of all of the serialization techniques discussed so far. In .NET Remoting, either the SOAP serialization or binary serialization techniques can be specified.

In fact, the formatters discussed in this article, the SoapFormatter and BinaryFormatter classes, both implement the IRemotingFormatter interface and can, therefore, be used for serialization of objects that are remoted.

.NET Remoting takes care of serialization and deserialization of the objects that are remoted. Only the type of formatter needs to be specified. A complete discussion on the workings of .NET Remoting is beyond the scope of this article; we will, therefore, focus on some of the deserialization rules that are employed by .NET Remoting.

.NET Remoting provides two levels of automatic deserialization. The default deserialization level is set to Low. The other possible setting is Full, which was introduced because.NET Remoting will need to deserialize remote streams before it can use it, and there could be malicious code which might exploit this.

The filter level of Full is required if ObjRef is passed as a parameter and objects implement the ISponsor interface. Listing F shows a sample .NET Remoting configuration file to set the typeFilterLevel for the formatter.

Serialization is an important tool when you want to convert an instance of an object into a format that can easily be transmitted or saved. In this article, we looked at custom serialization by implementing the ISerializable and the ICloneable interfaces. The .NET Framework offers a great deal of development and design flexibility for serialization. Take advantage of these advanced methods in your next application.

Editor's Picks