CLR via C#, 4th -- 【核心机制】 -- 第24章 运行时序列化

序列化是将对象或对象图"转换成字节流的过程。反序列化是将字节流转换回对象图的过程。

在对象和字节流之间转换是很有用的机制。下面是一些例子:

  • 应用程序的状态(对象图)可轻松保存到磁盘文件或数据库中,并在应用程序下次运行时恢复。ASP.NET就是利用序列化和反序列化来保存和还原会话状态的。
  • 一组对象可轻松复制到系统的剪贴板,再粘贴回同一个或另一个应用程序。事实上,Windows窗体和Windows Presentation Foundation(WPF)就利用了这个功能。
  • 一组对象可克隆并放到一边作为“备份”;与此同时,用户操纵一组“主”对象。
  • 一组对象可轻松地通过网络发送给另一台机器上运行的进程。Microsoft.NET Framework 的Remoting(远程处理)架构会对按值封送(marshaled by value)的对象进行序列化和反序列化。

24.1 序列化/反序列化快速入门

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);  
   } 
}

格式化器是实现了System.Runtime.Serialization.IFormatter接口的类型,它知道如何序列化和反序列化对象图。
FCL提供了两个格式化器:
BinaryFormatter(在System.Runtime.Serialization.Formatters.Binary命名空间中定义)
SoapFormatter(在System.Runtime.Serialization.Formatters.Soap命名空间中定义)

序列化对象图只需调用格式化器的Serialize方法,并向它传递两样东西:对流对象的引用,以及对想要序列化的对象图的引用。
流对象标识了序列化好的字节应放到哪里,它可以是从System.IlO.Stream抽象基类派生的任何类型的对象。

注意 下面是一个有趣而实用的方法,它利用序列化创建对象的深拷贝(或者说克隆体):

private static Object DeepClone(Object original) { 
   // Construct a temporary memory stream  
   using (MemoryStream stream = new MemoryStream()) {  
 
      // Construct a serialization formatter that does all the hard work 
      BinaryFormatter formatter = new BinaryFormatter();  
 
      // This line is explained in this chapter's "Streaming Contexts" section  
      formatter.Context = new StreamingContext(StreamingContextStates.Clone); 
 
      // Serialize the object graph into the memory stream  
      formatter.Serialize(stream, original); 
 
      // Seek back to the start of the memory stream before deserializing  
      stream.Position = 0;  
 
      // Deserialize the graph into a new set of objects and   
      // return the root of the graph (deep copy) to the caller  
      return formatter.Deserialize(stream);  
   } 
}

有几点需要注意
首先,是由你来保证代码为序列化和反序列化使用相同的格式化器。
其次,可将多个对象图序列化到一个流中,这是很有用的一个操作。

//假如有以下两个类定义:
[Serializable] internal sealed class Customer { /* ... */ } 
[Serializable] internal sealed class Order    { /* ... */ }

//然后,在应用程序的主要类中定义了以下静态字段:
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>();

//现在,可利用如下所示的方法将应用程序的状态序列化到单个流中:
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); 
}

//要重新构建应用程序的状态,可以使用如下所示的一个方法反序列化状态:
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); 
}

最后一个注意事项与程序集有关。序列化对象时,类型的全名和类型定义程序集的全名会被写入流。

重要提示
有的可扩展应用程序使用Assembly.LoadFrom加载程序集,但在反序列化时,格式化器会调用Assembly的Load方法(而非LoadFrom方法)来加载程序集。从而造成SerializationException异常。
如果应用程序使用Assembly.LoadFrom加载程序集,再对程序集中定义的类型进行序列化,那么在调用格式化器的Deserialize方法之前,建议实现一个方法,它的签名要匹配System.ResolveEventHandler委托,并向 System.AppDomain的AssemblyResolve事件注册这个方法。

24.2 使类型可序列化

类型默认是不可序列化的。可以向类型应用定制特性System.SerializableAttribute显式指出对象可以序列化。

SerializableAttribute这个定制特性只能应用于引用类型(class)、值类型(struct)、枚举类型(enum)和委托类型(delegate)。注意,枚举和委托类型总是可序列化的,所以不必显式应用SerializableAttribute特性。

除此之外,SerializableAttribute特性不会被派生类型继承。

24.3控制序列化和反序列化

有两个原因造成我们不想序列化部分实例字段。

  • 字段含有反序列化后变得无效的信息。例如,假定对象包含Windows内核对象(如文件、进程、线程、互斥体、事件、信号量等)的句柄,那么在反序列化到另一个进程或另一台机器之后,就会失去意义。因为Windows内核对象是跟进程相关的值。
  • 字段含有很容易计算的信息。这时要选出那些无须序列化的字段,减少需要传输的数据,增强应用程序的性能。

System.NonSerializedAttribute

以下代码使用System.NonSerializedAttribute定制特性指出类型中不应序列化的字段。注意,该特性也在System(而非System.Runtime.Serialization)命名空间中定义。

[Serializable] 
internal class Circle {  
   private Double m_radius; 
 
   [NonSerialized]  
   private Double m_area; 
 
   public Circle(Double radius) {  
      m_radius = radius; 
      m_area = Math.PI * m_radius * m_radius; 
   } 
 
   ...  
}

System.Runtime.Serialization.OnDeserializedAttribute

[Serializable] 
internal class Circle {  
   private Double m_radius; 
 
   [NonSerialized]  
   private Double m_area; 
   
   public Circle(Double radius) {  
      m_radius = radius; 
      m_area = Math.PI * m_radius * m_radius; 
   } 
 
   [OnDeserialized] 
   private void OnDeserialized(StreamingContext context) {  
      m_area = Math.PI * m_radius * m_radius; 
   } 
}

修改过的Circle类包含一个标记了System.Runtime.Serialization.OnDeserializedAttribute定制特性的方法。每次反序列化类型的实例,格式化器都会检查类型中是否定义了应用了该特性的方法。如果是,就调用该方法。调用这个方法时,所有可序列化的字段都会被正确设置。在该方法中,可能需要访问这些字段来执行一些额外的工作,从而确保对象的完全反序列化。

除了OnDeserializedAttribute这个定制特性,System.Runtime.Serialization命名空间还定义了包括OnSerializingAttribute,OnSerializedAttribute和OnDeserializingAttribute。

[Serializable] 
public class MyType {  
   Int32 x, y; [NonSerialized] Int32 sum;  
 
   public MyType(Int32 x, Int32 y) {  
      this.x = x; this.y = y; sum = x + y; 
   } 
 
   [OnDeserializing]   
   private void OnDeserializing(StreamingContext context) { 
      // Example: Set default values for fields in a new version of this type   
   } 
     
   [OnDeserialized]  
   private void OnDeserialized(StreamingContext context) {  
      // Example: Initialize transient state from fields  
      sum = x + y;  
   } 
 
   [OnSerializing]  
   private void OnSerializing(StreamingContext context) { 
         // Example: Modify any state before serializing  
   } 
 
   [OnSerialized]   
   private void OnSerialized(StreamingContext context) {  
      // Example: Restore any state after serializing  
   } 
}
View Code

使用这4个属性中的任何一个时,你定义的方法必须获取一个StreamingContext参数并返回void.方法名可以是你希望的任何名称。另外,应将方法声明为private,以免它被普通的代码调用;格式化器运行时有充足的安全权限,所以能调用私有方法。

System.Runtime.Serialization.OptionalFieldAttribute

如果序列化类型的实例,在类型中添加新字段,然后试图反序列化不包含新字段的对象,格式化器会抛出SerializationException异常。

类型中新增的每个字段都要应用OptionalFieldAttribute特性。然后,当格式化器看到该特性应用于一个字段时,就不会因为流中的数据不包含这个字段而抛出SerializationException.

24.4 格式化器如何序列化类型实例

为了简化格式化器的操作,FCL在System.Runtime.Serialization命名空间提供了一个FormatterServices类型。该类型只包含静态方法,而且该类型不能实例化。

以下步骤描述了格式化器如何自动序列化类型应用了SerializableAttribute特性的对象。

1,格式化器调用FormatterServices的GetSerializableMembers方法:

public static MemberInfo[] GetSerializableMembers(Type type, StreamingContext context);

这个方法利用反射获取类型的public和private实例字段(标记了NonSerializedAttribute特性的字段除外),方法返回由MemberInfo对象构成的数组,其中每个元素都对应一个可序列化的实例字段。
2,对象被序列化,System.Reflection.Memberlnfo对象数组传给FormatterServices的静态方法GetObjectData:

public static Object[] GetObjectData(Object obj, MemberInfo[] members);

这个方法返回一个Object数组,其中每个元素都标识了被序列化的那个对象中的一个字段的值。这个Object数组和Memberlnfo数组是并行(parallel)的;换言之,Object数组中的元素0是MemberInfo数组中的元素0所标识的那个成员的值。
3,格式化器将程序集标识和类型的完整名称写入流中。
4,格式化器然后遍历两个数组中的元素,将每个成员的名称和值写入流中。

以下步骤描述了格式化器如何自动反序列化类型应用了SerializableAttribute特性的对象。
1,格式化器从流中读取程序集标识和完整类型名称。如果程序集当前没有加载到AppDomain中,就加载它(这一点前面已经讲过了)。如果程序集不能加载,就抛出一个SerializationException异常,对象不能反序列化。如果程序集已加载,格式化器将程序集标识信息和类型全名传给FormatterServices的静态方法GetTypeFromAssembly:

public static Type GetTypeFromAssembly(Assembly assem, String name);

这个方法返回一个System.Type对象,它代表要反序列化的那个对象的类型。

2,格式化器调用FormatterServices的静态方法GetUninitializedObject:

public static Object GetUninitializedObject(Type type);

这个方法为一个新对象分配内存,但不为对象调用构造器。然而,对象的所有字节都被初始化为成null或0.
3,格式化器现在构造并初始化一个MemberInfo数组,具体做法和前面一样,都是调用FormatterServices的GetSerializableMembers方法。这个方法返回序列化好、现在需要反序列化的一组字段。
4,格式化器根据流中包含的数据创建并初始化一个Object数组。
5,将新分配对象、MemberInfo数组以及并行Object数组(其中包含字段值)的引用传给FormatterServices的静态方法PopulateObjectMembers:

public static Object PopulateObjectMembers(  
   Object obj, MemberInfo[] members, Object[] data);

这个方法遍历数组,将每个字段初始化成对应的值。到此为止,对象就算是被彻底反序列化了。

24.5控制序列化/反序列化的数据

System.Runtime.Serialization.ISerializable

格式化器内部使用的是反射,而反射的速度是比较慢的,这会增大序列化和反序列化对象所花的时间。为了对序列化/反序列化的数据进行完全的控制,并避免使用反射,你的类型可实现System.Runtime.Serialization.ISerializable接口,它的定义如下:

public interface ISerializable { 
   void GetObjectData(SerializationInfo info, StreamingContext context); 
}

ISerializable接口最大的问题在于,一旦类型实现了它,所有派生类型也必须实现它,而且派生类型必须保证调用基类的GetObjectData方法和特殊构造器。此外,一旦类型实现了该接口,便永远不能删除它,否则会失去与派生类型的兼容性。

ISerializable接口和特殊构造器旨在由格式化器使用。但其他代码可能调用GetObjectData来返回敏感数据。另外,其他代码可能构造对象,并传入损坏的数据。因此,建议向GetObjectData方法和特殊构造器应用以下特性:

[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]

格式化器序列化对象图时会检查每个对象。如果发现一个对象的类型实现了ISerializable接口,就会忽略所有定制特性,改为构造新的System.Runtime.Serialization.Serializationinfo对象。该对象包含了要为对象序列化的值的集合。
构造SerializationInfo对象时,格式化器要传递两个参数:Type和System.Runtime.Serialization.
IFormatterConverter.Type参数标识要序列化的对象。唯一性地标识一个类型需要两个部分的信息:类型的字符串名称及其程序集标识(包括程序集名、版本、语言文化和公钥)。

以下代码展示了Dictionary<TKey,TValue>类型如何实现ISerializable和IDeserializationCallback接口来控制其对象的序列化和反序列化。

[Serializable] 
public class Dictionary<TKey, TValue>: ISerializable, IDeserializationCallback { 
   // Private fields go here (not shown) 
 
   private SerializationInfo m_siInfo;  // Only used for deserialization 
 
   // Special constructor (required by ISerializable) to control deserialization 
   [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]  
   protected Dictionary(SerializationInfo info, StreamingContext context) { 
      // During deserialization, save the SerializationInfo for OnDeserialization  
      m_siInfo = info; 
   } 
 
   // Method to control serialization 
   [SecurityCritical]  
   public virtual void GetObjectData(SerializationInfo info, StreamingContext context) {  
     
      info.AddValue("Version", m_version); 
      info.AddValue("Comparer", m_comparer, typeof(IEqualityComparer<TKey>)); 
      info.AddValue("HashSize", (m_ buckets == null) ? 0 : m_buckets.Length); 
      if (m_buckets != null) { 
         KeyValuePair<TKey, TValue>[] array = new KeyValuePair<TKey, TValue>[Count]; 
         CopyTo(array, 0);  
         info.AddValue("KeyValuePairs", array, typeof(KeyValuePair<TKey, TValue>[])); 
      } 
   } 
   
   // Method called after all key/value objects have been deserialized   
   public virtual void IDeserializationCallback.OnDeserialization(Object sender) { 
      if (m_siInfo == null) return; // Never set, return  
 
      Int32 num = m_siInfo.GetInt32("Version"); 
      Int32 num2 = m_siInfo.GetInt32("HashSize"); 
      m_comparer = (IEqualityComparer<TKey>)  
         m_siInfo.GetValue("Comparer", typeof(IEqualityComparer<TKey>)); 
      if (num2 != 0) { 
         m_buckets = new Int32[num2]; 
         for (Int32 i = 0; i < m_buckets.Length; i++) m_buckets[i] = ­1; 
         m_entries = new Entry<TKey, TValue>[num2];  
         m_freeList = ­1; 
         KeyValuePair<TKey, TValue>[] pairArray = (KeyValuePair<TKey, TValue>[])   
            m_siInfo.GetValue("KeyValuePairs", typeof(KeyValuePair<TKey, TValue>[])); 
         if (pairArray == null)  
            ThrowHelper.ThrowSerializationException( 
               ExceptionResource.Serialization_MissingKeys); 
 
         for (Int32 j = 0; j < pairArray.Length; j++) { 
            if (pairArray[j].Key == null)  
               ThrowHelper.ThrowSerializationException( 
                  ExceptionResource.Serialization_NullKey); 
             
            Insert(pairArray[j].Key, pairArray[j].Value, true);  
         } 
      } else { m_buckets = null; } 
      m_version = num; 
      m_siInfo = null; 
}
View Code

特殊构造器也可以不调用上面列出的各个Get方法,而是调用GetEnumerator。该方法返回一个ystem.Runtime.Serialization.SerializationlnfoEnumerator对象,可用该对象遍历SerializationInfo对象中包含的所有值。枚举的每个值都是一个System.Runtime.Serialization.
SerializationEntry对象。
当然,完全可以定义自己的类型,让它从实现了ISerializable的GetObjectData方法和特殊构造器类型派生。如果你的类型也实现了ISerializable,那么在你实现的GetObjectData方法和特殊构造器中,必须调用基类中的同名方法,确保对象能正确序列化和反序列化。

要实现ISerializable但基类型没有实现怎么办?
在这种情况下,派生类必须手动序列化基类的字段,具体的做法是获取它们的值,并把这些值添加到SerializationInfo集合中。然后,在你的特殊构造器中,还必须从集合中取出值,并以某种方式设置基类的字段。如果基类的字段是public或protected的,那么一切都很容易实现。如果是private字段,就很难或者根本不可能实现。

以下代码演示了如何正确实现ISerializable的GetObjectData方法和它的隐含的构造器,使基类的字段能被序列化:

[Serializable] 
internal class Base {  
   protected String m_name = "Jeff";  
   public Base() { /* Make the type instantiable */ }  
} 
 
[Serializable] 
internal sealed class Derived : Base, ISerializable {  
   private DateTime m_date = DateTime.Now; 
   public Derived() { /* Make the type instantiable*/ } 
 
   // If this constructor didn't exist, we'd get a SerializationException  
   // This constructor should be protected if this class were not sealed 
   [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]  
   private Derived(SerializationInfo info, StreamingContext context) { 
      // Get the set of serializable members for our class and base classes 
      Type baseType = this.GetType().BaseType;  
      MemberInfo[] mi = FormatterServices.GetSerializableMembers(baseType, context); 
 
      // Deserialize the base class's fields from the info object 
      for (Int32 i = 0; i < mi.Length; i++) { 
         // Get the field and set it to the deserialized value 
         FieldInfo fi = (FieldInfo)mi[i];  
         fi.SetValue(this, info.GetValue(baseType.FullName + "+" + fi.Name, fi.FieldType));  
      } 
 
      // Deserialize the values that were serialized for this class 
      m_date = info.GetDateTime("Date"); 
   }
   
    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]  
   public virtual void GetObjectData(SerializationInfo info, StreamingContext context) {  
      // Serialize the desired values for this class 
      info.AddValue("Date", m_date);  
 
      // Get the set of serializable members for our class and base classes 
      Type baseType = this.GetType().BaseType;  
      MemberInfo[] mi = FormatterServices.GetSerializableMembers(baseType, context); 
 
      // Serialize the base class's fields to the info object  
      for (Int32 i = 0; i < mi.Length; i++) { 
         // Prefix the field name with the fullname of the base type  
         info.AddValue(baseType.FullName + "+" + mi[i].Name,   
            ((FieldInfo)mi[i]).GetValue(this)); 
      } 
   } 
   public override String ToString() {  
      return String.Format("Name={0}, Date={1}", m_name, m_date); 
   } 
}
View Code

24.6 流上下文

StreamingContext(流上下文)的结构是一个非常简单的值类型,它只提供了两个公共只读属性。

TABLE 24-1  StreamingContext ’s Public Read-Only Properties

Member Name

Member Type

Description

State

StreamingContextStates

A set of bit flags indicating the source or destination of the

objects being serialized/deserialized

Context

Object

A reference to an object that contains any user-desired

context information

接受一个StreamingContext结构的方法能检查State属性的位标志,判断要序列化反序列化的对象的来源或目的地。

TABLE 24-2  StreamingContextStates’s Flags

Flag Name

Flag Value

Description

CrossProcess

0x0001

The source or destination is a different process on the same  

machine.

CrossMachines

0x0002

The source or destination is on a different machine.

File

0x0004

The source or destination is a file. Don’t assume that the same  

process will deserialize the data.

Persistence

0x0008

The source or destination is a store such as a database or a file.

Don’t assume that the same process will deserialize the data.

Remoting

0x0010

The source or destination is remoting to an unknown location. The location may be on the same machine but may also be on another machine.

Other

0x0020

The source or destination is unknown.

Clone

0x0040

The object graph is being cloned. The serialization code may as-sume that the same process will deserialize the data, and it is therefore safe to access handles or other unmanaged resources.

CrossAppDomain

0x0080

The source or destination is a different AppDomain.

All

0x00FF

The source or destination may be any of the above contexts. This is the default context.

IFormatter接口(同时由BinaryFormatter和SoapFormatter类型实现)定义了StreamingContext类型的可读/可写属性Context.构造格式化器时,格式化器会初始化它的Context属性,将StreamingContextStates设为All,将对额外状态对象的引用设为null.
格式化器构造好之后,就可以使用任何StreamingContextStates位标志来构造一个StreamingContext结构,并可选择传递一个对象引用(对象中包含你需要的任何额外的上下文信息)

24.7 类型序列化为不同类型以及对象反序列化为不同对象

  • 有的类型(比如System.DBNull和System.Reflection.Missing)设计成每个AppDomain一个实例。经常将这些类型称为单实例(singleton)类型。给定一个DBNull对象引用,序列化和反序列化它不应造成在AppDomain中新建一个DBNull对象。反序列化后,Domain中现有的DBNull对象。
  • 对于某些类型(例如System.Type和System.Reflection.Assembly,以及其他反射类型,例如MemberInfo),每个类型、程序集或者成员等都只能有一个实例。
  • 对于远程控制的对象,CLR序列化与服务器对象有关的信息。在客户端上反序列化时,会造成CLR创建一个代理对象。

下面来看看一些示例代码,它们展示了如何正确地序列化和反序列化单实例类型:

// There should be only one instance of this type per AppDomain  
[Serializable] 
public sealed class Singleton : ISerializable { 
   // This is the one instance of this type  
   private static readonly Singleton s_theOneObject = new Singleton(); 
 
   // Here are the instance fields 
   public String Name = "Jeff";  
   public DateTime Date = DateTime.Now; 
 
   // Private constructor allowing this type to construct the singleton  
   private Singleton() { }  
 
   // Method returning a reference to the singleton  
   public static Singleton GetSingleton() { return s_theOneObject; } 
   
   // Method called when serializing a Singleton  
   // I recommend using an Explicit Interface Method Impl. Here  
   [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]  
   void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { 
      info.SetType(typeof(SingletonSerializationHelper)); 
      // No other values need to be added  
   } 
 
   [Serializable] 
   private sealed class SingletonSerializationHelper : IObjectReference {  
      // Method called after this object (which has no fields) is deserialized  
      public Object GetRealObject(StreamingContext context) {  
         return Singleton.GetSingleton();  
      } 
   } 
 
   // NOTE: The special constructor is NOT necessary because it's never called  
}

Singleton类所代表的类型规定每个AppDomain只能存在它的一个实例。

以下代码测试Singleton的序列化和反序列化代码,保证AppDomain中只有Singleton类型的一个实例:

private static void SingletonSerializationTest() { 
   // Create an array with multiple elements referring to the one Singleton object 
   Singleton[] a1 = { Singleton.GetSingleton(), Singleton.GetSingleton() }; 
   Console.WriteLine("Do both elements refer to the same object? "  
      + (a1[0] == a1[1])); // "True"  
 
   using (var stream = new MemoryStream()) { 
      BinaryFormatter formatter = new BinaryFormatter();  
 
      // Serialize and then deserialize the array elements  
      formatter.Serialize(stream, a1);  
      stream.Position = 0;  
      Singleton[] a2 = (Singleton[])formatter.Deserialize(stream);  
 
      // Prove that it worked as expected: 
      Console.WriteLine("Do both elements refer to the same object? "  
         + (a2[0] == a2[1])); // "True" 
      Console.WriteLine("Do all  elements refer to the same object? "  
         + (a1[0] == a2[0])); // "True" 
   } 
}

24.8 序列化代理

应用程序代码之所以要重写(覆盖)类型的行为,主要是出于两方面的考虑。

  • 允许开发人员序列化最初没有设计成要序列化的类型。
  • 允许开发人员提供一种方式将类型的一个版本映射到类型的一个不同的版本。

简单地说,为了使这个机制工作起来,首先要定义一个“代理类型”(surrogate type),它接管对现有类型进行序列化和反序列化的行动。然后,向格式化器登记该代理类型的实例,告诉格式化器代理类型要作用于现有的哪个类型。

序列化代理类型必须实现System.Runtime.Serialization.ISerializationSurrogate接口

public interface ISerializationSurrogate { 
   void GetObjectData(Object obj, SerializationInfo info, StreamingContext context); 
 
   Object SetObjectData(Object obj, SerializationInfo info, StreamingContext context, 
      ISurrogateSelector selector); 
}

虽然不能修改FCL自带的DateTime类型,但可以定义自己的序列化代理类,它能控制DateTime对象的序列化和反序列化方式。
下面展示了如何定义代理类:

internal sealed class UniversalToLocalTimeSerializationSurrogate : ISerializationSurrogate { 
   public void GetObjectData(Object obj, SerializationInfo info, StreamingContext context) { 
      // Convert the DateTime from local to UTC 
      info.AddValue("Date", ((DateTime)obj).ToUniversalTime().ToString("u"));   
   } 
 
   public Object SetObjectData(Object obj, SerializationInfo info, StreamingContext context, 
      ISurrogateSelector selector) {  
      // Convert the DateTime from UTC to local   
      return DateTime.ParseExact(info.GetString("Date"), "u", null).ToLocalTime(); 
   } 
}

序列化/反序列化一个DateTime对象时,格式化器怎么知道要用这个ISerializationSurrogate类型呢?

private static void SerializationSurrogateDemo() { 
   using (var stream = new MemoryStream()) { 
      // 1. Construct the desired formatter  
      IFormatter formatter = new SoapFormatter(); 
 
      // 2. Construct a SurrogateSelector object  
      SurrogateSelector ss = new SurrogateSelector();  
 
      // 3. Tell the surrogate selector to use our surrogate for DateTime objects  
      ss.AddSurrogate(typeof(DateTime), formatter.Context,  
         new UniversalToLocalTimeSerializationSurrogate()); 
 
      // NOTE: AddSurrogate can be called multiple times to register multiple surrogates  
 
      // 4. Tell the formatter to use our surrogate selector 
      formatter.SurrogateSelector = ss; 
      
       // Create a DateTime that represents the local time on the machine & serialize it 
      DateTime localTimeBeforeSerialize = DateTime.Now; 
      formatter.Serialize(stream, localTimeBeforeSerialize); 
 
      // The stream displays the Universal time as a string to prove it worked  
      stream.Position = 0;  
      Console.WriteLine(new StreamReader(stream).ReadToEnd()); 
 
      // Deserialize the Universal time string & convert it to a local DateTime 
      stream.Position = 0;  
      DateTime localTimeAfterDeserialize = (DateTime)formatter.Deserialize(stream);  
 
      // Prove it worked correctly: 
      Console.WriteLine("LocalTimeBeforeSerialize ={0}", localTimeBeforeSerialize);  
      Console.WriteLine("LocalTimeAfterDeserialize={0}", localTimeAfterDeserialize); 
   } 
}

步骤1到步骤4执行完毕后,格式化器就准备好使用已登记的代理类型。调用格式化器的Serialize方法时,会在SurrogateSelector维护的集合(一个哈希表)中查找(要序列化的)每个对象的类型。如果发现一个匹配,就调用ISerializationSurrogate对象的GetObjectData方法来获取应该写入流的信息。

SurrogateSelector对象在内部维护了一个私有哈希表。调用AddSurrogate时,Type和StreamingContext构成了哈希表的键(key),对应的值(value)就是ISerializationSurrogate对象。

代理选择器链
多个SurrogateSelector对象可链接到一起。

如果有多个希望格式化器使用的SurrogateSelector对象,必须把它们链接到一个链表中。
SurrogateSelector类型实现了ISurrogateselector接口,该接口定义了三个方法。这些方法全部跟链接有关。下面展示了ISurrogateSelector接口是如何定义的:

public interface ISurrogateSelector { 
   void ChainSelector(ISurrogateSelector selector);  
   ISurrogateSelector GetNextSelector(); 
   ISerializationSurrogate GetSurrogate(Type type, StreamingContext context,  
      out ISurrogateSelector selector); 
}

ChainSelector方法紧接在当前操作的ISurrogateSelector对象(this对象)之后插入一个ISurrogateSelector对象。GetNextSelector方法返回对链表中的下一个ISurrogateSelector对象的引用:如果当前操作的对象是链尾,就返回null.
FCL定义了一个ISurrogateSelector接口,还定义了一个实现了该接口的SurrogateSelector类型。

24.9 反序列化对象时重写程序集/类型

在下面列举的情形中,有必要将对象反序列化成和序列化时不同的类型。

  • 开发人员可能想把一个类型的实现从一个程序集移动到另一个程序集。例如,程序集版本号的变化造成新程序集有别于原始程序集。
  • 服务器对象序列化到发送给客户端的流中。客户端处理流时,可以将对象反序列化成完全不同的类型,该类型的代码知道如何向服务器的对象发出远程方法调用。
  • 开发人员创建了类型的新版本,想把已序列化的对象反序列化成类型的新版本。

利用System.Runtime.Serialization.SerializationBinder类,可以非常简单地将一个对象反序列化成不同类型。为此,要先定义自己的类型,让它从抽象类SerializationBinder派生。

internal sealed class Ver1ToVer2SerializationBinder : SerializationBinder { 
   public override Type BindToType(String assemblyName, String typeName) { 
      // Deserialize any Ver1 object from version 1.0.0.0 into a Ver2 object  
 
      // Calculate the assembly name that defined the Ver1 type  
      AssemblyName assemVer1 = Assembly.GetExecutingAssembly().GetName();  
      assemVer1.Version = new Version(1, 0, 0, 0); 
 
      // If deserializing the Ver1 object from v1.0.0.0, turn it into a Ver2 object  
      if (assemblyName == assemVer1.ToString() && typeName == "Ver1")  
         return typeof(Ver2); 
 
      // Else, just return the same type being requested  
      return Type.GetType(String.Format("{0}, {1}", typeName, assemblyName)); 
   } 
}

SerializationBinder类还可重写BindToName方法,从而在序列化对象时更改程序集/类型信息,这个方法看起来像下面这样:

public virtual void BindToName(Type serializedType,  
   out string assemblyName, out string typeName)
posted @ 2019-10-31 00:30  FH1004322  阅读(128)  评论(0)    收藏  举报