When an
object
is streamed to disk, its various member data must be
serialized—
that is, written out to the
stream as a series of bytes. The object will also be serialized when
stored in a database or when marshaled across a context, app domain,
process, or machine boundary.
The CLR provides support for serializing an
object-graph—
an object and all the member
data of that object. As noted in Chapter 19, by
default types are not serialized; to serialize an object, you must
explicitly mark it with the [Serializable]
attribute.
In either case, the CLR will do the work of serializing your object for you. Because the CLR knows how to serialize all the primitive types, if your object consists of nothing but primitive types (all your member data consists of integers, longs, strings, etc.), you’re all set. If your object consists of other user-defined types (classes), you must ensure that these types are also serializable. The CLR will try to serialize each object contained by your object (and all their contained objects as well), but these objects themselves must either be primitive types or they must be serializable.
This was also evident in Chapter 19 when you
marshaled a Shape
object that contained a
Point
object as member data. The
Point
object in turn consisted of primitive data.
In order to serialize (and thus marshal) the Shape
object, its constituent member, the Point
object,
also had to be marked as serializable.
When an object is marshaled, either by value or by reference, it must
be serialized. The difference is only whether a copy is made or a
proxy is provided to the client. Objects marked with the
[Serializable]
attribute are marshaled by value;
those which derive from ObjectMarshalByRef
are
marshaled by reference, but both are serialized. See Chapter 19 for more information.
When data is serialized, it will eventually be read; either by the same program or by a different program running on another machine. In any case, the code reading the data will expect that data to be in a particular format. Most of the time in a .NET application, the expected format will either be native binary format or Simple Object Access Protocol (SOAP).
SOAP is a simple, lightweight XML-based protocol for exchanging information across the Web. SOAP is highly modular and very extensible. It also leverages existing Internet technologies, such as HTTP and SMTP.
When data is serialized the format of the serialization is determined
by the formatter you apply. In Chapter 19, you used formatters with channels when
communicating with a remote object. Formatter classes implement the
interface Iformatter
; you are also free to create your own
formatter, though very few programmers will ever need or want to! The
CLR provides both a
SoapFormatter
for Internet serialization and a
BinaryFormatter
that is useful for fast local storage.
You can instantiate these objects with their default constructors:
BinaryFormatter binaryFormatter = New BinaryFormatter( );
Once you have an instance of a formatter, you can invoke its
Serialize( )
method, passing in a stream and an
object to serialize. You’ll see how this is done in the next
example.
To see serialization at work, you’ll need
a sample class that you can serialize and then deserialize. You can
start by creating a class named SumOf
.
SumOf
has three member variables:
private int startNumber = 1; private int endNumber; private int[] theSums;
The member array theSums
represents the value of
the sums of all the numbers from startNumber
through endNumber
. Thus, if
startNumber
is 1
and
endNumber
is 10
, the array will
have the values:
1,3,6,10,15,21,28,36,45,55
Each value is the sum of the previous value plus the next in the
series. Thus if the series is 1,2,3,4
, the first
value in theSums
will be 1
. The
second value is the previous value (1
) plus the
next in the series (2
); thus,
theSums[1]
will hold the value
3
. Likewise, the third value is the previous value
(3
) plus the next in the
series—theSums[2]
is 6
.
Finally, the fourth value in theSums
is the
previous value (6
) plus the next in the series
(4
), for a value of 10
.
The constructor for the SumOf
object takes two
integers: the starting number and the ending number. It assigns these
to the local values and then calls a helper function to compute the
contents of the array:
public SumOf(int start, int end) { startNumber = start; endNumber = end; ComputeSums( );
The ComputeSums
helper function fills in the
contents of the array by computing the sums in the series from
startNumber
through endNumber
:
private void ComputeSums( ) { int count = endNumber - startNumber + 1; theSums = new int[count]; theSums[0] = startNumber; for (int i=1,j=startNumber + 1;i<count;i++,j++) { theSums[i] = j + theSums[i-1]; } }
At any time, you can display the contents of the array by using a
foreach
loop:
private void DisplaySums( ) { foreach(int i in theSums) { Console.WriteLine("{0}, ",i); } }
Now, mark the class as eligible for serialization with the
[Serializable]
attribute:
[Serializable] class SumOf
To invoke serialization, you first need a file stream object into
which you’ll serialize the SumOf
object:
FileStream fileStream = new FileStream("DoSum.out",FileMode.Create);
You are now ready to call the formatter’s Serialize( )
method, passing in the stream and the object to
serialize. Because this will be done in a method of
SumOf
, you can pass in the this
object, which points to the current object:
binaryFormatter.Serialize(fileStream,this);
This will serialize the SumOf
object to disk.
To
reconstitute the object, open the file and ask a binary formatter to
DeSerialize
it:
public static SumOf DeSerialize( ) { FileStream fileStream = new FileStream("DoSum.out",FileMode.Open); BinaryFormatter binaryFormatter = new BinaryFormatter( ); return (SumOf) binaryFormatter.Deserialize(fileStream); fileStream.Close( ); }
To make sure all this works, you’ll first instantiate a new
object of type SumOf
and tell it to serialize
itself. You’ll then create a new instance of type
SumOf
by calling the static deserializer and
asking it to display its values:
public static void Main( ) { Console.WriteLine("Creating first one with new..."); SumOf app = new SumOf(1,10); Console.WriteLine( "Creating second one with deserialize..."); SumOf newInstance = SumOf.DeSerialize( ); newInstance.DisplaySums( ); }
Example 21-15 provides the complete source code to illustrate serialization and deserialization.
Example 21-15. Serializing and deserializing an object
namespace Programming_CSharp { using System; using System.IO;using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
[Serializable]
class SumOf
{
public static void Main( )
{
Console.WriteLine("Creating first one with new...");
SumOf app = new SumOf(1,10);
Console.WriteLine("Creating second one with deserialize...");
SumOf newInstance = SumOf.DeSerialize( );
newInstance.DisplaySums( );
}
public SumOf(int start, int end) { startNumber = start; endNumber = end; ComputeSums( ); DisplaySums( ); Serialize( ); } private void ComputeSums( ) { int count = endNumber - startNumber + 1; theSums = new int[count]; theSums[0] = startNumber; for (int i=1,j=startNumber + 1;i<count;i++,j++) { theSums[i] = j + theSums[i-1]; } } private void DisplaySums( ) { foreach(int i in theSums) { Console.WriteLine("{0}, ",i); } }private void Serialize( )
{
Console.Write("Serializing...");
// create a file stream to write the file
FileStream fileStream =
new FileStream("DoSum.out",FileMode.Create);
// use the CLR binary formatter
BinaryFormatter binaryFormatter =
new BinaryFormatter( );
// serialize to disk
binaryFormatter.Serialize(fileStream,this);
Console.WriteLine("...completed");
fileStream.Close( );
}
public static SumOf DeSerialize( )
{
FileStream fileStream =
new FileStream("DoSum.out",FileMode.Open);
BinaryFormatter binaryFormatter =
new BinaryFormatter( );
return (SumOf) binaryFormatter.Deserialize(fileStream);
fileStream.Close( );
}
private int startNumber = 1; private int endNumber; private int[] theSums; } } Output: Creating first one with new... 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, Serializing......completed Creating second one with deserialize... 1, 3, 6, 10, 15, 21, 28, 36, 45, 55,
The output shows that the object was created, displayed, and then serialized. The object was then deserialized and output again, with no loss of data.
In some ways, the approach to serialization demonstrated in Example 21-15 is very wasteful. Because you can compute the contents of the array given its starting and ending numbers, there really is no reason to store its elements to disk. Although the operation might be inexpensive with a small array, it could become costly with a very large one.
You can tell the serializer not to serialize some data by marking it
with the [NonSerialized]
attribute:
[NonSerialized] private int[] theSums;
If you don’t serialize the array, however, the object you create will not be correct when you deserialize it. The array will be empty. Remember, when you deserialize the object, you simply read it up from its serialized form; no methods are run.
To fix the object before you return it to the caller, implement the
IDeserializationCallback
interface:
[Serializable] class SumOf : IDeserializationCallback
Also implement the one method of this interface:
OnDeserialization( )
. The CLR promises that if you implement
this interface, your class’s OnDeserialization( )
method will be called when the entire object graph has
been deserialized. This is just what you want; the CLR will
reconstitute what you’ve serialized, and then you’ll have
the opportunity to fix up the parts that were not serialized.
This implementation can be very simple; just ask the object to recompute the series:
public virtual void OnDeserialization (Object sender) { ComputeSums( ); }
This is a classic space/time trade-off; by not serializing the array
you make deserialization somewhat slower (because you must take the
time to recompute the array), but you make the file somewhat smaller.
To see if not serializing the array had any effect, I ran the program
with the digits 1 to 5,000. Before setting
[NonSerialized]
on the array, the serialized file
was 20K. After setting [NonSerialized]
, the file
was 1K. Not bad. Example 21-16 shows the source code
using the digits 1 to 5 as input (to simplify the output).
Example 21-16. Working with a nonserialized object
namespace Programming_CSharp { using System; using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; [Serializable]class SumOf : IDeserializationCallback
{ public static void Main( ) { Console.WriteLine("Creating first one with new..."); SumOf app = new SumOf(1,5); Console.WriteLine("Creating second one with deserialize..."); SumOf newInstance = SumOf.DeSerialize( ); newInstance.DisplaySums( ); } public SumOf(int start, int end) { startNumber = start; endNumber = end; ComputeSums( ); DisplaySums( ); Serialize( ); } private void ComputeSums( ) { int count = endNumber - startNumber + 1; theSums = new int[count]; theSums[0] = startNumber; for (int i=1,j=startNumber + 1;i<count;i++,j++) { theSums[i] = j + theSums[i-1]; } } private void DisplaySums( ) { foreach(int i in theSums) { Console.WriteLine("{0}, ",i); } } private void Serialize( ) { Console.Write("Serializing..."); // create a file stream to write the file FileStream fileStream = new FileStream("DoSum.out",FileMode.Create); // use the CLR binary formatter BinaryFormatter binaryFormatter = new BinaryFormatter( ); // serialize to disk binaryFormatter.Serialize(fileStream,this); Console.WriteLine("...completed"); fileStream.Close( ); } public static SumOf DeSerialize( ) { FileStream fileStream = new FileStream("DoSum.out",FileMode.Open); BinaryFormatter binaryFormatter = new BinaryFormatter( ); return (SumOf) binaryFormatter.Deserialize(fileStream); fileStream.Close( ); } // fix up the nonserialized datapublic virtual void OnDeserialization
(Object sender)
{
ComputeSums( );
}
private int startNumber = 1; private int endNumber;[NonSerialized] private int[] theSums;
} } Output: Creating first one with new... 1, 3, 6, 10, 15, Serializing......completed Creating second one with deserialize... 1, 3, 6, 10, 15,
You can see in the output that the data was successfully serialized to disk and then reconstituted by deserializaiton. The trade-off of disk storage space versus time does not make a lot of sense with five values, but it makes a great deal of sense with 5 million values.
So far you’ve streamed your data to disk for storage and across
the network for easy communication with distant programs. There is
one other time you might create a stream: to store permanent
configuration and status data on a per-user basis. For this purpose,
the
.NET
Frameworks offer
isolated
storage
.
3.148.107.254