Condividi tramite


Cenni preliminari sulla serializzazione della finestra di progettazione

Aggiornamento: novembre 2007

La serializzazione della finestra di progettazione consente di rendere persistente lo stato dei componenti in fase di progettazione o di esecuzione.

Serializzazione per oggetti

In .NET Framework sono supportati diversi tipi di serializzazione, ad esempio la generazione del codice, la serializzazione SOAP, la serializzazione binaria e la serializzazione XML.

La serializzazione della finestra di progettazione è una speciale forma di serializzazione che interessa il tipo di persistenza dell'oggetto solitamente associato agli strumenti di sviluppo. La serializzazione della finestra di progettazione è il processo di conversione di un oggetto grafico in un file di origine che è possibile utilizzare in un secondo momento per recuperare l'oggetto grafico. Un file di origine può contenere codice o persino informazioni su tabelle SQL. La serializzazione della finestra di progettazione funziona per tutti gli oggetti Common Language Runtime.

La serializzazione della finestra di progettazione si differenzia dalla tipica serializzazione di oggetti per diversi aspetti:

  • L'oggetto che esegue la serializzazione è separato dall'oggetto di runtime e pertanto è possibile rimuovere la logica di progettazione da un componente.

  • Lo schema di serializzazione è stato ideato presupponendo che l'oggetto verrà creato in uno stato di inizializzazione completo e successivamente modificato attraverso chiamate a proprietà e metodi durante la deserializzazione.

  • Le proprietà di un oggetto che dispongono di valori che non sono mai stati impostati sull'oggetto non vengono serializzate. Di contro, è possibile che il flusso di deserializzazione non inizializzi tutti i valori di proprietà. Per una descrizione più dettagliata delle regole di serializzazione, vedere la sezione "Regole generali di serializzazione" di seguito in questo argomento.

  • Viene posto l'accento sulla qualità del contenuto nel flusso di serializzazione piuttosto che sulla serializzazione completa di un oggetto. Se non è stata definita una procedura per la serializzazione di un oggetto, l'oggetto verrà ignorato invece di generare un'eccezione. La serializzazione della finestra di progettazione consente di serializzare un oggetto in un formato semplice e di facile lettura piuttosto che come un blob opaco.

  • Il flusso di serializzazione può contenere un numero di dati maggiore di quello necessario per la deserializzazione. La serializzazione del codice sorgente, ad esempio, presenta il codice utente misto al codice necessario a deserializzare un oggetto grafico. Il codice utente deve essere mantenuto durante la serializzazione e ignorato durante la deserializzazione.

Nota:

La serializzazione della finestra di progettazione può essere utilizzata sia in fase di esecuzione che di progettazione.

Nella tabella riportata di seguito sono visualizzati gli obiettivi di progettazione raggiunti grazie all'infrastruttura di serializzazione della finestra di progettazione di .NET Framework.

Obiettivo di progettazione

Descrizione

Modulare

Il processo di serializzazione può essere esteso per coprire nuovi tipi di dati che a loro volta possono fornire descrizioni utili e di facile lettura di se stessi.

Facilmente estendibile

Il processo di serializzazione può essere esteso per coprire facilmente nuovi tipi di dati.

Indipendente dal formato

Gli oggetti possono utilizzare molti formati file diversi e la serializzazione della finestra di progettazione non è associata a un tipo di dati particolare.

Architettura

L'architettura di serializzazione della finestra di progettazione si basa su metadati, serializzatori e gestore di serializzazione. Nella tabella riportata di seguito viene descritto il ruolo di ciascun aspetto dell'architettura.

Aspetto

Descrizione

Attributi dei metadati

Un attributo consente di correlare un tipo T con alcuni serializzatori S. L'architettura, inoltre, supporta un attributo di avvio che può essere utilizzato per installare un oggetto in grado di fornire serializzatori per i tipi che non ne dispongono.

Serializzatori

Un serializzatore è un oggetto in grado di serializzare un determinato tipo o un intervallo di tipi. Per ogni formato di dati è disponibile una classe base. Può esistere, ad esempio, una classe base DemoXmlSerializer in grado di convertire un oggetto in XML. L'architettura è indipendente da qualsiasi formato specifico di serializzazione e include un'implementazione dell'architettura generata su CodeDOM (Code Document Object Model).

Gestore di serializzazione

Un gestore di serializzazione è un oggetto che fornisce un archivio di informazioni per tutti i vari serializzatori utilizzati per serializzare un oggetto grafico. Per un grafico di 50 oggetti possono esistere 50 diversi serializzatori che generano ognuno il rispettivo output. Il gestore di serializzazione viene utilizzato dai serializzatori per comunicare tra loro.

Nell'illustrazione e nella procedura riportate di seguito viene descritto come serializzare gli oggetti in un grafico, in questo caso A e B.

Serializzazione di un oggetto grafico

Serializzazione di oggetti in un grafico

  1. Il chiamante richiede un serializzatore per l'oggetto A dal gestore di serializzazione:

    MySerializer s = manager.GetSerializer(a);
    
  2. L'attributo dei metadati sul tipo di A associa A a un serializzatore del tipo richiesto. Il chiamante chiede quindi al serializzatore di serializzare A:

    Blob b = s.Serialize(manager, a);
    
  3. Il serializzatore dell'oggetto A serializza A. Per ciascun oggetto rilevato durante la serializzazione di A, richiede ulteriori serializzatori al gestore di serializzazione:

    MySerializer s2 = manager.GetSerializer(b);
    Blob b2 = s2.Serialize(manager, b);
    
  4. Il risultato della serializzazione viene restituito al chiamante:

    Blob b = ...
    

Regole generali di serializzazione

Di norma, un componente espone diverse proprietà. Il controllo Button di Windows Form, ad esempio, dispone di proprietà quali BackColor, ForeColor e BackgroundImage. Quando si posiziona un controllo Button su un form in una finestra di progettazione e si visualizza il codice generato, si noterà che solo un sottoinsieme delle proprietà viene mantenuto nel codice. Solitamente, si tratta delle proprietà per le quali si imposta esplicitamente un valore.

La classe CodeDomSerializer associata al controllo Button definisce il comportamento di serializzazione. Nell'elenco riportato di seguito sono descritte alcune regole utilizzate da CodeDomSerializer per serializzare il valore di una proprietà:

  • Se alla proprietà è associato un oggetto DesignerSerializationVisibilityAttribute, il serializzatore lo utilizza per determinare se la proprietà è serializzata (come Visible o Hidden), nonché la procedura di serializzazione (Content).

  • Il valore Visible o Hidden specifica se la proprietà è serializzata. Il valore Content specifica la procedura utilizzata per la serializzazione della proprietà.

  • Per una proprietà denominata DemoProperty, se il componente implementa un metodo denominato ShouldSerializeDemoProperty, l'ambiente di progettazione effettuerà una chiamata ad associazione tardiva a questo metodo per determinare se effettuare la serializzazione. Per la proprietà BackColor, ad esempio, il metodo viene denominato ShouldSerializeBackColor.

  • Se per la proprietà è specificato un oggetto DefaultValueAttribute, il valore predefinito viene confrontato con il valore corrente del componente. La proprietà viene serializzata solo se il valore corrente non è predefinito.

  • La finestra di progettazione associata al componente può inoltre essere determinante nella scelta di eseguire la serializzazione mediante lo shadowing delle proprietà o l'implementazione dei metodi ShouldSerialize.

Nota:

Il serializzatore delega la decisione relativa alla modalità di serializzazione all'oggetto PropertyDescriptor associato alla proprietà e PropertyDescriptor utilizza le regole elencate in precedenza.

Se si desidera serializzare il componente in un modo diverso, è possibile scrivere la propria classe di serializzazione che deriva da CodeDomSerializer e associarla al proprio componente utilizzando l'oggetto DesignerSerializerAttribute.

Implementazione intelligente del serializzatore

Uno dei requisiti della progettazione del serializzatore è che quando è necessario un nuovo formato di serializzazione, tutti i tipi di dati devono essere aggiornati con un attributo di metadati che supporti tale formato. È tuttavia possibile soddisfare tale requisito mediante l'utilizzo di provider di serializzazione insieme ai serializzatori che utilizzano metadati di oggetti generici. In questa sezione viene descritto il sistema preferito per la progettazione di un serializzatore per un determinato formato, in modo da ridurre al minimo l'esigenza di molti serializzatori personalizzati.

Nello schema seguente viene definito un formato XML ipotetico per il quale verrà mantenuto un oggetto grafico.

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

Il formato viene serializzato utilizzando una classe inventata denominata DemoXmlSerializer.

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

È importante comprendere che DemoXmlSerializer è una classe modulare che genera una stringa da parti. L'oggetto DemoXmlSerializer relativo al tipo di dati Int32, ad esempio, restituirebbe la stringa "23" al passaggio di un valore integer 23.

Provider di serializzazione

Nell'esempio riportato nello schema precedente viene chiarito che esistono due tipi fondamentali da gestire:

  • Oggetti con proprietà figlio.

  • Oggetti che è possibile convertire in testo.

Sarebbe difficile dotare ogni classe di un serializzatore personalizzato in grado di convertire la classe in testo o in tag XML. I provider di serializzazione risolvono il problema mediante un meccanismo di callback in base al quale a un oggetto viene data l'opportunità di fornire un serializzatore per un determinato tipo. Per questo esempio l'insieme disponibile di tipi è limitato dalle seguenti condizioni:

  • Se è possibile convertire il tipo in una stringa utilizzando l'interfaccia IConvertible, verrà utilizzato l'oggetto StringXmlSerializer.

  • Se non è possibile convertire il tipo in una stringa ma è il tipo è pubblico e include un costruttore vuoto, verrà utilizzato l'oggetto ObjectXmlSerializer.

  • Se nessuna di queste condizioni è soddisfatta, il provider di serializzazione restituirà null, a indicare che non vi sono serializzatori per l'oggetto.

Nell'esempio di codice riportato di seguito viene visualizzata la procedura adottata dal serializzatore chiamante al verificarsi dell'errore.

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

Dopo aver definito un provider di serializzazione, è necessario renderlo disponibile per l'utilizzo. È possibile fornire a un gestore di serializzazione un provider di serializzazione attraverso il metodo AddSerializationProvider, ma è necessario che la chiamata venga effettuata al gestore di serializzazione. Un provider di serializzazione può essere aggiunto automaticamente a un gestore di serializzazione aggiungendo un oggetto DefaultSerializationProviderAttribute al serializzatore. L'attributo richiede che il provider di serializzazione disponga di un contruttore pubblico vuoto. Nell'esempio di codice riportato di seguito viene visualizzata la modifica che è necessario apportare a DemoXmlSerializer.

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

A questo punto, se a un gestore di serializzazione viene richiesto un qualsiasi tipo di oggetto DemoXmlSerializer, il provider di serializzazione predefinito verrà aggiunto al gestore di serializzazione, se questa operazione non è stata già eseguita.

Serializzatori

La classe DemoXmlSerializer di esempio presenta due classi di serializzatori concrete denominate StringXmlSerializer e ObjectXmlSerializer. Nell'esempio di codice riportato di seguito viene visualizzata l'implementazione di 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'implementazione di ObjectXmlSerializer è maggiormente interessata dal processo, in quanto è necessario enumerare le proprietà pubbliche dell'oggetto. Nell'esempio di codice riportato di seguito viene visualizzata l'implementazione di 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 chiama altri serializzatori per ciascun valore di proprietà. Questo tipo di esecuzione presenta due vantaggi. In primo luogo, garantisce che ObjectXmlSerializer sia molto semplice. In secondo luogo, fornisce estendibilità per tipi di terze parti. Se ObjectXmlSerializer viene presentato con un tipo che non può essere scritto da uno dei due serializzatori, per tale tipo è possibile fornire un serializzatore personalizzato.

Utilizzo

Per utilizzare questi nuovi serializzatori, è necessario creare un'istanza di IDesignerSerializationManager. Da questa istanza viene richiesto un serializzatore a cui indicare di serializzare gli oggetti. Per l'esempio di codice riportato di seguito, verrà utilizzata la classe Rectangle per la serializzazione di un oggetto, in quanto questo tipo dispone di un costruttore vuoto e di quattro proprietà che supportano l'oggetto IConvertible. Invece di implementare personalmente IDesignerSerializationManager, è possibile utilizzare l'implementazione fornita da 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);

Il codice sopra riportato consente di creare il seguente XML.

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

L'XML non è rientrato. Il problema può essere risolto facilmente mediante la proprietà Context sulla classe IDesignerSerializationManager. Ciascun livello di serializzatore potrebbe aggiungere un oggetto allo stack di contesti che contiene il livello di rientro corrente e ciascun serializzatore potrebbe cercare l'oggetto nello stack e utilizzarlo per fornire un rientro.

Vedere anche

Riferimenti

IDesignerSerializationManager

DesignerSerializationManager

DefaultSerializationProviderAttribute

IConvertible

CodeDomSerializerBase

Altre risorse

Estensione del supporto in fase di progettazione