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!

9 comentarios en “El misterioso caso de la ListBox con un solo elemento”

  1. Hola Eduard,

    buena entrada para tener en cuenta.

    no obstante y bajo mi punto de vista, igual lo correcto sería inicializar en el constructor de la clase Comando la propiedad Name.

    Otra alternativa suponiendo que queramos mantener el constructor de Comando como está, sería hacer la siguiente corrección en ToString():

    public override string ToString()
    {
    if (this.Name == null)
    {
    throw new ArgumentNullException(“Name”);
    }
    return this.Name;
    }

    De esta manera aseguramos que ToString() devuelve “algo” controlado.

    Pese a todo y simplemente como agregado de tu solución, comentar el uso de extensiones para simplificar este y otros comportamientos y que podría quedar de la siguiente manera:

    public static class Extensions
    {

    public static string ToString(this object element)
    {
    return (element ?? “Unnamed command”).ToString();
    }

    }

    public override string ToString()
    {
    return Extensions.ToString(this.Name);
    }

    Un saludo.

  2. Por cierto, tampoco nos olvidemos de la función IsNullOrEmpty que podría salir en nuestra ayuda también. 🙂

    String.IsNullOrEmpty(this.Name)

  3. @Jorge
    Efectivamente tienes razón. Debería haber inicializado bien la clase en el constructor (aunque podría haber casos en que un nombre null fuese correcto y en ningún sitio, que yo sepa, se dice que ToString() no pueda devolver null). Pero, ciertamente, en mi caso debería haberlo hecho.
    Dicho esto, lo cierto es que la excepción que lanza la ListBox no es para nada coherente. Un ArgumentException hubiese estado bien, y quizá un ArgumentNullException hubiese ayudado también. Incluso un NullReferenceException si quieres. Pero un OutOfMemoryException… :S

    @Juanma
    Vaya, no lo sabía. Aunque al menos está documentado 😛 Después de ver tu comentario he ido a la msdn a ver si decía algo que el Add() podía lanzar una OutOfMemoryException y no dicen nada. Honestamente creo que es un bug de la ListBox.

    Gracias a los dos por comentar!! 😀

  4. Hola,

    Aparte de coincidir 100% con vosotros en que esa clase debería inicializar Name en el constructor.

    Os comento el porque de este bug y sobre todo recalcar una mala documentación, si analizamos a fondo la exception y concretamente el método NativeAdd del stackTrace, podemos ver el porque.

    En este link teneís la explicación http://stackoverflow.com/questions/1757452/c-winforms-listbox-items-add-generates-an-outofmemoryexception-why.

    Vamos a analizar ese método.

    1. Envía un mensaje LB_ADDSTRING o 0x180, claro el lparam de la funcion SenMessage recibe un null y es esta la que falla devolviendo un LB_ERR(-1).

    Evidentemente esto es un error pero de quien de la funcion SendMessage, que de poco más informa http://msdn.microsoft.com/en-us/library/windows/desktop/bb775181(v=vs.85).aspx, puesto que los posibles valores son el indice del elemento o bien LB_ERR(-1), cualquier error que es este caso o LB_ERRSPACE(-2) que sería el más parecido a OutOfMemoryException.

    Efectivamente el ListBox está mal programado, pero también es una desgracia haber heredado cosas como estas en .Net y no haber reescrito los controles desde cero. Puesto que un ListBox se puede hacer perfectamente sin utilizar esto.

    Saludos,

  5. Buenas Pedro!!
    Gracias por el enlace, muy interesante… Así que resulta que, por un bug de Win98 (enviar LB_ERR cuando debería enviar LB_ERRSPACE) .NET trata todos el LB_ERR como OutOfMemoryException.
    Bueno, pues vale. Pero es un fail épico.

    Pero incluso, si eso no se puede arreglar porque es condenadamente jodido… no podrían haber metido un if en el método NativeAdd?
    Asumo mi culpa por que ToString() devolviese null, pero .NET también tiene su tela… 😛

    Saludos! 😉

  6. Totalmente de acuerdo Eduard.

    Un NullReferenceException creo que sería en este caso la excepción correcta. La OutOfMemoryException me recuerda a un object,… es decir, sirve para todo y es algo así como… “ahí va, una excepción de vete a saber tú”. 🙂

    Para matarlos… :)))

  7. @Edu cuendo dices:

    > en ningún sitio, que yo sepa, se dice que ToString() no pueda devolver null.

    Joer, sólo faltaría. Una propiedad que devielve un tipo string y no puede devolver un valor válido para un string. Eso sería para empezar a repartir hostias como panes, no?

    Brutal el comentario del código de LB_ADDSTRING antes de devolver el OutOfMemoryException en el post de StackOverflow :-O

    @Pedro: Vale, el LISTBOX es una castaña. Pero es una castaña de 20 años, y cuando se creó .NET se debería haber tenido en cuenta a la hora de hacer la llamada con LB_ADDSTRING. MegaFail.

    @Jorge: Para mi también sería más apropiado un NullReferenceException.

    Saludos a todos.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *