When you create an object in a .NET Framework application, you don't have to worry about how the application's data is stored in memory - the .NET Framework does that for you. However, if you want to send an object to a file, or another application, or across a network then the object will have to be converted to a different format. This is called serialization.
Serialization, as implemented in the System.Runtime.Serialization namespace, is the process of converting an object to a linear series of bytes that can be stored or transferred. In the code below, serialization is used to store a string (which wouldn't be necessary - you'd just write the string to a file) and the DateTime.
To serialize/deserialize the data, a FileStream is opened together with a BinaryFormatter.
using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; private static void Main() { string data = "This is the vitally important data" System.DateTime.Now; // Create a file to save the data to FileStream fs = new FileStream ("SerializedString.Data", FileMode.Create); // Create a BinaryFormatter object to perform the serialization BinaryFormatter bf = new BinaryFormatter(); // Use the BinaryFormatter object to serialize the data to the file bf.Serialize(fs, data); // Close the file fs.Close; }
deserialize:
using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; private static void Main() { // Open the file FileStream fs = new FileStream ("SerializedString.Data", FileMode.Open); // Create a BinaryFormatter object to perform the deserialization BinaryFormatter bf = new BinaryFormatter(); // Create the object top store the deserialized data string data = ""; ' Use the BinaryFormatter object to deserialize the data from the file data = (string)(bf.Deserialize(fs); // Close the file fs.Close; // Display the deserialized string Console.WriteLine(data); }
There are a two formats to choose from when serializing objects, BinaryFormatter and SOAPFormatter. SOAP stands for Simple Object Access Protocol and it is XML-based, and can therefore be used to transmit objects across a network and to applications outwith the .NET Framework. It is however, primarily intended for SOAP Web Services, and is not as flexible as XML serialization.
XML (eXtensible Markup Language) is a standardised, text-based document format for storing application-readable information. It's a standard that can be easily processed by computers. Any type of data (documents, images, binary files, music database information) can be stored using XML.
The .NET Framework includes several libraries for reading and writing XML files, including the System.Xml.Serialization namespace, which provides methods for converting objects to and from XML files. With XML you can write almost any object to a text file for later retrieval. There are three steps to serialize an object to XML:
using System.IO; using System.Xml.Serialization; private static void Main() { // Create the file to save the data FileStream fs = new FileStream("SerializedDate.XML", FileMode.Create); // Create an XmlSerializer object to perform the serialization XmlSerializer xs = new XmlSerializer(typeof(DateTime)); // Use the XmlSerializer object to serialize the data to the file, that data being the current date xs.Serialize(fs, System.DateTime.Now); // Close the file fs.Close; }
using System.IO using System.Xml.Serialization private static void Main() // Open the file from which the data is to be read FileStream fs = new FileStream("SerializedDate.XML", FileMode.Open); // Create an XmlSerializer object to perform the deserialization XmlSerializer xs = new XmlSerializer(typeof(DateTime)); // Use the XmlSerializer object to deserialize the data from the file and cast it to a DateTime DateTime previousTime = (DateTime)xs.Deserialize(fs); // Close the file fs.Close; // Display the deserialized date and time Console.WriteLine("Day: " + previousTime.DayOfWeek + (", Time: " + previousTime.TimeOfDay.ToString()) }
To create classes that can be serialized by using XML serialization, the following tasks must be performed:
If you serialize a class that meets the requirements fior XML serialization, but does not have any XML serialization attributes then default settings are used at runtime. These defaults use the class and member names, and each member is serialized as a separate XML element. The following simple class will be serialized as follows (sample values included):
public class ShoppingCartItem { public Int32 productId; public decimal price; public Int32 quantity; public decimal total; public ShoppingCartItem() { } } <?xml version="1.0" ?> <ShoppingCartItem > <productId>100</productId> <price>10.25</price> <total>20.50</total> <quantity>2</quantity> </ShoppingCartItem>
This might be perfectly fine. However, there may be times when your XML documents need to conform to specific standards, and thus you need to lay down how the serialization is to be structured. Supposing the ShoppingCartItem must be called CartItem, the productId is to be an attribute of CartItem rather than a separate XML element and the total is not to be serialized. The following changes should be made:
[XmlRoot ("CartItem")] public class ShoppingCartItem { [XmlAttribute] public int productId; public decimal price; public int quantity; [XmlIgnore]public decimal total; publicShoppingCartItem { } } <?xml version="1.0" ?> <CartItem productId="100"> <price>10.25</price> <quantity>2</quantity> </CartItem>
Typically, when two applications are going to exchange data in the form of XML files, the developers work together to create an XML schema file. An XML schema defines the structure of an XML document. Many types of XML schema already exist, and in many cases it makes sense to use an existing schema.
The XML Schema defimnition tool (xsd.exe) produces a set of classes that are strongly typed to the schema and annotated with attributes. When an instance of such a class is serialized, the generated XML adheres to the schema. To generate a class based on a schema follow these steps:
In addition to serializing a public class, an instance of a dataset object can also be serialized:
private void SerializeDataSet(stringfilename) { XmlSerializer ser = new XmlSerializer(GetType(DataSet)); // Creates a DataSet; adds a table, column and ten rows DataSet ds = new DataSet("myDataSet") DataTable t = new DataTable("table1") DataColumn c = new DataColumn("thing") t.Columns.Add(c); ds.Tables.Add(t); int i; for i = 0; i<10; i++) { r = t.NewRow(); r[0] = "Thing " + i; t.Rows.Add(r); } TextWriter writer = new StreamWriter(filename); ser.Serialize(writer, ds); writer.Close(); }
Similarly, you can serialize arrays, collections and instances of an XmlElement or Xml-Node class. Alternatively you can use DataSet.WriteXml, DatSet.ReadXml and DataSet.GetXml.
You can control the XML serialization of an object or create an alternate XML stream from the same set of classes by using attributes. XML serialization attributes customize how XmlSerializer maps a class, field, or property to an XML document and declare types that are not explicitly referenced in a source file. As a result, XML serialization attributes determine the resulting XML format for each class member associated with an attribute. The XML serialization attributes contain properties that provide information to the XmlSerializer class on how to map an object to XML format. These properties define XML namespaces, limit the scope of the attribute to a certain data type, specify the corresponding type in an XSD schema, control the form of the generated XML, and define whether to create XML for null references.
Consider an example where you are creating an application that first collects responses for a survey provided by different political candidates and then sends that information to the Server application for processing. In this application, you can mark the particular responses to questions in the resulting XML document that includes attributes to align the particular question within the survey for a specific candidate. The XML serialization attributes are divided into two categories, attributes that help you generate custom XML format and attributes that help you generate custom SOAP format. The following table describes these two categories of XML serialization attributes along with the corresponding code samples.
The IXmlSerializable interface provides custom formatting for XML serialization and deserialization. The XmlSerializer class implements the IXmlSerializable interface, and the custom classes use it to control how they will be serialized or deserialized through the XmlSerializer class. For a finer granularity of control on the code and a tighter direction of output, you should implement the IXmlSerializable interface in custom classes. You also obtain control of the XML file when serializing or deserializing objects at run time. As a result, by using the IXmlSerializable interface, you can control the way objects are serialized into XML format. However, you cannot control the serialization of objects into XML format if you use the XmlSerializer class. In addition, the implementation of the IXmlSerializable interface helps you chunk the serialized XML data in a better way.
For example, you need to develop an application that allows a teacher to collect and grade exams for students. These exams then need to be entered into the school’s data processing system for processing at diverse locations. To do this, the data is to be serialized and sent to the Student system, which would store the data to be used for creating report cards. Then, the serialized data needs to be sent to the Reporting application, which uses Extensible Stylesheet Language (XSL) and XSD to transform the data to be included in the reports for sending it to the Board of Education. Finally, the data would be serialized and sent to the Permanent Record application, which would then store the data in the students permanent record. In this situation, you can use the ISerializable interface to permit these diverse applications to use the exact same data as it is being rendered for each particular use.
The following table lists the methods that control serialization and deserialization in the IXmlSerializable interface.
The following code sample shows how to implement the IXmlSerializable interface and its methods. The code implements the IXmlSerializable interface in a class named Temperature and defines the ReadXml method to read data during deserialization. The code also defines the WriteXml method to write data during serialization. The Temperature class provides an equivalent reference type to represent a particular temperature. The class provides a property named Degree to represent the value for degree of the current Temperature object. Implementing the WriteXml method writes the degree value of the Temperature object into the XML stream as a string, and implementing the ReadXml method reads the degree value back from the XML stream and stores it into the corresponding field in the object. The Main method shows how to create and serialize an object of the Temperature class to an XML file on the disk. The object is serialized according to the implementation provided through the WriteXml method in the Temperature class. Finally, the code creates a second object of the Temperature class by deserializing the data in the XML file and displays the newly created object in the console. The data in the XML file is deserialized according to the implementation provided for the ReadXml method in the Temperature class.
using System; using System.IO; using System.Xml.Serialization; public class Temperature : IXmlSerializable { private float degreeOf; public float Degree { get { return this.degreeOf; } set { this.degreeOf = value; } } public System.Xml.Schema.XmlSchema GetSchema() { return null; } public void ReadXml(System.Xml.XmlReader reader) { this.degreeOf = Convert.ToSingle(reader.ReadString()); } public void WriteXml(System.Xml.XmlWriter writer) { writer.WriteString(this.degreeOf.ToString()); } public override string ToString() { return this.degreeOf.ToString(); } public static void Main() { try { StreamWriter tw = new StreamWriter("C:\\Temperature.xml"); XmlSerializer s = new XmlSerializer(typeof(Temperature)); Temperature t1 = new Temperature(); t1.Degree = 39.1C; s.Serialize(tw, t1); tw.Close(); FileStream fs = new FileStream("C:\\Temperature.xml", FileMode.Open); Temperature t2; t2 = ((Temperature)(s.Deserialize(fs))); Console.WriteLine(t2); fs.Close(); } catch (Exception ex) { Console.WriteLine(ex.ToString()); } finally { Console.WriteLine(); Console.Write("Press ENTER to exit..."); Console.ReadLine(); } } }
The common language runtime (CLR) supports reference types called delegates. In the .NET Framework, delegates are used for event handlers and callback functions. Delegates help you assign a method to be executed or processed in response to an event that occurs in an application. Delegates act as pointers from the event to the method, which is executing code. Delegates can be considered similar to a wire connecting a light switch to a light, and when you flip the switch, the light turns on or off. The event of Light_Flip causes the Change_Light_State method to execute changing the state of the light. A delegate type can represent any method with a compatible signature.
XML serialization delegates represent methods that can handle different events of an XmlSerializer class, such as the event raised when an unknown node, element, or attribute is found in the serialized stream during deserialization. You add an instance of the delegate to the event to associate the event with the event handler. The event handler is called whenever the corresponding event occurs during the process of serialization or deserialization being performed by the XmlSerializer class.
The following table describes the various XML serialization delegates that help you handle events during different situations.
The following code sample shows the use of XmlAttributeEventHandler, XmlNodeEventHandler, and XmlElementEventHandler delegates. It defines two classes, Company1 and Company2. Company2 contains more members than Company1. The code initially serializes an object of Company2 and then deserializes it to an object of Company1. To deal with the unknown members found during the process of deserialization, the code defines appropriate event handler methods to handle the UnknownAttribute, UnknownElement, and UnknownNode events with the help of the appropriate delegates for these events.
using System; using System.IO; using System.Xml.Serialization; using System.Xml; public class Company1 { public string CompanyName; } [XmlRoot(ElementName="Company1")]public class Company2 { public string CompanyName; [XmlAttribute]public string CompanyType; [XmlAttribute]public string CompanyId; public string CompanySize; public string CompanyLocaton; } public class MainClass { static void Main() { MainClass mainObject = new MainClass(); string filename = "c:\\Delegates.xml"; Company2 comp2 = new Company2(); comp2.CompanyId = "90067"; comp2.CompanyLocaton = "New York"; comp2.CompanyName = "ABC InfoSystems"; comp2.CompanySize = "Large"; comp2.CompanyType = "Technical"; XmlSerializer serializer = new XmlSerializer(typeof(Company2)); TextWriter writer =new StreamWriter(filename); serializer.Serialize(writer, comp2); XmlSerializer deserializer = new XmlSerializer(typeof(Company1)); writer.Close(); deserializer.UnknownAttribute += new XmlAttributeEventHandler(mainObject.Serializer_UnknownAttribute); deserializer.UnknownElement += new XmlElementEventHandler(mainObject.Serializer_UnknownElement); deserializer.UnknownNode += new XmlNodeEventHandler(mainObject.Serializer_UnknownNode); FileStream fs = new FileStream(filename, FileMode.Open); Company1 comp1 = (Company1)deserializer.Deserialize(fs); fs.Close(); Console.WriteLine("Press enter to exit"); Console.ReadLine(); } private void Serializer_UnknownNode(object sender, XmlNodeEventArgs e) { Console.WriteLine("UnknownNode Name: " + e.Name); Console.WriteLine("UnknownNode LocalName: " + e.LocalName); Console.WriteLine("UnknownNode Namespace URI: " + e.NamespaceURI); Console.WriteLine("UnknownNode Text: " + e.Text); Company1 comp1 = (Company1)e.ObjectBeingDeserialized; Console.WriteLine("Object being deserialized " + comp1.ToString()); XmlNodeType myNodeType = e.NodeType; Console.WriteLine(myNodeType); } private void Serializer_UnknownElement(object sender, XmlElementEventArgs e) { Console.WriteLine("Unknown Element"); Console.WriteLine(" " + e.Element.Name + " " + e.Element.InnerXml); Console.WriteLine(" LineNumber: " + e.LineNumber); Console.WriteLine(" LinePosition: " + e.LinePosition); Company1 comp1 = (Company1)e.ObjectBeingDeserialized; Console.WriteLine(comp1.CompanyName); Console.WriteLine(sender.ToString()); } private void Serializer_UnknownAttribute(object sender, XmlAttributeEventArgs e) { Console.WriteLine("Unknown Attribute"); Console.WriteLine(" " + e.Attr.Name + " " + e.Attr.InnerXml); Console.WriteLine(" LineNumber: " + e.LineNumber); Console.WriteLine(" LinePosition: " + e.LinePosition); Company1 comp1 = (Company1)e.ObjectBeingDeserialized; Console.WriteLine(comp1.CompanyName); Console.WriteLine(sender.ToString()); } }
Custom Serialization is the process of controlling the serialization, so that you can serialize and deserialize between different versions of a class. This is done by implementing your own serialization classes.
To override the serialization built into the .NET Framework, you use the ISerializable interface and apply the Serializable attribute to a class.
using System.Runtime.Serialization; using System.Security.Permissions; <Serializable()> class ShoppingCartItem : ISerializable { public int productId; public decimal price; public int quantity; [NonSerialized] public decimal total; // The standard, non-serialization constructor public ShoppingCartItem(int _productID, decimal _price, int, _quantity) { productId = _productID; price = _price; quantity = _quantity; total = price * quantity; } // The following constructor is for deserialization protected ShoppingCartItem(SerializationInfo info, StreamingContext context) { productId = info.GetInt32("Product ID"); price = info.GetDecimal("Price"); quantity = info.GetInteger("Quantity"); total = price * quantity; } // The following method is called during serialization [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter:=true)] public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Product ID", productId); info.AddValue("Price", price); info.AddValue("Quantity", quantity); } public override string ToString() { return productId + ": " + price + " x " + quantity + " = " + total; } }
Consider that you are creating an application and you need to serialize your objects to be stored in permanent structures or passed to another application for further processing. For example, you may need to serialize the information from a customer to a credit card processing application to complete the transaction and arrange the payment. Sometimes, when you are communicating with the components, you can use built-in serialization classes or any built-in classes available in the .NET Framework 2.0. However, you need to customize the interaction of components to ensure better control over object interaction. In this particular scenario, customizing the interaction of components helps you control how the customer’s credit card information is serialized and transmitted for processing.
The .NET Framework provides serialization interfaces that can be implemented by all built-in classes to enable the serialization functionality. The serialization interfaces are ISerializable, IDeserializationCallback, IFormatter, and IFormatterConverter. All of these interfaces belong to the System.Runtime.Serialization namespace.
The following table describes the various serialization interfaces and their members.
The built-in clases of the .NET Framework implement the ISerializable interface. You can make your own classes serializable by adding the Serializable attribute to a class. Some members of the class, such as temporary or calculated values, may not need to be stored. Consider the following ShoppingCartItem class, the fourth member, the total, is calculated from price times quantity and needn't be stored.
However, when the object is deserialized, the total will need to be recalculated before the object is used. To enable the class to initialze a nonserialized member automatically, the IDeserializationCallBack interface must be used.
The OptionalField comes in when a new member is added to a class in a later version of the application. To ensure compatibility with existing serialized objects created by earlier versions, any new member must be optional. (Also, you must never remove or rename a serialized field!)
[Serializable] class ShoppingCartItem : IDeserializationCallback public int productId; public decimal price; public int quantity; [NonSerialized] public decimal total; [OptionalField] public bool taxable; public ShoppingCartItem(int _productId, decimal _price, int _quantity) { productId = _productID; price = _price; quantity = _quantity; total = price * quantity; } private static void IDeserializationCallBack_OnDeserialization(object sender) Implements IDeserializationCallback.OnDeserialization // After deserialization, calculate the total total = price * quantity; } }
Consider that you have been assigned a task of creating an application that customers can use to order supplies from your company. These customers may belong to various classes such as New, Standard, and Preferred. You will need to store the data about the customers and use the stored data to allow the customers to place orders. Preferred customers gain a 10 percent savings on all orders. When a customer attains the Preferred status, you need to change the storage of the Customer object while serializing it.
The .NET Framework provides three Formatter classes: Formatter, FormatterConverter, and FormatterServices. These classes provide a base for such conversion and reconstruction of the serialized data into an object form. These classes belong to the System.Runtime.Serialization namespace. You can use these classes when creating custom formatters for a certain type to meet requirements that are either not achievable by the predefined formatters or can be implemented in a more efficient manner through customized formatters. For example, you can change the Standard customer to a Preferred customer.
The following table describes the Formatter classes and their commonly used members.
The .NET Framework serialization architecture provides certain serialization event handler attributes as a provision to execute application-specific activities based on certain events related to serialization and deserialization processes. Consider a situation where you need to create an E-commerce Insurance application, which allows customers to purchase insurance from different insurance companies through your agency. You can create a Customer object to represent the insurance-related information of a customer whenever the customer attempts to purchase the insurance by using a credit card.
You may want to call a method when the Customer object is serialized. This method encrypts the credit card information before it is sent to the processing application. You can use the event handler attributes as an instruction for particular methods, so that the methods get executed per the event corresponding to the specified attribute. The four types of events that you can handle are events before or after the serialization and events before or after the deserialization. The event handler attributes that you can use for these four events are OnSerializingAttribute, OnSerializedAttribute, OnDeserializingAttribute, and OnDeserializedAttribute, respectively. The method for which you specify any of these attributes has to contain a StreamingContext parameter so that the method is aware of the streaming and knows which StreamingContext to participate in. These attributes help you specify or designate a method to be executed in response to a particular action that they are associated with, such as serializing or deserializing.
By using the event handler attributes, you can customize the values in an object before it is serialized and restore it after serialization. You can perform similar tasks before and after deserialization of the corresponding object. This helps you perform customized context-specific serializations and restore the state of the object to ensure that the object retains a compatible state for the current context.
The following are the event handler attributes:
In addition to the above listed event handler attributes, the .NET Framework serialization architecture provides the OptionalFieldAttribute attribute.
The following code sample shows how to implement event handler attributes by using the ISerializable interface and IDeserializableCallback with CustomFormatter and its methods. The code implements both the ISerializable and the IDeserializableCallback interfaces in a class named Rectangle, which gives the result of the area of a rectangle whose length and width are passed through the constructor of the class. The class defines five variables, which are initialized through a constructor and five methods with different event handling attributes. There is an optional field among the variables marked by OptionalFieldAttribute. The class also defines the GetObjectData method to initialize the SerializationInfo class before serialization and the OnDeserialization method, which implements the OnDeserialization method of IDeserializableCallback to get the result after deserialization.
The Rectangle class also provides two methods, Serialize and Deserialize, which serialize and deserialize the object of the Rectangle class respectively, by using the CustomFormatter class. The CustomFormatter class implements IFormatter and IFormatterConverter interfaces to create the custom formatter. The CustomFormatter class provides three properties and two methods, Serialize and Deserialize, which implement the serialize and deserialize methods of the IFormatter interface. The length and the width of the rectangle, stored in the SerializationInfo class, are transformed into Bytes by the Convert method that implements the Convert method of the IFormatterConverter interface. Later in the code, the Main method shows how to create and serialize an object of the Rectangle class into a binary file on the disk by custom formatter and shows how EventHandlerAttributes and OptionalFieldAttribute are used during serialization and deserialization. While doing so, the object is serialized per the implementation provided by the serialize method in the CustomFormatter class. Finally, the code creates a second object of the Rectangle class by deserializing the data in the binary file and displays the newly created object on the console. The data in the binary file is deserialized per the implementation provided for the deserialize method in the CustomFormatter class.
using System; using System.IO; using System.Text; using System.Xml.Serialization; using System.Runtime.Serialization; class EventHandlerAttributes { public static void Main() { Rectangle ob = new Rectangle(); Console.WriteLine(" Before serialization the object contains: "); ob.Print(); Rectangle.Serialize(); Console.WriteLine(" After serialization the object contains: "); ob.Print(); Rectangle.Deserialize(); Console.WriteLine( " After deserialization the object contains: "); ob.Print(); Console.ReadLine(); } } [Serializable()]class Rectangle : ISerializable, IDeserializationCallback { public Double LengthRect; public Double WidthRect; private SerializationInfo InfoSerial; private StreamingContext ContextStream; [NonSerialized()]public Double AreaRect; public Rectangle(Double length, Double width) { this.LengthRect = length; this.WidthRect = width; } public Rectangle(SerializationInfo info, StreamingContext context) { this.InfoSerial = info; this.ContextStream = context; } public void OnDeserialization(Object sender) { this.LengthRect = this.InfoSerial.GetDouble("LengthRect"); this.WidthRect = this.InfoSerial.GetDouble("WidthRect"); AreaRect = LengthRect * WidthRect; } public override string ToString() { this.AreaRect = this.LengthRect * this.WidthRect; return string.Format("Length={0}, Width={1}, Area={2}", LengthRect, WidthRect, AreaRect); } public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { info.AddValue("LengthRect", this.LengthRect); info.AddValue("WidthRect", this.WidthRect); } public static void Serialize() { Rectangle c = new Rectangle(10, 20); Console.WriteLine("Object being serialized: " + c.ToString()); FileStream fs = new FileStream("c:\\DataFile.dat", FileMode.Create); CustomFormatter formatter = new CustomFormatter(); try { formatter.Serialize(fs, c); } catch (SerializationException e) { Console.WriteLine("Failed to serialize. Reason: " + e.Message); throw; } finally { fs.Close(); } } public static void Deserialize() { Rectangle c = null; FileStream fs = new FileStream("c:\\DataFile.dat", FileMode.Open); try { CustomFormatter formatter = new CustomFormatter(); c = ((Rectangle)(formatter.Deserialize(fs))); Console.WriteLine("Object being serialized: " + c.ToString()); } catch (SerializationException e) { Console.WriteLine("Failed to deserialize. Reason: " + e.Message); throw; } finally { fs.Close(); } } public int Var1; private String var2; [NonSerialized()]public string Var3; private String var4; [OptionalFieldAttribute(VersionAdded = 2)]String optionalField; public Rectangle() { Var1 = 11; var2 = "Hello World!"; Var3 = "This is a nonserialized value"; var4 = null; optionalField = "This is an optional value"; } public void Print() { Console.WriteLine(" variable1 = " + Var1); Console.WriteLine(" variable2 = " + var2); Console.WriteLine(" variable3 = " + Var3); Console.WriteLine(" variable4 = " + var4); Console.WriteLine(" variable5 = " + optionalField); } [OnSerializing()] internal void OnSerializingthisthod(StreamingContext context) { var2 = "This value went into the data file during serialization."; } [OnSerialized()] internal void OnSerializedthisthod(StreamingContext context) { var2 = "This value was reset after serialization."; } [OnDeserializing()] internal void OnDeserializingthisthod(StreamingContext context) { Var3 = "This value was set during deserialization"; } [OnDeserialized()] internal void OnDeserializedthisthod(StreamingContext context) { var4 = "This value was set after deserialization."; } } public class CustomFormatter : System.Runtime.Serialization.IFormatter, IFormatterConverter { public System.Runtime.Serialization.SerializationBinder Binder { get { throw new NotSupportedException(); } set { throw new NotSupportedException(); } } public System.Runtime.Serialization.StreamingContext Context { get { throw new NotSupportedException(); } set { throw new NotSupportedException(); } } public object Deserialize(System.IO.Stream serializationStream) { StreamReader st = new StreamReader(serializationStream); string str = st.ReadToEnd(); char[] temp = new char[] { char.Parse(",") }; string[] s = str.Split(temp); SerializationInfo ss = new SerializationInfo(typeof(Rectangle), new CustomFormatter()); ss.AddValue("LengthRect", double.Parse(s[0])); ss.AddValue("WidthRect", double.Parse(s[1])); Rectangle ob = new Rectangle(ss, new StreamingContext()); if ((ob) is IDeserializationCallback) { ob.OnDeserialization(ob); } return ob; } public void Serialize(System.IO.Stream serializationStream, object graph) { Rectangle g = ((Rectangle)(graph)); SerializationInfo ss = new SerializationInfo(typeof(Rectangle), new CustomFormatter()); if ((g) is ISerializable) { g.GetObjectData(ss, new StreamingContext()); } byte[] ba = (byte[])this.Convert(ss, System.TypeCode.Byte); serializationStream.Write(ba, 0, ba.Length); } public System.Runtime.Serialization.ISurrogateSelector SurrogateSelector { get { throw new NotSupportedException(); } set { throw new NotSupportedException(); } } public object Convert(object value, System.TypeCode typeCode) { byte[] ba = Encoding.ASCII.GetBytes(((SerializationInfo)(value)).GetDouble("LengthRect") + "," + ((SerializationInfo)(value)).GetDouble("WidthRect")); return ba; } public object Convert(object value, System.Type typeCode) { byte[] ba = Encoding.ASCII.GetBytes(((SerializationInfo)(value)).GetDouble("LengthRect") + "," + ((SerializationInfo)(value)).GetDouble("WidthRect")); return ba; } // All of these have to be here because IFormatterConverter is being implemented... public bool ToBoolean(object value) { throw new NotSupportedException(); } public byte ToByte(object value) { throw new NotSupportedException(); } public char ToChar(object value) { throw new NotSupportedException(); } public System.DateTime ToDateTime(object value) { throw new NotSupportedException(); } public decimal ToDecimal(object value) { throw new NotSupportedException(); } public double ToDouble(object value) { throw new NotSupportedException(); } public short ToInt16(object value) { throw new NotSupportedException(); } public int ToInt32(object value) { throw new NotSupportedException(); } public long ToInt64(object value) { throw new NotSupportedException(); } public SByte ToSByte(object value) { throw new NotSupportedException(); } public float ToSingle(object value) { throw new NotSupportedException(); } public string ToString(object value) { throw new NotSupportedException(); } public ushort ToUInt16(object value) { throw new NotSupportedException(); } public uint ToUInt32(object value) { throw new NotSupportedException(); } public ulong ToUInt64(object value) { throw new NotSupportedException(); } }
The .NET Framework supports a number of serialization events if you are using the BinaryFormatter class. For SoapFormatter or custom serialization, you are limited to to using the IDeserializationCallback interface. There are four serialization events:
The stream is untouched, any changes being made before or after. For a method to respond to any one of these events, it must have the attibute matching the event eg. OnSerializing and it must accept a StreamingContext object as its parameter, and it must be a Sub rather than a Function (in C# parlance, 'it must return void'). The code below shows an example:
[Serializable]
class ShoppingCartItem
public int product;
public decimal price;
public int quantity;
[NonSerialized]
public decimal total;
[OnSerializing]
void CalculateTotal(StreamingContext sc)
{
total = price * quantity;
}
[OnDeserialized]
void CheckTotal(StreamingContext sc)
{
if (total == 0) { CalculateTotal() }
}
}
}
You are creating an E-commerce Insurance application. This application allows the end user to select the type of insurance, enter data appropriate to the type of insurance, and then request that insurance with payment from insurance companies. The Insurer application receives the applications from the E-commerce User Interface application and then processes data according to the options selected by the users. When the E-commerce User Interface application submits the data to the Insurer application, it includes information about what type of insurance is requested.
The ObjectManager class assists you in recovering objects from the stream during deserialization. This class belongs to the System.Runtime.Serialization namespace. During deserialization, the formatter responsible for the corresponding deserialization queries the ObjectManager to determine whether a reference to an object in the serialized stream refers to an object that has already been deserialized (a backward reference), or to an object that has not yet been deserialized (a forward reference). In case of a backward reference, Formatter immediately completes the reference. In case of a forward reference, Formatter can register a fixup with the ObjectManager. Fixup refers to the process of finalizing object references that are not already completed during deserialization. This allows the ObjectManager to complete the reference after the required object is deserialized. Therefore, the ObjectManager class helps you track objects in a particular serialized stream when they are deserialized.
The following code sample shows how to implement the ObjectManager class during serialization. The code deals only with the deserialization part because the object manager plays a crucial role at the time of deserialization. The code provides one structure called SampleStructure and two serializable classes called SampleClass1 and SampleClass2, which implement the ISerializable interface. The code shows how to fix the variable varObject of type Object after deserialization. The Main method shows how to use the methods RegisterObject, RecordFixUp, and DoFixUps of the ObjectManager class.
using System; using System.Text; using System.Runtime.Serialization; class TestObjectManager { static void Main() { SerializationInfo infoClass1 = new SerializationInfo(typeof(SampleClass1), new FormatterConverter()); SampleStructure sampleStruct = new SampleStructure(); sampleStruct.VarInt = 100; infoClass1.AddValue("ObjSampleClass1", sampleStruct); // infoClass2 will be used to deserialize // an instance of SampleClass2. SerializationInfo infoClass2 = new SerializationInfo(typeof(SampleClass2), new FormatterConverter()); infoClass2.AddValue("VarInt", 4); // Build the object graph we are aming for using the // SerializationInfo objects. ObjectManager objMan = new ObjectManager(null, new StreamingContext(StreamingContextStates.All, null)); // Create an empty SampleClass1 and register it. object objSampleClass1 = FormatterServices.GetUninitializedObject(typeof(SampleClass1)); // Create an empty SampleStructure and register it. object obj = infoClass1.GetValue("ObjSampleClass1", typeof(SampleStructure)); objMan.RegisterObject(obj, 2, null, 1, null); // Ask the ObjectSampleStructure to perform // 'obj.varObject = objSampleClass2' // assignment during fixup. objMan.RecordFixup(2, typeof(SampleStructure).GetField("VarObject"), 3); // Create an empty SampleClass2 and register it. object objSampleClass2 = FormatterServices.GetUninitializedObject(typeof(SampleClass2)); objMan.RegisterObject(objSampleClass2, 3, infoClass2); objMan.RegisterObject(objSampleClass1, 1, infoClass1); Console.WriteLine("VarInt = {0}, VarObject = {1}", ((SampleStructure)obj).VarInt, ((SampleStructure)obj).VarObject == null ? "null" : "NOT null"); // varInt = 100, varObject = null // The fixup hasn't happend yet. objMan.DoFixups(); Console.WriteLine("VarInt = {0}, VarObject = {1}", ((SampleStructure)obj).VarInt, ((SampleStructure)obj).VarObject == null ? "null" : "NOT null"); // varInt = 100, varObject = NOT null // The fixup has happened. SampleClass1 root = (SampleClass1)objMan.GetObject(1); Console.WriteLine("VarInt = {0}, VarObject = {1}", root.ObjSampleClass1.VarInt, root.ObjSampleClass1.VarObject == null ? "null" : "NOT null"); // objSampleClass1 is being completed before the obj.varObject fixup, // so the value copy in SampleClass1 is copying the unfixed-up // object. Console.ReadLine(); } } [Serializable] class SampleClass1 : ISerializable { public SampleStructure ObjSampleClass1; SampleClass1(SerializationInfo info, StreamingContext context) { ObjSampleClass1 = (SampleStructure)info.GetValue("ObjSampleClass1", typeof(SampleStructure)); } public void GetObjectData(SerializationInfo info, StreamingContext context) { // We do not need this as we are only deserializing, not serializing. } } [Serializable] struct SampleStructure { public int VarInt; public object VarObject; } [Serializable] class SampleClass2 : ISerializable { public int VarInt; private SampleClass2(SerializationInfo info, StreamingContext context) { VarInt = info.GetInt32("VarInt"); } public void GetObjectData(SerializationInfo info, StreamingContext context) { // We do not need this as we are only deserializing, not serializing. } }