Formatters


A formatter is an object that knows how to write arbitrary objects to a stream. A formatter exposes this functionality by implementing the IFormatter information from the System.Runtime.Serialization namespace:

 interface IFormatter {   // Properties   SerializationBinder Binder { get; set; }   StreamingContext Context { get; set; }   ISurrogateSelector SurrogateSelector { get; set; }   // Methods  object Deserialize(Stream serializationStream);   void Serialize(Stream serializationStream, object graph);  } 

A formatter has two jobs. The first is to serialize arbitrary objects, specifically their fields, including nested objects. [1] The formatter knows which fields to serialize using Reflection, [2] which is the .NET API for finding out type information about a type at run time. An object is written to a stream via the Serialize method and is read from a stream via the Deserialize method.

[1] Formatters even make sure that cyclic data structures are handled properly, allowing you to serialize entire object graphs.

[2] For a thorough explanation of .NET Reflection, see Essential .NET (Addison-Wesley, 2003), by Don Box, with Chris Sells.

The second job of a formatter is to translate the data into some format at the byte level. The .NET Framework provides two formatters: BinaryFormatter and the SoapFormatter.

Just like BinaryWriter, the BinaryFormatter class, from the System. Runtime.Serialization.Formatters.Binary namespace, writes the data in a binary format. SoapFormatter, from the System.Runtime.Serialization. Formatters.Soap namespace, [3] writes data in XML according to the Simple Object Access Protocol (SOAP) specification. Although SOAP is the core protocol of Web services, using the SOAP formatter for the purposes of serializing settings or document data has nothing to do with Web services or even the Web. However, it is a handy format for a human to read.

[3] To access this namespace you must add a reference to the System.Runtime.Serialization.Formatters.Soap assembly.

There is one stipulation on any type that a formatter is to serialize: It must be marked with SerializableAttribute, or else the formatter will throw a run-time exception. After the type (and the type of any contained field) is marked as serializable, serializing an object is a matter of creating a formatter and asking it to serialize the object:

  using System.Runtime.Serialization;   using System.Runtime.Serialization.Formatters;   using System.Runtime.Serialization.Formatters.Soap;   ...   [SerializableAttribute]  class MyData {   // NOTE: Public fields should be avoided in general,   //       but are useful to simplify the code in this case   public string s = "Wahoo!";   public int n = 452; } static void DoSerialize() {   MyData data = new MyData();   using( Stream stream =            new FileStream(@"c:\temp\mydata.xml", FileMode.Create) ) {  // Write to the stream   IFormatter formatter = new SoapFormatter();   formatter.Serialize(stream, data);  // Reset the stream to the beginning     stream.Seek(0, SeekOrigin.Begin);  // Read from the stream   MyData data2 = (MyData)formatter.Deserialize(stream);  // Do something with the data     MessageBox.Show(data2.s + " " + data2.n);   } } 

After creating the formatter, the code makes a call to Serialize, which writes the type information for the MyData object and then recursively writes all the data for the fields of the object. To read the object, we call Deserialize and make a cast to the top-level object, which reads all fields recursively.

Because we chose the text-based SOAP formatter and a FileStream, we can examine the data that the formatter wrote:

 <SOAP-ENV:Envelope ...> <SOAP-ENV:Body> <a1:  Form1_x002B_MyData  id="ref-1" ...>  <s id="ref-3">Wahoo!</s>   <n>452</n>  </a1:  Form1_x002B_MyData  > </SOAP-ENV:Body> </SOAP-ENV:Envelope> 

Here we can see that an instance of Form1.MyData was written and that it contains two fields: one (called s) with the value "Wahoo!", and a second one (called n) with the value "452." This was just what the code meant to write.

Skipping a Nonserialized Field

We have some control over what the formatter writes, although probably not in the way you'd expect. For example, if we decide that we want to serialize the MyData class but not the n field, we can't stop the formatter by marking the field as protected or private. To be consistent at deserialization, an object will need the protected and private fields just as much as it needs the public ones (in fact, fields shouldn't be public at all!). However, if we apply NonSerializedAttribute to a field, it will be skipped by the formatter:

 [SerializableAttribute] class MyData {   public string s = "Wahoo!";  [NonSerializedAttribute]  public int n = 452; } 

Serializing an instance of this type shows that the formatter is skipping the nonserialized field:

 <SOAP-ENV:Envelope ...> <SOAP-ENV:Body> <a1:  Form1_x002B_MyData  id="ref-1" ...>  <s id="ref-3">Wahoo!</s>  </a1:  Form1_x002B_MyData  > </SOAP-ENV:Body> </SOAP-ENV:Envelope> 

IDeserializationCallback

Good candidates for the nonserialized attribute are fields that are calculated, cached, or transient, because they don't need to be stored. However, when an object is deserialized, the nonserialized fields may need to be recalculated to put the object into a valid state. For example, if we expand the duties of the n field of the MyData type to be a cache of the s field's length, there's no need to persist n, because it can be recalculated at any time.

However, to keep n valid, the MyData object must be notified when s changes. Using properties keeps the n and s fields controlled. However, when an instance of MyData is deserialized, only the s field is set, and not the n field (recall that the n field is nonserialized). To cache the length of the s field in n after deserialization, we must implement the IDeserializationCallback interface:

 interface IDeserializationCallback {   void OnDeserialization(object sender); } 

The single method, OnDeserialization, will be called after the formatter has deserialized all the fields. This is the time to make sure that the nonserialized fields of the object have the appropriate state:

 [SerializableAttribute] class MyData :  IDeserializationCallback  {   string s = "Wahoo!";  [NonSerializedAttribute]  int n = 6;   public string String {     get { return s; }     set { value = s; n = s.Length; }   }   public int Length {     get { return n; }   }  #region Implementation of IDeserializationCallback   public void OnDeserialization(object sender) {   // Cache the string's length   n = s.Length;   }   #endregion  } 

If you've got any fields marked as nonserialized, chances are you should be handling IDeserializationCallback to set those fields at deserialization time.



Windows Forms Programming in C#
Windows Forms Programming in C#
ISBN: 0321116208
EAN: 2147483647
Year: 2003
Pages: 136
Authors: Chris Sells

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net