.NET Framework Assemblies/Namespaces
- System.Runtime.Serialization
- System.Runtime.Serialization.Formatters.Binary
System.Runtime.Serialization.Formatters.Soap.dll <--for SOAP serialization
.NET Framework Classes
- Class1
- Class2
- etc...
Sample Code
Here is some code demonstrating the use of these classes.
Serializing/Deserializing .NET Framework classes/objects
First we start with some IronPython snippets. However binary serialization is pretty much the same regardless of the language. Its a matter of importing the needed serialization namespaces, creating a FileStream, a BinaryFormatter and calling Serialize or Deserialize as needed as demonstrated here.
from System.Runtime.Serialization import *
from System.Runtime.Serialization.Formatters.Binary import *
from System.IO import *
# Binary Serialization out and back in using
# FileStream and BinaryFormatter, that's all there is to it
aStringToSerialize = "This is a string to serialize"
fs = FileStream("data.ser", FileMode.Create)
bf = BinaryFormatter()
bf.Serialize(fs, aStringToSerialize)
fs.Close()
fs = FileStream("data.ser", FileMode.Open)
bf = BinaryFormatter()
aString = bf.Deserialize(fs)
fs.Close()
print aStringThe expected output is
This is a string to serialize
Note that in addition to the Deserialize() method, there also exists an UnsafeDeserialize() method which is faster, but requires uses unmanaged code and therefore requires full trust rather than managed and partially trusted code. You can see examples of this in the sample code for custom serialization. To test these events, place breakpoints in the debugger and observe when this methods are called.
Serializing Custom classes/objects
Making custom classes/object Serializable is as simple as marking the class with the [Serializable] attribute so next I tried this...
from System import Serializable
[Serializeable]
class LineItem:
def __init__(self, id, price, quantity):
self.itemId = 0
self.price = 0
self.quanity = 0
self.calcTotal()
def calcTotal(self):
self.total = self.price * self.quanity
# Binary Serialization out and back in using
# FileStream and BinaryFormatter, that's all there is to it
fs = FileStream("order.ser", FileMode.Create)
bf = BinaryFormatter()
bf.Serialize(fs, LineItem(1, 2, 2))
fs.Close()
fs = FileStream("data.ser", FileMode.Open)
bf = BinaryFormatter()
aLineItem = bf.Deserialize(fs)
fs.Close()but, it turns out that for some reason you cannot import the Serializable namespace in IronPython, so we'll drop back to C# for this example.
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
namespace _70_536
{
[Serializable]
class LineItem : IDeserializationCallback
{
private int id;
private int quantity;
private int price;
[NonSerialized]
private int total;
public LineItem(int id, int quantity, int price)
{
this.id = id;
this.quantity = quantity;
this.price = price;
}
private void calcTotal()
{
this.total = this.price * this.quantity;
}
public int Total { get { return this.total; } }
public void OnDeserialization(object sender)
{
this.calcTotal();
}
}
class Serialization
{
static void Main(string[] args)
{
FileStream fs = new FileStream("data.ser", FileMode.Create);
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(fs, new LineItem(1, 2, 2));
fs.Close();
fs = new FileStream("data.ser", FileMode.Open);
bf = new BinaryFormatter();
LineItem i = (LineItem)bf.Deserialize(fs);
System.Console.WriteLine(i.Total);
}
}
}Note the class is marked with the [Serializable] attribute. Also note the [NonSerialzied] attribute on the total member to avoid serializing some that can be derived from other attributes. The class also implements the IDeserializationCallback interface and its OnDeserialization method so that total can be calculated/initialized during deserialization of the object.
Binary Serialization and class/object Versioning
One should use the [OptionalField] attribute on newly added class members so that deserialization will not blow up. The new member will be initialized to its default - 0 or Null - depending upon whether is is a value or reference type. You can always override these by setting values using IDeserializationCall back implementation. Also remember to:
- Never removed a serialized field
Never apply the [NonSerialized] attribute to a field that was serialized previously.
- Never change the name or the type of a serialized field.
When adding a new field, use the [OptionalField] attribute.
When removing [NonSerialized] from a field, be sure to apply [OptionalField] to that field to avoid blowups.
Be sure to set reasonable defaults to non-serialized fields using the IDeserializationCallback method OnDeserialization() unless the defaults of 0 and null are acceptable.
Note that in .NET Framework version 1.0 and 1.1, an exception was thrown if a field was removed, .NET Framework version 2.0 handles this without throwing an exception.
More Control over Binary Serialization
One can exert more control over binary serialization by using either serialization events, or full blown custom serialization by implementing the ISerialization interface. Serialization events don't allow you to control serialization, but do provide you with hooks to perform special behavior just before or just after serialization. Custom serialization provides full control.
Serialization Events
You can create event handlers by marking your event handler methods with attributes. There are two events available for serilzation, two for deserialization. These are:
[OnSerializing] - called just before Serialization takes place
[OnSerialed] - called just after Serialization takes place
[OnDeserializing] - called just before Deserialization takes place
[OnDeserialized] - called just after Deserialization takes place
Methods must return void, and accept a StreamingContext parameter.
Note that the OnDeserialization() method from the IDeserializationCallback interface, is called just before the OnDeserialized event handler.
Custom Serialization
The C# class below is an example, similar the the class in the other examples, but it implements custom serialization for full control. Note the implementation of the ISerializable interface which means the class must provide the GetObjectDataMethod() with its SerializationInfo and StreamingContext parameters. Also notice that one must provide a constructor (called during deserialization) which accepts the same parameters.
[Serializable]
class CustomLineItem : ISerializable
{
private int id;
private int quantity;
private int price;
[NonSerialized]
private int total;
public CustomLineItem(int id, int quantity, int price)
{
this.id = id;
this.quantity = quantity;
this.price = price;
this.calcTotal();
}
public CustomLineItem(SerializationInfo info, StreamingContext context)
{
this.id = info.GetInt32("id");
this.quantity = info.GetInt32("quantity");
this.price = info.GetInt32("price");
this.calcTotal();
}
private void calcTotal()
{
this.total = this.price * this.quantity;
}
public int Total { get { return this.total; } }
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("id", this.id);
info.AddValue("quantity", this.quantity);
info.AddValue("price", this.price);
}
}
XML Serialization
Not much different here other than using an XmlSerialization class from the System.XML.Serialization namespace.
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml.Serialization;
namespace _70_536
{
class Serialization
{
static void Main(string[] args)
{
XMLSerialization();
}
private static void XMLSerialization()
{
FileStream fs = new FileStream("data.xml", FileMode.Create);
XmlSerializer s = new XmlSerializer(typeof(DateTime));
s.Serialize(fs, DateTime.Now);
fs.Close();
FileStream fs2 = new FileStream("data.xml", FileMode.Open);
XmlSerializer s2 = new XmlSerializer(typeof(DateTime));
DateTime then = (DateTime)s2.Deserialize(fs2);
System.Console.WriteLine(then);
}
}
}XML Serialization works for custom classes as well. Just note that it only works for public classes with no-arg/parameterless/default constructors and only public fields are serialized.
Conforming to a XML Schema
By default each public field is serialized as an element. You can control how fields are represented in the resulting XML by use of XML Serialization attributes. See http://msdn2.microsoft.com/en-us/library/bb552742.aspx.
Alternatively if you want to start with an XML Schema, you can then use the xsd.exe tool to create a class complete with XML Serialization code to conform to that XML Schema:
> xsd.exe schemafilename.xsd /classes /language:CS
Dealing with non-matching nodes, elements, attributes
This article by Espizito is about the only thing I've run across that explains the events that can be fired by !XMLSerializer that you can hook to deal with non-conformity to your class/object. You can read about these events in the class documentation here on MSDN.
Book Feedback
The book does not make mention of the UnsafeDeserialze() method which I ran across in the SelfTest practice exam that I also used for preparing for the exam.
The book does not discuss the xxx, xxx and xxx events of the XMLSerializer class which are used to deal with unexpected "stuff" in an XML document that you are trying to deserialize from. Again the SelfTest practice exam I used included this as one of its questions.