Serializing Objects

C# enables you to save an entire object, including all its data (called an object-graph ) through a process called serialization . Serialization lets you write entire objects out to disk and read them back in later. Objects that pass between assembly boundaries (through a process called marshalling ) are also serialized.

You use the BinaryFormatter class to serialize and deserialize objects; the significant public methods of this class appear in Table 5.19.

Table 5.19. Significant Public BinaryFormatter Methods

METHOD

DOES THIS

Deserialize

Deserializes a stream into an object-graph.

Serialize

Serializes an object to the given stream.

Here's an example, ch05_13.cs, which will serialize and deserialize an object. The ch05_13 class contains some internal values in an array named data , which are initialized in the constructor with the values 1, 2, and 3. When you create an object of this class, it'll serialize itself to disk automatically with a call to a method we'll name Serialize in the constructor. Here's how we start the ch05_13 class. Note the [Serializable] attribute at the beginningto make a class serializable, you must use this attribute:

 
 [Serializable] class ch05_13 {   private int[] data;   public ch05_13()   {     data = new int[3];     data[0] = 1;     data[1] = 2;     data[2] = 3;     Serialize();   }   .   .   . 

In this class's Serialize method, we will serialize the current object to a file named ch05_13.out. To do that, we'll use a FileStream object, using a BinaryFormatter object to do the actual serialization:

 
 private void Serialize() {   System.Console.WriteLine("Serializing it...");   FileStream output = new FileStream("ch05_13.out", FileMode.Create);   BinaryFormatter formatter = new BinaryFormatter();   formatter.Serialize(output, this);   output.Close(); } 

To deserialize the object, we read it back from the ch05_13.out filewe'll add a method named Deserialize . In this method, we use another FileStream object to read the object from disk, passing the FileStream object to a BinaryFormatter object's Deserialization method and returning the deserialized object:

 
 public ch05_13 Deserialize() {   System.Console.WriteLine("Deserializing it...");   FileStream input = new FileStream("ch05_13.out", FileMode.Open);   BinaryFormatter formatter = new BinaryFormatter();   return (ch05_13) formatter.Deserialize(input); } 

That's all we need. To see this in action, we start by creating a ch05_13 object. This object automatically serializes itself to ch05_13.out in the constructor; we can deserialize that object and store it in a new object variable named deserializedObject , displaying the data in deserializedObject with the code you see in ch05_13.cs, Listing 5.13.

Listing 5.13 Serializing an Object (ch05_13.cs)
 using System; using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; [Serializable] class ch05_13 {   private int[] data;  public static void Main()   {   System.Console.WriteLine("Creating an object...");   ch05_13 obj = new ch05_13();   ch05_13 deserializedObject = obj.Deserialize();   deserializedObject.DisplayItems();   }  public ch05_13()   {     data = new int[3];     data[0] = 1;     data[1] = 2;     data[2] = 3;     Serialize();   }  private void DisplayItems()   {   System.Console.WriteLine("Displaying the object's items...");   foreach (int value in data)   {   System.Console.WriteLine(value);   }   }  private void Serialize()   {     System.Console.WriteLine("Serializing it...");     FileStream output = new FileStream("ch05_13.out", FileMode.Create);     BinaryFormatter formatter = new BinaryFormatter();     formatter.Serialize(output, this);     output.Close();   }   public ch05_13 Deserialize()   {     System.Console.WriteLine("Deserializing it...");     FileStream input = new FileStream("ch05_13.out", FileMode.Open);     BinaryFormatter formatter = new BinaryFormatter();     return (ch05_13) formatter.Deserialize(input);   } } 

Here are the results you see when you run ch05_13. As you see, the code creates an object, serializes it to disk, and reads it back in successfully:

 
 C:\>ch05_13 Creating an object... Serializing it... Deserializing it... Displaying the object's items... 1 2 3 

Working with Non-Serialized Data

You don't have to serialize all the members of an object; for example, if you have a huge array filled with sequential numbers that's easy to re-create, there's no benefit to taking up a great deal of disk space by storing that array to memory. To mark members that you don't want serialized, you use the [NonSerialized] attribute.

For example, if you didn't want to serialize the data array in ch05_13.cs, you could mark it with [NonSerialized] ; note that when you use this attribute, you should implement the IDeserializationCallback interface, which we do here as well:

 
 [Serializable]  class ch05_14 : IDeserializationCallback  {  [NonSerialized] int[] data;  .     .     . 

To use the IDeserializationCallback interface, you must implement the OnDeserialization method, which is called during the deserialization process to allow you to re-create the data that wasn't serialized. For this example, that looks like this, where we re-create the data array in the OnDeserialization method:

 
 public virtual void OnDeserialization(Object obj) {   System.Console.WriteLine("Recreating data...");   data = new int[3];   data[0] = 1;   data[1] = 2;   data[2] = 3; } 

Now we can create an object of this new class, ch05_14 , serialize it, deserialize it, and check to make sure its data was correctly re-created. You can see the code in ch05_14.cs, Listing 5.14.

Listing 5.14 Handling Non-Serialized Data (ch05_14.cs)
 using System; using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; [Serializable] class ch05_14 : IDeserializationCallback {   [NonSerialized] int[] data;   public static void Main()   {  System.Console.WriteLine("Creating an object...");   ch05_14 obj = new ch05_14();   ch05_14 deserializedObject = obj.Deserialize();   deserializedObject.DisplayItems();  }   public ch05_14()   {     data = new int[3];     data[0] = 1;     data[1] = 2;     data[2] = 3;     Serialize();   }  private void DisplayItems()   {   System.Console.WriteLine("Displaying the object's items...");   foreach (int value in data)   {   System.Console.WriteLine(value);   }   }  private void Serialize()   {     System.Console.WriteLine("Serializing it...");     FileStream output = new FileStream("ch05_14.out", FileMode.Create);     BinaryFormatter formatter = new BinaryFormatter();     formatter.Serialize(output, this);     output.Close();   }   public ch05_14 Deserialize()   {     System.Console.WriteLine("Deserializing it...");     FileStream input = new FileStream("ch05_14.out", FileMode.Open);     BinaryFormatter formatter = new BinaryFormatter();     return (ch05_14) formatter.Deserialize(input);   }   public virtual void OnDeserialization(Object obj)   {     System.Console.WriteLine("Recreating data...");     data = new int[3];     data[0] = 1;     data[1] = 2;     data[2] = 3;   } } 

When you run it, you see the same results for ch05_14.cs as we just saw for ch05_13.cs. The new object is serialized and deserialized correctly. There is a difference, however; because we did not serialize the data array in ch05_14 , we saved some disk space. ch05_13.out is 141 bytes long, but ch05_14.out is only 107 bytes. The difference here is small in bytes but large in significance. Imagine the savings if we had avoided serializing a 100,000-by-100,000 element array.

Using Isolated Storage

You can also use streams to save configuration data for applications, using i solated storage . Isolated storage saves data for your application that you might have stored in the Registry before; for example, you might want to save the location of your application window's upper-left point, the background color as set by the user, window size as resized by the user , or other configuration data.

SHOP TALK : ISOLATED STORAGE OR THE REGISTRY

In isolated storage, you can see Microsoft's exploration of alternatives to the Windows Registry for holding application data. When first introduced, the Registry was of limited size. Microsoft hadn't anticipated the massive use that applications would make of the Registry when dumping their data under keys like HKEY_CURRENT_USER , and users soon started seeing Registry overflow messages regularly. Microsoft responded by allowing the Registry to reach arbitrary lengths in succeeding versions of Windows, but now it's not uncommon to see 15MB or longer registries. Because the Registry is read into memory when you boot, that can give you quite a performance hit. Such heavy use of the Registry makes unrecoverable corruption of the Registry ever more probable. Isolated storage is an attempt to address this problem by letting applications store their own configuration data in assembly-specific files in randomly named subdirectories of C:\Documents and Settings\ username \Local Settings\Application Data\IsolatedStorage (this path will vary with your version of Windows). In this way, they're a partial throwback to the early days of .INI files that the Registry was meant to supplant, but configuration files are handled by Windows instead in a well-defined way, and represent a significant improvementat least potentiallyto the huge amount of data some applications dump in the Registry.


To store data in isolated storage, you use the IsolatedStorageFileStream class; you can see the significant public properties of this class in Table 5.20, and its significant methods in Table 5.21.

Table 5.20. Significant Public IsolatedStorageFileStream Properties

PROPERTY

PURPOSE

CanRead

Returns true if the file can be read.

CanSeek

Returns true if seek operations are supported.

CanWrite

Returns true if you can write to the file.

IsAsync

Returns true if the stream was opened asynchronously.

Length

Returns the length of the stream.

Name

Returns the name of the file stream that was passed to the constructor.

Position

Returns or sets the current position in the stream.

Table 5.21. Significant Public IsolatedStorageFileStream Methods

METHOD

PURPOSE

BeginRead

Begins an asynchronous read.

BeginWrite

Begins an asynchronous write.

Close

Closes the stream.

EndRead

Ends an asynchronous read request.

EndWrite

Ends an asynchronous write.

Flush

Flushes the stream.

Lock

Prevents other processes from modifying the stream (while permitting read access).

Read

Reads bytes from the stream.

ReadByte

Reads a single byte from isolated storage.

Seek

Sets the current position in this stream.

SetLength

Sets the length of this stream.

Unlock

Allows access by other processes to a file that had been locked.

Write

Writes a block of bytes to the stream from a byte array.

WriteByte

Writes a single byte to the stream.

Here's an example; in this case, we'll write out the configuration string "Color = blue" to the configuration file ch05_15.cfg and then read that string back in. We do that by creating an IsolatedStorageFileStream object, passing that object to a StreamWriter object's constructor, and writing the configuration data to isolated storage like this:

 
 public class ch05_15 {   public static void Main()   {     IsolatedStorageFileStream storage =        new IsolatedStorageFileStream("ch05_15.cfg", FileMode.Create);     StreamWriter output = new StreamWriter(storage);     output.WriteLine("Color = blue");     output.Close();     storage.Close();     .     .     . 

That stores the data for the next time the application runs and wants to check that data. (Note that configuration data is stored by assembly. Don't try to read this data from a different assembly.) In this example, we'll read the configuration data back using an IsolatedStorageFileStream object and a StreamReader object, as you see in ch05_15.cs, Listing 5.15.

Listing 5.15 Working with Isolated Storage (ch05_15.cs)
 using System; using System.IO; using System.IO.IsolatedStorage; public class ch05_15 {   public static void Main()   {     IsolatedStorageFileStream storage =        new IsolatedStorageFileStream("ch05_15.cfg", FileMode.Create);     StreamWriter output = new StreamWriter(storage);     output.WriteLine("Color = blue");     output.Close();     storage.Close();  storage = new IsolatedStorageFileStream("ch05_15.cfg", FileMode.Open);   StreamReader input = new StreamReader(storage);   string item;   while((item = input.ReadLine()) != null)   {   Console.WriteLine(item);   }   input.Close();   storage.Close();  } } 

Here's what you see when you run this example. As you can see, we've been able to store and retrieve configuration data:

 
 C:\>ch05_15 Color = blue 


Microsoft Visual C#. NET 2003 Kick Start
Microsoft Visual C#.NET 2003 Kick Start
ISBN: 0672325470
EAN: 2147483647
Year: 2002
Pages: 181

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