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.
Section 12.2.2 demonstrated techniques how we can serialize
objects that are not marked serializable. We can either derive a new class that
implements the ISerializable interface or, if the class does not allow us to
derive from it, we can wrap it in a new class that will handle serialization.
Both techniques have their drawbacks. Maybe we cannot replace a class with a
different class everywhere it is referenced and wrapping an object does not
only introduce a completely unrelated type it also is pretty cumbersome.
The .NET Framework provides one more hook to customize object
serialization that allows full customization if the whole serialization process
and is completely transparent to the serialized objects. This solution requires
a little bit more coding than implementing the ISerializable interface, but it
will allow us to control serialization (and deserialization) for every class in
the system, regardless if it is marked Serializable. The trick is to reroute
the whole serialization process to a different class, the serialization
surrogate. This class has to implement the ISerializationSurrogate interface,
which is similar in nature to the ISerializable interface. Table 12.7 shows the
methods defined by the ISerializationSurrogate interface and their semantics.
1.10
Table 12.7 A
serialization surrogate takes over serialization for all instances of a certain
class. The surrogate has to implement the ISerializationSurrogate interface
with these methods.
Method
Description
void GetObjectData(
object obj,
SerializationInfo info,
StreamingContext context
);
Fills the
SerializationInfo info with the data to serialize the object obj. The StreamingContext contains
information to tune the serialized data to the intended deserialization
scenario.
object
SetObjectData(
object obj,
SerializationInfo info,
StreamingContext context,
ISurrogateSelector selector
);
Retrieves data to
deserialize the object obj from the
SerializationInfo info . The
StreamingContext contains information to identify source context of the
serialized data. The selector
parameter supplies the SurrogateSelector chain registered with this
formatter.
The GetObjectData() and SetObjectData() methods perform the same
functionality as GetObjectData() on the ISerializable interface and the
deserialization constructor. The difference is that these methods are not
implemented on the object that is serialized or deserialized. The parameters of
both methods are identical to their counterparts in the ISerializable
implementations. The SerializationInfo object serves as a container for the
serialized information and the StreamingContext provides details about the
context in which the operation executes. We already discussed these two classes
in more detail in the previous section, now we can focus implementing
ISerializationSurrogate.
Because ISerializationSurrogate is not implemented on the
serialized object directly the SetObjectData() and GetObjectData() methods only
have access to public fields and properties. In many cases the public fields
provide enough information to recreate objects later on. However,
SetObjectData() will receive a fresh, completely un-initialized object that was
created without(!) running any constructors. Likewise, no variable-initializers
were executed when this object is created, all fields are set to null when the
object is passed to SetObjectData(). You can probably imagine the catastrophic
effects an uninitialized private field can have on the behavior of an object.
WARNING: We must exercise extreme caution and thoroughly test
to ensure deserialized objects are fully functional if we implement surrogates
for 3rd party classes that do not persist all fields.
Now, there is a way to read and set private fields, but it is
only available in fully trusted environments. Just as the runtime formatters,
we can access private members through reflection. In order to do so our
applications require all security privileges related to reflection. Also the
penalty for accessing fields through reflection is very stiff, easily greater
than 100x compared to assignments through writable properties, for example.
However, by setting each member during deserialization we can guarantee an
object will behave just like the one serialized.
We have to register the surrogate with a formatter if we want the
formatter to channel serialization for certain classes through our surrogates
rather than checking the object for ISerializable or handling the class itself.
However, the registration is managed by a surrogate selector, not by the
formatter itself. The selector is a special container to register surrogates by
type and serialization context. Yes, you can register different surrogates for
different scenarios, e.g. one for long term object persistence in files and one
to transmit objects across process boundaries on the same machine. This gives us
the same flexibility for optimizing the serialization process as we have with
the ISerializable interface. Furthermore, it allows very fine grained control
over the scenarios in which we want to override a class’ built-in serialization
because we do not have to register a surrogate for all possible contexts. The
scenarios are also identified by a StreamingContext object with the semantics
we discussed in section 12.2 and table 12.3.
To register one or more selectors, the formatter exposes a
property named SurrogateSelector. With surrogate selectors registered, the
formatter iterates over all selectors to find a surrogate for the object type
it has to process in the given context. If it can locate an appropriate
surrogate it delegates serialization to the surrogate. Figure 12.1 illustrates
this interaction between the formatter, the surrogate selector and the
serialization surrogate.
Figure 12.1 Several
objects collaborate when we delegate serialization to a surrogate serializer: An
application registers a surrogate for a certain class with a surrogate
selector. Every time a serialization formatter reads or writes an object, it
checks with the surrogate selector if any surrogates are available for the
given class in the current context. If the surrogate can provide a surrogate,
all serialization activity as delegated to the surrogate.
A surrogate selector object has to implement the
ISurrogateSelector interface (table 12.8) to interact with the runtime
serialization formatters of the .NET Framework. The interface defines the
GetSurrogate() method to retrieve serialization surrogates by object type and
serialization scenario.
1.11
Table 12.8
Serialization formatters communicate with surrogate selectors over the
ISurrogateSelector interface. The interface allows
Method
Description
public virtual void ChainSelector(
ISurrogateSelectorselector
);
Adds a selector
object to the chain of selector objects
public virtual ISurrogateSelector GetNextSelector();
Returns the next
surrogate selector in the chain of selector objects.
public virtual ISerializationSurrogate GetSurrogate(
Typetype,
StreamingContextcontext,
out ISurrogateSelectorselector
);
Locate a surrogate
for type and context in the chain of selector objects. The selector parameter references the
surrogate selector containing the matching surrogate when the method returns.
The interface defines two additional methods, one to allow
chaining multiple selector objects and one to enable the serialization
formatter to traverse the chain. The interface does not define any methods to
add surrogates to the selector’s selection, but since the interface only
defines the interaction between the serialization formatter and the surrogate
selector a firm definition of this method is not required.
Now that you know how the ISurrogateSelector interface works, I
can tell you rarely have to implement it because the .NET Framework already
supplies a default implementation ready for us to use with the
SurrogateSelector class. In addition to the methods mandated by the
ISurrogateSelector interface this class also exposes the two methods shown in
table 12.9 to add and remove surrogate objects.
1.12
Table 12.9 The
SurrogateSelector class exposes methods to manage the contained serialization
surrogates
Method
Description
public virtual void AddSurrogate(
Typetype,
StreamingContextcontext,
ISerializationSurrogatesurrogate
);
Adds the surrogate to use for an object of type
type in the context specified
by context to the slection.
public virtual void RemoveSurrogate(
Typetype,
StreamingContextcontext
Removes the
surrogate for objects of type type and
the context specified by context
from the selection.