El misterioso caso de la ListBox con un solo elemento

Publicado 15/5/2012 15:26 por Eduard Tomàs i Avellana

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!

Archivado en:
Comparte este post:

Comentarios

# re: El misterioso caso de la ListBox con un solo elemento

Tuesday, May 15, 2012 5:04 PM by Jorge Serrano

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.

# re: El misterioso caso de la ListBox con un solo elemento

Tuesday, May 15, 2012 5:11 PM by Jorge Serrano

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

String.IsNullOrEmpty(this.Name)

# re: El misterioso caso de la ListBox con un solo elemento

Tuesday, May 15, 2012 8:56 PM by Juanma

El OutOfMemoryException debía de ser la excepción favorita de alguien en el equipo de desarrollo del framework.

El Image.FromFile también lanza una de esas cuando el formato es incorrecto (msdn.microsoft.com/.../stf701f5(v=vs.80).aspx) y la primera vez también te vuelves loco depurándolo.

# re: El misterioso caso de la ListBox con un solo elemento

Wednesday, May 16, 2012 8:23 AM by Eduard Tomàs i Avellana

@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 :P 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!! :D

# re: El misterioso caso de la ListBox con un solo elemento

Wednesday, May 16, 2012 10:17 AM by Pedro Hurtado

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 stackoverflow.com/.../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 msdn.microsoft.com/.../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,

# re: El misterioso caso de la ListBox con un solo elemento

Wednesday, May 16, 2012 5:07 PM by Eduard Tomàs i Avellana

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... :P

Saludos! ;-)

# re: El misterioso caso de la ListBox con un solo elemento

Wednesday, May 16, 2012 11:37 PM by Jorge Serrano

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... :)))

# re: El misterioso caso de la ListBox con un solo elemento

Thursday, May 17, 2012 8:39 AM by Pedro Hurtado

Hola,

Coincido plenamente con lo que cometáis los dos, es una verdadera chapu:)

Saludos,

# re: El misterioso caso de la ListBox con un solo elemento

Thursday, May 17, 2012 9:46 AM by Lluis Franco

@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.