设计器序列化概述

使用设计器序列化,您可以保留组件在设计时或运行时的状态。

对象的序列化

.NET Framework 支持几种序列化类型,如代码生成、SOAP 序列化、二进制序列化和 XML 序列化。

“设计器序列化”是一种特殊形式的序列化,它涉及那种通常与开发工具关联的对象持久性。 设计器序列化是将对象图转换为以后可用于恢复对象图的源文件的过程。 源文件可以包含代码、标记,甚至可以包含 SQL 表信息。 设计器序列化可用于所有公共语言运行时对象。

设计器序列化与常用的对象序列化之间存在以下几方面的差异:

  • 执行序列化的对象与运行时对象是分开的,因此可以从组件中移除设计时逻辑。

  • 序列化方案是在此假设下设计的:将在完全初始化的状态中创建对象,然后在反序列化过程中通过属性和方法调用对此对象进行修改。

  • 如果对象的属性的值从未在对象上设置过,则不序列化这些属性。 反过来,反序列化流也可能无法初始化所有属性值。 有关序列化规则的更详细的说明,请参见本主题中下文的“常规序列化规则”部分。

  • 强调序列化流中的内容的质量,而非对象的完全序列化。 如果没有定义对象的序列化方式,则将传递此对象,而不引发异常。 设计器序列化有一些能够以简单的、人类可读的形式序列化对象的方式,这些方式不会将对象序列化为不透明的 blob。

  • 序列化流包含的数据可能多于反序列化需要的数据。 例如,源代码序列化将用户代码与反序列化对象图所需的代码混合在了一起。 序列化时必须保留此用户代码,反序列化时必须传递此用户代码。

提示

设计器序列化可兼用于运行时和设计时。

下表列出了使用 .NET Framework 设计器序列化基础结构实现的设计目标。

设计目标

说明

模块式

可以扩展序列化过程以覆盖新的数据类型,这些数据类型可以提供有用的、人类可读的自我描述。

易扩展

可以轻松地扩展序列化过程以覆盖新的数据类型。

不限制格式

对象可参与许多不同的文件格式,设计器序列化并没有与特定的数据格式相关联。

体系结构

设计器序列化结构基于元数据、序列化程序和序列化管理器。 下表说明了此结构的各个方面的作用。

方面

说明

元数据特性

可使用一个特性将类型 T 与某个序列化程序 S 相关联。 此外,该体系结构还支持“引导”特性,可用于安装能够为没有序列化程序的类型提供此程序的项目。

序列化程序

序列化程序是一个可以序列化一个特定的类型或一组类型的对象。 每种数据格式都有一个基类。 例如,DemoXmlSerializer 基类可以将对象转换成 XML。 此结构独立于任何特定的序列化格式,它还包括这个结构的一个基于代码文档对象模型 (CodeDOM) 的实现。

序列化管理器

序列化管理器是一个提供信息存储区的对象,它为所有用于序列化对象图的各种序列化程序提供信息存储区。 一个包含 50 个对象的图可包含 50 个不同的序列化程序,这些序列化程序都会生成它们自己的输出。 这些序列化程序使用序列化管理器来相互进行通信。

下面的说明和过程演示如何序列化图中的对象,在本例中为 A 和 B。

图:对象的序列化

序列化图中的对象

  1. 调用方从序列化管理器为对象 A 请求序列化程序:

    MySerializer s = manager.GetSerializer(a);
    
  2. A 的类型上的元数据特性将 A 绑定到请求的类型的序列化程序。 然后调用方让序列化程序序列化 A:

    Blob b = s.Serialize(manager, a);
    
  3. 对象 A 的序列化程序将对 A 进行序列化。 对于序列化程序在对 A 进行序列化时遇到的每个对象,它都会从序列化管理器请求其他序列化程序:

    MySerializer s2 = manager.GetSerializer(b);
    Blob b2 = s2.Serialize(manager, b);
    
  4. 序列化的结果会返回给调用方:

    Blob b = ...
    

常规序列化规则

组件通常公开许多属性。 例如,Windows 窗体 Button 控件具有 BackColorForeColorBackgroundImage 等属性。 当您在设计器中将 Button 控件放置在窗体上然后查看生成的代码时,您会发现代码中只保留了部分属性。 通常,这些属性是您显式为其设置了值的属性。

Button 控件关联的 CodeDomSerializer 定义序列化行为。 下面的列表介绍了一些 CodeDomSerializer 用来序列化属性值的规则。

  • 如果属性上附加了 DesignerSerializationVisibilityAttribute,序列化程序将使用它来确定此属性是否已序列化(如 VisibleHidden),以及如何序列化(如 Content)。

  • VisibleHidden 值指定属性是否已序列化。 Content 值指定如何序列化属性。

  • 对于名为 DemoProperty 的属性,如果组件实现名为 ShouldSerializeDemoProperty 的方法,则设计器环境对此方法进行后期绑定调用,以确定是否进行序列化。 例如,对于 BackColor 属性,此方法叫做 ShouldSerializeBackColor。

  • 如果属性指定了 DefaultValueAttribute,则默认值将与组件上的当前值进行比较。 仅当当前值不是默认值时才序列化属性。

  • 与组件关联的设计器也可通过以下方式参与序列化决定:“影化”属性或自行实现 ShouldSerialize 方法。

提示

序列化程序遵从与属性关联的 PropertyDescriptor 的序列化决定,PropertyDescriptor 使用前面列出的规则。

如果您要以其他的方式序列化您的组件,可以编写您自己的从 CodeDomSerializer 派生的序列化程序类,并使用 DesignerSerializerAttribute 将其与您的组件关联。

智能序列化程序实现

序列化程序设计的一个要求是当需要新的序列化格式时,必须使用元数据特性更新所有的数据类型以支持此格式。 不过,通过将序列化提供程序与使用一般对象元数据的序列化程序一起使用,就可以满足此要求。 这部分介绍了一种为特定格式设计序列化程序以最大限度地降低对许多自定义序列化程序的需求的首选方法。

下面的架构定义了一种假想的、用于保留对象图的 XML 格式。

<TypeName>
    <PropertyName>
        ValueString
    </PropertyName>
</TypeName>

此格式是使用发明的一种名为 DemoXmlSerializer 的类序列化的。

public abstract class DemoXmlSerializer 
{
    public abstract string Serialize(
        IDesignerSerializationManager m, 
        object graph);
}

DemoXmlSerializer 是一个模块式类,它是用一点点累积的方式构建字符串的,理解这一点很重要。 例如,Int32 数据类型的 DemoXmlSerializer 在传递整数值“23”时将返回字符串“23”。

序列化提供程序

前面的架构示例清楚地说明了有两个基础类型需要处理:

  • 具有子属性的对象。

  • 可转换成文本的对象。

使用可将类转换成文本或 XML 标记的自定义序列化程序来修饰每个类是很困难的。 序列化提供程序解决了此问题,它提供了一个回调机制,使对象能够为给定类型提供序列化程序。 对于此示例,可用的类型集合受以下条件限制:

  • 如果可以使用 IConvertible 接口将类型转换成字符串,则使用 StringXmlSerializer。

  • 如果不能将类型转换成字符串,但类型是公共的并且有一个空构造函数,则会使用 ObjectXmlSerializer。

  • 如果这两个条件都不符合,则序列化提供程序将返回 null,表示此对象没有序列化程序。

下面的代码示例演示调用序列化程序如何解决发生此错误时的情况。

internal class XmlSerializationProvider : IDesignerSerializationProvider 
{
    object GetSerializer(
        IDesignerSerializationManager manager, 
        object currentSerializer, 
        Type objectType, 
        Type serializerType) 
    {

        // Null values will be given a null type by this serializer.
        // This test handles this case.
        if (objectType == null) 
        {
            return StringXmlSerializer.Instance;
        }

        if (typeof(IConvertible).IsSubclassOf(objectType)) 
        {
            return StringXmlSerializer.Instance;
        }

        if (objectType.GetConstructor(new object[]) != null) 
        {
            return ObjectXmlSerializer.Instance;
        }

        return null;
    }
}

定义序列化提供程序后,必须使用序列化提供程序。 可使用 AddSerializationProvider 方法向序列化管理器提供序列化提供程序,但这需要向序列化管理器发出此调用。 通过向序列化程序添加 DefaultSerializationProviderAttribute,可以将序列化提供程序自动添加到序列化管理器。 此特性要求序列化提供程序有一个公共的、空的构造函数。 下面的代码示例演示需要对 DemoXmlSerializer 做的更改。

[DefaultSerializationProvider(typeof(XmlSerializationProvider))]
public abstract class DemoXmlSerializer 
{
}

现在,每当向序列化管理器请求任何一种类型的 DemoXmlSerializer 时,默认序列化提供程序就会添加到序列化管理器中(如果还没有添加)。

序列化程序

示例 DemoXmlSerializer 类有两个具体的序列化程序类,名称分别是 StringXmlSerializer 和 ObjectXmlSerializer。 下面的代码示例演示 StringXmlSerializer 的实现。

internal class StringXmlSerializer : DemoXmlSerializer
{
    internal StringXmlSerializer Instance = new StringXmlSerializer();

    public override string Serialize(
        IDesignerSerializationManager m, 
        object graph) 
    {

        if (graph == null) return string.Empty;

        IConvertible c = graph as IConvertible;
        if (c == null) 
        {
            // Rather than throwing exceptions, add a list of errors 
            // to the serialization manager.
            m.ReportError("Object is not IConvertible");
            return null;
        }

    return c.ToString(CultureInfo.InvariantCulture);
    }
}

ObjectXmlSerializer 实现更为复杂,因为,需要枚举对象的公共属性。 下面的代码示例演示 ObjectXmlSerializer 的实现。

internal class ObjectXmlSerializer : DemoXmlSerializer 
{
    internal ObjectXmlSerializer Instance = new ObjectXmlSerializer();

    public override string Serialize(
        IDesignerSerializationManager m, 
        object graph) 
    {

        StringBuilder xml = new StringBuilder();
        xml.Append("<");
        xml.Append(graph.GetType().FullName);
        xml.Append(">");

        // Now, walk all the properties of the object.
        PropertyDescriptorCollection properties;
        Property p;

        properties = TypeDescriptor.GetProperties(graph);

        foreach(p in properties) 
        {
            if (!p.ShouldSerializeValue(graph)) 
            {
                continue;
            }

            object value = p.GetValue(graph);
            Type valueType = null;
            if (value != null) valueType = value.GetType();

            // Get the serializer for this property
            DemoXmlSerializer s = m.GetSerializer(
                valueType, 
                typeof(DemoXmlSerializer)) as DemoXmlSerializer;

            if (s == null) 
            {
                // Because there is no serializer, 
                // this property must be passed over.  
                // Tell the serialization manager
                // of the error.
                m.ReportError(string.Format(
                    "Property {0} does not support XML serialization",
                    p.Name));
                continue;
            }

            // You have a valid property to write.
            xml.Append("<");
            xml.Append(p.Name);
            xml.Append(">");

            xml.Append(s.Serialize(m, value);

            xml.Append("</");
            xml.Append(p.Name);
            xml.Append(">");
        }

        xml.Append("</");
        xml.Append(graph.GetType().FullName);
        xml.Append(">");
        return xml.ToString();
    }
}

ObjectXmlSerializer 为各个属性值调用其他序列化程序。 这有两个好处。 第一,可以使 ObjectXmlSerializer 变得很简单。 第二,为第三方类型提供了扩展点。 如果使用这两个序列化程序中的任何一个都无法编写 ObjectXmlSerializer 的类型,则可以为该类型提供自定义序列化程序。

用法

若要使用这些新序列化程序,必须创建 IDesignerSerializationManager 的实例。 通过此实例,您可以请求一个序列化程序,然后让此序列化程序序列化您的对象。 下面的代码示例中将为一个要序列化的对象使用 Rectangle,因为此类型有一个空构造函数,并且有四个支持 IConvertible 的属性。 您不用自己实现 IDesignerSerializationManager,您可以使用 DesignerSerializationManager 提供的实现。

Rectangle r = new Rectangle(5, 10, 15, 20);
DesignerSerializationManager m = new DesignerSerializationManager();
DemoXmlSerializer x = (DemoXmlSerializer)m.GetSerializer(
    r.GetType(), typeof(DemoXmlSerializer);

string xml = x.Serialize(m, r);

这将创建下面的 XML。

<System.Drawing.Rectangle>
<X>
5
</X>
<Y>
10
</Y>
<Width>
15
</Width>
<Height>
15
</Height>
</System.Drawing.Rectangle>

提示

此 XML 不是缩进的。 通过 IDesignerSerializationManager 上的 Context 属性,很容易进行缩进。 各个级别的序列化程序可以分别向包含当前缩进级别的上下文堆栈添加一个对象,各个序列化程序都可以在堆栈中搜索此对象并使用此对象来提供缩进。

请参见

参考

IDesignerSerializationManager

DesignerSerializationManager

DefaultSerializationProviderAttribute

IConvertible

CodeDomSerializerBase

其他资源

扩展设计时支持