Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Nbsp;   Serialization/Deserialization Quick Start

Let’s start off by looking at some code.

 

using System;

using System.Collections.Generic; using System.IO;

using System.Runtime.Serialization.Formatters.Binary;

 

internal static class QuickStart { public static void Main() {

// Create a graph of objects to serialize them to the stream

var objectGraph = new List<String> { "Jeff", "Kristin", "Aidan", "Grant" }; Stream stream = SerializeToMemory(objectGraph);

 

// Reset everything for this demo stream.Position = 0;

objectGraph = null;

 

// Deserialize the objects and prove it worked

objectGraph = (List<String>) DeserializeFromMemory(stream); foreach (var s in objectGraph) Console.WriteLine(s);

}

 

private static MemoryStream SerializeToMemory(Object objectGraph) {

// Construct a stream that is to hold the serialized objects MemoryStream stream = new MemoryStream();

 

// Construct a serialization formatter that does all the hard work BinaryFormatter formatter = new BinaryFormatter();

 

// Tell the formatter to serialize the objects into the stream formatter.Serialize(stream, objectGraph);

 

// Return the stream of serialized objects back to the caller return stream;

}

 

private static Object DeserializeFromMemory(Stream stream) {

// Construct a serialization formatter that does all the hard work BinaryFormatter formatter = new BinaryFormatter();

 

// Tell the formatter to deserialize the objects from the stream return formatter.Deserialize(stream);

}

}


Wow, look how simple this is! The SerializeToMemory method constructs a System.IO. MemoryStream object. This object identifies where the serialized block of bytes is to be placed. Then the method constructs a BinaryFormatter object (which can be found in the System. Runtime.Serialization.Formatters.Binary namespace). A formatter is a type (implementing the System.Runtime.Serialization.IFormatter interface) that knows how to serialize and de- serialize an object graph. The Framework Class Library (FCL) ships with two formatters: the Binary­ Formatter (used in this code example) and a SoapFormatter (which can be found in the System. Runtime.Serialization.Formatters.Soap namespace and is implemented in the System.

Runtime.Serialization.Formatters.Soap.dll assembly).

       
 
 
   

 

To serialize a graph of objects, just call the formatter’s Serialize method and pass it two things: a reference to a stream object and a reference to the object graph that you want to serialize. The stream object identifies where the serialized bytes should be placed and can be an object of any type derived from the System.IO.Stream abstract base class. This means that you can serialize an object graph to a MemoryStream, a FileStream, a NetworkStream, and so on.

The second parameter to Serialize is a reference to an object. This object could be anything: an Int32, a String, a DateTime, an Exception, a List<String>, a Dictionary<Int32, Dat­ Time>, and so on. The object referred to by the objectGraph parameter may refer to other objects.



For example, objectGraph may refer to a collection that refers to a set of objects. These objects may also refer to other objects. When the formatter’s Serialize method is called, all objects in the graph are serialized to the stream.

Formatters know how to serialize the complete object graph by referring to the metadata that describes each object’s type. The Serialize method uses reflection to see what instance fields are in each object’s type as it is serialized. If any of these fields refer to other objects, then the formatter’s Serialize method knows to serialize these objects, too.

Formatters have very intelligent algorithms. They know to serialize each object in the graph no more than once out to the stream. That is, if two objects in the graph refer to each other, then the formatter detects this, serializes each object just once, and avoids entering into an infinite loop.

In my SerializeToMemory method, when the formatter’s Serialize method returns, the MemoryStream is simply returned to the caller. The application uses the contents of this flat byte array any way it wants. For example, it could save it in a file, copy it to the clipboard, send it over a wire, or whatever.


The DeserializeFromStream method deserializes a stream back into an object graph. This method is even simpler than serializing an object graph. In this code, a BinaryFormatter is con- structed and then its Deserialize method is called. This method takes the stream as a parameter and returns a reference to the root object within the deserialized object graph.

Internally, the formatter’s Deserialize method examines the contents of the stream, constructs instances of all the objects that are in the stream, and initializes the fields in all these objects so that they have the same values they had when the object graph was serialized. Typically, you will cast the object reference returned from the Deserialize method into the type that your application is expecting.

       
   
 
 

 

At this point, I’d like to add a few notes to our discussion. First, it is up to you to ensure that your code uses the same formatter for both serialization and deserialization. For example, don’t write code that serializes an object graph by using the SoapFormatter and then deserializes the graph by using the BinaryFormatter. If Deserialize can’t decipher the contents of the stream, then a System. Runtime.Serialization.SerializationException exception will be thrown.

The second thing I’d like to point out is that it is possible and also quite useful to serialize mul- tiple object graphs out to a single stream. For example, let’s say that we have the following two class definitions.

 

[Serializable] internal sealed class Customer { /* ... */ } [Serializable] internal sealed class Order { /* ... */ }


And then, in the main class of our application, we define the following static fields.

 

private static List<Customer> s_customers = new List<Customer>(); private static List<Order> s_pendingOrders = new List<Order>(); private static List<Order> s_processedOrders = new List<Order>();

 

We can now serialize our application’s state to a single stream with a method that looks like this.

 

private static void SaveApplicationState(Stream stream) {

// Construct a serialization formatter that does all the hard work BinaryFormatter formatter = new BinaryFormatter();

 

// Serialize our application's entire state formatter.Serialize(stream, s_customers); formatter.Serialize(stream, s_pendingOrders); formatter.Serialize(stream, s_processedOrders);

}

 

To reconstruct our application’s state, we would deserialize the state with a method that looks like this.

 

private static void RestoreApplicationState(Stream stream) {

// Construct a serialization formatter that does all the hard work BinaryFormatter formatter = new BinaryFormatter();

 

// Deserialize our application's entire state (same order as serialized) s_customers = (List<Customer>) formatter.Deserialize(stream); s_pendingOrders = (List<Order>) formatter.Deserialize(stream); s_processedOrders = (List<Order>) formatter.Deserialize(stream);

}

 

The third and last thing I’d like to point out has to do with assemblies. When serializing an object, the full name of the type and the name of the type’s defining assembly are written to the stream. By default, BinaryFormatter outputs the assembly’s full identity, which includes the assembly’s file name (without extension), version number, culture, and public key information. When deserializing

an object, the formatter first grabs the assembly identity and ensures that the assembly is loaded into the executing AppDomain by calling System.Reflection.Assembly’s Load method (discussed in Chapter 23, “Assembly Loading and Reflection”).

After an assembly has been loaded, the formatter looks in the assembly for a type matching that of the object being deserialized. If the assembly doesn’t contain a matching type, an exception is thrown and no more objects can be deserialized. If a matching type is found, an instance of the type is created and its fields are initialized from the values contained in the stream. If the type’s fields don’t exactly match the names of the fields as read from the stream, then a SerializationException exception is thrown and no more objects can be deserialized. Later in this chapter, I’ll discuss some sophisticated mechanisms that allow you to override some of this behavior.


 

ImportantSome extensible applications use Assembly.LoadFrom to load an assembly and then construct objects from types defined in the loaded assembly. These objects can be serialized to a stream without any trouble. However, when deserializing this stream, the formatter attempts to load the assembly by calling Assembly’s Load method instead of calling the LoadFrom method. In most cases, the CLR will not be able to locate the assem- bly file, causing a SerializationException exception to be thrown. This catches many developers by surprise—because the objects serialized correctly, the developers expect that the objects will deserialize correctly as well.

If your application serializes objects whose types are defined in an assembly that your application loads using Assembly.LoadFrom, then I recommend that you implement a method whose signature matches the System.ResolveEventHandler delegate and register this method with System.AppDomain’s AssemblyResolve event just before calling a formatter’s Deserialize method. (Unregister this method with the event af- ter Deserialize returns.) Now, whenever the formatter fails to load an assembly, the CLR calls your ResolveEventHandler method. This method is passed the identity of the assembly that failed to load. The method can extract the assembly file name from the assembly’s identity and use this name to construct the path where the application knows the assembly file can be found. Then, the method can call Assembly.LoadFrom to load the assembly and return the resulting Assembly reference back from the ResolveEventHandler method.

 

This section covered the basics of how to serialize and deserialize object graphs. In the remain- ing sections, we’ll look at what you must do in order to define your own serializable types, and we’ll also look at various mechanisms that allow you to have greater control over serialization and deserialization.

 


Date: 2016-03-03; view: 713


<== previous page | next page ==>
Overriding the Assembly and/or Type | Nbsp;   Making a Type Serializable
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.009 sec.)