CLR 序列化

序列化的概念

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

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

序列化的好处是,一旦将对象序列化成内存中的字节流,就可以方便的以一些更有用的方式处理数据,比如:

  • 应用程序的状态可保存到磁盘文件或数据库中,并在应用程序下次允许时恢复。

  • 一组对象可轻松通过网络发送给另一台机器上运行的程序。

.net 可以支持二进制序列化、XML和SOAP 序列化、JSON 序列化。其中二进制序列化功能最强大,类型保真,XML只序列化公共属性和字段,JSON 只序列化公共属性。JSON 广泛用于web 上共享数据,是一个开放式标准。

本篇只介绍.net 中二进制序列化。

如何使类型可序列化

看一个序列化应用的例子:

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 接口的类型,它知道如何序列化和反序列化对象图。BinaryFormatter 就是FCL 提供的格式化器。

BinaryFormatter 格式化器的Serialize 方法将对象转为流数据,它的Deserialize 方法将流数据转为对象。

格式化器的Deserialize 方法检查流的内容,构造流中所有对象的实例,并初始化所有这些对象中的字段。

反序列化的过程是,格式化器首先获取程序集标识信息,并通过调用System.Reflection.Assembly 的load 方法,确保程序集已加载到正在执行的AppDomain中。如果程序集中没有反序列化实例的类型就会报错。

下面是一个利用序列化实现对象的深拷贝:

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);
  }
}
  • 注意要保证代码为序列化和反序列化使用相同的格式化器。

  • 可将多个对象图序列化到一个流中。

  • 在反序列化时要注意当前AppDomain中有对应的类型。

 

下面说一说如何让类型可序列化

很简单,像下面这样向类型应用定制特性 System.SerializableAttribute 。

[Serializable]
internal struct Point {public Int32 x, y;}
  • SerializableAttribute 这个定制特性只能应用于引用类型 class、值类型 struct、枚举类型 enum、和委托 delegate。

  • 枚举类型和 委托类型总是可序列化的,不必显示应用特性。

  • SerializableAttribute 不会被派生类型继承。

 

如何控制序列化和反序列化

将SerializableAttribute 定制特性应用于类型,所有实例字段都会被序列化。如果我不想让某一个字段序列化怎么办呢?。还有哪些情况不应该序列化呢?

  • windows 内核对象的句柄,它在反序列化到另一个进程或另一台机器上就会失去意义。

  • 字段含有很容易计算的信息,去掉它可以减少传输的数据,增强性能。

使用 System.NonSerializedAttribute 定制特性指出类型中不应该序列化的字段。

[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;
}
...
}

除了NonSerializedAttribute 定制特性之外,FCL 还提供了其他控制序列化和反序列化的特性。这些特性应用于不同的场景,我这里只简单列举一些常用的。

System.Runtime.Serialization //这个命名空间有好多其他特性
OnDeserializedAttribute //根据字段值初始化瞬时状态
OnSerializingAttribute //序列化前,修改任何需要修改的状态
OnSerializedAttribute  //序列化后,恢复任何需要恢复的状态
OnDeserializingAttribute //在类型新版本中为字段设置默认值

 

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

FCL在 System.Runtime.Serialization 命名空间提供了一个FormatterServices 类型。该类型只包含静态方法,而且该类型不能实例化。以下步骤描述了格式化器如何自动序列化类型,应用了 SerializableAttribute 特性的对象。

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

    通过反射获取public 和private实例字段,将它们放到MemberInfo 对象构成的数组中。

  • 2 对象被序列化,System.Reflection.MemberInfo 对象数组传给 FormatterServices静态方法GetObjectData:

  • 3 格式化器将程序 集标识和类型的完整名称写入流中。

  • 4 格式化器遍历两个数组中的元素,将每个成员的名称和值写入流中。

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

  • 格式化器从流中读取程序集标识和完整类型名称。将该程序集加载到AppDomain中。

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

    这个方法为新对象分配内存,但不为对象调用构造器。

  • 格式化器现在构造并初始化一个MemberInfo 数组,具体做法和前面一样,都是调用 FormatterServices的 GetSerializableMembers方法。

  • 格式化器根据流中包含的数据创建并初始化一个Object 数组。

  • 将新分配对象、MemberInfo数组以及并行Object数组的引用传给FormatterServices的静态方法 PopulateObjectMembers:

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

 

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

如果前面提到的特性:OnSerialized、OnDeserializing、OnDeserialized等,它们不能满足你想要的功能。另外格式化器使用的是反射,反射会影响程序的性能。为了对序列化/和反序列化的数据进行完全的控制,避免使用反射,你的类型可以实现 System.Runtime.Serialization.ISerializable 接口。它的定义如下:

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

它只有一个方法GetObjectData,实现这个接口的大多数类型还实现了一个特殊的构造器。

类型一旦实现了这个接口,所有派生类型也要实现它,而且派生类型必须保证调用基类的GetObjectData方法和特殊构造器。

 

格式化器序列化对象图时会检查每个对象。如果发现一个对象的类型实现了 ISerializable接口,就会忽略所有定制特性,改为构造新的System.Runtime.Serialization.SerializationInfo 对象。该对象包含了要为对象序列化的值的集合。

 

要实现ISerializable 但基类没有实现怎么办?

这种情况下,派生类必须手动序列化基类的字段,具体的做法就是获取它们的值,并把者些值添加到SerializationInfo 集合中。然后,在你的特殊构造器中,还必须从集合中取出值,以某种方式设置基类的字段。如果基类的字段是public 或 protected 的可以实现,但是private 字段,几乎不能实现。

 

流上下文

一组序列化好的对象可以有许多目的地:同一个进程、同一台机器的不同进程、不同机器上的不同进程等。

某些情况下,一个对象想知道它要在什么地方反序列化,从而以不同的方式生成它的状态。

例如,如果对象中包装了Windows 信号量对象,如果它知道要反序列化到同一个进程中,就可决定对它的内核句柄进行序列化,这是因为内核句柄在一个进程中有效。 但如果要反序列化到同一台计算机的不同进程中,就可决定对信号量的字符串名称进行序列化。 最后,如果要反序列化到不同计算机的进程,就可决定抛出异常,因为信号量只在一台机器中有效。

格式化器中的大量方法都接受一个StreamingContext 类型,StreamingContextStates 结构是一个非常简单的值类型,它只提供两个公共只读属性。

CLR 序列化

 

 

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

IFormatter 接口定义了StreamingContext类型的可读/可写性Context。构造格式化器时,格式化器会初始化它的 Context属性,将StreamingContextStates 设为All,对额外状态对象的引用设为null。

 

.net 由于元数据和IL 中间语言以及JIT 编译等特点使它在序列化方面有更多的灵活性。在.net 中类型还能序列化为不同类型,对象也能反序列化为不同的对象。.net 中的格式化器还允许不是 “类型实现的一部分” 的代码重写该类型 “序列化和反序列化其对象” 的方式。

上一篇:首页> C#>如何使用技术(所述)来处理C结构和.Net的指针?


下一篇:.NET程序执行原理