Mark Wilson I am the creator of TopXML. I am available for international and local (Australia) contracts. I am a Solution Architect/Business Analyst. I have worked in IT in several countries (NZ, Australia, South Africa, UK) building and training teams for government and very large non-governmental organizations. I am ex-Microsoft Consulting Services. I wrote the first book on Microsoft XML published in 2000 called XML Programming with VB and ASP. Most recently I have been building tools for the SEO industry. Ask me for a 37 point SEO health-checkup for your website.
For the remainder of this chapter we will develop an example to
demonstrate how we can register a serialization surrogate with a formatter.
First, we need a class that the surrogate is going to serialize. The class is
shown in the following listing (12.3).
22
Listing 12.3: A class
without the Serializable attribute.
public sealed class NonSerializable
{
public NonSerializable
()
{
Console.WriteLine(
"NonSerializable ctor" );
}
private string
_privateString;
public string
_publicString = "aPublicString";
}
There is nothing special about this class, actually, it’s pretty
useless. Nevertheless it will help us to understand how surrogates work. The
best way to serialize this class is through a surrogate because it is not
marked with the Serializable attribute and it is also declared sealed, therefore
we cannot derive from it to make it serializable. You will come across many
sealed classes when you get going with programming on the .NET platform.
Knowing how to serialize them, even when they are not marked serializable, is
very useful.
ISerializationSurrogate
Next, we are going to write the surrogate. The surrogate class
has to implement the ISerializationSurrogate methods: GetObjectData() and
SetObjectData() for the formatter to delegate serialization and deserialization
to the surrogate.
23
Listing 12.4: A
serialization surrogate for the NonSerializable class. The surrogate accesses
private data members of the NonSerializable objects through reflection.
using System;
using System.Reflection; // to access private data members
using System.Runtime.Serialization; // for ISerializationSurrogate
and
// related classes
// No [Serializable] required
public class NonSerializableSurrogate : ISerializationSurrogate
{
public void
GetObjectData(object obj,
SerializationInfo
info,
StreamingContext
context)
{
NonSerializable nsObj
= obj as NonSerializable; |#1
(annotation)
<#1 Safety check that we are really handling the correct object type.>
(annotation)
<#2 Retrieve the value of the private data field through type
refelection.>
(annotation)
<#3 Another safety check to make sure we are deserializing the correct
object type.>
(annotation)
<#4 Set the value of the private field through the reflection API.>
(annotation)
<#5 Return the object after all the values are set.>
GetObjectData()
This GetObjectData() implementation looks very much like the
GetObjectData() method in the example for the ISerializable interface. The only
difference is that the serialized object is passed in as a parameter. The
SerializationInfo object serves once more as the container for all the data we
want to serialize. We call the AddValue() method to add the objects we want to
serialize to the container. The SerializationInfo object will do everything
else: check if any surrogates are registered for the added objects, check the
objects for ISerializable and finally hand everything off to the formatter
object. The formatter classes then handle all the gritty details about how the
objects within the SerializationInfo are persisted and recreated upon
deserialization. When it is time to deserialize the object we can retrieve all
the stored values through the various Get* methods from the SerializationInfo
object passed to SetObjectData(). Retrieving objects by calling GetValue() will
also ensure that all retrieved objects are properly deserialized as well.
Our surrogate class above accesses the private field of the
NonSerializable class through reflection. Sometimes this might be our only
solution to properly handle 3rd party classes, but in general we
should design serializable classes and surrogates to avoid reflection in favor
of properties for example. Accessing fields through reflection bears a huge
overhead compared to direct access or access through properties. Reading a
private field through reflection, for example, can be more than 150x slower
than reading it through a property.
SoapFormatterWithSurrogate
The last step before we can serialize and deserialize objects
with our great, new surrogate is to create a serialization formatter and
register the surrogate with it. The follwing example creates a custom formatter
class that can always serialize objects of the NonSerializable class. The
SoapFormatterWithSurrogate wraps the SoapFormatter class, and performs the
registration of the surrogate and the surrogate selector in the constructor.
First, we register the surrogate with a SurrogateSelector and specify the
scenarios in which the formatter should delegate all action to this surrogate.
Our example calls AddSurrogate() method with a StreamingContext object
initialized with StreamingContextStates.All to register the surrogate for all
serialization scenarios because the NonSerializable class can never be
serialized on its own. Finally we pass the SurrogateSelector to the constructor
of the SoapFormatter object and the formatter is ready to go. Every time a
NonSerializable object is serialized (and deserialized) with a
SoapFormatterWithSurrogate, the surrogate will automatically handle persisting
and restoring the data.
24
Listing 12.5:
Registering a SerializerSurrogate.
using System.Runtime.Serializtion;
public class SoapFormatterWithSurrogate
{
private SoapFormatter
_Formatter;
public
SoapFormatterWithSurrogate()
{
SurrogateSelector
selector = new SurrogateSelector();
selector.AddSurrogate(
typeof( NonSerializable ),
new
StreamingContext( StreamingContextStates.All ),
new NonSerializableSurrogate() );
_Formatter =
new SoapFormatter(
selector,
new
StreamingContext( StreamingContextStates.All ) );
}
public void
SerializeWithSurrogate(Stream destination,
NonSerializable obj)
{
_Formatter.Serialize(
destination, obj );
}
public NonSerializable
DeserializeWithSurrogate(Stream source)
Object serialization in the .NET Framework
is valuable tool, not just in XML enabled .NET applications. This chapter
demonstrated how we can develop serializable classes and how we can apply
different techniques to override serialization behavior built into a class. The
serialization format was not important in this chapter, because what learned is
independent of the format. Yet it was important to understand the different
aspects of serializable objects before we focus on serializing objects with the
SoapFormatter. The SoapFormatter serializes objects into SOAP messages or parses
SOAP messages and extracts serialized objects from the message.