Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Serialization Surrogates
1. Serialization Surrogates
What it means and why you need it?
First, there may be a situation where you have been using a third party DLL (without the
available source code) in your application which contains a type that was not designed for
serialization and you need to serialize it as per your requirement.
Secondly, during the development process you may also have a requirement to deserilaze
an object to a different version of the type i.e. You need to map one version of a type to a
different version of a type.
This is where Serialization Surrogates come into picture!!!!
How the mechanism works?
You first define a “surrogate type” that takes over the actions required to serialize and
deserialize an existing type. Then you register an instance of your surrogate type with
the formatter, telling the formatter which existing type your surrogate type is
responsible for acting upon. When the formatter detects that it is trying to serialize or
deserialize an instance of the existing type, it will call methods defined by your surrogate
type.
The example below demonstrates how all this works.
Suppose that there is an existing type, Car, defined in an assembly for which you do not
have the source code, like:
Class Car {
public String make, model;
public Car(String make, String model)
{
this.make = make;
this.model = model;
}
}
The above type neither has a SerializableAttribute nor does it implement the ISerializable
interface, thus it does not allow instances of itself to be serialized. But by defining a
“surrogate type” you can force the formatter to serialize the object of this type.
The surrogate type must implement the
System.Runtime.Serialization.ISerializationSurrogate interface which is defined in the
Framework Class Library as follows:
public interface ISerializationSurrogate {
void GetObjectData(Object obj,
2. SerializationInfo info, StreamingContext context);
Object SetObjectData(Object obj,
SerializationInfo info, StreamingContext context,
ISurrogateSelector selector);
}
Step-1:Defien a Surrogate Type
Using this interface, a Car serialization surrogate type would be defined as shown in
Figure 1. The GetObjectData method in Figure 1 works just like the ISerializable
interface's GetObjectData method. The only difference is that ISerializationSurrogate's
GetObjectData method takes one additional parameter, a reference to the "real" object
that is to be serialized. In the GetObjectData method shown in Figure 1, this object is
cast to a Car and the object's field values are added to the SerializationInfo object.
Figure -1 CarSerializationSurrogate:
sealed class CarSerializationSurrogate : ISerializationSurrogate {
// Method called to serialize a Car object
public void GetObjectData(Object obj,
SerializationInfo info, StreamingContext context) {
Car c = (Car) obj;
info.AddValue("make", c.make);
info.AddValue("model", c.model);
}
// Method called to deserialize a Car object
public Object SetObjectData(Object obj,
SerializationInfo info, StreamingContext context,
ISurrogateSelector selector) {
Car c = (Car) obj;
c.make = info.GetString("make");
c.model = info.GetString("model");
return null; // Formatters ignore this return value
}
}
The SetObjectData method is called in order to deserialize a Car object. When this
method is called, it is passed a reference to a Car object that has been allocated (via
FormatterServices' static GetUninitializedObject method). This means that the object's
fields are all null and no constructor has been called on the object. SetObjectData simply
initializes the fields of this object using the values from the passed-in SerializationInfo
object.
3. Step-2: Registering a Surrogate Object with a Formatter
You must be wondering how the Formatter knows to use this ISerializationSurrogate type
when it tries to serialize or deserialize the Car object.
The code in Figure -2 demonstrates how to test the CarSerializationSurrogate type. After
the 5 steps have executed, the formatter is ready to use the registered surrogate types.
When the formatter's Serialize method is called, each object's type is looked up in the set
maintained by the SurrogateSelector. If a match is found, then the ISerializationSurrogate
object's GetObjectData method is called to get the information that should be written out
to the byte stream.
Figure -2 Testing the Type :
static void SerializationSurrogateDemo() {
// 1. Construct the desired formatter
IFormatter formatter = new SoapFormatter();
// 2. Construct a SurrogateSelector object
SurrogateSelector ss = new SurrogateSelector();
// 3. Construct an instance of our serialization surrogate type
CarSerializationSurrogate css = new CarSerializationSurrogate();
// 4. Tell the surrogate selector to use our object when a
// Car object is serialized/deserialized
ss.AddSurrogate(typeof(Car),
new StreamingContext(StreamingContextStates.All),
new CarSerializationSurrogate());
// NOTE: AddSurrogate can be called multiple times to register
// more types with their associated surrogate types
// 5. Have the formatter use our surrogate selector
formatter.SurrogateSelector = ss;
// Try to serialize a Car object
formatter.Serialize(stream, new Car("Toyota", "Celica"));
// Rewind the stream and try to deserialize the Car object
stream.Position = 0;
Car c = (Car) formatter.Deserialize(stream);
// Display the make/model to prove it worked
Console.WriteLine("make = {0}, model = {1}", c.make, c.model);
}
When the formatter's Deserialize method is called, the type of the object about to be
deserialized is looked up in the formatter's SurrogateSelector and, if a match is found, the
4. ISerializationSurrogate object's SetObjectData method is called to set the fields within
the object being deserialized.
Conclusion:
There are many uses of serialization and employing it properly can greatly reduce the
effort required to build applications, persist data and transfer data between processes and
even machines.