The Object Serialization Process

In the .NET Framework, object serialization is offered through the classes in the System.Runtime.Serialization namespace. These classes provide type fidelity and support deserialization. As you probably know, the deserialization process is the reverse of serialization. Deserialization takes in stored information and re-creates objects from that information.

Object serialization in the .NET Framework allows you to store public, protected, and private fields and automatically handles circular references. A circular reference occurs when a child object references a parent object and the parent object also references the child object. Serialization classes in the .NET Framework can detect these circular references and resolve them. Serialization can generate output data in multiple formats by using different made-to-m-easure formatter modules. The two system-provided formatters are represented by the BinaryFormatter and SoapFormatter classes, which write the object’s state in binary format and SOAP format.

Classes make themselves serializable through formatters in two ways: they can either support the [Serializable] attribute or implement the ISerializable interface. With the [Serializable] attribute, the class author has nothing else to do, as the serialization takes place governed by caller applications and the class data is obtained through reflection. The ISerializable interface, on the other hand, enables the class author to exercise closer control over how the bits of the living object are actually persisted.

A formatter is the .NET Framework object that obtains the serialized data from the target object. Data is requested either by calling the GetObjectData method on the ISerializable interface or through the services of the FormatterServices static class. In particular, the GetSerializableMembers method returns all the serializable members for a particular class.

In the .NET Framework, formatters are of two types, depending on the nature of the underlying stream they use. The binary formatter (available through the BinaryFormatter class) saves data to a binary stream. The SOAP formatter (available through the SoapFormatter class) saves data to a text stream, automatically encoding information in a SOAP message before writing.

The SOAP Formatter

To use the SOAP formatter, you must reference a distinct assembly—System.Runtime.Serialization.Formatters.Soap. You add this separate assembly through the Add Reference dialog box or manually on the compiler’s command line through the /reference switch. In addition to linking the assembly to the project, you still have to import the namespace with the same name as the assembly, as shown here:

using System.Runtime.Serialization.Formatters.Soap;

At this point, you prepare the output stream, instantiate the SOAP formatter, and call the Serialize method, as follows:

// emp is the object instance to process
StreamWriter writer = new StreamWriter(filename); 
SoapFormatter soap = new SoapFormatter();
soap.Serialize(writer.BaseStream, emp);
writer.Close();

Note that the Serialize method accepts only a stream object, which makes serializing to in-memory strings a little more difficult.

Let’s consider a rather simple class, such as the following Employee class:

[Serializable]      
public class Employee
{
    public int ID; 
    public string FirstName;
    public string LastName;
    public string Position;
    public int[] Territories;
}

Upon instantiation, only the numeric ID field has a determined value (0). All the other members are null, as shown here:

Employee emp = new Employee();

After the Employee class has been instantiated, the SOAP formatter generates the following script:

<SOAP-ENV:Envelope 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" 
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0"
  SOAP-ENV:encodingStyle=
    "http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
  <a1:Employee id="ref-1" 
    xmlns:a1=
      "http://schemas.microsoft.com/clr/nsassem/XmlNet.SoapStuff/
      SoapFormatter_CS%2C%20Version%3D1.0.922.19048%2C%20
      Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
    <ID>0</ID>
    <FirstName xsi:null="1"/>
    <LastName xsi:null="1"/>
    <Position xsi:null="1"/>
    <Territories xsi:null="1"/>
  </a1:Employee>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

As you can see, the class representation is perfect, and the fidelity between the SOAP description and the class is total. Information about the namespace is preserved and null values are listed. But what about types?

Retrieving Type Information

The formatter’s TypeFormat property lets you indicate how type descriptions are laid out in the serialized stream. By default, TypeFormat is set to TypesWhenNeeded, which means that type information is inserted only when strictly necessary. This is true for arrays of objects, generic Object objects, and nonprimitive value types. If you want to force type description, use either the TypesAlways or the XsdString option. The difference between these two options is in the format used to describe the type: SOAP in the former case; XSD in the latter. All the type format options are gathered in the FormatterTypeStyle enumeration.

Serializing to Strings

Because the SOAP formatter and the binary formatter write only to streams, to avoid creating disk files you can use the MemoryStream object, as shown here:

// emp is the object instance to process
MemoryStream ms = new MemoryStream();
SoapFormatter soap = new SoapFormatter();
soap.Serialize(ms, emp);

Reading back data is a bit trickier. First you must get the size of the serialized stream. This information is stored in the Length property of the MemoryStream class. Bear in mind, however, that Length moves the internal pointer ahead to the end of the stream. To be able to read the specified number of bytes, you must first reset the internal pointer. The Seek method serves just this purpose, as shown here:

int size = (int) ms.Length;    // Moves the pointer forward
byte[] buf = new byte[size];
ms.Seek (0, SeekOrigin.Begin); 
ms.Read(buf, 0, size);
ms.Close();
string soapText = Encoding.UTF8.GetString(buf);  

The MemoryStream object reads data only as bytes. Especially in a strong-typed environment like the .NET Framework, an array of bytes and a string are as different as apples and oranges. Fortunately, the encoding classes provide for handy conversion methods. The Encoding static class belongs to the System.Text namespace.

Deserializing Objects

To rebuild a living instance of a previously serialized object, you call the Deserialize method on the specified formatter. The deserializer returns an object that you cast to the particular class type you need, as shown here:

StreamReader reader = new StreamReader(filename); 
Employee emp1 = (Employee) soap.Deserialize(reader.BaseStream);
reader.Close();

The .NET Framework serialization mechanism also allows you to control the post-deserialization processing and explicitly handle data being serialized and deserialized. In this way, you are given a chance to restore transient state and data that, for one reason or another, you decide not to serialize. Remember that by marking a field with the [NonSerializable] attribute, you keep it out of the serialized stream.

By implementing the IDeserializationCallback interface, a class indicates that it wants to be notified when the deserialization of the entire object is complete. The class can easily complete the operation by re-creating parts of the state and adding any information not made serializable. The OnDeserialization method is called after the type has been deserialized.

Finally, it goes without saying that you can’t serialize to, say, SOAP, and then pretend to deserialize using the binary formatter. See the section “Further Reading,” on page 518, for more information about run-time binary and SOAP serialization.

From SOAP to XML Serialization

A second, very special type of .NET Framework serialization is XML serialization. Compared to ordinary .NET Framework object serialization, XML serialization is so different that it shouldn’t even be considered another type of formatter. It is similar to SOAP and binary formatters because it also persists and restores the object’s state, but when you examine the way each serializer works, you see many significant differences.

XML serialization is handled by using the XmlSerializer class, which also enables you to control how objects are encoded into elements of an XML schema. In addition to differences in goals and implementation details, the strongest difference between run-time and XML serialization is in the level of type fidelity they provide.

Run-time object serialization guarantees full type fidelity. For this reason, binary and SOAP serialization are particularly well-suited to preserving the state of an object across multiple invocations of an application. For example, .NET Framework remoting (see Chapter 12) uses run-time serialization to marshal objects by value from one AppDomain to another. Whereas run-time serialization is specifically aimed at serializing object instances, XML serialization is a system-provided (as opposed to object-provided) mechanism for serializing the data stored in an object instance into a well-formed schema.

The primary goal of XML serialization is making another application, possibly an application running on a different platform, effectively able to consume any stored data. Let’s recap the key differences between run-time and XML serialization:

  • Persisted properties

    Run-time serialization takes into account any properties, regardless of the scope a property has in the context of the class. XML serialization, on the other hand, avoids private, protected, and read-only properties; does not handle circular references; and works only with public classes. In addition, if one property is set to null in the particular instance being serialized, the XML serializer just ignores the property. The XML serializer never includes type information.

  • Object identity

    Run-time serialization maintains information about the original class name, namespace, and assembly. All this information—the object’s identity—is irreversibly lost with XML serialization.

  • Control of the output

    Run-time serialization lets you indicate the data to serialize by adding values to a cargo collection. You can’t control how these values are actually written, however. The schema of the persisted data is fixed and hard-coded in the formatter. In this respect, the XML serializer is much more flexible. The XML serializer lets you specify namespaces, the name of the XML element that will contain a particular property, and even whether a given property should be rendered as an attribute, text, or an element.

    Important

    During serialization, the .NET Framework formatters get information dynamically from the target object and write any bytes to the specified stream. The XML serializer uses any object information to create a couple of highly specialized reader and writer classes in a C# source file. The file is then silently compiled into a temporary assembly. As a result, XML serialization and deserialization for an object are actually performed using the classes in the temporary assembly. (More on this in the section “The Temporary Assembly,” on page 513.)


One final note about SOAP and XML serialization: Although it’s more powerful in terms of the information carried, SOAP is significantly more verbose than XML serialization and of course much less flexible. In fact, SOAP is just a particular XML dialect with vocabulary and syntax rules defined by the SOAP specification. With XML serialization, you define the schema you want, and the process is designed to return a more compact output.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.32.46