¡Cuidado con los bucles For Each!

For Each

La estructura de bucle For Each… Next es una de las instrucciones digamos «nuevas» (los que llevamos ya unos añitos programando recordamos la época de solo For o While clásicos) que más interesantes me parecen. Tengo que reconocer que no la solía utilizar mucho hasta que en una práctica de la universidad me tocó hacer un juego en Java (el mítico RType). Para recorrer arrays de objetos en Java es la manera más sencilla y además la más clara bajo mi punto de vista.

Pues bien, en Visual Basic también utilizo bastante esta estructura, sobre todo para recorrer colecciones. La verdad es que aunque ahora se supone que Visual Basic ya es un lenguaje orientado a objetos en toda regla, los que somos de la vieja escuela recordamos que antes los defensores de la orientación a objetos criticaban Visual Basic diciendo que no era Orientado a objetos ya que no admitía herencia, ni encapsulamiento ni polimorfismo (sobrecarga de métodos).

Estoy de acuerdo en que no era un lenguaje orientado a objetos puro, pero a los que veníamos de Visual Basic no nos costó tanto pasarnos a la filosofía de la orientación a objetos ya que desde un principio hemos trabajado con formularios, controles, bases de datos, etc. que actúan como objetos. Y si, la mejor manera de recorrer colecciones de objetos es un bucle For Each.

Os preguntaréis a qué viene todo este tocho filosófico en un blog de Access y Visual Basic. Pues bien, todo esto viene a cuento por algo que me ha pasado el otro día mientras desarrollaba un sistema de vinculación de tablas para el Administrador de aplicaciones Access (próximamente lo publicaré con una explicación de su desarrollo).

El problema surgió cuando estaba borrando la colección de tabledefs de un Frontend para vincularlo con un BackEnd nuevo. Lo hacía de la siguiente manera:

For Each TablaDef In Base.TableDefs

        sConexion = Nz(TablaDef.Connect, "")
        If InStr(1, sConexion, "DATABASE=") > 0 Then 'Controlamos que sea una tabla vinculada
            Base.TableDefs.Delete TablaDef.name
        End If

    Next

Todo parecía correcto, así que ejecuté una prueba. Recorre bien el tabledef pero deja algunas tablas sin borrar. Lo vuelvo a ejecutar y borra bien, pero sigue dejando alguna (sin sentido aparente). Mi primera reacción fue pensar que no refrescaba bien el tabledef, así que le meti la siguiente instrucción:

For Each TablaDef In Base.TableDefs

        sConexion = Nz(TablaDef.Connect, "")
        If InStr(1, sConexion, "DATABASE=") > 0 Then 'Controlamos que sea una tabla vinculada
            Base.TableDefs.Delete TablaDef.name
            Base.TableDefs.Refresh
        End If

    Next

Seguía haciendo lo mismo…

Entonces recordé aquella práctica en Java, aquel Rtype (que la verdad es que me quedó bastante decente) en el que me ocurrió algo similar. Tenía un array de «Misiles» (objetos misil) que iba borrando con un bucle For Each según salían de la pantalla, o golpeaban un enemigo. Y también me fallaba sin sentido aparente.

Al final después de muchos horas de instrucciones paso a paso (en Java no es como en Visual Basic, el concepto es otro y la depuración es mucho más difícil) encontré la solución. Los índices en el For Each se actualizan dinámicamente, haciendo que al eliminar objetos de la colección, se salga del bucle antes de tiempo. A ver si puedo explicarlo con un ejemplo.

Imaginad una colección de este tipo:

  • Elemento 1: aaaaaaa
  • Elemento 2: bbbbbbb
  • Elemento 3: ccccccc
  • Elemento 4: ddddddd
  • Elemento 5: eeeeeee

Lo recorremos con un bucle For Each y borramos en la primera pasada el primer elemento y nos queda de la siguiente manera:

  • Nuevo 1 = Viejo 2:bbbbbbb
  • Nuevo 2 = Viejo 3:ccccccc
  • Nuevo 3 = Viejo 4:ddddddd
  • Nuevo 4 = Viejo 5:eeeeeee

Seguimos con el bucle y borramos el elemento (que originariamente era el tercero) y nos queda:

  • Nuevo 1 = Viejo 2:bbbbbbb
  • Nuevo 2 = Viejo 4:ddddddd
  • Nuevo 3 = Viejo 5:eeeeeee

Borramos el siguiente elemento (el actual 3 que era el 5 al principio). Nos sale del bucle y nos quedan el elemento 2 y el elemento 4 sin borrar. Así que tenemos que encontrar una solución para borrar colecciones de objetos y esta no es otra que recorrerlas con un índice externo. Os paso el código que utilizo yo para borrar todas las tablas vinculadas en un FrontEnd:

For i = Base.TableDefs.count - 1 To 0 Step -1 'Así que borramos con un bucle externo

    Set TablaDef = Base.TableDefs(i)
    sConexion = Nz(TablaDef.Connect, "")
        If InStr(1, sConexion, "DATABASE=") > 0 Then 'Controlamos que sea una tabla vinculada
            Base.TableDefs.Delete TablaDef.name
        End If
Next

Espero que os sirva y que no paséis las horas que pasé yo en su día encontrando una solución a los misiles que no se borraban.

The following two tabs change content below.
Llevo más de 10 años programando, sobre todo en Visual Basic y con bases de datos Access. Para mí, VBA y Access siguen siendo herramientas muy potentes. He desarrollado varios proyectos con PHP y MySql. Si sumo las webs que he tenido, probablemente pasaría de 100. Ahora prefiero dedicar todo mi esfuerzo a este blog (aunque sigo manteniendo unas cuantas...). Trabajo en la administración pública (si, soy funcionario), pero he trabajado en pequeñas empresas e incluso en una "grande" de las telecomunicaciones. Ultimamente estoy bastante metido en abrirme nuevos horizontes con C# y .NET. Renovarse o morir!

Deja una respuesta