Drizzle

博客园 首页 新随笔 联系 订阅 管理

.NET Framework 开发人员指南 
自定义序列化 

自定义序列化是控制某种类型的序列化和反序列化的过程。通过控制序列化,可以确保序列化的兼容性,即可以在某个类型的不同版本之间序列化和反序列化,而不会破坏该类型的核心功能。例如,在某个类型的第一个版本中,可能只有两个字段。在某个类型的下一版本中,又增加了几个字段。然而,应用程序的第二版必须能够对这两种类型进行序列化和反序列化。以下各节描述如何控制序列化。

在序列化期间和之后运行自定义方法
最佳做法也是最简单的方法(在 .Net Framework 2.0 版中引入),就是在序列化期间和之后将下列属性应用于用于更正数据的方法:

OnDeserializedAttribute

OnDeserializingAttribute

OnSerializedAttribute

OnSerializingAttribute

这些属性使该类型可以参与序列化和反序列化过程的任何一个阶段或全部四个阶段。属性指定在每个阶段应调用的该类型的方法。这些方法不会访问序列化流,而是允许您在序列化或反序列化之前和之后更改对象。属性可以应用于类型继承层次结构的所有级别,每种方法在层次结构中从基础到派生程度最大依次调用。此机制通过将序列化和反序列化的职责分配给派生程度最大的实现,可以避免实现 ISerializable 接口的复杂性以及可能产生的任何问题。此外,此机制还允许格式化程序忽略字段的填充以及从序列化流中的检索。有关控制序列化和反序列化的详细信息和示例,请单击上面的任意链接。

此外,在向现有可序列化类型中添加新字段时,将 OptionalFieldAttribute 属性应用于该字段。在处理缺少新字段的流时,BinaryFormatter 和 SoapFormatter 将忽略该字段的不存在。

实现 ISerializable 接口
控制序列化的另一种方式是在对象上实现 ISerializable 接口。但是应注意,在控制序列化时,上一节中的方法优先于此方法。

此外,对于用 Serializable 属性标记且在类级别上或其构造函数上具有声明性或命令性安全的类,不应使用默认序列化。相反,这些类应始终实现 ISerializable 接口。

实现 ISerializable 涉及实现 GetObjectData 方法以及在反序列化对象时使用的特殊构造函数。以下示例代码说明如何对前面节中的 MyObject 类实现 ISerializable。

C# 复制代码
[Serializable]
public class MyObject : ISerializable
{
  public int n1;
  public int n2;
  public String str;

  public MyObject()
  {
  }

  protected MyObject(SerializationInfo info, StreamingContext context)
  {
    n1 = info.GetInt32("i");
    n2 = info.GetInt32("j");
    str = info.GetString("k");
  }
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter
=true)]
  public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
  {
    info.AddValue("i", n1);
    info.AddValue("j", n2);
    info.AddValue("k", str);
  }
}

当序列化期间调用 GetObjectData 时,您负责填充由方法调用提供的 SerializationInfo。只需将要被序列化的变量作为名称/值对添加。任何文本都可用作该名称。您可以自由决定将哪些成员变量添加到 SerializationInfo 中,前提是序列化了足够多的数据,可以在反序列化期间还原该对象。如果基对象实现了 ISerializable,则派生类应对该基对象调用 GetObjectData 方法。

请注意,序列化可使其他代码得以查看或修改本来不可访问的对象实例数据。因此,执行序列化的代码要求指定了 SerializationFormatter 标志的 SecurityPermission。在默认策略下,通过 Internet 下载的代码或 Intranet 代码不会被授予该权限;只有本地计算机上的代码才会被授予该权限。必须对 GetObjectData 方法进行显式保护,方法是要求指定了 SerializationFormatter 标志的 SecurityPermission 或要求其他专门保护私有数据的权限。

如果私有字段中存储了敏感信息,应要求 GetObjectData 具有适当权限,以保护数据。请记住,被授予指定了 SerializationFormatter 标志的 SecurityPermission 的代码可以查看和修改存储在私有字段中的数据。被授予了此 SecurityPermission 的恶意调用方可以查看隐藏目录位置或授予的权限等数据,并使用这些数据攻击计算机的安全漏洞。有关可以指定的安全权限标志的完整列表,请参见 SecurityPermissionFlag 枚举。

有一点必须强调,在向类中添加 ISerializable 时,必须实现 GetObjectData 和特殊构造函数。如果缺少 GetObjectData,编译器将发出警告。但是,由于无法强制实现构造函数,因此如果缺少构造函数,编译器不会发出警告;并且当试图反序列化不带构造函数的类时,会引发异常。

当前的设计优于 SetObjectData 方法,可以解决潜在的安全性和版本控制问题。例如,如果 SetObjectData 方法被定义为接口的一部分,则该方法必须是公共的;这样,用户必须编写代码以防止多次调用 SetObjectData 方法。否则,在执行操作的过程中,调用对象的 SetObjectData 方法的恶意应用程序会导致潜在的问题。

在反序列化期间,使用为此目的提供的构造函数将 SerializationInfo 传递给类。在反序列化对象时,对构造函数施加的任何可见性约束均被忽略,因此可以将类标记为 public、protected、internal 或 private。但是,如果类未被密封,则最好将构造函数标记为 protected;如果类被密封了,则应将构造函数标记为 private。构造函数还应执行彻底的输入验证。为了避免恶意代码的滥用,构造函数应强制使用任何其他构造函数获取类实例所需的同样的安全检查和权限。如果不采用此建议,恶意代码将可以预先序列化对象,通过指定了 SerializationFormatter 标志的 SecurityPermission 获得控制权,然后在客户端计算机上反序列化对象,避开本来在标准实例构造过程中会使用公共构造函数应用的任何安全性。

要还原该对象的状态,只需使用在序列化期间使用的名称从 SerializationInfo 检索变量的值即可。如果基类实现 ISerializable,则应调用基构造函数以允许基对象还原其变量。

当您从实现 ISerializable 的类中派生一个新类时,派生类必须同时实现该构造函数以及 GetObjectData 方法(如果派生类具有需要序列化的变量)。下面的代码示例说明如何使用前面所示的 MyObject 类做到这一点。

C# 复制代码
[Serializable]
public class ObjectTwo : MyObject
{
  public int num;

  public ObjectTwo() : base()
  {
  }

  protected ObjectTwo(SerializationInfo si, StreamingContext context) : base(si,context)
  {
    num = si.GetInt32("num");
  }
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter
=true)]
  public override void GetObjectData(SerializationInfo si, StreamingContext context)
  {
    base.GetObjectData(si,context);
    si.AddValue("num", num);
  }
}

不要忘记在反序列化构造函数中调用基类。如果没有调用基类,将永远不会调用基类上的构造函数,并且在反序列化后该对象将不会被完全构造。

对象将被彻底重新构造,并且在反序列化期间调用方法可能会产生不良的副作用,因为被调用的方法可能引用在进行此调用时尚未被反序列化的对象引用。如果反序列化的类实现 IDeserializationCallback,则在整个对象图已被反序列化后将自动调用 OnDeserialization 方法。此时,所有引用的子对象均已被完全还原。哈希表就是在不使用事件侦听器的情况下很难反序列化的类的典型示例。在反序列化期间检索键/值对十分容易,但将这些对象添加回哈希表可能会造成一些问题,因为不能保证从哈希表派生的类已被反序列化。因此,不建议在此阶段对哈希表调用方法。

请参见

posted on 2006-11-17 14:51  岁月随风  阅读(1548)  评论(0编辑  收藏  举报