Serialización: XmlSerializer y BinaryFormater

La serialización es un proceso a través del cual almacenamos un objeto existente en la memoria de un ordenador en un medio de almacenamiento - por ejemplo un archivo.

Simplificando mucho - partiendo de un objeto existente en la memoria de un programa -, al serializarlo lo que estamos haciendo es guardar ese objeto en un fichero (u otro medio de almacenamiento). Posteriormente podemos utilizar ese fichero para crear un nuevo objeto que es idéntico en todo al objeto serializado originalmente.

Como veremos en este articulo la afirmación anterior no es del todo cierta, ya que el objeto obtenido como resultado de recuperar el objeto inicial, no es siempre identico en todo al original. Depende de la forma en la que lo serialicemos.

La serializacion XML solo guarda las propiedades públicas del objeto, mientras que la serializacion binaria guarda tanto las propiedades publicas como privadas.

Veamos un ejemplo. Creamos un nuevo proyecto de Consola (en mi caso Visual Studio 2008)

Creamos una nueva clase Foo, esta clase es muy simple, solo tiene tres propiedades:ID, Nombre y Edad.
El matiz está en la propiedad Edad , es privada. La edad se asigna a través del método AsignaEdad y se recupera con DameEdad. Por último sobreescribe el método ToString para que muestre en pantalla los datos.

La clase está marcada con el atributo Serializable.

 

[Serializable]

public class Foo

{

public int ID { get; set; }

public string Nombre { get; set; }

private int Edad { get; set; }

public void AsignaEdad(int edad)

{

Edad = edad;

}

 

public int DameEdad()

{

return Edad;

}

 

public override string ToString()

{

return String.Format("ID:{0}\nNombre:{1}\nEdad:{2}\n",

ID, Nombre, Edad);

}

}

Para que el ejemplo funcione correctamente debemos realizar las siguientes importaciones de espacios de nombres.

 

using System.Xml.Serialization;

using System.Runtime.Serialization;

using System.Runtime.Serialization.Formatters.Binary;

Lo que queremos hacer es sencillo. Creamos una nueva instancia de Foo, y asignamos la edad llamando al método AsignaEdad.

Primeramente serializamos la nuestra instancia en Xml y posteriormente la recuperamos (desserializar), mostramdo el contenido de la clase en ambos casos a través de una llamada al método ToString().

 
static void Main(string[] args) {

Foo f = new Foo();

f.ID = 1;

f.Nombre = "El nombre";

f.AsignaEdad(7);

 

Console.WriteLine("Primero en Xml");

XmlSerializer xml = new XmlSerializer(f.GetType());

using (FileStream fs = new FileStream(@"C:\temp\foo1.xml",

FileMode.Create , FileAccess.ReadWrite, FileShare.None))

{

xml.Serialize(fs, f);

fs.Close();

Console.WriteLine(

"Esto es lo que hemos serializado(xml)");

Console.WriteLine(f.ToString());

}

//Ahora recuperamos

using (FileStream fs = new FileStream (@"C:\temp\foo1.xml",

FileMode.Open , FileAccess.Read , FileShare.None))

{

Console.WriteLine("");

Foo otroFoo = (Foo)xml.Deserialize(fs);

fs.Close();

Console.WriteLine("Esto es lo que recuperamos(xml)");

Console.WriteLine(otroFoo.ToString());

}

 

Console.ReadLine();

}

La salida del programa es la siguiente:


Primero en Xml
Esto es lo que hemos serializado(xml)
Id:1
Nombre:El nombre
Edad:7


Esto es lo que recuperamos(xml)
Id:1
Nombre:El nombre
Edad:0

Podemos ver claramente que la propiedad privada - Edad -, tenía el valor 7 antes de serializarse y que vale 0 despues de desserializar. De hecho, si miramos el archivo foo1.xml tendremos lo siguiente:

 

<?xml version="1.0"?>

<Foo

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:xsd="http://www.w3.org/2001/XMLSchema">

<ID>1</ID>

<Nombre>El nombre</Nombre>

</Foo>

Es lógico, la clase XmlSerializer - definida en System.Xml.Serialization - utiliza internamente reflexión para crear el documento XML de salida, por lo que no puede tener acceso a las propiedades privadas.

Si lo que queremos es una copia exacta del objeto tendremos que utilizar la serialización binaria. Veamos el mismo ejemplo, pero utilizando la serialización binaria.

 
static void Main(string[] args) {

Foo f = new Foo();

f.ID = 1;

f.Nombre = "El nombre";

f.AsignaEdad(7);

 

Console.WriteLine("Ahora lo mismo en binario");

 

IFormatter formatter = new BinaryFormatter();

using (FileStream fs = new FileStream(@"C:\temp\foo1.bin",

FileMode.Create , FileAccess.Write, FileShare.None))

{

formatter.Serialize(fs, f);

fs.Close();

Console.WriteLine(

"Esto es lo que hemos serializado(bin)");

Console.WriteLine(f.ToString() );

}

 

using (FileStream fs = new FileStream(@"C:\temp\foo1.bin",

FileMode.Open, FileAccess.Read , FileShare.None))

{

Console.WriteLine("");

Foo otroFoo = (Foo)formatter.Deserialize(fs);

fs.Close();

Console.WriteLine("Esto es lo que recuperamos(bin)");

Console.WriteLine(otroFoo.ToString());

}

 

Console.ReadLine();

}

El resultado de la ejecucion es :


Ahora en binario
Esto es lo que hemos serializado(bin)
Id:1
Nombre:El nombre
Edad:7


Esto es lo que recuperamos(bin)
Id:1
Nombre:El nombre
Edad:7
 

Podemos ver que en este caso hemos recuperado el objeto exactamente igual que el objeto inicial. Esto es así porque en este caso lo que estamos serializando es directamente el buffer de memoria - es decir, el array de bytes que representa la objeto.

Pero ... ¿que hacemos si necesitamos serializar en XML pero queremos mantener también los miembros privados?. En este caso tenemos que trabajar un poquillo más, y hacer una breve introduccion a Base64. Base64 es un algoritmo que nos permite representa como un tipo string un array de bytes.

Lo primero que necesitamos es crear una nueva clase - FooWrapper -, que dispondrá de una propiedad pública FooString pensada para almacenar la representacion en Base64 de la instancia de Foo.

 

public class FooWrapper

{

public string FooString { get; set; }

}

La idea es serializar de forma binaria el objeto que tenemos inicialmente, pero no a un fichero como en el ejemplo anterior, sino a un MemoryStream. Posteriormente obtendremos el string en Base64 de este MemoryStream y lo asignamos a la propiedad pública FooString del FooWrapper. Finalmente serializamos en XML el FooWrapper.

Vamos a ayudarnos de la clase Convert, que dispone de métodos para convertir y recuperar bufferes a string y viceversa.

Mejor lo vemos con un ejemplo:

 

static void Main(string[] args)

{

Foo f = new Foo();

f.ID = 1;

f.Nombre = "El nombre";

f.AsignaEdad(7);

 

Console.WriteLine("Ahora en XML con un wrapper en Base64");

/*

* Primero realizamos una serializacion binaria a

* un MemoryStream y obtenemos su representacion

* en Base64

*/

string b64String = String.Empty ;

using (MemoryStream ms =new MemoryStream())

{

IFormatter formatter = new BinaryFormatter();

formatter.Serialize(ms, f);

ms.Close();

b64String = Convert.ToBase64String(ms.ToArray());

Console.WriteLine(

"Esto es lo que hemos serializado:\n{0}\n", b64String);

Console.WriteLine(f.ToString());

}

Console.WriteLine("");

 

//Creamos el wrapper y lo serializamos en XML

XmlSerializer xml = new XmlSerializer(typeof(FooWrapper) );

using (FileStream fs = new FileStream(@"C:\temp\fooWrapper.xml",

FileMode.Create , FileAccess.ReadWrite, FileShare.None))

{

FooWrapper fw = new FooWrapper();

fw.FooString = b64String;

xml.Serialize(fs, fw);

fs.Close();

}

 

//Ahora desserailziamos

using (FileStream fs = new FileStream(@"C:\temp\fooWrapper.xml",

FileMode.Open, FileAccess.Read, FileShare.None))

{

FooWrapper FooWrapper =

(FooWrapper)xml.Deserialize(fs);

fs.Close();

// Ahora recuperamos el Foo del wrapper

byte[] fooBytes =

Convert.FromBase64String(otroFooWrapper.FooString);

MemoryStream ms = new MemoryStream(fooBytes);

IFormatter formatter = new BinaryFormatter();

Foo foo = (Foo)formatter.Deserialize(ms) ;

Console.WriteLine("Esto es lo que recuperamos(xml)");

Console.WriteLine(foo.ToString());

}

Console.ReadLine();

}

La salida que obtenemos es la siguiente:

 

Ahora en XML con un wrapper en Base64
Esto es lo que hemos serializado:

AAEAAAD/////AQAAAAAAAAAMAgAAAEpDb25zb2xlQXBwbGljYXRpb2
43LCBWZXJzaW9uPTEuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUH
VibGljS2V5VG9rZW49bnVsbAUBAAAAF0NvbnNvbGVBcHBsaWNhdGlvb
jcuRm9vAwAAABM8SUQ+a19fQmFja2luZ0ZpZWxkFzxOb21icmU+a19
fQmFja2luZ0ZpZWxkFTxFZGFkPmtfX0JhY2tpbmdGaWVsZAABAAgIAg
AAAAEAAAAGAwAAAAlFbCBub21icmUHAAAACw==

Id:1
Nombre:El nombre
Edad:7

Esto es lo que recuperamos(xml con wrapper)
Id:1
Nombre:El nombre
Edad:7

El resultado que queriamos. Seguro que a más un desarrollador web le parece esta viendo el ViewState ...

Por úlitmo, decir que no todas la clases son compatibles con la serializacion. La serialización XML no es compatible con objetos que implementan la interface IDictionary , un Hashtable por ejemplo.

Para poder serializar de forma binaria una clase esta debe estar marcada con el atributo Serializable.

Saludos, DJK

 

Pedro  Herrarte  Sánchez
Serialización: XmlSerializer y BinaryFormater
Pedro Herrarte Sánchez

Pedro Herrarte, es consultor independiente, ofreciendo servicios de consultoría, análisis, desarrollo y formación. Posee mas de diez años de experiencia trabajando para las principales empresas de España. Es especialista en tecnologías .NET, entornos Web (ASP.NET, ASP.NET MVC,jQuery, HTML5), bases de datos (SQL Server y ORACLE) e integración de sistemas. Es experto en desarrollo (C#, VB.Net, T-SQL, PL/SQL, , ASP, CGI , C, Pro*C, Java, Essbase, Vignette, PowerBuilder y Visual Basic ...) y bases de datos (SQL Server y ORACLE). Pedro es MCP y MAP 2012, es fundador, diseñador y programador de www.devjoker.com..
Fecha de alta:18/06/2009
Última actualizacion:18/06/2009
Visitas totales:8016
Valorar el contenido:
Últimas consultas realizadas en los foros
Últimas preguntas sin contestar en los foros de devjoker.com