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
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 DataYou 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 StorageYou 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.
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
Table 5.21. Significant Public IsolatedStorageFileStream Methods
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 |