adszone.org
venerdì 20 settembre 2024
"Il modo corretto di pensare il software"
home
consulenza  vb.net  contatti 
  Visual Basic .NET
  Serializzazione di oggetti
Domande

Serializzazione di oggetti

Per serializzazione di un oggetto s'intende: fare un'istantanea del suo stato corrente e trasformarla in una forma adatta ad essere trasferita e riprodotta. Se avete in mente come funziona il teletrasporto di Star Trek l'idea dovrebbe essere chiara. Serializzando, produciamo un flusso di byte che rappresenta l'oggetto in un preciso istante. Tale flusso si adatta a trasportare l'oggetto e memorizzarlo. La deserializzazione riproduce l'oggetto completando la clonazione. Questo puo' essere utile per memorizzare un oggetto su disco ed in seguito, ricaricarlo con lo stesso stato in cui lo avevamo lasciato. Oppure possiamo trasferirlo, via rete, da un computer ad un altro. Notate, parliamo del trasferimento un'istanza dell'oggetto.

Il Framework .Net implementa due tipi di serializzazione. Quella binaria puo' coinvolgere l'intera struttura interna dell'oggetto, quella XML si limita a trasferire campi e proprieta' pubbliche. In un certo senso, la forma binaria serializza l'oggetto come e' al suo interno, quella XML lo serializza per come esso viene visto all'esterno, ignorando cioe', il suo tipo di struttura. La forma binaria trasferisce lo stato dell'oggetto, cioe' tutti i valori delle variabili che ne costituiscono la struttura, quella XML trasferisce soltanto i valori delle sue proprieta', nel modo in cui la classe le rende pubbliche. Qual'e' la differenza? Es. La proprieta' pubblica Perimetro di un oggetto Rettangolo potrebbe essere implementata dalla formula (m_base + m_altezza) * 2. La serializzazione XML trasferirebbe soltanto il valore del Perimetro, mentre la binaria trasferirebbe m_base, m_altezza.

In questo contesto parleremo soltanto del tipo binario di serializzazione. Non tutti gli oggetti sono serializzabili. Lo sono solamente quelli la cui classe e' definita con l'attributo <Serializable()>. La seguente classe e' serializzabile:

<Serializable()>  _
Public Class MagicClass

    Private m_Numero1 As Integer
    Private m_Numero2 As Integer
    Private m_Rnd As Random

    Sub New()
        m_Rnd = New Random()
        m_Numero1 = m_Rnd.Next(1, 5)
        m_Numero2 = m_Rnd.Next(-5, -1)
    End Sub

    Public ReadOnly Property NumeroMagico() As Integer
        Get
            Return m_Numero1 + m_Numero2
        End Get
    End Property

    Public ReadOnly Property Numero1() As Integer
        Get
            Return m_Numero1
        End Get
    End Property

    Public ReadOnly Property Numero2() As Integer
        Get
            Return m_Numero2
        End Get
    End Property

    Public Sub SorteggiaNumero()
        m_Numero1 = m_Rnd.Next(1, 5)
        m_Numero2 = m_Rnd.Next(-5, -1)
    End Sub

End Class

La classe che abbiamo appena definito contiene, come campi, due variabili intere ed un generatore di numeri casuali. La proprieta' NumeroMagico ci fornisce un numero casuale generato dalla somma dei valori delle due variabili intere. Questi valori vengono generati casualmente dal metodo SorteggiaNumero. Due proprieta' a sola lettura, Numero1 e Numero2, ci indicano, separatamente, quali sono questi valori.

Testiamo la classe in un Form di Visual Basic, le seguenti definizioni includeranno i giusti spazi dei nomi da dove prenderemo le classi specifiche per ottenere la serializzazione:

Imports System.IO
Imports System.Runtime.Serialization
Imports _
  System.Runtime.Serialization.Formatters.Binary.BinaryFormatter

Il Form conterra':

Controllo Text Name
Button Sorteggia btnSorteggio
Button Serializza btnSerializza
Button Deserializza btnDeserializza
Label Numero 1: lblN1
Label Numero 2: lblN2
Label Numero magico: lblNumeroMagico

Segue il codice da inserire nel form. magic sara' un riferimento all'istanza dell'oggetto di tipo MagicClass. Il Form_Load crea l'oggetto. Il metodo Display copia nelle Label i valori delle proprieta' dell'oggetto. btnSorteggio_Click gestisce il click del bottone Sorteggia: ogni volta facciamo generare all'oggetto una nuova coppia di numeri casuali.

    Dim magic As MagicClass

    Private Sub Form1_Load(ByVal sender As Object, _
            ByVal e As System.EventArgs) Handles MyBase.Load
        magic = New MagicClass()
        Display()
    End Sub

    Private Sub Display()
        lblN1.Text = "Numero 1: " & magic.Numero1
        lblN2.Text = "Numero 2: " & magic.Numero2
        lblNumeroMagico.Text = "Numero magico: " & magic.NumeroMagico
    End Sub

    Private Sub btnSorteggio_Click(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles btnSorteggio.Click
        magic.SorteggiaNumero()
        Display()
    End Sub

Ora possiamo serializzare. btnSerializza_Click si occupa di serializare l'oggetto magic in forma binaria e lo memorizza su file. Tutto ruota intorno al metodo Serialize della classe BinaryFormatter. Ci basta creare il serializzatore BinaryFormatter a cui ci riferiamo con l'interfaccia IFormatter (serializzatore) ed un FileStream (fs) indicante il file dove scrivere. Passiamo lo stream ed il nostro oggetto al metodo serializzatore ed il gioco e' fatto.

    Private Sub btnSerializza_Click(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles btnSerializza.Click
        Dim serializzatore As IFormatter = New System.Runtime. _
            Serialization.Formatters.Binary.BinaryFormatter()
        Dim fs As Stream
        Try
            fs = New FileStream("magic.bin", FileMode.Create)
        Catch ex As Exception
            MessageBox.Show(ex.Message)
            Return
        End Try

        serializzatore.Serialize(fs, magic)
        fs.Close()
    End Sub

La deserializzazione e' il processo inverso. Tutto ruota intorno al metodo Deserialize della classe BinaryFormatter. Al metodo, passiamo uno stream che indica il file dove trovare il clone dell'oggetto che vogliamo ricreare. Quindi visualizziamo il contenuto dell'oggetto clonato.

    Private Sub btnDeserializza_Click(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles btnDeserializza.Click
        Dim deserializzatore As IFormatter = New System.Runtime. _
              Serialization.Formatters.Binary.BinaryFormatter()
        Dim fs As Stream
        Try
            fs = New FileStream("magic.bin", FileMode.Open)
        Catch ex As Exception
            MessageBox.Show(ex.Message)
            Return
        End Try

        magic = deserializzatore.Deserialize(fs) 
        fs.Close()
        Display()
    End Sub

Provate a sorteggiare varie volte e salvate un sorteggio con il pulsante Serializza, quindi eseguite altri sorteggi. Successivamente, provate a caricare l'oggetto salvato. Dovreste vedere che il metodo Display, visualizza lo stato dell'oggetto al momento in cui era stato salvato. Con questo schema, si possono salvare anche oggetti piu' complessi che, a loro volta, contengono altri oggetti. Si puo' implementare il RollBack di una transazione o il classico Undo e magari trasferire l'oggetto over the network.

Personalizzare la serializzazione

Puo' capitare che non sia necessario serializzare l'intera struttura dell'oggetto. La classe MagicClass, ad esempio, contiene il campo m_Rnd che potrebbe benissimo essere ricostruito dopo la serializzazione, cioe': m_Rnd = New Random(). E', quindi, superfluo trasferirlo.

Se vogliamo escludere dalla serializzazione un campo della classe lo dobbiamo marcare con l'attributo <NonSerialized()>. Quello che faremo e' modificare la classe in questo modo:

<NonSerialized()> Private m_Rnd As Random

Ora pero' sorge un problema. Poiche' m_Rnd non e' stato serializzato, quando andiamo a deserializzare l'oggetto, al campo m_Rnd del clone, non sara' associato alcun oggetto (Nothing). In termini operativi, quando invocheremo il metodo SorteggiaNumero otterremo un errore. Cosi', se escludiamo dei campi dalla serializzazione, dobbiamo proggettare la classe in modo da controllare e rigenerare tutti i campi omessi, nel nostro caso ci salveranno righe del genere:

If IsNothing(m_Rnd) Then m_Rnd = New Random()

Qualcuno potrebbe voler evitare il rompicapo di inserire tutta questa serie di righe e serializzare comunque tutto l'oggetto, ma chi ce lo fa fare? Ho notato che omettendo m_Rnd la dimensione del file creato dalla serializzazione passa da circa 500 byte a poco meno di 200. Vi sembra poco? E se avessimo molti oggetti la cui serializzazione e' superflua? E se dovessimo serializzare l'oggetto attraverso Internet? Quanta banda risparmieremmo se omettessimo tutta quella zavorra?

Detto cio', siamo tutti unanimamente convinti che personalizzare la serializzazione e' una tecnica da prendere in considerazione. C'e' di piu'. Abbiamo visto che l'attributo <Serializable()> all'inizio della definizione della classe, automatizza il processo di serializzazione. Per controllare la serializzazione, invece, possiamo decidere di stabilire noi cosa trasferire o meno. Per poter personalizzare la serializzazione di una classe dobbiamo fare in modo che implementi l'interfaccia ISerializable

Questa interfaccia ci obbliga ad implementare due metodi:

GetObjectData(ByVal Info As SerializationInfo, ByVal Context As StreamingContext)

E' il metodo che viene invocato dalla classe serializzatrice per ottenere i campi che vogliamo trasferire. Tramite il metodo AddValue del parametro Info, forniamo il valore del campo da serializzare, associandogli una chiave con la quale identificarlo.

New(ByVal Info As SerializationInfo, ByVal Context As StreamingContext)

E' il costruttore che verra' invocato dal deserializzatore per clonare l'oggetto. Tramite i vari metodi GetXXX, possiamo recuperare i valori precedentemente memorizzati con GetObjectData. I metodi hanno nomi che specificano il Tipo di dato da reperire, GetString, GetInt16, etc. Il loro parametro indica la chiave sotto la quale il valore era stato memorizzato.

Segue il codice della classe MagicClass, modificata per personalizzare la serializzazione in base a quello che abbiamo detto ora. In rosso sono evidenziate le modifiche. Notate che abbiamo omesso la serializzazione di m_Rnd in GetObjectData e che poi lo abbiamo rigenerato in New. Per motivi di sicurezza il costruttore New non deve essere necessariamente Public, noi lo abbiamo dichiarato privato.

L'impiego di ISerializable con i due metodi presentati individua logicamente due sezioni di codice dove gestire esaurientemente i compiti della serializzazione e deserializzazione. Pertanto si rende superfluo l'uso, sparso nel codice, di toppe come l'If IsNothing ... che abbiamo visto prima. Da qui segue il nostro inno alla Pigrizia:

La pigrizia non e' mancanza di voglia di fare le cose, ma la voglia di non eseguire compiti che non siano preventivamente pianificati e resi, quindi, poco comprensibili, percio' faticosamente accessibili.

<Serializable()> _
Public Class MagicClass2
    Implements ISerializable 

    Private m_Numero1 As Integer
    Private m_Numero2 As Integer
    Private m_Rnd As Random

    Private Sub New(ByVal Info As SerializationInfo, _
             ByVal Context As StreamingContext)
        m_Numero1 = Info.GetInt16("n1")
        m_Numero2 = Info.GetInt16("n2")
        m_Rnd = New Random()
    End Sub 

    Public Overloads Sub GetObjectData(ByVal Info _
            As SerializationInfo, ByVal Context As StreamingContext) _
            Implements ISerializable.GetObjectData
        Info.AddValue("n1", m_Numero1)
        Info.AddValue("n2", m_Numero2)
        ' Non serializziamo m_Rnd
    End Sub 

    Sub New()
        m_Rnd = New Random()
        m_Numero1 = m_Rnd.Next(1, 5)
        m_Numero2 = m_Rnd.Next(-5, -1)
    End Sub

    Public ReadOnly Property NumeroMagico() As Integer
        Get
            Return m_Numero1 + m_Numero2
        End Get
    End Property

    Public ReadOnly Property Numero1() As Integer
        Get
            Return m_Numero1
        End Get
    End Property

    Public ReadOnly Property Numero2() As Integer
        Get
            Return m_Numero2
        End Get
    End Property

    Public Sub SorteggiaNumero()
        m_Numero1 = m_Rnd.Next(1, 5)
        m_Numero2 = m_Rnd.Next(-5, -1)
    End Sub

End Class
Ultimo aggiornamento 13/01/2004