Binary Serialization


As mentioned earlier, binary serialization takes a class and packages it onto a stream as a series of bytes that can be rebuilt into an exact copy of the object at the destination. For binary serialization to work, both the serializer and deserializer must have access to the assembly that contains the definition for the object. The .NET Framework offers considerable flexibility in controlling how data is serialized. Before an object can be binary serialized, it must be marked as such by adding the Serializable attribute to the class, as shown here:

C#

 [Serializable] publicclassMyBasicData { publicintIntField1; publicstringStringField; privateintIntField2; } 

Visual Basic .NET

 <Serializable()>_ ClassMyBasicData PublicIntField1asInteger PublicStringField1asString PrivateIntField2asInteger EndClass 

Each class that is to be serialized requires the Serializable attribute, which means that classes derived from a serializable class must also have the Serializable attribute set for them to be serialized. Likewise, if a class contains instances of other classes as members , those too must be marked with the Serializable attribute for the entire structure to be packaged. If an error is encountered during serialization or deserialization, the System.Runtime.Serialization.SerializationException exception is thrown.

The next step is to create an instance of the formatter, which will perform the work of serializing the class. In the case of binary serialization, a binary formatter object is created that implements the IFormatter interface. After the object is created, the Serialize method is invoked on a valid stream with the object to be serialized. The BinaryFormatter class is part of the System.Runtime.Serialization.Formatters.Binary namespace. The following C# code illustrates serializing a simple class (defined earlier) to a file stream.

C#

 FileStreamfileStream; MyBasicDatamyData=newMyBasicData() FileStreambinFileStream; IFormatterbinFormatter=newBinaryFormatter(); binFileStream=newFileStream(//Openafilestreamtowriteto  Binary_Serialization.bin", FileMode.Create, FileAccess.Write, FileShare.None); try { binFormatter.Serialize(binFileStream,myData);//Serializeit binFileStream.Close(); } catch(System.Runtime.Serialization.SerializationExceptionerr) { Console.WriteLine(Erroroccurredduringserialization:  +err.Message); } 

Visual Basic .NET

 DimmyDataasMyBasicData=newMyBasicData() DimbinFileStreamasFileStream DimbinFormatterasIFormatter=newBinaryFormatter() binFileStream=newFileStream(_  Binary_Serialization.bin",_ FileMode.Create,_ FileAccess.Write,_ FileShare.None_) Try binFormatter.Serialize(binFileStream,myData) binFileStream.Close() CatcherrasSystem.Runtime.Serialization.SerializationException Console.WriteLine(Erroroccurredduringserialization:  +err.Message) EndTry 

This code serializes an instance of the MyBasicData class to a file. The basic steps are

  1. Create an instance of the serializable class thats to be serialized.

  2. Create the stream on which the data is to be serialized.

  3. Create the binary formatter.

  4. Serialize the object to the stream.

Once the object is serialized to a binary stream, the following code will deserialize the object:

C#

 FileStreambinFileStream; IFormatterbinFormatter; MyBasicDatamyData; binFileStream=newFileStream(Binary_Serialization.bin", FileMode.Open, FileAccess.Read, FileShare.Read); binFormatter=newBinaryFormatter(); try{ myData=(MyBasicData)binFormatter.Deserialize(binFileStream); } catch(System.Runtime.Serialization.SerializationExceptionerr) { Console.WriteLine(Anerroroccurredduringdeserialization:{0}", err.Message); } 

Visual Basic .NET

 DimbinFileStreamasFileStream DimbinFormatterasIFormatter DimmyDataasMyBasicData binFileStream=newFileStream(_  "Binary_Serialization.bin",_ FileMode.Open,_ FileAccess.Read,_ FileShare.Read_) binFormatter=newBinaryFormatter() Try myData=binFormatter.Deserialize(binFileStream) CatcherrasSystem.Runtime.Serialization.SerializationException Condole.WriteLine("Anerroroccurredduringdeserialization:{0}",_ err.Message) EndTry 

To summarize the binary deserialization process:

  1. Open the file stream where the serialized object is contained.

  2. Create an instance of the binary formatter.

  3. Call the Deserialize method on the formatter with the stream as an argument.

If an error occurs while serializing or deserializing the data, the exception SerializationException is thrown. This exception typically occurs during deserialization if the data on the stream does not match a valid serialized object header. For example, while serializing the object, if the text string hello is written to the FileStream before the serialized object, and on deserialization, those 5 bytes are not consumed beforehand, the deserialization method will encounter the invalid header, which is the string.

As you can see, the binary serialization process is simple, straightforward, and powerful. Imagine having a class that represents some data set such as a customer database. The entire set of customers (for example, multiple instances of the customer class) can be serialized to a file using a file stream or even transmitted across the network using a network stream.

Controlling Binary Serialization

In the previous code sample, the entire class was serialized to the data stream. There could be a situation in which you do not want all the class data serialized. For example, it would not be a good idea to serialize the password data contained in a class that describes a user account (at the very least, not in clear text). There are two methods for controlling how data is binary serialized: selectively serializing class properties by adding an additional attribute, and implementing a custom serialization interface. If the issue is that only certain fields should be serialized, the attribute NonSerialized can be placed before each field not to be serialized. The following class definition illustrates this method:

 [Serializable] publicclassMySelectiveData { publicintUserId; publicstringUserName; [NonSerialized]privatestringPassword; } 

In this class definition, the Password element is marked as NonSerialized , which means that this field will not be packaged for transport over a stream when the Serialize method is invoked.

If exact control over how serialization and deserialization occurs is required, the process can be further customized by having the class implement the ISerializable interface. This method is useful when certain marshaled data is no longer valid in the process where deserialization takes place. For example, if a serializable class contains a reference to the local IP address and is then marshaled to a process on a different machine, it might be desirable to have the local IP field reflect the current machine. If a class implements a distributed service, which can reside on any machine in the network, the class representing this service can be serialized to another machine for load balancing purposes and would need to update the local IP information to re-create sockets to handle client requests .

For a class to implement the ISerializable interface, it must implement the following two methods:

 publicvirtualvoidGetObjectData(SerializationInfoinfo, StreamingContextcontext); protectedMyObjectConstructor(SerializationInfoinfo, StreamingContextcontext); 

The GetObjectData method is used in the serialization process. Each field that is to be serialized is assigned a value name in the SerializationInfo object, which is achieved by calling the AddValue method of SerializationInfo with the value name and the field. The second required method is a constructor for the class, which is called when the object is deserialized. This constructor retrieves the serialized field values and initializes the member properties to values that are meaningful in the current context. The following code illustrates custom serialization:

C#

 [Serializable] publicclassMyCustomData:ISerializable { publicintIntField1; publicstringStringField1; publicIPAddressLocalAddress; //Defaultconstructor publicMyCustomData() { IntField1=1234; StringField1= "InitializeData"; LocalAddress=IPAddress.Any; } //Calledintheserializationprocess publicvirtualvoidGetObjectData(SerializationInfoinfo, StreamingContextcontext) { info.AddValue("IntField1",IntField1); info.AddValue("whatever",StringField1); info.AddValue("LocalIP",LocalAddress); } //Constructorusedinthedeserializationprocess protectedMyCustomData(SerializationInfoinfo,StreamingContextcontext) { IPHostEntryipHost=Dns.GetHostByName("localhost"); IPAddressresolveAddress; //RetrievethevalueofLocalAddress try { ipHost=Dns.GetHostByName("localhost"); if(ipHost.AddressList.Length>0 resolveAddress=ipHost.AddressList[0]; else resolveAddress=IPAddress.Loopback; } catch(SocketExceptionerr) { Console.WriteLine("Unabletoresolvelocalhost;usingloopback"); resolveAddress=IPAddress.Loopback; } IntField1=info.GetInt32("IntField1"); StringField1=info.GetString("whatever"); LocalAddress=resolveAddress; } } 

Visual Basic .NET

 <Serializable()>_ PublicClassMyCustomDataImplementsISerializable PublicIntField1AsInteger PublicStringField1AsString <NonSerialized()>PublicLocalAddressAsIPAddress Defaultconstructor PublicSubNew() IntField1=1234 StringField1= "InitializeData" LocalAddress=IPAddress.Any EndSub Calledintheserializationprocess SubGetObjectData(_ ByValinfoAsSerializationInfo,_ ByValcontextAsStreamingContext_)_ ImplementsISerializable.GetObjectData info.AddValue("IntField1",IntField1) info.AddValue("whatever",StringField1) EndSub Constructorusedinthedeserializationprocess PrivateSubNew(_ ByValinfoAsSerializationInfo,_ ByValcAsStreamingContext_) DimipHostAsIPHostEntry IntField1=info.GetInt32("IntField1") StringField1=info.GetString("whatever") RetrievethevalueofLocalAddress Try ipHost=Dns.GetHostByName(Dns.GetHostName()) If(ipHost.AddressList.Length>0)Then LocalAddress=ipHost.AddressList(0) Else LocalAddress=IPAddress.Loopback EndIf CatcherrAsSystem.Net.Sockets.SocketException Console.WriteLine("Unabletoresolvelocalhost;usingloopback") LocalAddress=IPAddress.Loopback EndTry EndSub EndClass 

In this example, the GetObjectData method assigns values to each property that is to be serialized. As you can see, the string values assigned can be arbitrary values, and all the GetObjectData method needs to do is assign a value to each property that is to be serialized.

The constructor for the class takes SerializationInfo and StreamingContext objects for parameters. In the preceding example, the constructor retrieves the stored values for IntField1 as well as StringField1 . Because the LocalAddress field might not have meaning at the destination process, the custom constructor resolves the current host name and assigns the first IPAddress resolved to the member field. If the DNS lookup fails, the Internet Protocol version 4 (IPv4) loopback address is assigned.

The binsoapserial.cs sample illustrates the methods of binary serialization covered earlier. You can find this program in the companion content in the folder Chap04\binary_and_soap\simple\cs. The sample is also available in Microsoft Visual Basic .NET under the vb.net directory. The sample illustrates three ways to serialize different classes: a simple class, a class with the NonSerialized attribute, and a class that implements the ISerializable interface. See the sample for a complete description of its usage.

A more advanced serialization sample that serializes data over a TCP socket connection is located in the folder Chap04\binary_and_soap\socket. Again, there are C# and Visual Basic .NET subfolders (named cs and vb.net).




Network Programming for the Microsoft. NET Framework
Network Programming for the MicrosoftВ® .NET Framework (Pro-Developer)
ISBN: 073561959X
EAN: 2147483647
Year: 2003
Pages: 121

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