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   Property Binder() As SerializationBinder   Property Context() As StreamingContext   Property SurrgateSelector() As ISurrogateSelector   ' Methods   Function Deserialize(serializationStream As Stream) As Object   Sub Serialize(serializationStream As Stream, graph As Object) End Interface 

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:

 
 Imports System.Runtime.Serialization Imports System.Runtime.Serialization.Formatters Imports System.Runtime.Serialization.Soap ... <Serializable()> _ Class MyData   ' NOTE: Public fields should be avoided in general,   '       but are useful to simplify the code in this case   Public s As String = "Wahoo!"   Public n As Integer = 452 End Class Shared Sub DoSerialize()   Dim data As MyData = New MyData()   Dim mystream As Stream = New FileStream("c:\temp\mydata.xml", _       FileMode.Create)       ' Write to the stream       Dim formatter As IFormatter = New SoapFormatter()       Formatter.Serialize(mystream, data)       ' Reset the stream to the beginning       mystream.Seek(0, SeekOrigin.Begin)      ' Read from the stream      Dim data2 As MyData = _ CType(formatter.Deserialize(mystream), MyData)      ' Do something with the data      MsgBox(data2.s & " " & data2.n)   mystream.Dispose() End Sub 

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:

 
 <Serializable()> _ Class MyData   Public s As String = "Wahoo!"   <NonSerializable()> Public n As Integer = 452 End Class 

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   Sub OnDeserialization(sender As Object) End Interface 

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:

 
 <Serializable()> _ Class MyData   Implements IDeserializationCallback   Dim s As String = "Wahoo!"   <NonSerializable()> n As Integer = 6   Public Property MyString() As String       Get           Return s       End Get       Set           s = value           n = s.Length       End Set   End Property   Public ReadOnly Property Length() As Integer       Get          Return n       End Get   End Property   #region Implementation of IDeserializationCallback   Public Sub OnDeserialization(sender As Object) Implements _       IDeserializationCallback.OnDeserialization       ' Cache the string's length       n = s.Length   End Sub   #endregion End Class 

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 Visual Basic .NET
Windows Forms Programming in Visual Basic .NET
ISBN: 0321125193
EAN: 2147483647
Year: 2003
Pages: 139

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