首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >运行时序列化 3

运行时序列化 3

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

1. 如何将某类型的对象序列化成另一个类型的数据流?

2. 如何将某类型的数据流反序列化成另一个类型的对象?

下面列举几个场景,会遇到上面的两个问题:

1. 单实例类型(singleton),对于这种类型对象的序列化和反序列不应该在AppDomain中创建新的对象,应该使用已经存在的单实例对象。

2. 对于远程控制的对象,CLR序列化服务器端对象有关的信息,并通过网络传输给客户端,在客户端反序列化的时候,会创建一个本地代理对象,这个代理对象的类型不同于服务器端对象的类型。但是这对于客户端代码来说是透明的,客户端直接使用本地的代理对象,代理对象内部会请求远程服务器,由服务器端实际执行具体的操作。

单实例类型代码演示:

[Serializable]
public class Singleton : ISerializable
{
  private static readonly Singleton _singleton = new Singleton();

  public string Name = "Nestor";
  public DateTime Date = DateTime.Now;

  private Singleton() { }

  //注意:特殊构造器是不必要的,因为它永远不会调用到
  public static Singleton GetSingleton() { return _singleton; }
  void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
  {
    info.SetType(typeof(SingletonSerializationHelper));
  }
  [Serializable]
  private sealed class SingletonSerializationHelper : IObjectReference
  {
    object IObjectReference.GetRealObject(StreamingContext context)
    {
      return Singleton.GetSingleton();
    }
  }
}

Singleton[] singletons = { Singleton.GetSingleton(), Singleton.GetSingleton() };
// TRUE
bool isSame1 = object.ReferenceEquals(singletons[0], singletons[1]);

using (var stream = new MemoryStream())
{
  BinaryFormatter formatter = new BinaryFormatter();
  formatter.Serialize(stream, singletons);

  stream.Position = 0;
  Singleton[] singletons2 = (Singleton[])formatter.Deserialize(stream);
  // TRUE
  bool isSame2 = object.ReferenceEquals(singletons2[0], singletons2[1]);
  // TRUE
  bool isSame3 = object.ReferenceEquals(singletons[0], singletons2[1]);
}

对上面的代码进行总结:

1. Singleton类型加载到AppDomain时,CLR会调用它的静态构造器初始化一个Singleton对象,并保存在_singleton字段里。

2. Singleton类型没有提供任何公共构造器,防止其他地方构造多个实例。

3. 创建Singleton[]数组,每个元素都调用GetSingleton()方法,返回单Singleton对象的引用。

序列化部分:

4. 格式化器的Serialize方法,在序列化的时候检测到Singleton类型实现了ISerializable接口,就会调用接口的GetObjectData方法。

5. GetObjectData方法里调用了SetType方法,是告诉格式化器将Singleton对象序列化到SingletonSerializationHelper对象。

6. 格式化器检测出数组中两个元素都引用同一个对象,格式化器只会序列化一个对象。

反序列化部分:

7. 格式化器的Deserialize方法,对数据流进行反序列时,数据流中保存的类型是SingletonSerializationHelper,所以格式化器尝试反序列化一个SingletonSerializationHelper对象。

8. 在构造这个对象后,格式化器会检查对象的类型是否实现了IObjectReference接口,如果对象的类型实现了IObjectReference接口,格式化器会调用接口里的GetRealObject方法,这个方法会返回反序列好之后真正的类型的对象。

IObjectReference接口定义如下:

public interface IObjectReference
{
    /// <summary>Returns the real object that should be deserialized, rather than the object that the serialized stream specifies.</summary>
    /// <returns>Returns the actual object that is put into the graph.</returns>
    /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> from which the current object is deserialized. </param>
    /// <exception cref="T:System.Security.SecurityException">The caller does not have the required permission. The call will not work on a medium trusted server.</exception>
    [SecurityCritical]
    object GetRealObject(StreamingContext context);
}

通过上面一步步分析代码,我们知道:

1. 序列化到不同类型,通过GetObjectData方法里调用了SetType方法做到。

2. 反序列到不同类型,通过对象的类型是否实现了IObjectReference接口来完成。

序列化到不同类型代码演示:

[Serializable]
class MyClass1 : ISerializable
{
  public int MyProperty1;

  void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
  {
    info.SetType(typeof(MyClass2));
    info.AddValue("MyProperty1", this.MyProperty1);
  }
}

[Serializable]
class MyClass2 : ISerializable
{
  public int MyProperty2;

  private MyClass2(SerializationInfo info, StreamingContext context)
  {
    this.MyProperty2 = info.GetInt32("MyProperty1");
  }

  void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
  {
    throw new NotImplementedException();
  }
}

MyClass1 my = new MyClass1() { MyProperty1 = 1 };
using (var stream = new MemoryStream())
{
  BinaryFormatter formatter = new BinaryFormatter();
  formatter.Serialize(stream, my);

  stream.Position = 0;
  MyClass2 my2 = (MyClass2)formatter.Deserialize(stream);
}

总结上面的代码,MyClass1对象序列化到MyClass2类型的数据流,MyClass2类型的数据流反序列到MyClass2类型的对象。

因为MyClass1不会被反序列化,所以不用提供特殊构造器。

MyClass2不会被序列化,所以不用提供ISerializable.GetObjectData方法的实现。

反序列到不同类型代码演示:

[Serializable]
class MyClass1 : ISerializable, IObjectReference
{
  public int MyProperty1;

  public MyClass1() { }

  private MyClass1(SerializationInfo info, StreamingContext context)
  {
    this.MyProperty1 = info.GetInt32("MyProperty1");
  }

  void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
  {
    info.AddValue("MyProperty1", this.MyProperty1);
  }

  object IObjectReference.GetRealObject(StreamingContext context)
  {
    return new MyClass2() { MyProperty2 = this.MyProperty1 };
  }
}

class MyClass2
{
  public int MyProperty2;
}

MyClass1 my = new MyClass1() { MyProperty1 = 1 };
using (var stream = new MemoryStream())
{
  BinaryFormatter formatter = new BinaryFormatter();
  formatter.Serialize(stream, my);

  stream.Position = 0;
  MyClass2 my2 = (MyClass2)formatter.Deserialize(stream);
}

总结上面的代码,MyClass1对象序列化到MyClass1类型的数据流,MyClass1类型的数据流反序列到MyClass2类型的对象。

主要通过检查反序列化出来的对象的类型是否实现了IObjectReference接口,如果实现该接口会做一次GetRealObject方法的调用。

FCL的逻辑如下:

if (this.TopObject is IObjectReference)
{
  this.TopObject = ((IObjectReference)this.TopObject).GetRealObject(this.m_context);
}

总结全文,主要讨论以下两个问题:

1. 如何将某类型的对象序列化成另一个类型的数据流?

2. 如何将某类型的数据流反序列化成另一个类型的对象?

答案是:

1. 序列化到不同类型,通过GetObjectData方法里调用了SetType方法做到。

2. 反序列到不同类型,通过对象的类型是否实现了IObjectReference接口来完成。

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

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

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

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

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

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