Friday, February 1, 2008

Serialization in .Net

Serialization in .Net


Serializing Objects


When you create an object in a .NET Framework application, you probably never

think about how the data is stored in memory. You shouldn't have to—the .NET

Framework takes care of that for you. However, if you want to store the contents of an

object to a file, send an object to another process, or transmit it across the network,

you do have to think about how the object is represented because you will need to convert it to a different format. This conversion is called serialization.


What Is Serialization?


Serialization, as implemented in the System.Runtime.Serialization namespace, is the

process of serializing and deserializing objects so that they can be stored or transferred

and then later re-created. Serializing is the process of converting an object into

a linear sequence of bytes that can be stored or transferred. Deserializing is the process

of converting a previously serialized sequence of bytes into an object.


How to Serialize an Object


At a high level, the steps for serializing an object are as follows:

1. Create a stream object to hold the serialized output.

2. Create a BinaryFormatter object (located in System.Runtime.Serialization.Formatters

.Binary).

3. Call the BinaryFormatter.Serialize method to serialize the object, and output the

result to the stream.

At the development level, serialization can be implemented with very little code. The following

console application—which requires the System.IO, System.Runtime.Serialization,

and System.Runtime.Serialization.Formatters.Binary namespaces—demonstrates this:


' VB

Dim data As String = "This must be stored in a file."

' Create file to save the data to

Dim fs As FileStream = New FileStream("SerializedString.Data", _

FileMode.Create)

' Create a BinaryFormatter object to perform the serialization

Dim bf As BinaryFormatter = New BinaryFormatter

' Use the BinaryFormatter object to serialize the data to the file

bf.Serialize(fs, data)

' Close the file

fs.Close



// C#

string data = "This must be stored in a file.";

// Create 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();



If you run the application and open the SerializedString.Data file in Notepad, you'll

see the contents of the string you stored surrounded by binary information (which

appears as garbage in Notepad), as shown in Figure 5-1. The .NET Framework stored

the string as ASCII text and then added a few more binary bytes before and after the

text to describe the data for the deserializer


If you just needed to store a single string in a file, you wouldn't need to use serialization—

you could simply write the string directly to a text file. Serialization becomes

useful when storing more complex information, such as the current date and time. As

the following code sample demonstrates, serializing complex objects is as simple as

serializing a string:


' VB

' Create file to save the data to

Dim fs As FileStream = New FileStream("SerializedDate.Data", _

FileMode.Create)

' Create a BinaryFormatter object to perform the serialization

Dim bf As BinaryFormatter = New BinaryFormatter

' Use the BinaryFormatter object to serialize the data to the file

bf.Serialize(fs, System.DateTime.Now)

' Close the file

fs.Close()


// C#

// Create file to save the data to

FileStream fs = new FileStream("SerializedDate.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, System.DateTime.Now);

// Close the file

fs.Close();

How to Deserialize an Object


Deserializing an object allows you to create a new object based on stored data. Essentially,deserializing restores a saved object. At a high level, the steps for deserializing an

object are as follows:


1. Create a stream object to read the serialized output.

2. Create a BinaryFormatter object.

3. Create a new object to store the deserialized data.

4. Call the BinaryFormatter.Deserialize method to deserialize the object, and cast it

to the correct type.


At the code level, the steps for deserializing an object are easy to implement. The following

console application—which requires the System.IO, System.Runtime.Serialization,

and System.Runtime.Serialization.Formatters.Binary namespaces—demonstrates how to

read and display the serialized string data saved in an earlier example:


' VB

' Open file to read the data from

Dim fs As FileStream = New FileStream("SerializedString.Data", _

FileMode.Open)

' Create a BinaryFormatter object to perform the deserialization

Dim bf As BinaryFormatter = New BinaryFormatter

' Create the object to store the deserialized data

Dim data As String = ""

' Use the BinaryFormatter object to deserialize the data from the file

data = CType(bf.Deserialize(fs),String)

' Close the file

fs.Close

' Display the deserialized string

Console.WriteLine(data)














// C#

// Open file to read the data from

FileStream fs = new FileStream("SerializedString.Data", FileMode.Open);

// Create a BinaryFormatter object to perform the deserialization

BinaryFormatter bf = new BinaryFormatter();

// Create the object to 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);



Deserializing a more complex object, such as DateTime, works exactly the same. The

following code sample displays the day of the week and the time stored by a previous

code sample:


' VB

' Open file to read the data from

Dim fs As FileStream = New FileStream("SerializedDate.Data", FileMode.Open)

' Create a BinaryFormatter object to perform the deserialization

Dim bf As BinaryFormatter = New BinaryFormatter

' Create the object to store the deserialized data

Dim previousTime As DateTime = New DateTime

' Use the BinaryFormatter object to deserialize the data from the file

previousTime = CType(bf.Deserialize(fs),DateTime)

' Close the file

fs.Close

' Display the deserialized time

Console.WriteLine(("Day: " _

+ (previousTime.DayOfWeek + (", Time: " _

+ previousTime.TimeOfDay.ToString))))












// C#

// Open file to read the data from

FileStream fs = new FileStream("SerializedDate.Data", FileMode.Open);

// Create a BinaryFormatter object to perform the deserialization

BinaryFormatter bf = new BinaryFormatter();

// Create the object to store the deserialized data

DateTime previousTime = new DateTime();

// Use the BinaryFormatter object to deserialize the data from the file

previousTime = (DateTime) bf.Deserialize(fs);

// Close the file

fs.Close();

// Display the deserialized time

Console.WriteLine("Day: " + previousTime.DayOfWeek + ", _

Time: " + previousTime.TimeOfDay.ToString());



As these code samples demonstrate, storing and retrieving objects requires only a few

lines of code, no matter how complex the object is.


How to Create Classes That Can Be Serialized


You can serialize and deserialize custom classes by adding the Serializable attribute to

the class. This is important to do so that you, or other developers using your class, can

easily store or transfer instances of the class. Even if you do not immediately need serialization,

it is good practice to enable it for future use.


If you are satisfied with the default handling of the serialization, no other code besides

the Serializable attribute is necessary. When your class is serialized, the runtime serializes

all members, including private members.


You can also control serialization of your classes to improve the efficiency of your class

or to meet custom requirements. The sections that follow discuss how to customize

how your class behaves during serialization.



How to Disable Serialization of Specific Members

Some members of your class, such as temporary or calculated values, might not need

to be stored. For example, consider the following class, ShoppingCartItem:






' VB

<Serializable()> Class ShoppingCartItem

Public productId As Integer

Public price As Decimal

Public quantity As Integer

Public total As Decimal

Public Sub New(ByVal _productID As Integer, ByVal _price As Decimal, _

ByVal _quantity As Integer)

MyBase.New

productId = _productID

price = _price

quantity = _quantity

total = (price * quantity)

End Sub

End Class


// C#

[Serializable]

class ShoppingCartItem

{

public int productId;

public decimal price;

public int quantity;

public decimal total;

public ShoppingCartItem(int _productID, decimal _price, int _quantity)

{

productId = _productID;

price = _price;

quantity = _quantity;

total = price * quantity;

}

}


The ShoppingCartItem includes three members that must be provided by the application

when the object is created. The fourth member, total, is dynamically calculated by

multiplying the price and quantity. If this class were serialized as-is, the total would be

stored with the serialized object, wasting a small amount of storage. To reduce the size

of the serialized object (and thus reduce storage requirements when writing the serialized

object to a disk, and bandwidth requirements when transmitting the serialized

object across the network), add the NonSerialized attribute to the total member:





' VB

<NonSerialized()> Public total As Decimal

// C#

[NonSerialized] public decimal total;


Now, when the object is serialized, the total member will be omitted. Similarly, the

total member will not be initialized when the object is deserialized. However, the

value for total must still be calculated before the deserialized object is used.

To enable your class to automatically initialize a nonserialized member, use the

IDeserializationCallback interface, and then implement IDeserializationCallback

.OnDeserialization. Each time your class is deserialized, the runtime will call the

IDeserializationCallback.OnDeserialization method after deserialization is complete.

The following example shows the ShoppingCartItem class modified to not serialize the

total value, and to automatically calculate the value upon deserialization:


' VB

<Serializable()> Class ShoppingCartItem

Implements IDeserializationCallback

Public productId As Integer

Public price As Decimal

Public quantity As Integer

<NonSerialized()> Public total As Decimal

Public Sub New(ByVal _productID As Integer, ByVal _price As Decimal, _

ByVal quantity As Integer)

MyBase.New

productId = _productID

price = _price

quantity = _quantity

total = (price * quantity)

End Sub

Sub IDeserializationCallback_OnDeserialization(ByVal sender As Object)

Implements IDeserializationCallback.OnDeserialization

' After deserialization, calculate the total

total = (price * quantity)

End Sub

End Class



// C#

[Serializable]

class ShoppingCartItem : IDeserializationCallback {

public int productId;

public decimal price;

public int quantity;

[NonSerialized] public decimal total;

public ShoppingCartItem(int _productID, decimal _price, int _quantity)

{

productId = _productID;

price = _price;

quantity = _quantity;

total = price * quantity;

}

void IDeserializationCallback.OnDeserialization(Object sender)

{

// After deserialization, calculate the total

total = price * quantity;

}

}


With OnDeserialization implemented, the total member is now properly defined and

available to applications after the class is deserialized.



How to Provide Version Compatibility

You might have version compatibility issues if you ever attempt to deserialize an

object that has been serialized by an earlier version of your application. Specifically, if

you add a member to a custom class and attempt to deserialize an object that lacks

that member, the runtime will throw an exception. In other words, if you add a member

to a class in version 3.1 of your application, it will not be able to deserialize an

object created by version 3.0 of your application.


To overcome this limitation, you have two choices:


Implement custom serialization, as described in Lesson 3, that is capable of

importing earlier serialized objects.

Apply the OptionalField attribute to newly added members that might cause

version compatibility problems.


The OptionalField attribute does not affect the serialization process. During deserialization,

if the member was not serialized, the runtime will leave the member's value as

null rather than throwing an exception. The following example shows how to use the

OptionalField attribute:

' VB

<Serializable()> Class ShoppingCartItem

Implements IDeserializationCallback

Public productId As Integer

Public price As Decimal

Public quantity As Integer

<NonSerialized()> Public total As Decimal

<OptionalField()> Public taxable As Boolean

// C#

[Serializable]

class ShoppingCartItem : IDeserializationCallback

{

public int productId;

public decimal price;

public int quantity;

[NonSerialized] public decimal total;

[OptionalField] public bool taxable;

If you need to initialize optional members, either implement the IDeserialization-

Callback interface as described in the "How to Disable Serialization of Specific Members"

section earlier in this lesson, or respond to serialization events, as described in


The .NET Framework 2.0 is capable of deserializing objects that have unused members, so you can

still have the ability to deserialize an object if it has a member that has been removed since serialization.

This behavior is different from .NET Framework 1.0 and 1.1, which threw an exception if

additional information was found in the serialized object.


Best Practices for Version Compatibility


To ensure proper versioning behavior, follow these rules when modifying a custom

class from version to version:

Never remove a serialized field.

Never apply the NonSerializedAttribute attribute to a field if the attribute was not

applied to the field in a previous version.

Never change the name or type of a serialized field.

When adding a new serialized field, apply the OptionalFieldAttribute attribute.

When removing a NonSerializedAttribute attribute from a field that was not serializable

in a previous version, apply the OptionalFieldAttribute attribute.

For all optional fields, set meaningful defaults using the serialization callbacks

unless 0 or null as defaults are acceptable.


Choosing a Serialization Format

The .NET Framework includes two methods for formatting serialized data in the System

.Runtime.Serialization namespace, both of which implement the IRemotingFormatter

interface:


BinaryFormatter Located in the System.Runtime.Serialization.Formatters.Binary

namespace, this formatter is the most efficient way to serialize objects that will

be read by only .NET Framework–based applications.


SoapFormatter Located in the System.Runtime.Serialization.Formatters.Soap

namespace, this XML-based formatter is the most reliable way to serialize objects

that will be transmitted across a network or read by non–.NET Framework applications.

SoapFormatter is more likely to successfully traverse firewalls than

BinaryFormatter.

In summary, you should choose BinaryFormatter only when you know that all clients

opening the serialized data will be .NET Framework applications. Therefore, if you are

writing objects to the disk to be read later by your application, BinaryFormatter is perfect.

Use SoapFormatter when other applications might read your serialized data and

when sending data across a network. SoapFormatter also works reliably in situations

where you could choose BinaryFormatter, but the serialized object can consume three

to four times more space.

While SoapFormatter is XML-based, it is primarily intended to be used by SOAP Web

services. If your goal is to store objects in an open, standards-based document that

might be consumed by applications running on other platforms, the most flexible

way to perform serialization is to choose XML serialization. Lesson 2 in this chapter

discusses XML serialization at length.


How to Use SoapFormatter

To use SoapFormatter, add a reference to the System.Runtime.Serialization.Formatters

.Soap.dll assembly to your project. (Unlike BinaryFormatter, it is not included by

default.) Then write code exactly as you would to use BinaryFormatter, but substitute

the SoapFormatter class for the BinaryFormatter class.


While writing code for BinaryFormatter and SoapFormatter is very similar, the serialized

data is very different. The following example is a three-member object serialized

with SoapFormatter that has been slightly edited for readability:

<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

<SOAP-ENV:Body>

<a1:ShoppingCartItem id="ref-1">

<productId>100</productId>

<price>10.25</price>

<quantity>2</quantity>

</a1:ShoppingCartItem>

</SOAP-ENV:Body>

</SOAP-ENV:Envelope>



How to Control SOAP Serialization

Binary serialization is intended for use only by .NET Framework–based applications.

Therefore, there is rarely a need to modify the standard formatting. However, SOAP

serialization is intended to be read by a variety of platforms. Additionally, you might

need to serialize an object to meet specific requirements, such as predefined SOAP

attribute and element names.


Guidelines for Serialization

Keep the following guidelines in mind when using serialization:

When in doubt, mark a class as Serializable. Even if you do not need to serialize

it now, you might need serialization later. Or another developer might need to

serialize a derived class.

Mark calculated or temporary members as NonSerialized. For example, if you

track the current thread ID in a member variable, the thread ID is likely to not be

valid upon deserialization. Therefore, you should not store it.

Use SoapFormatter when you require portability. Use Binary

Enclosures
URL MIME Type
Add enclosure link
ComposeEdit Html
Font size
Bold Italic
Text Color
Link
Align Left Align Center Align Right Justify Full
Numbered List Bulleted List Blockquote
Check Spelling
Add Image
Add Video
Remove Formatting from selection
Preview
Post Options
Labels for this post:
e.g. scooters, vacation, fall
Show all

Shortcuts: press Ctrl with: B = Bold, I = Italic, P = Publish, D = Draft more

Could not connect to Blogger.com. Saving and publishing may fail. Test connection now. Contacting Blogger.com...

Formatter for greatest

efficiency.