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
|