前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >运行时序列化 4

运行时序列化 4

作者头像
小蜜蜂
发布2019-07-22 10:58:12
3860
发布2019-07-22 10:58:12
举报
文章被收录于专栏:明丰随笔

不修改某类型的代码,也能重写该类型的对象的序列化和反序列化?

对于很多第三方类库的代码,如果里面的类型没有定义成可序列化的,但是我们也想对其进行序列化操作,我们通过学习本文就可以做到。

应用程序之所以要重写某类型的序列化和反序列化,主要因为以下三个方面:

1. 允许开发人员序列化最初没有设计成可序列化的类型。

2. 允许开发人员提供一种方式将类型的一个版本映射到另一个的版本。

3. 允许开发人员重写默认的序列化逻辑。

为了达到目的,需要做一下几步:

1. 我们需要一个“代理类型”,它会重写目标类型的序列化和反序列化的逻辑。

2. 向格式化器注册“代理类型”和目标类型对应关系。

3. 格式化器对目标类型的实例进行序列化或反序列化,就会调用“代理类型”里面定义的方法。

序列化代理类型必须实现该接口:

代码语言:javascript
复制
//namespace System.Runtime.Serialization
public interface ISerializationSurrogate
{
  void GetObjectData(object obj, SerializationInfo info, StreamingContext context);
  object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector);
}

GetObjectData方法和ISerializable接口的GetObjectData方法差不多,只是多了一个参数obj,它就是要序列化的真实对象的引用。

SetObjectData方法用于反序列化一个对象,object obj对象就是要反序列出来的对象,但是它的字段都没有初始化好,可以设置好这个对象的字段,然后SetObjectData方法返回null,序列化器会知道应该使用object obj对象作为反序列出来的对象。也可以无视object obj对象,在SetObjectData方法内部创建一个完全不同的对象,甚至创建不同类型的对象,并返回该对象,那么序列化器会将SetObjectData方法返回的对象作为反序列出来的对象。通过SerializationInfo info对象可以获取流中的数据。

我们写一个例子,把一个本地时间的DateTime对象序列化到流中,并保存为UTC时间。反序列化的时候,从流中取出UTC时间,并转换成本地时间,然后返回。

代码语言:javascript
复制
class UniversalToLocalTimeSerializationSurrogate : ISerializationSurrogate
{
  void ISerializationSurrogate.GetObjectData(object obj, SerializationInfo info, StreamingContext context)
  {
    string uDateTimeStr = ((DateTime)obj).ToUniversalTime().ToString("u");
    info.AddValue("Date", uDateTimeStr);
  }

  object ISerializationSurrogate.SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
  {
    string uDateTimeStr = info.GetString("Date");
    DateTime uDateTime = DateTime.ParseExact(uDateTimeStr, "u", null);
    DateTime localDateTime = uDateTime.ToLocalTime();
    return localDateTime;
  }
}

using (MemoryStream stream = new MemoryStream())
{
  IFormatter formatter = new BinaryFormatter();
  SurrogateSelector ss = new SurrogateSelector();
  ss.AddSurrogate(typeof(DateTime), formatter.Context, new UniversalToLocalTimeSerializationSurrogate());
  formatter.SurrogateSelector = ss;

  DateTime localDateTime = DateTime.Now;
  formatter.Serialize(stream, localDateTime);

  stream.Position = 0;
  var localDateTime2 = (DateTime)formatter.Deserialize(stream);
}

分析上面的代码,

1. 代理序列化类和目标类是注册在SurrogateSelector对象里面的。

2. SurrogateSelector对象传给格式化器的SurrogateSelector属性。

完成这两步,格式化器就知道了所有的代理序列化类和目标类的对应关系。

当调用格式化器的Serialize方法时,格式化器会在SurrogateSelector属性中查找要序列化的目标类型,如果匹配到一个代理类,就会调用代理类的GetObjectData方法,来负责写入流的信息。

当调用格式化器的Deserialize方法时,格式化器会在SurrogateSelector属性中查找要反序列化的目标类型,如果匹配到一个代理类,就会调用代理类的SetObjectData方法,来负责要反序列化对象的设置。

代理选择器链

FCL默认实现的SurrogateSelector类其实实现了ISurrogateSelector接口。这个接口定义如下:

代码语言:javascript
复制
public interface ISurrogateSelector
{
  void ChainSelector(ISurrogateSelector selector);
  ISurrogateSelector GetNextSelector();
  ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector);
}

通过调用ChainSelector方法,可以把多个ISurrogateSelector对象链接在一起形成一个链。根据业务场景不同,我们可以分别使用不同的ISurrogateSelector对象来维护代理序列化类和目标类的映射关系。但是最后一定要把不同的ISurrogateSelector对象链接起来。GetSurrogate方法会在当前的ISurrogateSelector对象中查找目标类,如果找不到就访问链中的下一个ISurrogateSelector对象,直到找到目标类,并返回匹配的代理类。如果链中的所有ISurrogateSelector对象都找不到要序列化的目标类,就会返回null。

回顾全文:

  1. “序列化代理类型”和目标类型一一对应
  2. 对应关系维护在ISurrogateSelector对象中
  3. 多个ISurrogateSelector对象形成链
  4. 设置格式化器的SurrogateSelector属性
  5. 格式化器的序列化和反序列化会查找SurrogateSelector里面的注册信息,根据查找结果最终决定是否使用序列化代理类来执行序列化的动作

-纸上得来终觉浅,绝知此事要躬行-

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 明丰随笔 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档