Recipe 3.33 Choosing a Serializer
Problem
The
FCL contains several classes to allow objects to be serialized into
different formats. Choosing the correct format for your task and
remembering how to use that format can become a chore, especially
when there is a mixture of different formats and all of them are on
disk. You need some way of simplifying the serialization interfaces
to make serialization easy without worrying about the underlying
differences in the serialization classes. This will also allow other
developers on your team to become proficient with the use of the
various serializers more quickly.
Solution
Use the façade design
pattern to create the following
Serializer class:
using System;
using System.Collections;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml.Serialization;
using System.Runtime.Serialization.Formatters.Soap;
// Note that you must also add a reference to the following assembly:
// System.Runtime.Serialization.Formatters.Soap.dll
[Serializable]
public class Serializer
{
public Serializer( ) {}
protected Hashtable serializationMap = new Hashtable( );
protected Hashtable serializationTypeOfMap = new Hashtable( );
// Serialize an object
public void SerializeObj(object obj, string destination)
{
SerializeObj(obj, destination, SerializationAction.Default);
}
public void SerializeObj(
object obj, string destination, SerializationAction action)
{
if (action == SerializationAction.RetainAssemblyInfo ||
action == SerializationAction.RetainPrivateMembers ||
action == SerializationAction.SmallestFootprint ||
action == SerializationAction.Default)
{
BinarySerializeObj(obj, destination);
serializationMap.Add(destination.ToUpper( ), DeserializationType.Binary);
}
else if (action == SerializationAction.MakePortable ||
action == SerializationAction.AsSOAPMsg)
{
SoapSerializeObj(obj, destination);
serializationMap.Add(destination.ToUpper( ), DeserializationType.SOAP);
}
else if (action == SerializationAction.AsXML ||
action == SerializationAction.SendToXMLWebService)
{
XmlSerializeObj(obj, destination);
serializationMap.Add(destination.ToUpper( ), DeserializationType.XML);
serializationTypeOfMap.Add(destination.ToUpper( ),
obj.GetType( ).FullName);
}
}
private void BinarySerializeObj(object obj, string destination)
{
BinaryFormatter binFormatter = new BinaryFormatter( );
Stream fileStream = new FileStream(destination, FileMode.Create,
FileAccess.Write, FileShare.None);
binFormatter.Serialize(fileStream, obj);
fileStream.Close( );
}
private void SoapSerializeObj(object obj, string destination)
{
SoapFormatter SOAPFormatter = new SoapFormatter( );
Stream fileStream = new FileStream(destination, FileMode.Create,
FileAccess.Write, FileShare.None);
SOAPFormatter.Serialize(fileStream, obj);
fileStream.Close( );
}
private void XmlSerializeObj(object obj, string destination)
{
XmlSerializer XMLFormatter = new XmlSerializer(obj.GetType( ));
Stream fileStream = new FileStream(destination, FileMode.Create,
FileAccess.Write, FileShare.None);
XMLFormatter.Serialize(fileStream, obj);
fileStream.Close( );
}
// DeSerialize an object
public object DeSerializeObj(string source)
{
return (DeSerializeObj(source,
(DeserializationType)serializationMap[source.ToUpper( )]));
}
public object DeSerializeObj(string source, DeserializationType type)
{
object retObj = null;
if (type == DeserializationType.Binary)
{
retObj = BinaryDeSerializeObj(source);
serializationMap.Remove(source.ToUpper( ));
}
else if (type == DeserializationType.SOAP)
{
retObj = SoapDeSerializeObj(source);
serializationMap.Remove(source.ToUpper( ));
}
else if (type == DeserializationType.XML)
{
retObj = XmlDeSerializeObj(source);
serializationMap.Remove(source.ToUpper( ));
serializationTypeOfMap.Remove(source.ToUpper( ));
}
return (retObj);
}
private object BinaryDeSerializeObj(string source)
{
BinaryFormatter binFormatter = new BinaryFormatter( );
Stream fileStream = new FileStream(source, FileMode.Open, FileAccess.Read,
FileShare.None);
object DeserializedObj = binFormatter.Deserialize(fileStream);
fileStream.Close( );
return (DeserializedObj);
}
private object SoapDeSerializeObj(string source)
{
SoapFormatter SOAPFormatter = new SoapFormatter( );
Stream fileStream = new FileStream(source, FileMode.Open, FileAccess.Read,
FileShare.None);
object DeserializedObj = SOAPFormatter.Deserialize(fileStream);
fileStream.Close( );
return (DeserializedObj);
}
private object XmlDeSerializeObj(string source)
{
XmlSerializer XMLFormatter = new
XmlSerializer(Type.GetType((string)serializationTypeOfMap
[source.ToUpper( )]));
Stream fileStream = new FileStream(source, FileMode.Open,
FileAccess.Read, FileShare.None);
object DeserializedObj = XMLFormatter.Deserialize(fileStream);
fileStream.Close( );
return (DeserializedObj);
}
}
public enum SerializationAction
{
Default = 0,
RetainAssemblyInfo,
RetainPrivateMembers,
MakePortable,
SmallestFootprint,
SendToXMLWebService,
AsSOAPMsg,
AsXML
}
public enum DeserializationType
{
Binary = 0,
SOAP,
XML
}
Discussion
The façade design pattern uses a
façade class to provide a simple interface to a group of
underlying objects that do similar work. Any client that wants to use
one of the underlying objects can go through the façade
object. In effect, the façade pattern abstracts away the
complexities and disparities between the underlying classes. This
allows a uniform, and much easier to use, interface to be presented
to the clients that wish to use any of these underlying objects.
The façade object can decide which underlying object will
be used to perform the action requested, but it is not required to do
so. The user could even pass in one or more arguments allowing the
façade object to determine which underlying object to use.
The nice thing about this pattern is that if the client decides that
they need more flexibility than is provided by the façade
object, they can choose to use the underlying objects and contend
with their individual complexities. Also, if other serialization
classes are created, they can easily be added to the
façade object without breaking the existing code.
The class that acts as the façade in this recipe is the
Serializer class. This class abstracts away the
various interfaces to the various serializers that ship with the FCL,
namely:
- System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
- System.Xml.Serialization.XmlSerializer
- System.Runtime.Serialization.Formatters.Soap.SoapFormatter
In addition, this class provides an enumeration called
SerializationAction, which can be passed to the
SerializeObj method for the
Serializer to choose the best type of
serialization object to use to serialize the input data. The various
values of the SerializationAction enumeration and
their meanings are:
- Default
-
Uses the default serialization object, which is
BinaryFormatter.
- RetainAssemblyInfo
-
Uses the BinaryFormatter. This value is used when
the assembly information needs to be retained by the serialization
process.
- RetainPrivateMembers
-
Uses the BinaryFormatter. This value is used when
private members need to be added to the serialization stream.
- SmallestFootprint
-
Uses the BinaryFormatter. This value is used when
the client wants the serialization data in the most compact form
possible. As an added benefit, this serialization method is also the
fastest.
- MakePortable
-
Uses the SoapFormatter. This value is used when
the serialization data needs to be in the most portable form (i.e.,
SOAP).
- AsSOAPMsg
-
Uses the SoapFormatter. This value tells the
façade object to explicitly use the
SoapFormatter.
- SendToXMLWebService
-
Uses the XmlSerializer. This value is used when
the serialized object will be sent to an ASP.NET XML web service.
- AsXML
-
Uses the XmlSerializer. This value tells the
façade object to explicitly use the
XmlSerializer.
The interface to the Serializer object contains
two sets of overloaded methods: SerializeObj and
DeSerializeObj. Both
SerializeObj methods accept an object to be
serialized in the obj parameter and a
location to store the serialized object in the
destination parameter. The second
SerializeObj method also has a parameter that
accepts a SerializationAction enumeration, which
was previously discussed. The first SerializeObj
method does not have this parameter and so defaults to using the
SerializationAction.Default enumeration value.
 |
You need to have permissions to open a FileStream
directly from your code in order to use this recipe. This recipe
cannot be used in a partial-trust environment where you are obliged
to get your FileStreams either from
IsolatedStorage or from a
FileDialog.
|
|
Both DeSerializeObj methods accept a source string
indicating where the serialized object is located. The second
overloaded DeSerializeObj method also accepts a
DeserializationType enumeration. This enumeration
contains three values-Binary,
SOAP, and XML-and is used
to explicitly inform the underlying Deserialize
methods of which serialization objects to use. If the first
DeSerializeObj method is called, the values cached
in the SerializeObj methods are used to
deserialize the object without the client having to remember various
small details about the serialization process used to initially
serialize the object. If the SerializeObj methods
are not used to serialize the object, one of the various
DeserializationType enumeration values can be
explicitly passed as an argument to inform the
DeSerializeObj method which underlying
deserialization method to call.
The serializationMap and
serializationTypeOfMap
Hashtables are used to cache various pieces of
information during the serialization process. The
SerializeObj methods use the
serializationMap Hashtable to
map the destination of the serialized object to the type of
serialization process used. This allows the
DeSerializeObj methods to use the source parameter
to locate the pertinent information in the
serializationMap Hashtable. The
serializationTypeOfMap is used only when the
XmlSerializer object is used for serialization.
Upon deserialization, the XmlSerializer uses the
serializationTypeOfMap to locate the full type
name that is to be deserialized.
The following code serializes an integer array to the file
TestBinSerXML.txt and then deserializes it into
the retArray variable:
Serializer s = new Serializer( );
s.SerializeObj(new int[10] {1,2,3,4,5,6,7,8,9,10}, @"C:\TestBinSerXML.txt",
SerializationAction.AsXML);
int[] retArray = (int[])s.DeSerializeObj(@"c:\TestBinSerXML.txt");
See Also
See the "Serializing Objects,"
"Introducing XML Serialization,"
and "Serialization Guidelines"
topics in the MSDN documentation.
|