Información general sobre serialización de diseñadores

Con la serialización de diseñadores puede conservar el estado de sus componentes en tiempo de diseño o de ejecución.

Serialización para objetos

.NET Framework admite varios tipos de serialización, como generación de código, serialización SOAP, serialización binaria y serialización XML.

La serialización de diseñadores es una forma especial de serialización que implica el tipo de persistencia de objeto que suele estar asociado a las herramientas de desarrollo. La serialización de diseñador es el proceso de convertir un gráfico de objetos en un archivo de código fuente que se puede utilizar después para recuperar el gráfico de objetos. Un archivo de código fuente puede contener código, marcado o, incluso, información de tablas SQL. La serialización de diseñadores funciona para todos los objetos de Common Language Runtime.

La serialización de diseñadores se diferencia de la serialización de objetos típica en varios aspectos:

  • El objeto que realiza la serialización es independiente del objeto en tiempo de ejecución, de modo que la lógica en tiempo de diseño se pueda quitar de un componente.

  • El esquema de serialización está concebido a partir de la suposición de que el objeto se creará en un estado totalmente inicializado y después se modificará a través de invocaciones a propiedades y métodos durante la deserialización.

  • No se serializan las propiedades de un objeto con valores que nunca se establecieron en el objeto. A la inversa, la secuencia de deserialización no puede inicializar todos los valores de propiedad. Para obtener una descripción más detallada de las reglas de serialización, vea más adelante en este mismo tema la sección "Reglas generales de serialización".

  • Se hace más hincapié en la calidad del contenido dentro de la secuencia de la serialización que en la serialización completa de un objeto. Si no hay ninguna manera definida de serializar un objeto, no se iniciará una excepción, sino que se pasará por alto el objeto. La serialización de diseñadores ofrece medios de serializar un objeto de forma sencilla en lenguaje natural, en lugar de como un objeto binario opaco.

  • La secuencia de serialización puede tener más datos de los que se necesitan para la deserialización. Por ejemplo, la serialización de código fuente tiene código de usuario mezclado con el código necesario para deserializar un gráfico de objetos. Este código de usuario debe conservarse en la serialización y pasarse por alto en la deserialización.

Nota

La serialización de diseñadores se puede utilizar tanto en tiempo de ejecución como en tiempo de diseño.

En la tabla siguiente se muestran los objetivos de diseño que se cumplen con la infraestructura de serialización de diseñadores de .NET Framework.

Objetivo del diseño

Descripción

Modular

El proceso de serialización se puede extender para cubrir nuevos tipos de datos, los cuales pueden proporcionar útiles descripciones de sí mismos en lenguaje natural.

Fácilmente extensible

El proceso de serialización se puede extender con facilidad para cubrir nuevos tipos de datos.

Neutro con respecto al formato

Los objetos pueden participar en muchos formatos de archivo diferentes y la serialización de diseñadores no está vinculada a ningún formato de datos determinado.

Arquitectura

La arquitectura de serialización de diseñadores está basada en metadatos, serializadores y un administrador de serialización. En la tabla siguiente se describe la función de cada aspecto de la arquitectura.

Aspecto

Descripción

Atributos de metadatos

Se utiliza un atributo para relacionar un tipo T con algún serializador S. Asimismo, la arquitectura admite un atributo de "arranque" que se puede utilizar para instalar un objeto capaz de proporcionar serializadores para tipos que no los tienen.

Serializadores

Un serializador es un objeto que puede serializar un tipo determinado o un intervalo de tipos. Hay una clase base para cada formato de datos. Por ejemplo, puede haber una clase base DemoXmlSerializer capaz de convertir un objeto en XML. La arquitectura es independiente de cualquier formato de serialización. También hay una implementación de esta arquitectura integrada en CodeDOM (Code Document Object Model).

Administrador de serialización

Un administrador de serialización es un objeto que proporciona un almacén de información para los distintos serializadores que se utilizan en la serialización de un gráfico de objetos. Un gráfico de 50 objetos puede tener 50 serializadores diferentes, cada uno de los cuales genera su propio resultado. El administrador de serialización es utilizado por estos serializadores para comunicarse entre sí.

En la ilustración y el procedimiento siguientes se muestra cómo se pueden serializar los objetos de un gráfico (A y B, en este caso).

Gráfico Serializar un objeto

Serializar los objetos de un gráfico

  1. El llamador solicita un serializador para el objeto A al administrador de serialización:

    MySerializer s = manager.GetSerializer(a);
    
  2. El atributo de metadatos en el tipo de A enlaza A a un serializador del tipo solicitado. El llamador pide a continuación al serializador que serialice A:

    Blob b = s.Serialize(manager, a);
    
  3. El serializador del objeto A serializa A. Para cada objeto que encuentra durante la serialización de A, solicita serializadores adicionales del administrador de serialización:

    MySerializer s2 = manager.GetSerializer(b);
    Blob b2 = s2.Serialize(manager, b);
    
  4. El resultado de la serialización se devuelve al llamador:

    Blob b = ...
    

Reglas generales de serialización

Un componente suele exponer varias propiedades. Por ejemplo, el control de Windows Forms Button tiene propiedades como BackColor, ForeColor y BackgroundImage. Cuando coloque un control Button en un formulario en un diseñador y vea el código generado, descubrirá que sólo un subconjunto de las propiedades se conserva en el código. Normalmente, éstas son las propiedades para las que ha establecido un valor explícitamente.

El objeto CodeDomSerializer asociado al control Button define el comportamiento de la serialización. En la lista siguiente se describen algunas de las reglas utilizadas por CodeDomSerializer para serializar el valor de una propiedad:

  • Si la propiedad tiene DesignerSerializationVisibilityAttribute asociada, el serializador utiliza esta asociación para determinar si se serializa la propiedad (como Visible o Hidden) y cómo serializar (Content).

  • Los valores Visible o Hidden especifican si se serializa la propiedad. El valor Content especifica cómo se serializa la propiedad.

  • Para una propiedad denominada DemoProperty, si el componente implementa un método denominado ShouldSerializeDemoProperty, el entorno del diseñador realiza una llamada en tiempo de ejecución a este método para determinar si se debe serializar. Por ejemplo, para la propiedad BackColor, el método se denomina ShouldSerializeBackColor.

  • Si la propiedad tiene un objeto DefaultValueAttribute especificado, el valor predeterminado se compara con el valor actual en el componente. Se serializa la propiedad sólo si el valor actual es no predeterminado.

  • El diseñador asociado al componente también puede desempeñar un papel en la decisión de serialización al sombrear propiedades o implementar métodos ShouldSerialize directamente.

Nota

El serializador deja la decisión de serialización al objeto PropertyDescriptor asociado a la propiedad y PropertyDescriptor utiliza las reglas enumeradas anteriormente.

Si desea serializar el componente de otro modo, puede escribir sus propia clase de serializador derivada de CodeDomSerializer y asociarla al componente mediante el objeto DesignerSerializerAttribute.

Implementación de serializador inteligente

Uno de los requisitos del diseño del serializador es que, cuando se necesita un nuevo formato de serialización, todos los tipos de datos deben actualizarse con un atributo de metadatos para admitir ese formato. Sin embargo, este requisito se puede cumplir si se utilizan proveedores de serialización acoplados con serializadores que utilicen metadatos de objeto genéricos. En esta sección se describe la forma preferida de diseñar un serializador para un formato determinado, de modo que se minimice la necesidad de un gran número de serializadores personalizados.

En el siguiente esquema se define un formato XML hipotético en el que se conservará un gráfico de objetos.

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

Este formato se serializa con una clase inventada, denominada DemoXmlSerializer.

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

Es importante entender que DemoXmlSerializer es una clase modular que compila una cadena a partir de fragmentos. Por ejemplo, DemoXmlSerializer para el tipo de datos Int32 devolvería la cadena "23" cuando se pasa un entero de valor 23.

Proveedores de serialización

En el ejemplo del esquema anterior queda claro que hay dos tipos fundamentales que se deben controlar:

  • Los objetos que tienen propiedades secundarias.

  • Los objetos que se pueden convertir en texto.

Sería difícil acompañar a todas y cada una de las clases de un serializador personalizado capaz de convertir la clase en texto o etiquetas XML. Los proveedores de serialización solucionan esta limitación al ofrecer un mecanismo de devolución de llamada en el que se da la oportunidad a un objeto de proporcionar un serializador para un tipo determinado. Para este ejemplo, las condiciones siguientes restringen el conjunto de tipos disponibles:

  • Si el tipo se puede convertir en una cadena mediante la interfaz IConvertible, se utilizará StringXmlSerializer.

  • Si el tipo no se puede convertir en una cadena, pero es público y tiene un constructor vacío, se utilizará ObjectXmlSerializer.

  • Si no se cumple ninguna de estas condiciones, el proveedor de serialización devolverá null, lo que indica que no hay ningún serializador para el objeto.

En el siguiente ejemplo de código se muestra cómo resuelve lo que pasa el serializador que realiza la llamada cuando se produce este error.

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;
    }
}

Cuando se define un proveedor de serialización, se debe poner en servicio. Un administrador de serialización puede recibir un proveedor de serialización a través del método AddSerializationProvider; pero para ello, esta llamada debe realizarse al administrador de serialización. Un proveedor de serialización se puede agregar automáticamente a un administrador de serialización si se agrega un atributo DefaultSerializationProviderAttribute al serializador. Este atributo requiere que el proveedor de serialización tenga un constructor público vacío. En el siguiente ejemplo de código se muestra el cambio necesario para DemoXmlSerializer.

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

Ahora, siempre que se pida un administrador de serialización para cualquier tipo de DemoXmlSerializer, se agregará el proveedor de serialización predeterminado al administrador de serialización si aún no se ha hecho.

Serializadores

La clase DemoXmlSerializer de muestra tiene dos clases de serializador concretas denominadas StringXmlSerializer y ObjectXmlSerializer. En el siguiente ejemplo de código se muestra la implementación de 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);
    }
}

La implementación de ObjectXmlSerializer es más complicada, porque es necesario enumerar las propiedades públicas del objeto. En el siguiente ejemplo de código se muestra la implementación de 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 invoca a otros serializadores para cada valor de propiedad. Esto tiene dos ventajas: Primero, permite que ObjectXmlSerializer sea muy simple. Segundo, proporciona un punto de extensibilidad para tipos de terceros. Si ObjectXmlSerializer se encuentra ante un tipo que ninguno de estos serializadores puede escribir, existe la posibilidad de proporcionar un serializador personalizado para ese tipo.

Uso

Para utilizar estos nuevos serializadores, debe crearse una instancia de IDesignerSerializationManager. Desde esta instancia, solicite un serializador y, a continuación, pida al serializador que serialice sus objetos. En el siguiente ejemplo de código se utilizará Rectangle para la serialización de un objeto, ya que este tipo tiene un constructor vacío y cuatro propiedades compatibles con IConvertible. En lugar de implementar IDesignerSerializationManager, puede utilizar la implementación proporcionada por 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);

Esto crearía el XML siguiente.

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

Nota

No se aplica sangría a XML. Esto se podría conseguir con facilidad a través de la propiedad Context en IDesignerSerializationManager. Cada nivel de serializador podría agregar un objeto a la pila de contexto que contiene el nivel de sangría actual, y cada serializador podría buscar ese objeto en la pila y utilizarlo para proporcionar una sangría.

Vea también

Referencia

IDesignerSerializationManager

DesignerSerializationManager

DefaultSerializationProviderAttribute

IConvertible

CodeDomSerializerBase

Otros recursos

Ampliar compatibilidad en tiempo de diseño