Vue d'ensemble de la sérialisation du concepteur

Avec la sérialisation du concepteur, vous pouvez rendre persistant l'état de vos composants au moment du design ou de l'exécution.

Sérialisation pour les objets

Le .NET Framework prend en charge plusieurs types de sérialisations, tels que la génération de code, la sérialisation SOAP, la sérialisation binaire et la sérialisation XML.

La sérialisation du concepteur est une forme particulière de sérialisation qui implique le type de persistance d'objets généralement associée aux outils de développement. La sérialisation du concepteur est le processus de conversion d'un graphique d'objets en un fichier source qui peut être utilisé ultérieurement pour récupérer le graphique d'objets. Un fichier source peut contenir du code, un balisage ou même des informations sur les tables SQL. La sérialisation du concepteur fonctionne pour tous les objets Common Language Runtime.

La sérialisation du concepteur diffère de la sérialisation d'objets classique à plusieurs égards :

  • L'objet qui exécute la sérialisation est distinct de l'objet d'exécution, de sorte que la logique au moment du design peut être supprimée d'un composant.

  • Le schéma de sérialisation a été imaginé en partant du principe que l'objet est créé dans un état complètement initialisé, puis modifié via les appels de propriété et de méthode au cours de la désérialisation.

  • Les propriétés d'un objet dont les valeurs n'ont jamais été définies sur l'objet ne sont pas sérialisées. Inversement, il se peut que le flux de désérialisation ne puisse pas initialiser toutes les valeurs de propriété. Pour une description plus détaillée des règles de sérialisation, consultez la section « Règles générales de sérialisation » de cette rubrique.

  • L'accent est mis sur la qualité du contenu au sein du flux de sérialisation plutôt que sur la sérialisation complète d'un objet. S'il n'existe aucune façon définie de sérialiser un objet, cet objet ne sera pas pris en compte, plutôt que de lever une exception. La sérialisation du concepteur fournit des méthodes pour sérialiser un objet sous une forme simple et explicite plutôt que comme un blob opaque.

  • Le flux de sérialisation peut disposer de plus de données que nécessaire pour la désérialisation. Par exemple, la sérialisation de code source dispose de code utilisateur mélangé au code nécessaire pour désérialiser un graphique d'objets. Ce code utilisateur doit être conservé pour la sérialisation et non pris en compte pour la désérialisation.

Notes

La sérialisation du concepteur peut être utilisée aussi bien au moment de l'exécution qu'au moment du design.

Le tableau suivant affiche les objectifs de design atteints avec l'infrastructure de sérialisation du concepteur .NET Framework.

Objectif de design

Description

Modulaire

Le processus de sérialisation peut être étendu pour couvrir de nouveaux types de données et ces types de données peuvent fournir des descriptions utiles et explicites d'eux-mêmes.

Facilement extensible

Le processus de sérialisation peut être facilement étendu pour couvrir de nouveaux types de données.

Indépendant du format

Les objets peuvent participer à de nombreux formats de fichier différents et la sérialisation du concepteur n'est pas liée à un format de données particulier.

Architecture

L'architecture de la sérialisation du concepteur est basée sur les métadonnées, les sérialiseurs et un gestionnaire de sérialisation. Le tableau suivant décrit le rôle de chaque aspect de l'architecture.

Aspect

Description

Attributs de métadonnées

Un attribut est utilisé pour mettre en relation un type T avec certains sérialiseurs S. De plus, l'architecture prend en charge un attribut « bootstrapping » qui peut être utilisé pour installer un objet qui peut fournir des sérialiseurs pour les types qui n'en ont pas.

Sérialiseurs

Un sérialiseur est un objet qui peut sérialiser un type particulier ou une plage de types. Il existe une classe de base pour chaque format de données. Par exemple, il peut exister une classe de base DemoXmlSerializer qui convertit un objet en XML. L'architecture est indépendante de tout format de sérialisation spécifique et inclut également une implémentation de cette architecture générée sur le modèle CodeDOM (Code Document Object Model).

Gestionnaire de sérialisation

Un gestionnaire de sérialisation est un objet qui fournit une banque d'informations pour les divers sérialiseurs utilisés afin de sérialiser un graphique d'objets. Un graphique de 50 objets peut disposer de 50 sérialiseurs différents qui génèrent tous leur propre sortie. Le gestionnaire de sérialisation est utilisé par ces sérialiseurs pour communiquer les uns avec les autres.

L'illustration et la procédure suivantes montrent comment les objets d'un graphique, dans le cas présent A et B, peuvent être sérialisés.

Graphique Sérialisation d'un objet

Sérialisation d'objets dans un graphique

  1. L'appelant demande au gestionnaire de sérialisation un sérialiseur pour l'objet A :

    MySerializer s = manager.GetSerializer(a);
    
  2. L'attribut de métadonnées sur le type de l'objet A lie A à un sérialiseur du type demandé. L'appelant demande ensuite au sérialiseur de sérialiser A :

    Blob b = s.Serialize(manager, a);
    
  3. Le sérialiseur de l'objet A sérialise A. Pour chaque objet qu'il rencontre lors de la sérialisation de A, il demande des sérialiseurs supplémentaires au gestionnaire de sérialisation :

    MySerializer s2 = manager.GetSerializer(b);
    Blob b2 = s2.Serialize(manager, b);
    
  4. Le résultat de la sérialisation est retourné à l'appelant :

    Blob b = ...
    

Règles générales de sérialisation

Un composant expose généralement plusieurs propriétés. Par exemple, le contrôle Button Windows Forms possède des propriétés comme BackColor, ForeColor et BackgroundImage. Lorsque vous placez un contrôle Button sur un formulaire dans un concepteur et que vous affichez le code généré, vous découvrez qu'un seul sous-ensemble de propriétés est persistant dans le code. En général, il s'agit des propriétés pour lesquelles vous avez explicitement défini une valeur.

Le CodeDomSerializer associé au contrôle Button définit le comportement de sérialisation. La liste suivante décrit certaines règles utilisées par le CodeDomSerializer pour sérialiser la valeur d'une propriété :

  • Si un DesignerSerializationVisibilityAttribute est attaché à la propriété, le sérialiseur l'utilise pour déterminer si la propriété est sérialisée (comme Visible ou Hidden), et comment la sérialiser (Content).

  • Les valeurs Visible ou Hidden spécifient si la propriété est sérialisée. La valeur Content spécifie comment la propriété est sérialisée.

  • Pour une propriété appelée DemoProperty, si le composant implémente une méthode appelée ShouldSerializeDemoProperty, l'environnement concepteur effectue un appel à liaison tardive à cette méthode pour déterminer s'il faut la sérialiser. Par exemple, pour la propriété BackColor, la méthode est appelée ShouldSerializeBackColor.

  • Si la propriété possède un DefaultValueAttribute spécifié, la valeur par défaut est comparée à la valeur en cours sur le composant. La propriété est sérialisée uniquement si la valeur en cours n'est pas définie par défaut.

  • Le concepteur associé au composant peut également jouer un rôle dans la prise de décision de sérialisation en occultant des propriétés ou en implémentant des méthodes ShouldSerialize lui-même.

Notes

Le sérialiseur défère la décision de sérialisation au PropertyDescriptor associé à la propriété et PropertyDescriptor utilise les règles répertoriées précédemment.

Si vous souhaitez sérialiser votre composant d'une autre façon, vous pouvez écrire votre propre classe de sérialiseur dérivant de CodeDomSerializer et l'associer à votre composant à l'aide de DesignerSerializerAttribute.

Implémentation de sérialiseur intelligente

L'une des exigences du design de sérialiseur est que, lorsqu'un nouveau format de sérialisation est nécessaire, tous les types de données doivent être mis à jour avec un attribut de métadonnées afin de prendre en charge ce format. Toutefois, l'utilisation de fournisseurs de sérialisation associés à des sérialiseurs qui utilisent des métadonnées d'objet génériques satisfait cette exigence. Cette section décrit la meilleure façon de concevoir un sérialiseur pour un format particulier afin que la nécessité de nombreux sérialiseurs personnalisés soit réduite.

Le schéma suivant définit un format XML hypothétique pour lequel un graphique d'objets sera persistant.

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

Ce format est sérialisé à l'aide d'une classe inventée appelée DemoXmlSerializer.

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

Il est important de comprendre que DemoXmlSerializer est une classe modulaire qui génère une chaîne à partir d'éléments. Par exemple, le DemoXmlSerializer pour le type de données Int32 retournerait la chaîne « 23 » lorsqu'un entier avec la valeur 23 serait passé.

Fournisseurs de sérialisation

L'exemple de schéma précédent indique parfaitement qu'il existe deux types fondamentaux à gérer :

  • les objets qui ont des propriétés enfants ;

  • les objets qui peuvent être convertis en texte.

Il serait difficile d'équiper chaque classe d'un sérialiseur personnalisé pouvant convertir cette classe en texte ou en balises XML. Les fournisseurs de sérialisation résolvent ce problème en proposant un mécanisme de rappel dans lequel un objet a la possibilité de fournir un sérialiseur pour un type donné. Pour cet exemple, l'ensemble de types disponible est limité par les conditions suivantes :

  • Si le type peut être converti en chaîne à l'aide de l'interface IConvertible, le StringXmlSerializer sera utilisé.

  • Si le type ne peut pas être converti en chaîne mais qu'il est public et possède un constructeur vide, le ObjectXmlSerializer sera utilisé.

  • Si aucune de ces conditions ne s'applique, le fournisseur de sérialisation retournera null, indiquant qu'il n'existe aucun sérialiseur pour l'objet.

L'exemple de code suivant montre comment le sérialiseur appelant résout ce qui se produit lorsque cette erreur survient.

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

Une fois qu'un fournisseur de sérialisation est défini, il doit être utilisé. Un fournisseur de sérialisation peut être attribué à un gestionnaire de sérialisation par le biais de la méthode AddSerializationProvider, mais il est nécessaire que cet appel soit effectué au gestionnaire de sérialisation. Un fournisseur de sérialisation peut être ajouté automatiquement à un gestionnaire de sérialisation en ajoutant un DefaultSerializationProviderAttribute au sérialiseur. Cet attribut requiert que le fournisseur de sérialisation possède un constructeur public vide. L'exemple de code suivant montre les modifications nécessaires apportées au DemoXmlSerializer.

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

Maintenant, chaque fois qu'un type quelconque de DemoXmlSerializer est demandé à un gestionnaire de sérialisation, le fournisseur de sérialisation par défaut sera ajouté au gestionnaire de sérialisation si ce n'est pas déjà fait.

Sérialiseurs

L'exemple de classe DemoXmlSerializer dispose de deux classes de sérialiseur concrètes nommées StringXmlSerializer et ObjectXmlSerializer. L'exemple de code suivant montre l'implémentation 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);
    }
}

L'implémentation ObjectXmlSerializer est plus complexe car il est nécessaire d'énumérer les propriétés publiques de l'objet. L'exemple de code suivant montre l'implémentation 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 appelle d'autres sérialiseurs pour chaque valeur de propriété. Cela a deux avantages. Tout d'abord, cela permet au ObjectXmlSerializer d'être très simple. Ensuite, cela fournit un point d'extensibilité pour les types tiers. Si ObjectXmlSerializer est présenté avec un type qui ne peut pas être écrit par l'un de ces sérialiseurs, un sérialiseur personnalisé peut être fourni pour ce type.

Utilisation

Pour utiliser ces nouveaux sérialiseurs, une instance de IDesignerSerializationManager doit être créée. Depuis cette instance, demandez un sérialiseur, puis demandez-lui de sérialiser vos objets. Pour l'exemple de code suivant, Rectangle sera utilisé pour un objet à sérialiser car ce type possède un constructeur vide et quatre propriétés qui prennent en charge IConvertible. Au lieu d'implémenter IDesignerSerializationManager vous-même, vous pouvez utiliser l'implémentation fournie par 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);

Cela crée le XML suivant.

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

Notes

Le XML n'est pas mis en retrait.Cette opération peut facilement être réalisée par le biais de la propriété Context sur IDesignerSerializationManager.Chaque niveau de sérialiseur peut ajouter un objet à la pile de contexte qui contient le niveau de retrait actuel et chaque sérialiseur peut rechercher cet objet dans la pile et l'utiliser pour fournir un retrait.

Voir aussi

Référence

IDesignerSerializationManager

DesignerSerializationManager

DefaultSerializationProviderAttribute

IConvertible

CodeDomSerializerBase

Autres ressources

Extension de la prise en charge au moment du design