El misterioso caso de la ListBox con un solo elemento

Un post rapidito, para comentar algo que sucedió ayer…

Ayer por la tarde puse el siguiente tweet: http://twitter.com/#!/eiximenis/status/202060274260389888. Básicamente mostraba una ListBox en la cual tras añadirle un único elemento soltaba una OutOfMemoryException indicando que había demasiados elementos en la dicha lista:

As3cv7bCMAEOizK 

Vale que winforms tiene sus limitaciones, pero eso parece un poco excesivo, ¿no?

Mirando el valor de lstComandos.Count puedo ver que el elemento se ha añadido (antes de hacer el Add la lista estaba vacía) pero que después me lanza la excepción.

La propiedad InnerException está vacía:

image

Bueno… tras una rápida investigación (basada en F9 y F5) pude elaborar una suposición de lo que ocurría. El objeto que añadía a la lista era de una clase tal como la siguiente:

class Comando

{

    private readonly Guid _id;

    public Comando()

    {

        _id = Guid.NewGuid();

    }

    public Guid Id { get { return _id; } }

    public string Name { get; set; }

    public override string ToString()

    {

        return Name;

    }

}

Y como lo añadia a la lista:

lstComandos.Items.Add(new Comando());

Este Add ya daba la excepción antes mencionada.

¿Cuál es el verdadero problema? Pues simple y llanamente que el método ToString() (que es el que llama la ListBox para convertir los objetos de la clase Comando en una cadena para mostrar) devuelve null.

Basta con modificar el código del ToString:

public override string ToString()

{

    return Name ?? "Unnamed comamand";

}

Y todo pasa a funcionar a la perfección. 🙂

¿Moraleja final? Pues básicamente que si lanzas una excepción asegúrate de que es el tipo correcto de excepción. Porque de “demasiados elementos en la lista” y OutOfMemoryException nada de nada… 😉

Saludos!

Cargar información de reflection sin cargar el assembly

Bueno… veamos un post rapidito. En un proyecto en el que he participado hemos estado personalizando Visual Studio a través de varios custom editors, plugins, packages y demás fauna que pulula por la selva de extensibilidad de Visual Studio.

Estos editores, addines y demás necesitaban acceder a información de Reflection de la propia DLL que se estaba compilando. Teóricamente obtener la información es muy sencillo. Basta con obtener la ruta a la DLL que se está compilando:

private static EnvDTE.DTE DTE

{

    get { return (EnvDTE.DTE)Package.GetGlobalService(typeof(EnvDTE.DTE)); }

}

 

public static string ObtenerRutaEnsamblado()

{

    var project = DTE.ActiveDocument.ProjectItem.ContainingProject;

    return project.Properties.Item("LocalPath").Value.ToString() +

        project.ConfigurationManager.ActiveConfiguration.Properties.Item("OutputPath").Value.ToString();

}

 

public static string ObtenerNombreEnsamblado()

{

    var project = DTE.ActiveDocument.ProjectItem.ContainingProject;

    return string.Concat(ObtenerRutaEnsamblado(), project.Properties.Item("OutputFileName").Value.ToString());

}

El método ObtenerNombreEnsamblado da la ruta física de la DLL que se está compilando. A partir de aquí, debería bastar con usar LoadAssembly, para cargar la DLL y listos. Pero por supuesto, si esto fuese así, esta entrada del blog no existiría 🙂

El tema está en que cuando accedemos a un Assembly via Reflection, este assembly se carga en el CLR.  Y una vez un Assembly está cargado no puede ni cargarse de nuevo (para ver las modificaciones, por ejemplo, recordad que estamos cargando la propia DLL que el usuario está creando en VS) ni tampoco descargarse. Además el archivo físico se puede crear bloqueado (lo que en nuestro caso impedía que pudieses compilar el proyecto, ya que estaba bloqueado por el addin). Si alguno de vosotros está pensando en cargar el proyecto “solo para Reflection”, que se olvide. Cargar un assembly “solo para Reflection” lo carga igual y tampoco se puede ni cargar de nuevo ni descargar.

¿La solución? Bueno, pues utilizar un AppDomain nuevo. Para los que no lo sepáis los AppDomains son como “procesos” dentro del CLR. Un programa se ejecuta dentro de un AppDomain pero puede crear más AppDomains, del mismo modo que un proceso puede crear procesos hijos. Por supuesto la comunicación entre dos AppDomains se trata como comunicación interproceso: o a través de proxies (objetos MarshalByRef) o pasando objetos serializables. ¡Viva la vida!

Al final, terminé con una clase AppDomainUtils, con métodos estáticos parecidos a los siguientes:

    /// <summary>

    /// Carga el tipo TObj en un AppDomain nuevo.

    /// TObj DEBE ser MarshalByRef

    /// </summary>

    private static TObj LoadFromType<TObj>(AppDomain appDomain)

    {

        var tokens = typeof(TObj).AssemblyQualifiedName.Split(‘,’);

        var assName = tokens[1];

        var typeName = tokens[0];

        var obj = appDomain.CreateInstanceAndUnwrap(assName, typeName);

        return (TObj)obj;

    }

 

    /// <summary>

    /// Obtiene información (de tipo TR) de un System.Type.

    /// </summary>

    /// <typeparam name="TR">Tipo de información que se devuelve. Debe ser Serializable</typeparam>

    /// <typeparam name="TU">Tipo de la clase que extrae la información a partir del System.Type</typeparam>

    /// <param name="fullName">Nombre del System.Type a cargar (con assembly incorporado)</param>

    /// <param name="locationPath">Ruta fisica real del assembly</param>

    /// <returns>La información extraída del System.Type</returns>

    public static TR GetTypeInfo<TR, TU>(string fullName, string locationPath)

        where TU : TypeLoader

    {

        var appDomain = AppDomain.CreateDomain(Guid.NewGuid().ToString());

        var tloader = LoadFromType<TU>(appDomain);

        var result = tloader.LoadTypeInfo<TR>(fullName, locationPath);

        AppDomain.Unload(appDomain);

        return result;

    }

}

La clase TypeLoader es como sigue:

/// <summary>

/// Carga información de un tipo.

/// </summary>

public class TypeLoader : MarshalByRefObject

{

    /// <summary>

    /// Carga el tipo y extrae la información

    /// </summary>

    public TR LoadTypeInfo<TR>(string fullName, string locationPath)

    {

        var type = Type.GetType(fullName);

        if (type == null)

        {

            var tokens = fullName.Split(‘,’).Select(x => x.Trim()).ToArray();

            var assFileName = tokens[1];

            var assFileNameWithExtension = string.Concat(assFileName.Trim(), ".dll");

            var assembly = AssemblyLoader.CargarAssemblyDesdeByteArray(Path.Combine(locationPath, assFileNameWithExtension));

            var typeName = tokens[0];

            type = assembly.GetTypes().FirstOrDefault(x => x.FullName == typeName);

        }

        return type != null ? (TR)Select(type) : default(TR);

    }

 

    /// <summary>

    /// Este método recibe un Type y debe devolver la info que se necesita de dicho Type.

    /// Este objeto DEBE ser serializable y debe ser una instancia (o casteable) de TR

    /// </summary>

    protected virtual object Select(Type type) { return null; }

La idea es cargar un System.Type, extraer información de él y devolverla. Evidentemente esto debe hacerse en un AppDomain nuevo. El método GetTypeInfo lo que hace es crear este AppDomain nuevo y luego, dentro de este AppDomain crear una instancia de un objeto propio, de un tipo cualquiera TU, pero que TU derive de TypeLoader. Y llama al método LoadTypeInfo de este objeto propio. El método LoadTypeInfo (definido en la clase TypeLoader) es el método que:

  1. Carga el assembly (usando un método propio que lo carga desde un array de bytes para asegurar que el fichero no se queda bloqueado. Simplemente lee todo el contenido del fichero en un byte[] y luego usa Assembly.Load pasándole este byte[]).
  2. Obtiene el tipo (System.Type) especificado.
  3. Llama al método Select que recibe un System.Type y debe devolver un objeto serializable con la información. Este objeto es el que se transmitirá al AppDomain principal (de ahí que deba ser serializable). Y no, System.Type no lo es.

El uso al final es bastante sencillo:

var data = AppDomainUtils.GetTypeInfo<TypeIdInfo, TypeIdInfoLoader>(tag.TypeName, OperativaReader.ObtenerRutaEnsamblado());

En la variable tag.TypeName está el nombre del tipo (full-qualified) a cargar. Al ejecutar esta línea en data tenemos un objeto de tipo TypeIdInfo que contiene la información que nos interesaba extraer del objeto System.Type. La clase TypeIdInfoLoader es la que transforma un System.Type en un TypeIdInfo:

class TypeIdInfoLoader : TypeLoader

{

    protected override object Select(Type type)

    {       

        var data = new TypeIdInfo() { FullName = type.FullName };

        return data;

    }

}

El código del méotdo Select de la clase TypeIdInfoLoader se ejecuta en el otro AppDomain, de ahí que deba devolver un objeto serializable (la clase TypeIdInfo debe estar marcada como tal).

En fin… comentar tan solo que todo este peñazo de usar AppDomains es porque los señores de Microsoft no han tenido a bien proporcionar una API que permite usar Reflection sin cargar la DLL dentro del CLR. Y no, lo siento, pero esta API no me sirve. Quiero algo que para usarlo no deba morir mil veces.

Saludos! 😉

[C# Básico] Métodos con parámetros variables

¡Hey! Dos entradas de la serie C# Básico en menos de un mes… ¿Señal de algo? Quien sabe… 😛

Antes que nada el aviso típico de esta serie: En esos posts exploramos elementos, digamos, básicos del lenguaje. No es un tutorial ni un libro ni nada. Cada post es independiente del resto y pueden ser leídos en el orden en que prefiráis… Dicho esto, al tajo.

Bueno, en este post veremos como funcionan los métodos con parámetros variables en C#. Cuando digo parámetros variables me refiero a que el número de parámetros es variable (o sea le puedes pasar 1 parámetro, ó 10 ó 100).

Primera opción: sobrecarga

Cuando aprendemos C# por norma general nos dicen que al definir un método debemos indicar que parámetros acepta, y que todos esos parámetros deben pasarse para invocar el método. Tranquilo, no te han engañado, es cierto. Todos sabemos que si tengo un método:

class Foo

{

    public void DoBar(int baz, string bazbar) { }

}

Si quiero invocar el método DoBar, debo pasarle los dos parámetros: un int y una cadena. No hay otra. O se hace o el código no compila.

Entonces… ¿como puedo hacer métodos con parámetros variables? Pues, la primera opción, muy usada, para proporcionar la ilusión de un número variable de parámetros (pues en este caso se trata de una ilusión) es aprovechar el mecanismo de sobrecarga. Sobrecargar un método significa que hay dos métodos métodos independientes pero que tienen el mismo nombre. El compilador tan solo pedirá una cosa: que la lista de parámetros sea distinta, o bien en número, o bien en el tipo de parámetros. Luego, cuando se llama el método, el compilador invocará al método que toque según el tipo y número de parámetros que se le pasen. Si alguien viene de C (pero no de C++) igual le sorprende esto, pero es realmente útil. Recuerdas toda aquella pléyade de funciones itoa, ltoa, ultoa y similares? Todas hacían lo mismo, convertir un número a cadena, pero claro la primera convertía un int, la segunda un long y la tercera un unsigned long. Esto mismo en C# se puede conseguir declarando tres funciones, pero todas ellas llamadas igual, p.ej. numberToString y teniendo cada una un tipo de parámetros variables. Así tenemos que recordar un solo nombre de método en lugar de n-mil.

El framework está lleno de métodos sobrecargados. ¿Un ejemplo? El método ToInt32 de la clase Convert:

image

Aquí lo veis, tiene 19 sobrecargas! Eso significa que hay 19 métodos llamados ToInt32 en la clase Convert. Insisto en que se trata de métodos independientes, si se quisiera el tipo de retorno podría ser distinto en cada caso y por supuesto podrían hacer cosas completamente distintas  (aunque hay que estar un poco mal de la cabeza para poner dos métodos que se llamen igual pero hagan cosas distintas :p).

Sobrecargando un método puedo dar la ilusión de que el número de parámetros es variable:

class Foo

{

    public void DoBar(int baz, string bazbar) { }

    public void DoBar(int baz) { }

    public void DoBar(string bazbar) { }

}

class Program

{

    static void Main(string[] args)

    {

        var foo = new Foo();

        foo.DoBar(10);

        foo.DoBar("edu");

        foo.DoBar(20, "edu");

    }

Bueno, esta técnica puede usarse para casos con pocos parámetros, pero en el fondo no tenemos realmente un “número de parámetros variable”, ya que no le puedo pasar 8 parámetros a DoBar.

Segunda opción: params

Antes que nada, a alguien se le ocurre un método en C# en el que se le pueda pasar un número arbitrario de parámetros (uno, diez o cien)? Pues hay varios, pero el más conocido es string.Format.

Seguro que todos habéis usado string.Format. Y sabéis que en función del primer parámetro, se le pasan luego tantos parámetros adicionales como sea necesario. La verdad es que string.Format tiene varias sobrecargas, pero te lo aseguro, no tiene todas las posibles. Entonces, ¿como lo hace? Pues usando la palabra clave params en C#:

class Foo

{

    public void DoBar(int baz, params string[] bazbar) { }

}

class Program

{

    static void Main(string[] args)

    {

        var foo = new Foo();

        foo.DoBar(10, "hola", "adios", "bye");

        foo.DoBar(20);

        foo.DoBar(10, "hi!");

    }

}

Este código compila y es perfectamente válido. El método DoBar tiene realmente dos parámetros: un int y un array de cadenas. Pero el uso de la palabra clave params permite pasar todas las cadenas del array no como un array sino como n parámetros separados por comas. Pero lo que DoBar recibe es un array de cadenas. Así el siguiente programa:

    public void DoBar(int baz, params string[] bazbar)

    {

        Console.WriteLine("# cadenas: " + bazbar.Length);

        for (int i = 0; i < bazbar.Length; i++)

        {

            Console.Write(bazbar[i] + ",");

        }

        Console.WriteLine();

    }

}

class Program

{

    static void Main(string[] args)

    {

        var foo = new Foo();

        foo.DoBar(10, "hola", "adios", "bye");

        foo.DoBar(20);

        foo.DoBar(10, "hi!");

    }

}

Genera la siguiente salida:

image

Así como podéis ver, realmente el método DoBar tiene dos parámetros: un entero que debo pasar siempre y un array de cadenas que el compilador me deja pasar como si fuesen N parámetros, pero que realmente es un array de N cadenas. De hecho, incluso eso es válido:

foo.DoBar(10, new string[] { "hola", "adios" });

En resumen, el uso de la palabra clave params me permite pasar un array como si fuesen N parámetros, pero no me obliga a ello (puedo seguir pasando el array como un array). Pero recordad: es un truco del compilador 😉

¡Ah, s¡! Y tened presente que:

  1. Tan solo puedo tener un parámetro params en un método…
  2. …Y debe ser el último

Por supuesto, si queréis que los “parámetros variables” puedan ser de cualquier tipo, podéis declarar que vuestro método recibe un params[] object:

class Foo

{

    public void DoBar(int baz, params object[] bazbar) { }

}

class Program

{

    static void Main(string[] args)

    {

        var foo = new Foo();

        foo.DoBar(10, "veinte", 20, new ClaseMia());

    }

}

En este caso, dentro de DoBar, bazbar es un array de objects con dos objetos (una cadena “veinte” y un objeto ClaseMia). Por supuesto que el método DoBar sepa de que tipo son los objetos que están dentro de bazbar es otra historia… 😉

Tercera opción (aunque no reconoceré haberlo dicho): __arglist

Aunque params por si solo ya es suficientemente interesante, vamos a hablar de una de esos pequeños aspectos de C# que son en general, desconocidos… Me refiero a __arglist (sí, con DOS subrayados delante, lo cual ya indica algo). El uso de esa palabra clave (pues es una palabra clave) significa “y a partir de aquí más argumentos”. En efecto, podemos declarar nuestro método DoBar:

class Foo

{

    public void DoBar(int baz, __arglist) { }

}

class Program

{

    static void Main(string[] args)

    {

        var foo = new Foo();

        foo.DoBar(10, __arglist("veinte", 20, 30, "bye"));

    }

Bueno, antes de continuar un par de cosas:

  1. Clarísimamente (como diría Cruyff) __arglist está pensada para que no la usemos. Es decir se trata de una palabra clave no documentada. La verdad es que funciona desde, al menos VS2005 y supongo que funcionará en todas las versiones de C#. Pero insisto: no está documentada, no es “oficial” (de ahí el “no reconoceré haberlo dicho” del título).
  2. Fijaos que debo invocar el método pasándole los parámetros opcionales usando también la palabra clave __arglist.

Ahora nos queda la cuestión final: como accede DoBar a esos parámetros opcionales. Eso no es como params que tenemos un array. No. Aquí si que tenemos un número arbitrario de parámetros de verdad. Pues nada, vamos a echar mano de la estructura ArgIterator:

public void DoBar(int baz, __arglist)

{

    var args = new ArgIterator(__arglist);

    while (args.GetRemainingCount() > 0)

    {

        Console.WriteLine("Param {0} valor {1}",

            Type.GetTypeFromHandle(args.GetNextArgType()).Name,

            TypedReference.ToObject(args.GetNextArg()).ToString());

    }

}

El uso de ArgIterator nos permite iterar sobre la lista de argumentos. Por cada argumento básicamente obtenemos:

  • Su valor, pero a través de un objeto TypedReference que se puede convertir a un object usando el método estático ToObject de la clase TypedReference.
  • Su tipo, pero a través de un objeto RuntimeTypeHandle que se puede convertir a un Type a través del método estático GetTypeFromHandle de la clase Type.

Probablemente estés pensando que no vale la pena usar __arglist, ArgIterator y todo este coñazo de TypedReference y RuntimeTypeHandle y tendrás razón. Por algo la palabra clave __arglist no está documentada. El hecho de que exista tiene que ver más con P/Invoke que no con una necesidad propiamente dicha del lenguaje.

Y bueno… eso es todo! 😛 Espero que os haya sido interesante… 😉

Saludos!