The .NET Framework Overview: Serialization in C#

1. Serialization

Serialization is the process used by the runtime to persist objects in some sort of storage or to transfer them from one location to another.

The metadata information on an object contains sufficient information for the runtime to serialize the fields, but the runtime needs a little help to do the right thing.

Two attributes provide this help. The [Serializable] attribute marks an object as okay to serialize. You can apply the [NonSerialized] attribute to a field or property to indicate that it shouldn’t be serialized. This is useful if it’s a cache or derived value.

The following example has a container class named MyRow that has elements of the MyElement class. The cacheValue field in MyElement is marked with the [NonSerialized] attribute to prevent it from being serialized.

In this example, the MyRow object is serialized and deserialized to a binary format and then to an XML format:

// file=serial.cs

// compile with: csc serial.cs

using System;

using System.IO;

using System.Collections;

using System.Runtime.Serialization;

using System.Runtime.Serialization.Formatters.Binary;

using System.Runtime.Serialization.Formatters.Soap;

[Serializable]

public class MyElement

{

public MyElement(string name)

{

this.name = name;

this.cacheValue = 15;

}

public override string ToString()

{

return(String.Format(“{0}: {1}”, name, cacheValue));

}

string name;

// this field isn’t persisted.

[NonSerialized]

int cacheValue;

}

[Serializable] public class MyRow {

public void Add(MyElement my)

{

row.Add(my);

}

public override string ToString()

{

string temp = null;

foreach (MyElement my in row) temp += my.ToString() + “\n”;

return(temp);

}

ArrayList row = new ArrayList();

}

class Test {

public static void Main()

{

MyRow row = new MyRow();

row.Add(new MyElement(“Gumby”));

row.Add(new MyElement(“Pokey”));

Console.WriteLine(“Initial value”);

Console.WriteLine(“{0}”, row);

// write to binary, read it back

Stream streamWrite = File.Create(“MyRow.bin”);

BinaryFormatter binaryWrite = new BinaryFormatter();

binaryWrite.Serialize(streamWrite, row);

streamWrite.Close();

Stream streamRead = File.OpenRead(“MyRow.bin”);

BinaryFormatter binaryRead = new BinaryFormatter();

MyRow rowBinary = (MyRow) binaryRead.Deserialize(streamRead);

streamRead.Close();

Console.WriteLine(“Values after binary serialization”);

Console.WriteLine(“{0}”, rowBinary);

// write to SOAP (XML), read it back

streamWrite = File.Create(“MyRow.xml”);

SoapFormatter soapWrite = new SoapFormatter();

soapWrite.Serialize(streamWrite, row);

streamWrite.Close();

streamRead = File.OpenRead(“MyRow.xml”);

SoapFormatter soapRead = new SoapFormatter();

MyRow rowSoap = (MyRow) soapRead.Deserialize(streamRead);

streamRead.Close();

Console.WriteLine(“Values after SOAP serialization”);

Console.WriteLine(“{0}”, rowSoap);

}

}

The example produces the following output:

Initial value

Gumby: 15

Pokey: 15

Values after binary serialization

Gumby: 0

Pokey: 0

Values after SOAP serialization

Gumby: 0

Pokey: 0

The field cacheValue isn’t preserved, since it was marked as [NonSerialized]. The file MyRow.Bin will contain the binary serialization, and the file MyRow.xml will contain the XML version.

The XML encoding is in SOAP, which is a textual dump of the objects, in an (almost) human-readable form. To produce a specific XML encoding, use the XmlSerializer class.

2. Custom Serialization

If the standard serialization doesn’t do exactly what you want or doesn’t give sufficient control, a class can define exactly how it wants to be serialized, such as in this example:

using System;

using System.IO;

using System.Runtime.Serialization;

using System.Runtime.Serialization.Formatters.Soap;

class Employee: ISerializable

{

int id;

string name;

string address;

public Employee(int id, string name, string address)

{

this.id = id;

this.name = name;

this.address = address;

}

public override string ToString()

{

return(String.Format(“{0} {1} {2}”, id, name, address));

}

Employee(SerializationInfo info, StreamingContext content)

{

id = info.GetInt32(“id”);

name = info.GetString(“name”);

address = info.GetString(“address”);

}

// called to save the object data…

public void GetObjectData(SerializationInfo info, StreamingContext content)

{

info.AddValue(“id”, id);

info.AddValue(“name”, name);

info.AddValue(“address”, address);

}

}

class Test {

public static void Serialize(Employee employee, string filename)

{

Stream streamWrite = File.Create(filename);

IFormatter writer = new SoapFormatter();

writer.Serialize(streamWrite, employee);

streamWrite.Close();

}

public static Employee Deserialize(string filename)

{

Stream streamRead = File.OpenRead(filename);

IFormatter reader = new SoapFormatter();

Employee employee = (Employee) reader.Deserialize(streamRead);

streamRead.Close();

return(employee);

}

public static void Main()

{

Employee employee = new Employee(15, “Fred”, “Bedrock”);

Serialize(employee, “emp.dat”);

employee = Deserialize(“emp.dat”);

Console.WriteLine(“Employee: {0}”, employee);

}

}

To do customer serialization, an object must implement the ISerializable interface. The GetObjectData () method is the only method on that interface. The implementation of that method stores each value by calling AddValue() on each value and passing in a name for the field and the field value.

To deserialize an object, the runtime relies on a special constructor. This constructor will call the appropriate get function to fetch a value based on the name.

Although this approach takes some extra space to store the names—and a bit of time to look them up—it versions well, allowing new values to be added without invalidating existing stored files.

Source: Gunnerson Eric, Wienholt Nick (2005), A Programmer’s Introduction to C# 2.0, Apress; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *