深刻挖掘.NET序列化机制——实现更易用的序列化方案

.NET框架为程序员提供了“序列化和反序列化”这一有力的工具,使用它,咱们能很容易的将内存中的对象图转化为字节流,并在须要的时候再将其恢复。这一技术的典型应用场景包括[1]html

  • 应用程序运行状态的持久化;
  • 在应用程序之间经过剪切板传送对象;
  • 建立对象复本,以隔离用户操做形成的影响;
  • 在网络间传送对象。

然而,.NET框架提供的默认序列化行为也存在着有诸多限制,尤为是在版本控制方面——好比一个使用SerializableAttribute标记,而未实现ISerializable的类型,在经过重构修改了某个字段的名称后,再反序列化以前的序列化结果时就会失败。程序员

本文首先举例说明了.NET默认序列化方案的限制;而后描述了经过扩展.NET序列化框架而指望达到的目标——咱们已经在实际开发中实现;接下来介绍了.NET序列化框架所提供的扩展点,最后详细说明了如何经过这些扩展点实现一个更易用的序列化方案。数据库

须要特别说明的两点是:网络

  1. 完整的.NET序列化框架是很是强大的,而咱们的改进方案仍然处于这一框架以内。本文所说的”局限”只是指使用SerializableAttribute、ISerializable时的局限,并非整个序列化框架的局限。若是你已经对ISurrogateSelector、ISerializationSurrogate、SerializationBinder等概念很熟悉了,我想你也许已经有了本身的解决方案。
  2. 为何要在.NET序列化框架内进行扩展,而不是实现另一套独立的解决方案呢?一来咱们想充分利用框架提供的机制,好比对象图的访问与维护——这些逻辑实现起来应该会比较困难;再者能够充分利用已有的序列化代码,好比基础类库中的类型和第三方库中的类型——它们大都仍是使用的.NET默认序列化机制,这样能够节省很是大的工做量。

.NET默认序列化方案及其的限制

在.NET中,为了使某个类型成为可序列化的,最简单的方式是使用SerializableAttribute属性,以下代码所示。app

[Serializable]
class Person
{
    private string name;

    private int agee;

    ...
}

var formatter = new BinaryFormatter();
formatter.Serialize(someStream, aPerson);

但在实际的开发项目中,若是真的这样作了,那么在产品的第一个版本发布后,就颇有可能会面临下面的问题。框架

  • 你发现agee拼写错了,很是想改正它(嗯,我是完美主义者)!
  • 你发现Person这个名字太宽泛了,也许改成Employer更好。
  • 你想添加一个新字段,但又不想或不能在IDeserializationCallback中为新字段赋值——确实存在这种状况,请参考个人另外一篇文章http://www.cnblogs.com/brucebi/archive/2013/04/01/2993968.html
  • 你想在Person的序列化过程当中得到更多的控制,因此想改成实现ISerializable接口。

全部这些都不能实现,由于它们所带来的代码修改都将形成软件再也不兼容以前的版本。是的,咱们总能够选择在最开始的时候就使用ISerializable接口(和一个反序列化构造函数),但若是你也有”懒惰“的美德,就会以为这样作很不爽!ide

此外,咱们在开发过程当中还遇到了以下几种状况:函数

  • 序列化第三方库中的对象,但这些类型没有标记为Serializable。
  • 有时咱们须要把某个属性(property)序列化,而非相应的字段,这样反序列化时就能够经过属性的set方法执行一些额外的逻辑。
  • 某些对象的序列化过程效率很低,咱们想提供一个更高效的实现。
  • 咱们想实现一个”对象/存储映射“方案,以使咱们能像”对象/关系映射“那样在内存与存储设备(包括数据库、文件系统等)间进行转储。咱们的方案不像ORM那样复杂,但它更适合咱们的产品,效率更高,自动化程度更高。

而要解决这些问题,都要诉诸于对.NET序列化框架更深刻的理解。工具

咱们能作到什么

在最终的方案中咱们将给出一个PersistedAttribute和一个IPersistable接口,它们与SerializableAttribute和ISerializable很相似,但能解决前面提到的问题。下面的代码说明了PersistedAttribute的使用方法及其功能。优化

// 使用PersistedAttribute代替SerializableAttribute,以使用自定义的序
// 列化机制进行处理。类的名字能够修改,只要保证Persisted的参数不变就
// 能够。
[Persisted("a unique identifier of the class")]
class Person 
{
    // 与SerializableAttribute不一样,只有明确标记为Persisted的字段
    // 或属性才会被序列化。并且每一个序列化项均可以指定一个名称,这
    // 样,当字段名称改变后,只要此项的名称不变,就能兼容以前的版
    // 本。好比,能够把name改成fullName,而无须作其它任何修改。
    [Persisted("Name")]
    private string name;

    [Persisted("Age")]
    private int age;

    // 对于新添加的字段,经过将其声明为“可选的”,反序列化过程就
    // 不会出错,以后能够在IDeserializationCallback中为其赋值,或
    // 者能够经过Value属性为其指定默认值。
    [Persisted("Id", Optional = true, Value = "any thing")]
    private string id;

    // 对于新添加的可选项,也能够为其指定一个计算函数,这样系统在
    // 反序列化时若是发现流中没有存储相应的值,就会调用此函数来为
    // 其计算相应的值。
    [Persisted("Gender", Optional = true, Calculator = CalculateGenderById)]
    private Gender gender;

    // 属性也能够被序列化。在序列化时,系统将保存get方法的结果,而
    // 反序列化时,则会经过set方法设置属性的值,此方法中的全部逻辑
    // 都将会执行。
    [Persisted("SomeProperty")]
    public int SomeProperty
    {
        get
        {
            ...
        }

        set
        {
            ...
        }
    }
}

 IPersistable接口则能够与PersistedAttribute进行各类组合、替换,以下面的代码所示。

public interface IPersistable
{
    // 在序列化时获取对象的数据。
    void GetObjectData(SerializationInfo info);

    // 在反序列化时设置对象的数据。
    void SetObjectData(SerializationInfo info);
}

// 与ISerializable同样,实现IPersistable接口也要求有Persisted标记。
[Persisted("a unique identifier of the class")]
class Person : IPersistable
{
    // Persisted标记与IPersistable接口能够共存,系统会先处理
    // 被标记的字段或属性,而后调用IPersistable接口的成员。
    [Persisted("Name")]
    private string name;

    // 能够去掉以前使用的Persisted标记,而后在IPersistable
    // 接口中以一样的名称进行读取或设置。
    // [Persisted("Age")]
    private int age;

    private Gender gender;

    void GetObjectData(SerializationInfo info)
    {
        info.SetValue("Age", age);

        // 保存额外的数据。
        info.SetValue("G", gender);
    }

    void SetObjectData(SerializationInfo info)
    {
        // 读取以前使用标记序列化的内容。
        age = info.GetInt32("Age");

        try
        {
            // 处理版本兼容问题。
            gender = (Gender)info.GetValue("G");
        }
        catch (Exception e)
        {
        }
    }
}

此外,新的方案还提供了IPersistor接口,经过它能够为任何类型提供自定义的序列化代码,无论这个类型是否是可序列化的。

// 能够为其它类型的对象提供序列化功能的接口。
public interface IPersistor
{
    void GetObjectData(object obj, SerializationInfo info);
    void SetObjectData(object obj, SerializationInfo info);
}

// 为List<int>类型的对象提供自定义的序列化代码,虽然List<int>自己已是可序列化的.
public class IntListPersistor : IPersistor
{
    void GetObjectData(object obj, SerializationInfo info)
    {
        var list = (List<int>)obj;
        // some more efficient codes.
        ... ...
        info.SetValue(...);
    }

    void SetObjectData(object obj, SerializationInfo info)
    {
        var list = (List<int>)obj;
        // some more efficient codes.
        ... ...
        someValue = info.GetValue(...);
    }
}

// 能够为其它非可序列化类型提供自定义的序列化代码。
public class RelativePersistor : IPersistor
{
    void GetObjectData(object obj, SerializationInfo info)
    {
        var target = (Some3rdPartyNonSerializableClass)obj;
        ... ...
    }

    void SetObjectData(object obj, SerializationInfo info)
    {
        var target = (Some3rdPartyNonSerializableClass)obj;
        ... ...
    }
}

// 须要在程序开始的时候将实现类注册到系统中。
PersistManager.Register(typeof(List<int>), new IntListPersistor());
PersistManager.Register(typeof(Some3rdPartyNonSerializableClass, new RelativePersistor());

下面,咱们将介绍相应技术的实现思路。

如何切入.NET的序列化框架

首先要解决的问题是:如何将自定义的序列化机制插入到.NET序列化框架中。我假设你已经知道如何使用BinaryFormatter或者SoapFormatter,而在此我想简单的描述一下formatter的一些行为细节。后文中咱们将以BinaryFormatter为例。

BinaryFormatter上有一个SurrogateSelector属性(surrogate是“代理”的意思,surrogate selector即是代理选择器),它的类型以下代码所示:

public interface ISurrogateSelector 
{
   void ChainSelector(ISurrogateSelector selector);

   ISurrogateSelector GetNextSelector();

   ISerializationSurrogate GetSurrogate(Type type,
      StreamingContext context, out ISurrogateSelector selector);
}

其中用到的ISerializationSurrogate(serialization surrogate即是序列化代理喽)的定义以下:

public interface ISerializationSurrogate
{
   void GetObjectData(Object obj,
      SerializationInfo info, StreamingContext context);

   Object SetObjectData(Object obj,
      SerializationInfo info, StreamingContext context,
      ISurrogateSelector selector);
}

当BinaryFormatter在序列化一个对象obj时,它会检查本身的SurrogateSelector属性是否非空,若是非空,便会以obj的类型为参数调用其GetSurrogate方法,若是此方法返回一个有效的对象surrogate(ISerializationSurrogate),则formatter会调用surrogate.GetObjectData(obj, ...),这时surrogate对象便得到机会来执行自定义的逻辑了。

对,这就是奇迹发生的地方!

咱们要作的就是实现自定义的ISurrogateSelector和ISerializationSurrogate类,在合适的时候调用自定义的代码。而后,在使用时将其注入到BinaryFormatter中,以下面代码所示。

var formatter = new BinaryFormatter(
        new TheSurrogateSelector,  
        new StreamingContext(StreamingContextStates.All));
formatter.Serialize(stream, objectGraph);

 

实现更易用的序列化方案

 先来看ISurrogateSelector的实现(为简洁起见,去掉了不少优化相关的代码)。

public class PersistSurrogateSelector : SurrogateSelector
{
    private readonly PersistSurrogate inner = new PersistSurrogate();

    private readonly ISerializationSurrogate surrogate;

    public PersistSurrogateSelector()
    {
        surrogate = FormatterServices.GetSurrogateForCyclicalReference(inner);
    }

    public override ISerializationSurrogate GetSurrogate(Type type,
        StreamingContext context, out ISurrogateSelector selector)
    {
   
        return inner.CanHandle(type) ? surrogate
            : base.GetSurrogate(type, context, out selector);
    }
}

其中的PersistSurrogate便是咱们自定义的序列化代理,其代码以下所示:

public class PersistSurrogate : ISerializationSurrogate
{
    public void GetObjectData(object obj, SerializationInfo info, 
        StreamingContext context)
    {
        // 使用注册的IPersistor实现来对对象进行序列化和反序列化。
        var manager = PersistorManager.Instance;
        var persistor = manager.GetPersistor(obj.GetType());
        persistor.GetObjectData(obj, info);
    }

    public object SetObjectData(object obj, SerializationInfo info, 
        StreamingContext context, ISurrogateSelector selector)
    {
        var manager = PersistorManager.Instance;
        var persistor = manager.GetPersistor(obj.GetType());
        return persistor.SetObjectData(obj, info);
    }

    internal bool CanHandle(Type type)
    {
        // 若是已经为类型注册了IPersistor接口实现,则自定义机制能够处理。
        return PersistorManager.Instance.IsPersistable(type);
    }
}

代码中的PersistorManager就是管理自定义的IPersistor接口实现与相应的类型对应的管理类(前面的代码中提到过),一下子咱们会提到PersistorManager.Instance.IsPersistable的实现,至于此类的其它功能则再也不赘述。

这样咱们就实现了对任意类型进行自定义序列化的功能,下面简要总结一下:

  1. 为要序列化的类型T定义一个IPersistor接口的实现P;
  2. 将T与P注册到PersistorManager中;
  3. 在建立BinaryFormatter时,将PersistSurrogateSelector对象传入;

有了这个基础,再实现IPersistable和PersistedAttribute功能就比较简单了,来看PersistorManager::IsPersistable方法的实现:

public bool IsPersistable(Type type)
{
    Utility.CheckNotNull(type);
    var at = typeof(PersistedAttribute);
    return Attribute.GetCustomAttribute(type, at, false) != null
        || exactMatches.ContainsKey(type)
        || derivedMatches.GetValue(type) != null
        || dynamicMatches.Any(i => i.CanHandle(type));
}

其中的Attribute.GetCustormAttribute便是在判断具体的类型上是否有PersistedAttribute标记,若是有咱们就能够为其合成一个IPersistor接口的实现——这样,使用PersistedAttribute标记的类型,则再也不须要显示的注册。代码中的exactMatches,derivedMatches和dynamicMatches分别用于处理那些可以为某个具体的类型提供序列化功能,可以为一个派生体系提供序列化功能及可以动态决定是否能够提供序列化功能的IPersistor实现。

咱们能够定义一个Persistor实现来统一处理那些有PersistedAttribute标记的类型,在此以前先来看PersistorManager::GetPersistor的定义:

public IPersistor GetPersistor(Type mapped)
{
    Utility.CheckNotNull(mapped);
    if (exactMatches.ContainsKey(mapped))
    {
        return exactMatches[mapped];
    }

    var derived = derivedMatches.GetValue(mapped);
    if (derived != null)
    {
        exactMatches[mapped] = derived;
        return derived;
    }

    var dynamic = dynamicMatches.FirstOrDefault(i => i.CanHandle(mapped));
    if (dynamic != null)
    {
        exactMatches[mapped] = dynamic;
        return dynamic;
    }

    var auto = new Persistor(mapped);
    exactMatches[mapped] = auto;
    return auto;
}

而Persistor的实现逻辑以下:

  1. 提取mapped上全部有PersistedAttributed标记的成员;
  2. 在序列化时(IPersistor::GetObjectData中),迭代全部的标记项,用标记的名称和具体的字段值调用info.AddValue;
  3. 在反序列化时(IPersistor::SetObjectData中),迭代全部标记项,用标记的名称从SerializationInfo中取值,并做以下处理:
    1. 若是取值成功,则将其设置给要反序列化的对象;
    2. 若是获取失败,则
      1. 若是此项未标记为Optional,则抛出异常,不然:
      2. 若是此项有Value或Calculator设置,则经过它们为当前字段赋值,不然保留字段的默认值(default(T))。

至于具体实现代码,咱们这里就省略了。 

省略掉的部分

读到这里,相信你们对.NET的序列化框架的扩展就有所感觉了。不过咱们尚未介绍如何处理类型名称改变的问题,这里只给出一个引子——使用BinaryFormatter的Binder属性和自定义的SerializationBinder派生类——更多的细节相信你们都能搞定的。

其实.NET序列化机制还有不少能够挖掘的地方,好比IObjectReference,每个看似简单的接口都能给咱们无限发挥的空间。

好了,就到这里吧。欢迎你们来探讨。


 

参考资料:

[1] http://msdn.microsoft.com/en-us/magazine/cc301761.aspx

相关文章
相关标签/搜索