控制对象序列化的方式
- 综述
序列化是将对象转换成流的过程,与之相反,反序列化将流转换为对象。利用序列化技术可以将对象序列化到磁盘、内存或者网络,在进程间、应用程序域间或者计算机之间传递对象。
.net框架提供了二进制格式和XML格式(纯XML或SOAP)的序列化,如果需要,也可以根据Iformatter接口实现自己需要的格式。利用Iformatter接口的Serialize方法和Deserialize方法可以将对象序列化/反序列化。
有三种方式可以声明序列化的方式。下面分别比较。
先准备一个待序列化的类User。
User类
1 public class User
2 {
3 public string Name;
4
5 public DateTime Birthday;
6
7 public int Age;
8
9 public User()
10 {
11 }
12 }
- Serializable特性 利用[Serializable]特性将类声明为可序列化。这样Iformatter接口在序列化时会自动将类的所有字段序列化。
-
如果有不想被序列化的字段,可以用[NonSerialized]特性声明,序列化时会忽略相应的字段。
-
如果需要在序列化前后或者反序列化前后进行额外的处理,可以利用[OnSerializing]、[OnSerialized]、[OnDeserializing]、[OnDeserialized]这四个特性声明相应的处理方法。
-
对上面的User类,利用特性声明序列化非常简单。
-
不过我们需要做一些特殊的处理:因为Age字段可以利用Birthday字段计算得出,我们将不序列化Age字段以减少流的长度,但相应的,在反序列化后要计算出Age的值。
Serializable特性
1 [Serializable]
2 public class User
3 {
4 public string Name;
5
6 public DateTime Birthday;
7
8 [NonSerialized]
9 public int Age;
10
11 public User()
12 {
13 }
14
15 [OnDeserialized]
16 internal void OnDeserialized(StreamingContext context)
17 {
18 Age = DateTime.Now.Year - Birthday.Year;
19 }
20 }
-
虽然利用特性将类声明为可序列化非常方便,但还存在一些不足:
-
1.[Serializable]特性不能被继承,在扩展时会比较麻烦。如果基类没有声明[Serializable]特性,子类就无法利用[Serializable]特性序列化基类的字段。
-
2.除了利用[NonSerialized]、[OnDeserialized]等等特性之外,无法实现其他自定义的方式对序列化的数据进行完全控制。
-
3.整个序列化过程基于反射技术,性能存在问题。
- Iserializable接口
利用Iserializable接口可以实现任何自定义的序列化过程。既然由用户控制序列化过程,也就不会有扩展问题。用户直接指定了序列化的字段和相应的值,也就避免了因为使用反射引起的性能问题。
类的序列化方法是定义在Iserializable接口中,但反序列化是利用一个特殊的构造函数实现的。考虑扩展和安全的需求,这个构造函数可声明成protected。
我们可以在反序列化时使用的构造函数中计算Age的值,但这样逻辑不清晰,计算Age并不属于反序列化的内容。因此继续将这段代码放在反序列化之后,这样需要实现另外一个接口IdeserializationCallback。
ISerializable接口
1 [Serializable]
2 public class User : ISerializable, IDeserializationCallback
3 {
4 public string Name;
5
6 public DateTime Birthday;
7
8 public int Age;
9
10 public User()
11 {
12 }
13
14 protected User(SerializationInfo info, StreamingContext context)
15 {
16 Name = info.GetString("Name");
17 Birthday = info.GetDateTime("Birthday");
18 //Age = DateTime.Now.Year - Birthday.Year;
19 }
20
21 public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
22 {
23 info.AddValue("Name", Name);
24 info.AddValue("Birthday", Birthday);
25 }
26
27 void IDeserializationCallback.OnDeserialization(object obj)
28 {
29 Age = DateTime.Now.Year - Birthday.Year;
30 }
31 }
无论是利用[Serializable]特性,还是Iserializable接口,我们都需要修改待序列化类的代码。有时候,我们需要把处理序列化数据的代码放在类之外。
还有一种情况,待序列化的类是一个第三方提供的封闭类,无法修改类的代码,也无法进行扩展以实现Iserializable接口。
要解决这些问题,我们可以利用序列化代理,额外实现一个序列化代理类,对指定的类进行序列化。
- IserializationSurrogate接口
序列化代理类需要实现IserializationSurrogate接口,接口的两个方法GetObjectData和SetObjectData分别在序列化和反序列化时调用,方法的具体实现与Iserializable接口非常类似。唯一不同点是待序列化的对象利用方法的参数和返回值传递。
ISerializationSurrogate接口
1 public class UserSerializationSurrogate : ISerializationSurrogate
2 {
3 public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
4 {
5 User user = (User)obj;
6
7 info.AddValue("Name", user.Name);
8 info.AddValue("Birthday", user.Birthday);
9 }
10
11 public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
12 {
13 User user = (User)obj;
14
15 user.Name = info.GetString("Name");
16 user.Birthday = info.GetDateTime("Birthday");
17 user.Age = DateTime.Now.Year - user.Birthday.Year;
18
19 return user;
20 }
21 }
利用序列化代理类进行具体序列化操作的代码与前两种方式有所不同,需要为Iformatter指定一个代理选择器。代理选择器维护一个哈希表,待序列化类的类型和序列化上下文构成键,相应的序列化代理类构成值。序列化时代理选择器会在哈希表中查找匹配的键,以获取相应的代理类。如果查找失败将会抛出异常。
IFormatter
1 private T GetFormatterWithUserSurrogate<T>() where T : IFormatter, new()
2 {
3 ISerializationSurrogate surrogate = new UserSerializationSurrogate();
4 SurrogateSelector selector = new SurrogateSelector();
5 selector.AddSurrogate(typeof(User), new StreamingContext(StreamingContextStates.All), surrogate);
6
7 T formatter = new T();
8 formatter.SurrogateSelector = selector;
9
10 return formatter;
11 }