Una de las cuestiones que más quebraderos de cabeza puede causar a quienes empiezan a programar utilizando cualquiera de los lenguajes de programación orientados a objetos (OOP, por sus iniciales en inglés), es qué ocurre dentro de los métodos cuando pasamos nuestros objetos y/o valores primitivos como argumentos, ¿se pasan como copias que no “mutan” o alteran el contenido de las variables o bien se hace como referencia, en cuyo caso sí se alteran dichos contenidos?
De hecho esta es una de las cuestiones que ha vuelto a salir últimamente (en función de cuando se lea esta entrada, claro está) en el foro de Xojo, en este caso como un hilo en el foro de Xojo en español. Y si bien la documentación recoge este aspecto del lenguaje, me he animado a publicar esta entrada con el ánimo de que pueda aclarar cualquier duda. Este contenido se corresponde con la verión Xojo 2015r2.3.
Por supuesto, esta y otras cuestiones son las que se detallan de principio a fin en el libro “Programación Multiplataforma Xojo”.
Datos primitivos: ¡siempre se pasan como copias!
¿Qué son los datos primitivos? En Xojo se consideran como tales todos los valores de tipo entero (Integer), coma flotante o reales (Double) y también las cadenas de texto (String). Por tanto, cuando se pasa a un método las variables declaradas como tales, lo que obtendremos dentro del método será en todo momento una copia de la variable original. Dicho de otro modo, es como si se hubiese declarado tal variable en el cuerpo del método en cuestión. Esto significa que independientemente del modo en el que se modifique o manipule el valor de dicha variable, en ningún momento se verá afectada la variable original.
Pongamos por caso el siguiente código como parte del evento Open en la instancia Window1 del proyecto:
dim n as integer = 12 cambiaEntero(n) MsgBox n.toText
Y que el método cambiaEntero está declarado de la siguiente forma:
sub cambiaArray( n as integer )
De modo que el código a ejecutar por dicho método sea el siguiente:
n = n / 2
¿Qué cadena mostrará la sentencia ‘MsgBox’ en consecuencia, tras haberse ejecutado el método ‘cambiaArray’? Efectivamente, dado que al método se pasa una copia de la variable de tipo array, sus contenidos no se verán afectados en ningú momento; motivo por el que MsgBox mostrará el contenido “12″
Struct también se pasa por valor
Además de los valores primitivos y los objetos, en Xojo también podemos crear nuestras propias estructuras de datos (Insert > Struct). Para ello se utiliza en el IDE un editor muy similar al que nos permite crear enumeraciones o bien localizar nuestras aplicaciones.
Hemos de tener en cuenta que las estructuras son un tipo de datos compuesto por una serie de campos de longitud conocida y, por tanto, limitada en su composición a los tipos de datos primitivos, arrays de tipos de datos primitivos o bien otras estructuras. En definitiva, Xojo debe de conocer el tamaño de una estructura en tiempo de compilación. En otros lenguajes de programación, sin embargo, los campos de las estructuras pueden ser clases; por tanto conviene tener en cuenta dicha diferencia.
Pues bien, las estructuras también se pasan a los métodos por omisión como parámetros por valor. Es decir, como copias.
Pongamos por caso el siguiente código en el evento Open de una instancia Window1 (la definición de la estructura MyStruc es la que se ve en la anterior imagen):
dim s as MyStruct s.name = "John" s.surname = "Doe" s.age = 19 s.sex = true changeStruct( s ) MsgBox s.name + “ “ + s.surname
Y que la signatura del método changeStruct sea la siguiente:
sub changeStruct( s as MyStruct ) De modo que el código a ejecutar por dicho método sea el siguiente: s.name = “Javier” s.surname = “Rodríguez"
¿Que texto será el mostrado por la línea ‘MsgBox s.name + “ “ + s.surname, al regreso de la ejecución del método changeStruct( s )? Efectivamente, dado que la estructura se pasa por valor (como copia) y no por referencia… el resultado será “John Doe”.
Objetos: ¡siempre se pasan por referencia!
Visto el asunto del paso de datos por valor en las variables de tipos primarios y estructuras, ¿cómo podríamos hacer que sus contenidos se modificasen sin recurrir a la devolución de un nuevo dato por parte del método? Sencillo, para ello el lenguaje de programación multiplataforma Xojo proporciona el modificador de parámetro ByRef.
Cuando en la declaracion de un método utilizamos ‘ByRef’ precediendo a la variable de un parámetro, lo que hará Xojo será tomar una referencia hacia la ubicacion en memoria de la variable pasada como argumento, en vez de crear una copia tal y como se haría por omisión. De este modo, cualquier referencia y/o alteración en el contenido del parámetro se estará realizando sobre el contenido de la variable original.
Veamos un nuevo ejemplo que utiliza un número entero:
Dim numero as Integer = 5 multiplicaPorDos(numero) MsgBox numero.toText
En esta ocasión la signatura del método multiplicaPorDos será:
sub multiplicaPorDos( ByRef n as integer )
Con un código tan sencillo como este:
n = n * 2
Al ejecutar dicha prueba se observa que, en esta ocasión, la cadena mostrada por la instrucción “MsgBox numero.toText” se corresponde con 10. Es decir, hemos alterado el contenido del Array original almacenado en la variable “n”.
Continuando con el segundo de los ejemplos, si modificamos la signatura del método para que pase a ser:
sub changeStruct( ByRef s as MyStruct )
Manteniendo el mismo código ejecutado por el método, entonces el resultado de ‘MsgBox s.name + “ “ + s.surname’ pasará a ser en esta ocasión “Javier Rodríguez”.
¿Y qué ocurre en el caso de las variables que apunten a objetos y que se pasen como argumentos a los métodos? En esta situación la documentación de Xojo indica que todos los objetos, así como los Array, se pasan a los métodos por omisión como referencia.
Pero ojo, esto significa que podremos modificar las propiedades de dichos objetos (si dichas propiedades están declaradas con un ámbito público o el método, claro), pero no podemos reemplazar el objeto apuntado por la variable de origen.
Por ejemplo, pongamos que hemos definido una clase Persona con las propiedades “name as String” y “surname as String” (en ambos casos con un Scope Public). También habremos creado para dicha clase un método Constructor de conveniencia con la siguiente signatura y código:
Constructor(theName as String=“John”, theSurname as String=“Doe”) name = theName surname = theSurname
Ahora veamos el siguiente código en el Evento Open de Window1:
dim p as New Person changePerson( p ) MsgBox p.name + “ “ + p.surname
Teniendo en cuenta que la signatura de changePerson es:
sub changePerson( p as Person)
Y se encarga de ejecutar el siguiente código:
dim a1 as new Person("Javier", "Rodriguez") p.name = “Juan” p.surname = “Sin Miedo" p = a1
¿Cuál será el resultado mostrado por la instrucción ‘MsgBox p.name + p.surname’ tras regresar de la llamada a changePerson( p )? Efectivamente, será “Juan Sin Miedo”, dado que no tendrá efecto la asignación a la variable ‘p’ del nuevo objeto creado dentro del método y almacenado por ‘a1’.
Ahora bien, la cosa cambia si añadimos ‘ByRef’ en la declaración del parametro aceptado por el método changePerson:
sub ChangePerson(ByRef p as Person) dim a1 as new Person("Javier", "Rodriguez") p.name = “Juan” p.surname = “Sin Miedo" p = a1
Si volvemos a ejecutar el código, en esta ocasión el resultado será “Javier Rodríguez” puesto que ahora sí se está modificando el objeto al que apunta originalmente la variable ‘p’.
Y para ver el comportamiento de una variable de tipo Array, pongamos por caso el siguiente código como parte del evento Open en la instancia Window1 del proyecto:
dim n() as integer = Array(1, 2, 3, 4, 5) cambiaArray(n) dim s as string For each elemento as integer in n s = s + elemento.toText + “, " next MsgBox s
Y que el método cambiaArray está declarado de la siguiente forma:
sub cambiaArray( n() as integer )
De modo que el código a ejecutar por dicho método sea el siguiente:
For i as integer = 5 to 9 n(i-5) = i Next
¿Cuál será el resultado mostrado por la instrucción ‘MsgBox s? En este caso, dado que los Array se pasan como los objetos por referencia, también podremos modificar el contenido de dicho Array y, por tanto, se mostrará como resultado “5, 6, 7, 8, 9, “.
Conclusiones
Como se ha visto, una vez se han visto los diferentes casos de paso de variables a los métodos seguramente quede más claro lo que podemos esperar en el comportamiento de nuestras variables después de usarlas en los métodos, obteniendo así un código más predecible en su comportamiento o que, sencillamente, haga lo que esperamos.