Serialization

I l @ ve RuBoard

Simply put, serialization is the process of deconstructing information about a type into some basic format. The reverse process, deserialization, reconstructs the original object from the serialized data. The advantages of serialization include the ability to persist in-memory objects to disk, to marshal object across process boundaries, and to marshal objects over network connections.

Imagine an application that over time builds up a set of in-memory objects. It might be desirable for these objects to persist even if the application itself is terminated . Using serialization to persist these objects to a file or database would be extremely useful. Another possibility might be a custom user state maintained by a Web site. Using serialization, you can store an object in a database for easy retrieval.

Serialization has many uses within an application, including the following:

  • Sharing objects through the Clipboard

  • Persisting in-memory objects to disk

  • Marshaling data over Remoting and XML Web services

  • Serializing a type to a stream

Before we continue our discussion on the uses of serialization, we'll look at the available serialization formats.

Serialization Formats

Three major forms of serialization formats are available through the .NET Framework: binary, SOAP, and XML. Binary and SOAP serialization preserves complete type fidelity. This means that the deserialized objects preserve all of the type details of the original objects. Binary serialization is not available for use with marshaling XML Web service parameters or return types. XML serialization, on the other hand, serializes only public read/write types and fields of a type. (It does also support read-only collections.) SOAP and binary serialization are the most applicable to Remoting and XML Web services. XML serialization (using the Xml.Serialization namespace) is most applicable to custom serialization needs, so I won't cover that topic here.

As long as a type supports serialization ”we'll look at how you do this shortly ”you can use either SOAP or binary serialization, which provide a great deal of flexibility. Remoting supports both the binary and XML serialization formats, and XML Web services support only the SOAP serialization format. Binary serialization is performed using the BinaryFormatter class from the System.Runtime.Serialization.Formatters.Binary namespace. SOAP serialization is performed with the SoapFormatter class from the System.Runtime.Serialization.Formatters.Soap namespace.

The following sample demonstrates how to manually serialize an Integer array ”a type that does support serialization ”using both the BinaryFormatter and SoapFormatter classes:

 OptionStrictOn ImportsSystem.IO ImportsSystem.Xml.Serialization ImportsSystem.Runtime.Serialization.Formatters ImportsSystem.Runtime.Serialization.Formatters.Binary ImportsSystem.Runtime.Serialization.Formatters.Soap ModuleModule1 SubMain() DimiAry()AsInteger=NewInteger(9){0,1,2,3,4,5,6,7,8,9} DimbfAsNewBinaryFormatter() DimsfAsNewSoapFormatter() Console.WriteLine("SerializingthearrayusingBinaryFormatter") DimbinaryMsAsNewMemoryStream() bf.Serialize(binaryMs,iAry) DimbinaryBytes()AsByte=binaryMs.GetBuffer() Console.WriteLine(BitConverter.ToString(binaryBytes)) binaryMs.Close() Console.WriteLine("SerializingthearrayusingSoapFormatter:") DimsoapMsAsNewMemoryStream() sf.Serialize(soapMs,iAry) DimsoapBytes()AsByte=soapMs.GetBuffer() Console.WriteLine(System.Text.Encoding.UTF8.GetString(soapBytes)) soapMs.Close() Console.WriteLine("DeserializingthearraywithBinaryFormatter:") binaryMs=NewMemoryStream(binaryBytes) DimbAry()AsInteger=CType(bf.Deserialize(binaryMs),Integer()) Console.WriteLine("DeserializingthearraywithSoapFormatter") soapMs=NewMemoryStream(soapBytes) DimsAry()AsInteger=CType(sf.Deserialize(soapMs),Integer()) Console.WriteLine(vbCrLf& "Serializationstatistics") Console.WriteLine(vbTab& "SoapSize:{0}",soapBytes.Length) Console.WriteLine(vbTab& "BinarySize:{0}",binaryBytes.Length) Console.WriteLine("Thedeserializedarraysmatched") Console.ReadLine() EndSub EndModule 

The code starts by defining the array we want to serialize. All blittable types (that is, those types that do not require conversion when passed between unmanaged and managed code) and arrays of blittable types are serializable. We then created instances of a BinaryFormatter and SoapFormatter class to do all of the work. The rest of the sample consists of variations on calling the Serialize and Deserialize methods on the formatters.

If you run the sample ObjectSerialization project, you'll immediately see the differences between binary and XML serialization. First of all, the binary representation of the array is 68 bytes and looks like the following:

 00-01-00-00-00-FF-FF-FF-FF-01-00-00-00-00-00-00-00-0F-01-00-00-00-0A-00-00- 00-08-00-00-00-00-01-00-00-00-02-00-00-00-03-00-00-00-04-00-00-00-05-00-00- 00-06-00-00-00-07-00-00-00-08-00-00-00-09-00-00-00-0B 

The SOAP representation is much larger ”in fact, larger by an order of magnitude (655 bytes versus 68 bytes for the binary representation). The nice thing about it is that you can at least read it. Check this out:

 <SOAP-ENV:Envelopexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:x sd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC= "http://schemas.xmlsoap.org /soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmln s:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-ENV: encodingStyl e="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <SOAP-ENC:ArraySOAP-ENC:arrayType="xsd:int[10]"> <item>0</item> <item>1</item> <item>2</item> <item>3</item> <item>4</item> <item>5</item> <item>6</item> <item>7</item> <item>8</item> <item>9</item> </SOAP-ENC:Array> </SOAP-ENV:Body> </SOAP-ENV:Envelope> 

Pretty easy, right? What I've demonstrated here is how to manually serialize types using the BinaryFormatter and SoapFormatter classes. When you use either Remoting or XML Web services, the serialization is done for you. You can, however use the formatter classes whenever you need to perform your own serialization.

The Serializable Attribute

All types in Visual Basic .NET are inherently serializable, but that doesn't mean they automatically support serialization. All types that are support serialization must first be marked as serializable. This is done by using the Serializable attribute. Many objects within the .NET Framework support serialization, but such support is far from universal. Your own custom types (classes and structures) do not support serialization by default ”you must explicitly use the Serializable attribute.

Here's a simple class that has the Serializable attribute specified:

 <Serializable>_ PublicClassEmployee PublicFNameAsString PublicLNameAsString PrivateSSNAsString EndClass 

Surprisingly, that's it. You do not need to do anything else. Pretty darn easy, isn't it? Of course, this doesn't give you much control. What if you want to control what is and isn't serialized? You have two options: selective serialization and custom serialization.

Selective Serialization

Selective serialization is the simplest way to control serialization of an object. Since the default behavior of binary serialization with the Serializable attribute is all-inclusive (it causes everything to be serialized), the designers of the .NET Framework decided to provide a simple exclusion mechanism: the NonSerialized attribute. You can control whether an individual member field is serialized by simply applying the NonSerialized attribute to that field. You might need this, for example, for fields that represent pointers to memory that cannot be serialized, for information that would not be meaningful in a different context, or information that for security reasons you do not want to be transportable or persistable. The following example demonstrates how this works:

 <Serializable>_ PublicClassEmployee PublicFNameAsString PublicLNameAsString <NonSerialized>PublicSSNAsString EndClass 

This is, again, a simple approach that does not necessarily work for every situation. What if you want even more control over how your type is serialized? You need to look at implementing a custom serialization solution for your type.

Custom Serialization

Custom serialization gives you complete control over how a type is serialized, although it requires more work to implement. To understand how custom serialization works, you must understand how the SerializationInfo class and the StreamingContext structure fit into the picture.

Generally speaking, the SerializationInfo class is the most important part of implementing custom serialization. This class provides methods for storing and retrieving member information ( name , type, and value). You add members to a SerializationInfo class at serialization time. When you deserialize an object, members are retrieved from this class. The StreamingContext structure is also available during serialization and deserialization; it provides information about the context of the serialization event.

To support custom serialization on a custom type, you must do all of the following:

  • Mark your class or structure with the Serializable attribute

  • Implement the ISerializable interface

  • Provide a serialization constructor with the following syntax: Public Sub New(ByVal info As SerializationInfo, ByVal context As Streaming ­Context)

The following class, which you can find in the CustomSerialization project, demonstrates how to implement custom serialization that fulfills the three requirements listed above:

 OptionStrictOn ImportsSystem.Runtime.Serialization <Serializable()>PublicClassPerson ImplementsISerializable PublicFirstNameAsString PublicLastNameAsString PublicBirthDateAsDate PublicSSNAsString PublicChildren()AsPerson PublicSubNew(ByValinfoAsSerializationInfo,_ ByValcontextAsStreamingContext) FirstName=info.GetString("FirstName") LastName=info.GetString("LastName") BirthDate=info.GetDateTime("BirthDate") SSN=info.GetString("SSN") Children=CType(info.GetValue("Children",GetType(Object)),_ Person()) EndSub PublicSubGetObjectData(ByValinfoAsSerializationInfo,_ ByValcontextAsStreamingContext)_ ImplementsISerializable.GetObjectData info.AddValue("FirstName",FirstName) info.AddValue("LastName",LastName) info.AddValue("BirthDate",BirthDate) info.AddValue("SSN",SSN) info.AddValue("Children",Children) EndSub PublicSubNew(ByValfNameAsString,ByVallNameAsString) MyBase.New() Me.FirstName=fName Me.LastName=lName Me.SSN= "123-45-6789" Me.BirthDate=NewDate(1976,6,27) EndSub PublicOverloadsOverridesFunctionToString()AsString ReturnMe.ToString(0) EndFunction PrivateOverloadsFunctionToString(ByValindentLevelAsInteger)_ AsString DimsbAsNewSystem.Text.StringBuilder() DimindentAsNewString(CChar(vbTab),indentLevel) 'Printthetype sb.AppendFormat("{0}{1}{2}",indent,Me.GetType(),vbCrLf) 'Printtheproperties indent&= " " sb.AppendFormat("{0}FirstName={1}{2}",_ indent,Me.FirstName,vbCrLf) sb.AppendFormat("{0}LastName={1}{2}",_ indent,Me.LastName,vbCrLf) sb.AppendFormat("{0}SSN={1}{2}",_ indent,Me.SSN,vbCrLf) sb.AppendFormat("{0}BirthDate={1}{2}",_ indent,Me.BirthDate,vbCrLf) sb.AppendFormat("{0}Children={1}{2}",_ indent,Me.Children,vbCrLf) 'Printthechildren IfNotMe.ChildrenIsNothingThen DimpAsPerson ForEachpInMe.Children sb.Append(p.ToString(indentLevel+1)) Next EndIf Returnsb.ToString() EndFunction EndClass 

When this class is to be serialized, the GetObjectData method is called. As you can see from this example, storing fields in the SerializationInfo class is trivial. The only thing you need to be careful about is that you must store the member with a string representation of the name of that member. When the number of properties on a class becomes larger, maintaining a consistent naming scheme becomes more challenging. Since your goal should be minimizing confusion, I'd recommend that you provide a string that matches the name of the field being stored. This might lead to a lot of typing, but you'll greatly reduce the chances for confusion.

The Person class demonstrates another interesting serialization aspect. Notice how it supports the Children property. This property is a Person array, which means that an instance of the Person class represents a hierarchy of other Person objects. This example also helps demonstrate how to serialize nested classes. Notice that this is not difficult ”the SerializationInfo class essentially takes care of calling the GetObjectData methods on the classes contained in the Children field.

Deserialization happens when the public constructor is called. Here all of the stored members are extracted from the SerializationInfo class. For most of the standard built-in types (such as Byte , Char , Double , and String ), the SerializationInfo class provides a set of overloads that allow you to extract those types directly. Thus, for most of our fields in the Person class, we call the GetString method. The exception is the Children property, which is an array of Person objects. In this case, you must call the generic GetValue method, passing GetType(Object) . (I'm not sure why a GetObject method isn't supported.) You can then cast the returned object to the correct type.

Note

You should be able to pass the specific type to the GetValue method, but it really won't help you. You need to do the cast anyway, and in the first release of the .NET Framework, passing a specific type to this method will result in an exception if the type doesn't support IConvertible . This is a bug and should be fixed in future versions of the .NET Framework.


Limitations of the Serializable Attribute

It should be no great surprise that the Serializable attribute has limitations. Here are the major ones:

  • The Serializable attribute cannot be inherited and must be explicitly specified for each class that you want to make serializable.

  • Serialization cannot be added to a class after it has been compiled. (Modifying a class through reflection won't work.)

More Info

If you're interested in learning more about serialization, see the documentation that accompanies Visual Basic .NET and the Framework SDK ”both contain lots of great information about serialization and associated topics.


I l @ ve RuBoard


Designing Enterprise Applications with Microsoft Visual Basic .NET
Designing Enterprise Applications with Microsoft Visual Basic .NET (Pro-Developer)
ISBN: 073561721X
EAN: 2147483647
Year: 2002
Pages: 103

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