Optimización de XmlSerializer


Ayer, haciendo una de revisión en un servicio,  nos dimos cuenta que se hacían continuamente llamadas al csc.exe. Esto provocaba que el servicio fuera muy lento y además consumiera mucha memoria, así que nos pusimos manos a la obra.

Como sabíamos que el servicio hacía un uso intensivo de la serialización Xml con el XmlSerializer no hizo falta hacer debug sino que fuimos a mirar el código que probablemente invocaba al csc.

Paso directamente a describir cómo funciona y el porqué del comportamiento que encontramos.

En .Net serializar una clase sencilla (sin referencias circulares, con al menos el constructor por defecto, etc…) a Xml es bastante directo, sólo necesitas crear un XmlSerializer para la clase en cuestión, por ejemplo, si tenemos un conjunto de clases así:
public class BaseClass
{
public int BaseProperty { get; set; }
}

public class TestClass
{
public string MyProperty1 { get; set; }
public BaseClass MyProperty2{get;set;}
}

Para serializar TestClass podríamos usar el siguiente código:

TestClass xmlSerializableClass =
new TestClass { MyProperty1 = "test", MyProperty2 = new BaseClass()};
XmlSerializer serializer = new XmlSerializer(typeof(TestClass));
serializer.Serialize(Console.Out, xmlSerializableClass);

Al ejecutarse genera un archivo .cs con el código de serialización de nuestra clase lo compila y lo ejecuta para serializarlo. La siguiente vez la clase XmlSerializer no volverá a compilar pues guarda internamente una lista con (casi) todos los serializadores que va creando.

Si heredamos de BaseClass y queremos que nuestra nueva clase se incluya en el serializador hay que agregar un atributo para que el XmlSerializer la tenga en cuenta automáticamente, en caso contrario nos daría un error en tiempo de ejecución:
public class AnotherClass:BaseClass
{
public int AnotherProperty { get; set; }
}

[XmlInclude(typeof(AnotherClass))]
public class BaseClass
{
public int BaseProperty { get; set; }
}

De esta manera el siguiente código seguirá funcionando y utilizando el caché:

TestClass xmlSerializableClass =
new TestClass { MyProperty1 = "test", MyProperty2 = new AnotherClass()};
XmlSerializer serializer = new XmlSerializer(typeof(TestClass));
serializer.Serialize(Console.Out, xmlSerializableClass);

Hasta aquí bien, pero ¿qué pasa si en tiempo de compilación aún no sabemos exactamente cuantas clases heredarán de BaseClass? Podría ocurrir que tuvieramos una aplicación con plugins y que pudieran añadir más clases ahí. Para eso el XmlSerializer tiene otro constructor donde le podemos pasar una lista de tipos a añadir al serializador aparte de los que haya indicados en el atributo:

XmlSerializer serializer = new XmlSerializer(typeof(TestClass),
new Type[]{typeof(NotIncludedClass)});

El único problema de este constructor es que no guarda la clase autogenerada en el caché. Por lo tanto, cada vez que se ejecute el código se ejecutará un csc.exe y se cargará un nuevo assembly dentro de nuestra aplicación. Eso nos produce dos efectos:

  1. Nuestra aplicación se ralentiza
  2. Como los assemblies no se descargan nunca, la memoria no se libera

En estos casos nos conviene guardar una referencia al serializador. Espero haber ayudado a alguien. Podéis descargar el código de ejemplo aquí.

Para saber más, hace ya unos cuantos años escribí un pequeño  de introducción al XmlSerializer que podéis encontrar aquí.
Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s