jueves, 18 de agosto de 2016

VBA: Cuándo y Cómo pasar argumentos por ByVal o por ByRef

Si llevas un tiempo programando, seguro que te ha surgido la duda en determinados momentos de cómo y cuándo emplear y definir tus argumentos (de procedimientos Sub o Function) como ByVal o como ByRef.
Trataré hoy de aclarar este aspecto importante para el comportamiento de nuestras macros.


Lo primero a indicar es que la omisión, esto es, por defecto, en VBA para Excel, se pasan los argumentos Por Referencia (ByRef). (OJO por que lo normal en otros lenguajes es que sea por valor!).

1 - ByVal - "Por Valor":
Si tenemos distintos procedimientos (Sub y/o Function) y en todos ellos empleamos la misma variable ByVal, entonces tomamos un valor en un primer procedimiento y cuando este valor cambie en otro procedimiento, sólo cambiará en el segundo y no en el primero.
Dicho de otro modo, pasamos un valor numérico, este número se copia y se usa, por ejemplo, en una función.... si cambiamos el número, se cambiará en la función.

2 - ByRef - "Por Referencia":
Si tenemos distintos procedimientos (Sub y/o Function) y en ambos se emplea la misma variable, con ByRef toma un valor en un primer proceso, si este valor cambia en el segundo, cambiará también en el primero.
Las referencias son instancias de la misma variable; es como la misma variable usada en muchos lugares. Por tanto si la modificamos en la función, lo hará en el resto de nuestra programación.


Hablemos de cuándo emplear uno y otro...
La ventaja de pasar un argumento con ByRef es que el procedimiento puede devolver un valor al código de llamada por medio del argumento.
La ventaja de pasarlo con ByVal es que protege a la variable de los cambios que sobre ella pueda efectuar el procedimiento.

Aunque el mecanismo que usemos para pasar argumentos afecta al rendimiento de nuestras macros, la diferencia es insignificante. Una excepción es cuando se pasa un tipo de valor grande con ByVal (copia todo el contenido de los datos del argumento), en cuyo caso, lo más eficiente es pasarlo como ByRef.

En definitiva, cuando el elemento del código de llamada subyacente al argumento es un elemento no modificable, declararemos el parámetro correspondiente ByVal, ya que ningún código podrá cambiar el valor de un elemento no modificable.
Por contra, si el procedimiento necesita realmente modificar el valor subyacente en el código de llamada, declararemos el parámetro correspondiente ByRef.


Mejor vemos el comportamiento con un ejemplo.
Plantearemos un par de procedimientos iguales, con la única diferencia que en el primer caso se pasará como ByVal y en el segundo como ByRef.


Primer caso: Pasando un argumento Por Referencia (ByRef).

Sub PorReferencia()
Dim dato As Integer
dato = 13

Debug.Print "Dato pto1:=" & dato
'Pasamos la variable Por Valor (ByRef) con valor 13
'a través del procedimeinto SumarPorValor
Call SumarPorReferencia(dato)

'Muestra que el valor= 1013
'ya que se modificó al usar la función SumarPorReferencia
Debug.Print "Dato pto3:=" & dato

End Sub
Sub SumarPorReferencia(ByRef Valor As Integer)
'Modifica la variable
Valor = Valor + 1000
Debug.Print "Dato pto2:=" & Valor
End Sub


Al ejecutar el procedimiento 'PorReferencia' observamos en la ventana de Inmediato las tres etapas por las que pasa la variable:
Dato pto1:=13
Dato pto2:=1013
Dato pto3:=1013

Es decir, observamos la transformación del dato.. al cambiar en el segundo procedimiento 'SumarPorReferencia' lo modifica en el primero...


Veamos la diferencia ahora si empleamos y pasamos la variable Por Valor (ByVal).

Sub PorValor()
Dim dato As Integer
dato = 13

Debug.Print "Dato pto1:=" & dato
'Pasamos la variable Por Valor (ByVal) con valor 13
'a través del procedimeinto SumarPorValor
Call SumarPorValor(dato)

'Muestra que el valor= 13
'no se modificó al usar la función SumarPorValor
Debug.Print "Dato pto3:=" & dato

End Sub
Sub SumarPorValor(ByVal Valor As Long)
'Modifica la variable
Valor = Valor + 1000
Debug.Print "Dato pto2:=" & Valor
End Sub



Al ejecutar el procedimiento 'PorValor' observamos en la ventana de Inmediato las tres etapas por las que pasa la variable:
Dato pto1:=13
Dato pto2:=1013
Dato pto3:=13

Es decir, observamos la NO transformación del dato.. al cambiar en el segundo procedimiento 'SumarPorValor' NO se modifica en el primero...


Un último ejemplo...

Sub PasandoArgumentos()
Dim Valor1 As Integer, Valor2 As Integer

'Damos valores a las variables
Valor1 = 888: Valor2 = 999        'por defecto ambos pasados

Debug.Print "Antes = Valor1: " & CStr(Valor1), "Valor2: " & CStr(Valor2)

'cambiamos el modo en que pasamos los argumentos
Call Cambio(X:=Valor1, Y:=Valor2)
Debug.Print "Después =  Valor1: " & CStr(Valor1), "Valor2: " & CStr(Valor2)
End Sub

Sub Cambio(ByRef X As Integer, ByVal Y As Integer)
'valores después de la llamada
X = 222     'pasado por referencia
Y = 444     'pasado por valor
End Sub



Aquí se observa, en la ventana de inmediato:
Antes = Valor1: 888 Valor2: 999
Después = Valor1: 222 Valor2: 999
como la variable que se ha pasado como valor (ByVal) no cambia (=999)... mientras que la que pasamos como referencia toma el valor modificado por el segundo procedimiento (de 111 a 222).

4 comentarios:

Nota: solo los miembros de este blog pueden publicar comentarios.