It's up to the client to decide which stream type to use for serialization and in which format the data should be represented. A formatter is an object that implements the IFormatter interface, defined in the System.Runtime.Serialization namespace:
public interface IFormatter
{
object Deserialize(Stream serializationStream);
void Serialize(Stream serializationStream, object graph);
/* Other methods */
}
IFormatter's significant methods are Serialize( ) and Deserialize( ), which perform the actual serialization and deserialization. .NET ships with two formatters: a binary formatter
and a Simple Object Access Protocol (SOAP) formatter. The binary
formatter generates a compact binary representation of the object's
state. It's relatively fast to serialize an object into binary format
and to deserialize an object from binary format. The SOAP formatter, as
the name implies, persists the object's state using a text-based XML
format. Naturally, the SOAP formatter is considerably slower than the
binary formatter, because serialization requires composing a SOAP
envelope and deserialization requires SOAP parsing. In addition, the
resulting serialization data (i.e., the file size or memory footprint)
is bigger. The only advantage of using the SOAP formatter is that it's
platform-neutral: you can provide the serialization information (via
network stream or file access) to applications running on
non-Windows-based platforms, and they can deserialize equivalent objects
on their side or serialize their objects back to the .NET side. In
general, unless cross-platform interoperability is required, you should
always use the binary formatter.
1. The Binary Formatter
The binary formatter is the class BinaryFormatter, defined in the System.Runtime.Serialization.Formatters.Binary namespace. To serialize an object with the binary formatter, create a stream object (such as a file stream) and call the Serialize( )
method of the formatter, providing the formatter with the object to
serialize and the stream to serialize it to. When you are done with the
stream, remember to dispose of it. Example 1 shows how to serialize an object of type MyClass to a file stream.
Example 1. Binary serialization using a file stream
using System.Runtime.Serialization.Formatters.Binary;
MyClass obj = new MyClass( );
IFormatter formatter = new BinaryFormatter( );
//Creating a stream
Stream stream;
stream = new FileStream(@"C:\temp\obj.bin",FileMode.Create,FileAccess.Write);
using(stream)
{
formatter.Serialize(stream,obj);
}
|
To deserialize an object, you need to open the
appropriate stream (matching the type of stream used for serialization)
and call the Deserialize( ) method of the formatter, as shown in Example 2. You will receive back an object reference.
Example 2. Binary deserialization using a file stream
using System.Runtime.Serialization.Formatters.Binary;
MyClass obj; //No
new!
IFormatter formatter = new BinaryFormatter( );
//Opening a stream
Stream stream;
stream = new FileStream(@"C:\temp\obj.bin",FileMode.Open,FileAccess.Read);
using(stream)
{
obj = (MyClass)formatter.Deserialize(stream);
}
|
There are a few things worth mentioning regarding
deserialization. First, make sure to open a stream on an existing
medium, instead of creating a new one and destroying the existing
serialized information. Second, note that there is no need to create an
object explicitly using new. The Deserialize( )
method creates a new object and returns a reference to it. In fact,
during deserialization, no constructor is ever called. Third, Deserialize( )
returns an amorphous object reference, so you need to downcast it to
the correct object type being deserialized. If the downcast type is
different from the type that was serialized, an exception is thrown.
Finally, be sure to dispose of the stream when you are finished.
2. The SOAP Formatter
The SoapFormatter class, defined in the System.Runtime.Serialization.Formatters.Soap namespace, is used exactly like the BinaryFormatter class. The only difference is that it serializes using a SOAP format instead of binary format. Example 3 demonstrates serialization into a file stream using the SOAP formatter.
Example 3. SOAP serialization using a file stream
using System.Runtime.Serialization.Formatters.Soap;
//In the MyClassLibrary assembly:
namespace MyNamespace
{
[Serializable]
public class MyClass
{
public int Number1;
public int Number2;
}
}
//In the client's assembly:
MyClass obj = new MyClass( );
obj.Number1 = 123;
obj.Number2 = 456;
IFormatter formatter = new SoapFormatter( );
Stream stream;
stream = new FileStream(@"C:\temp\obj.xml",FileMode.Create,FileAccess.Write);
using(stream)
{
formatter.Serialize(stream,obj);
}
|
Example 4 shows the resulting file content. The deserialization using a SOAP formatter is exactly like that in Example 9-3, except it uses the SOAP formatter instead of the binary one.
Example 4.
<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:MyClass
id="ref-1"
xmlns:a1="http://schemas.microsoft.com/clr/nsassem/
MyNamespace/MyClassLibrary%2C%20
Version%3D1.0.898.27976%2C%20
Culture%3Dneutral%2C%20
PublicKeyToken%3Dnull">
<Number1>123</Number1>
<Number2>456</Number2>
</a1:MyClass>
/SOAP-ENV:Body>
</SOAP-ENV:Envelope>
|
3. Generic Formatters
The IFormatter interface was defined
before generics were available in .NET, but with the introduction of
generics in .NET 2.0 you can improve on the available implementations of
IFormatter by providing generic and type-safe wrappers around them. You can define the IGenericFormatter interface that provides similar methods to IFormatter, except it uses generic methods:
public interface IGenericFormatter
{
T Deserialize<T>(Stream serializationStream);
void Serialize<T>(Stream serializationStream,T graph);
}
.NET provides an alternate serialization infrastructure, available in the System.Xml.Serialization namespace. XML serialization
is similar in concept to the text-based SOAP formatter. The main
difference is that with XML serialization, you have granular control
over the layout and structure of the serialized information. Such
control is often needed when interoperating with other platforms, or
when you need to comply with a predefined data schema. Although
XML-based serialization is used in much the same way as the
serialization support described so far, it does have several
limitations: it must operate on public classes; it cannot handle
interface references, so no fields should be interface references; and
there is no support for dealing with circular references in the object
graph. XML-based serialization is oriented toward exchanging data across
the Internet with other platforms, while .NET serialization is
component-based and oriented toward applications and interacting
objects. |
Using generic methods is preferable to making the
whole interface generic, because it allows you to use the same
formatter instance but change the type parameter being serialized or
deserialized in every call. You can simply implement IGenericFormatter by encapsulating a non-generic formatter, and delegate the calls to it. Example 5 shows the generic class GenericFormatter<F>, which implements IGenericFormatter.
Example 5. Implementing IGenericFormatter
public class GenericFormatter<F> : IGenericFormatter where F : IFormatter,new( )
{
IFormatter m_Formatter = new F( );
public T Deserialize<T>(Stream serializationStream)
{
return (T)m_Formatter.Deserialize(serializationStream);
}
public void Serialize<T>(Stream serializationStream,T graph)
{
m_Formatter.Serialize(serializationStream,graph);
}
}
public class GenericBinaryFormatter : GenericFormatter<BinaryFormatter>
{}
public class GenericSoapFormatter : GenericFormatter<SoapFormatter>
{}
|
GenericFormatter<F> is defined using the generic type parameter F. F is constrained to implement IFormatter and provide a default constructor. Then, GenericFormatter<F> declares a member of type IFormatter and assigns into it a new F object:
IFormatter m_Formatter = new F( );
Example 5 also defines two subclasses of GenericFormatter<F>: GenericBinaryFormatter and GenericSoapFormatter. All they do is provide the binary or the SOAP formatter, respectively, as a type parameter to GenericFormatter<F>. You could define GenericBinaryFormatter and GenericSoapFormatter with the using statement, like this:
using GenericBinaryFormatter = GenericFormatter<BinaryFormatter>;
using GenericSoapFormatter = GenericFormatter<SoapFormatter>;
but that would have file scope only. In this
case, inheritance is good for strong typing and shorthand across files
and assemblies.
Example 6 demonstrates the use of the generic, type-safe formatters.
Example 6. Using the generic formatters
[Serializable]
public class MyClass
{...}
MyClass obj1 = new MyClass( );
MyClass obj2;
IGenericFormatter formatter = new GenericBinaryFormatter( );
Stream stream;
stream = new FileStream(@"C:\temp\obj.bin",FileMode.Create,FileAccess.ReadWrite);
using(stream)
{
formatter.Serialize(stream,obj1);
stream.Seek(0,SeekOrigin.Begin);
obj2 = formatter.Deserialize<MyClass>(stream);
}
|
The next version of the serialization infrastructure (a part of Indigo) will contain a generic version of IFormatter similar to IGenericFormatter. |
|
4. Serialization of Generic Type Parameters
.NET allows you to have serializable generic types:
[Serializable]
public class MyClass<T>
{...}
If the serializable type is generic, the metadata
captured by the formatter contains information about the specific type
parameters used. Consequently, each permutation of a generic type with a
specific parameter type is considered a unique type. For example, you
cannot serialize an object of type MyClass<int> and then deserialize it into an object of type MyClass<string>.
Serializing an instance of a generic type is no different from
serializing a non-generic type. However, when you deserialize that type,
you need to declare a variable with the matching specific type
parameter, and you must specify these types again when you call Deserialize( ). Example 7 demonstrates serialization and deserialization of a generic type.
Example 7. Client-side serialization of a generic type
[Serializable]
public class MyClass<T>
{...}
MyClass<int> obj1 = new MyClass<int>( );
MyClass<int> obj2;
IGenericFormatter formatter = new GenericBinaryFormatter( );
Stream stream;
stream = new FileStream(@"C:\temp\obj.bin",FileMode.Create,FileAccess.ReadWrite);
using(stream)
{
formatter.Serialize(stream,obj1);
stream.Seek(0,SeekOrigin.Begin);
obj2 = formatter.Deserialize<MyClass<int>>(stream);
}
|
You cannot use the SOAP formatter to serialize generic types. |