Nel post di ieri abbiamo visto come serializzare una
classe, sia in formato XML sia in formato binario. Abbiamo anche detto che uno
degli scopi della serializzazione è quello di rendere trasmissibile un
oggetto da un componente all'altro, dove con componente intendo un
qualsiasi altro assembly .NET, un altro PC della nostra LAN o un server Web
dall'altra parte del pianeta. Si arriva quindi al proposito di voler ricostruire
in memoria un oggetto che era stato serializzato, così da poterlo maneggiare
anche dopo la trasmissione stessa.
Deserializzazione da stream
XML
Questa operazione è nota con il nome di
deserializzazione ed è facilmente intuibile, quindi,
considerarla come l'operazione inversa della serializzazione.
La deserializzazione si attua sempre attraverso la classe XmlSerializer già esaminata ieri, solo che questa volta useremo il
metodo Deserialize invece del
Serialize. Fin qua, nulla di
complicato. Partendo da questi primi concetti
teorici, quindi, possiamo già scrivere un metodo che possa deserializzare la
nostra classe Shelf in questo modo:
static public Shelf Deserialize(string fileName)
{
XmlReader rd = XmlReader.Create(fileName);
XmlSerializer ser = new XmlSerializer(typeof(Shelf));
if (ser.CanDeserialize(rd) == true)
return ((Shelf)ser.Deserialize(rd));
else
return (null);
}
In questo caso abbiamo istanziato un'oggetto XmlReader, che
ci permette di aprire uno stream verso il file XML che vogliamo deserializzare.
Poi, istanziamo un XmlSerializer e controlliamo che lo stream aperto contenga
realmente un qualcosa che possa essere serializzato. Se il metodo
CanDeserialize ritorna true, ritorniamo semplicemente un'istanza di Shelf. Se lo
stream non può essere deserializzato, ritorniamo al chiamante null. Questo approccio può tornare molto utile se implementato
con i generics:
static public T Deserialize<T>(string fileName) where T : new()
{
XmlReader rd = XmlReader.Create(fileName);
XmlSerializer ser = new XmlSerializer(typeof(T));
if (ser.CanDeserialize(rd) == true)
return ((T)ser.Deserialize(rd));
else
return (default(T));
}
Questo metodo statico inserito in una classe XMLSerialization permette di
deserializzare da un qualsiasi file un qualsiasi oggetto, il cui tipo è indicato
da T. Ho
applicato un constraint al tipo generico T forzando il fatto
che deve essere possibile poter creare un'istanza di T
tramite la keywork new(). Ecco un esempio di utilizzo:
Shelf mensola = XMLSerialization.Deserialize<Shelf>("D:\\Shelf.xml");
Chapter cp = XMLSerialization.Deserialize<Chapter>("D:\\Chapter.xml");
Come vedete, usiamo sempre lo stesso metodo dalla stessa classe, il quale
però essendo generico può ritornare un'istanza di Shelf, Book, Chapter ed in
genere (gioco di parole) qualsiasi classe .NET.
Con questi semplici passaggi, quindi, siamo in grado di persistere una classe
su uno stream: in questi esempi abbiamo usato un file, ma in realtà potrebbe
trattarsi di uno stream di ogni tipo, come MemoryStream, una banale StringBuilder. Date un'occhiata ai diversi overload della classe XmlSerializer per farvi
un'idea.
Deserializzazione da stream binario
La stessa
logica con i generics può essere utilizzata per la deserializzazione da uno
stream binario. Ovviamente cambiano alcune classi in gioco, ed dobbiamo
modificare il codice per alcuni accorgimenti per fare in modo che tutto funzioni
regolarmente:
- il file binario viene letto da un oggetto FileStream
- al momento della deserializzazione, il FX utilizza un costruttore privato
della classe. Tale costruttore ha due parametri come il metodo
GetObjectData visto ieri: un SerializationInfo e un StreamingContext. Useremo il primo per valorizzare i
membri della classe che ci servono
Ecco il codice che ho scritto io per deserializzare da uno stream
binario:
static public T DeserializeBinary<T>(string fileName) where T : new()
{
FileStream rd = new FileStream(fileName, FileMode.Open);
BinaryFormatter form = new BinaryFormatter();
T obj = (T) form.Deserialize(rd);
return (obj);
}
Il FileStream accede al file in modalità FileMode.Open, mentre il
BinaryFormatter fa il resto. Nella chiamata al metodo Deserialize, l'oggetto di
tipo T viene creato sfruttando il costruttore privato di cui
parlavo prima. Per semplicità, riporto qui sotto quello implementato per la
classe Chapter che dispone solo della public property
Title.
private Chapter(SerializationInfo info, StreamingContext context)
{
_Title = info.GetString("Title");
}
Se avessi dovuto fare la stessa cosa per la classe Book,
potrei usare lo stesso metodo DeserializeBinary<T> visto
sopra, ma avrei dovuto creare il costruttore privato valorizzando tutte le
property Title, Author, Pages
e così via. Notare che nella deserializzazione binaria non abbiamo a
disposizione il metodo CanDeserialize, che funziona solamente quando si ha a che
fare con XML. Quindi, è opportuno usare un try...catch per intercettare tutte le
Exceptions possibili durante questa fase.