índice del curso de VB .NET

Curso de iniciación a la programación
con Visual Basic .NET

Entrega número doce, (30/Mar/2003)
Publicada el 30/Mar/2003 (revisado 10/Ago/2003)


Como te comenté en la entrega anterior, lo que vamos a tratar en esta duodécima entrega será una continuación del tema anterior: la herencia y las clases. Aunque en realidad todas las entregas, al menos las que restan, estarán basadas de una forma u otra en esos mismos conceptos, ya que, prácticamente todo lo que hagamos en .NET estará relacionado con las clases y una gran parte tendrá que ver con la herencia, así que... lo mismo hoy te cuento otra cosa distinta...
Pero no adelantemos acontecimientos, ni nos enfrasquemos en discusiones que al final voy a ganar yo, ya que soy el que escribe y de alguna forma tengo "el poder", je, je.

En serio, en esta entrega del curso de iniciación a la programación con Visual Basic .NET vamos a profundizar un poco en el tema de los procedimientos ya que es algo que necesitaremos conocer más o menos a fondo para poder usarlos en las clases.
También veremos con un poco más de detalles conceptos sobre el ámbito de esos procedimientos, así como otras cosas relacionadas con los procedimientos o lo que es lo mismo: los métodos de las clases y algo sobre las propiedades... aunque no se si me alcanzará a ver todas estas cosas en una sola entrega, así que... sigue leyendo para ver hasta dónde llegamos.

 

Las partes o elementos de un proyecto de Visual Basic .NET

En la primera entrega de este curso de Visual Basic .NET vimos una serie de elementos en los que podemos "repartir" el código de un proyecto. Repasemos un poco para ir enterándonos bien de todo y que no se quede nada colgado...

Empecemos con los ensamblados.

Los ensamblados (assembly)
Para simplificar, un ensamblado es el ejecutable o la librería que podemos crear con VB .NET.
En un ensamblado podemos tener clases, módulos y otros elementos tal como los espacios de nombres.

Los espacios de nombres (namespace)
Los espacios de nombres se usan para agrupar clases y otros tipos de datos que estén relacionados entre sí.
Para acceder a los tipos incluidos en un espacio de nombres hay que indicar el namespace seguido de un punto y el nombre de ese tipo, por ejemplo una clase. Por ejemplo, para acceder a la clase Console que está en el espacio de nombres system, habría que hacerlo así:
System.Console.
Para poder definir nuestros propios espacios de nombres, tendremos que usar la instrucción Namespace seguida del nombre que queramos darle, y para indicar cuando termina ese espacio de nombres, lo indicaremos con End Namespace.
Dentro de un espacio de nombres podemos declarar otros espacios de nombres.

Nota:
Cuando creamos un proyecto de Visual Basic, por defecto se crea un espacio de nombres llamado de la misma forma que el proyecto, aunque si el nombre del proyecto incluye espacios u otros caracteres "raros", estos serán sustituidos por guiones bajos. Todas las declaraciones que hagamos en dicho proyecto se "supondrán" incluidas en ese espacio de nombres, por tanto, para poder acceder a ellas desde fuera de ese proyecto, habrá que usar ese espacio de nombres.
Aunque esta forma automática de crear espacios de nombres "ocultos" podemos cambiarla indicándole al Visual Basic de que no cree un espacio de nombres predeterminado, para ello, en las propiedades del proyecto deberemos dejar en blanco el valor indicado en "Espacio de nombres de la raíz:" de la ficha General, y especificar en el código el nombre que nos interese que tenga el espacio de nombres indicándolo con Namespace y el nombre que queramos.
En en la siguiente figura podemos ver la página de propiedades del proyecto creado en la entrega anterior y el nombre que por defecto le dio Visual Basic al espacio de nombres.


Figura 1, las propiedades Generales del proyecto

 

Los módulos y las clases
En Visual Basic .NET podemos crear clases de dos formas distintas, usando la instrucción Module o usando la instrucción Class, en ambos casos, a continuación de esas instrucciones, se indicará el nombre que tendrá ese elemento.
Tanto los módulos como las clases, deben estar declarados dentro de un espacio de nombres.
Dentro de una clase podemos definir otras clases, pero no podemos definir módulos dentro de otros módulos.

La diferencia entre un módulo y una clase, es que un módulo define todos sus miembros como compartidos (Shared), esto lo veremos con más detalle en otra ocasión, pero te lo explicaré de forma simple, para que no te quedes con la duda:
Como sabemos, cuando queremos crear un objeto basado en una clase, debemos usar New para crear una nueva instancia en la memoria, cada nuevo objeto creado con New será independiente de los otros que estén basados en esa clase. Por otro lado, para usar los elementos contenidos en un módulo, no necesitamos crear una nueva instancia, los usamos directamente y asunto arreglado; esto es así porque esos elementos están compartidos (o si lo prefieres) son estáticos, es decir, siempre existen en la memoria y por tanto no es necesario crear un nuevo objeto. Al estar siempre disponible, sólo existe una copia en la memoria.

Las enumeraciones
Como te expliqué en la entrega 7, las enumeraciones son constantes que están relacionadas entre sí.
Las enumeraciones podemos declararlas a nivel de espacios de nombres o a nivel de clases (y/o módulos).

Las estructuras (Structure)
Las estructuras o tipos definidos por el usuario, son un tipo especial de datos que veremos en otra ocasión con más detalle, pero que se comportan casi como las clases, permitiendo tener métodos, propiedades, etc. (el etcétera lo veremos en esta misma entrega), la diferencia principal entre las clases y las estructuras es que éstas últimas son tipos por valor, mientras que las clases son tipos por referencia.
Las estructuras, al igual que las clases, las podemos declarar a nivel de espacios de nombres y también dentro de otras estructuras e incluso dentro de clases y módulos.

 

Bien, tal como hemos podido comprobar, tenemos un amplio abanico de posibilidades entre las que podemos escoger a la hora de crear nuestros proyectos, pero básicamente tenemos tres partes bien distintas:
-Los ensamblados,
-los espacios de nombres y
-el resto de elementos de un proyecto.

En realidad, tanto los espacios de nombres como las clases, estructuras y enumeraciones estarán dentro de los ensamblados. Pero las clases, estructuras y enumeraciones suelen estar incluidas dentro de los espacios de nombres, aunque, como te he comentado antes, no tiene porqué existir un espacio de nombres para que podamos declarar las clases, estructuras y enumeraciones, aunque lo habitual es que siempre exista un espacio de nombres, el cual es definido de forma explícita al crear un nuevo proyecto.
En el caso de que no definamos un espacio de nombres, y eliminemos el que Visual Basic crea para nosotros (ver figura 1), no habrá ningún espacio de nombres y por tanto las clases o miembros que definamos en el proyecto estarán accesibles a nivel de ensamblado, sin necesidad de usar ningún tipo de "referencia" extra... esto los veremos con más detalle dentro de un momento...

 

Además de los elementos que te he relacionado, dentro de las clases, módulos y estructuras, podemos tener otros elementos (o miembros).

Las partes o elementos de una clase

Tal y como hemos tenido la oportunidad de ver en las entregas anteriores, podemos declarar variables y usar procedimientos. Cuando esas variables y procedimientos forman parte de una clase, módulo o estructura tienen un comportamiento "especial", (realmente se comportan de la misma forma que en cualquier otro lado, pero...), ya que dejan de ser variables y procedimientos para convertirse en "miembros" de la clase, módulo o estructura (e incluso de la enumeración) que lo declare.

Te resumiré los distintos miembros de las clases y por extensión de los módulos y las estructuras:

En las clases podemos tener: campos, propiedades, métodos y eventos.
Los métodos son procedimientos de tipo Sub o Function que realizan una acción.
Los campos son variables usadas a nivel de la clase, es decir son variables normales y corrientes, pero que son accesibles desde cualquier parte dentro de la clase e incluso fuera de ella.
Las propiedades podemos decir que son procedimientos especiales, que al igual que los campos, representan una característica de las clases, pero a diferencia de los campos nos permiten hacer validaciones o acciones extras que un campo nunca podrá hacer.
Los eventos son mensajes que utilizará la clase para informar de un hecho que ha ocurrido, es decir, se podrán usar para comunicar al que utilice la clase de que se ha producido algo digno de notificar.

La diferencia entre los campos y propiedades lo veremos en otra ocasión posterior, así como el tema de los eventos, ya que ahora lo que nos interesa es saber algo más de:

Los procedimientos: métodos de las clases.

Ya hemos estado usando procedimientos en varias ocasiones. De hecho, en toda aplicación de consola existe un procedimiento de tipo Sub llamado Main que es el que se utiliza como punto de entrada del ejecutable.

Los métodos de una clase pueden ser de dos tipos: Sub o Function.
Los procedimientos Sub son como las instrucciones o palabras clave de Visual Basic: realizan una tarea.
Los procedimientos Function además de realizar una tarea, devuelven un valor, el cual suele ser el resultado de la tarea que realizan. Debido a que las funciones devuelven un valor, esos valores se pueden usar para asignarlos a una variable además de poder usarlos en cualquier expresión.

Para que veamos claras las diferencias entre estos dos tipos de procedimientos, en el siguiente ejemplo vamos a usar tanto un procedimiento de tipo Sub y otro de tipo Function:

Module Module1
    Sub Main()
        MostrarS()
        Dim s As String = MostrarF()
        Console.WriteLine(s)
        '
        Console.ReadLine()
    End Sub
    '
    Sub MostrarS()
        Console.WriteLine("Este es el procedimiento MostrarS")
    End Sub
    '
    Function MostrarF() As String
        Return "Esta es la función MostrarF"
    End Function
End Module

La salida producida por este código será la siguiente:

Este es el procedimiento MostrarS
Esta es la función MostrarF

En este módulo tenemos tres procedimientos, dos de tipo Sub y uno de tipo Function, el Sub Main ya lo conocemos de entregas anteriores, pero al fin y al cabo es un procedimiento de tipo Sub y como ya hemos comprobado ejecuta el código que esté entre la definición del procedimiento, el cual empieza con la declaración del procedimiento, que siempre se hace de la misma forma, es decir: usando Sub seguido del nombre del procedimiento y termina con End Sub.
Por otro lado, los procedimientos de tipo Function empiezan con la instrucción Function seguido del nombre de la función y el tipo de dato que devolverá la función, ya que, debido a que las funciones siempre devuelven un valor, lo lógico es que podamos indicar el tipo que devolverá. El final de la función viene indicado por End Function.
Pero como te he comentado, las funciones devuelven un valor, el valor que una función devuelve se indica con la instrucción Return seguido del valor a devolver. En este ejemplo, el valor devuelto por la función MostrarF es el texto que está entrecomillado.

En el procedimiento Main utilizamos el procedimiento Sub usando simplemente el nombre del mismo: MostrarS. Ese procedimiento se usa en una línea independiente; cuando la ejecución del código llegue a esa línea, se procesará el contenido del mismo, el cual simplemente muestra un mensaje en la consola.
Por otro lado, el resultado devuelto por la función MostrarF se asigna a la variable s. Cuando Visual Basic se encuentra con este tipo de asignación, procesa el código de la función y asigna el valor devuelto, por tanto la variable s contendrá la cadena "Esta es la función MostrarF" y tal como podemos comprobar por la salida producida al ejecutar este proyecto, eso será lo que se muestre en la consola.

Cuando los procedimientos de tipo Sub o las funciones (Function) pertenecen a una clase se dicen que son métodos de esa clase. Los métodos siempre ejecutan una acción, y en el caso de las funciones, esa acción suele reportar algún valor, el cual se podrá usar para asignarlo a una variable o para usarlo en una expresión, es decir, el valor devuelto por una función se puede usar en cualquier contexto en el que se podría usar una variable o una constante. Por otro lado, los procedimientos de tipo Sub sólo ejecutan la acción y nada más. (Sí, ya se que todo esto ya lo sabes, sobre todo porque acabo de explicarlo, pero lo repito para que no te queden dudas).

Cuando los procedimientos se convierten en métodos, (porque están declarados en una clase), estos suelen representar lo que la clase (o módulo o estructura) es capaz de hacer. Es decir, siempre representarán una acción de dicha clase.
¿Te ha quedado claro? Espero que sí.

 

Parámetros o argumentos de los procedimientos

Cuando queramos que un procedimiento realice una tarea, es posible que necesitemos indicarle alguna información adicional. Esa información se suele indicar mediante parámetros o argumentos. Los argumentos pasados a los procedimientos se indican a continuación del nombre del procedimiento y deben estar incluidos dentro de los paréntesis que siempre hay que usar con los procedimientos.
Por ejemplo, el método WriteLine de la clase Console permite que se indiquen mediante parámetros (o argumentos) los datos a mostrar en la consola.

Para indicar que un procedimiento acepta argumentos estos se indicarán de la siguiente forma:
<tipo procedimiento> <nombre del procedimiento>([<parámetro>][,<parámetro>])
Para verlo de forma clara, supongamos que tenemos un procedimiento llamado Saludar, al cual hay que pasarle un parámetro de tipo cadena. Dicho procedimiento usará ese parámetro como parte de un mensaje que tendrá que mostrar por la consola. Sería algo como esto:

Sub Saludar(ByVal nombre As String)
    Console.WriteLine("Hola " & nombre)
End Sub

En este ejemplo, nombre sería el parámetro o argumento del método Saludar.
Para usar este procedimiento lo podríamos hacer de esta forma:

Saludar("Guillermo")

Si necesitamos que el procedimiento reciba más de un parámetro, se podrán indicar separándolos unos de otros con una coma. Veamos el método anterior en el que se indica además del nombre el tipo de saludo a realizar:

Sub Saludar(ByVal tipoSaludo As String, ByVal nombre As String)
    Console.WriteLine(tipoSaludo & " " & nombre)
End Sub

Este procedimiento con dos parámetros lo usaríamos de la siguiente forma:

Saludar("Hello", "Guille")

 

Parámetros por valor y parámetros por referencia

Normalmente, cuando se pasa un parámetro a un procedimiento, éste se suele pasar o indicar lo que se llama por valor, es decir, el parámetro será una copia del valor indicado, en el caso de Saludar("Guillermo") la constante "Guillermo" se copiará en la variable nombre. Cualquier cambio que se realice dentro del procedimiento a la variable nombre no afectará al parámetro. Seguramente dirás que sería imposible cambiar el contenido de una constante, que es al fin y al cabo lo que se le pasa al procedimiento Saluda en el ejemplo que te he mostrado, y estarías en lo cierto... (se ve que vas aprendiendo). Pero... (je, je, ¿te creías que no iba a haber un pero...), ¿que ocurre si ese parámetro es una variable en lugar de una constante? Por ejemplo, si tenemos el siguiente procedimiento:

Sub Saludar2(ByVal nombre As String)
    nombre = "Hola " & nombre
    Console.WriteLine(nombre)
End Sub

Al que llamamos con este otro código:

Sub PruebaSaludar2()
    Dim elNombre As String
    elNombre = "Guillermo"
    Saludar2(elNombre)
    '
    ' ¿qué valor mostraría esta línea?
    Console.WriteLine(elNombre)
End Sub

¿Que valor tendrá la variable elNombre después de llamar al procedimiento Saludar2?

La respuesta es: el que tenía antes de llamar al procedimiento.
¿Por qué? Si el valor se ha modificado...
Porque se ha pasado por valor (ByVal) y por tanto, lo que se ha pasado al procedimiento es una copia del contenido de la variable elNombre, con lo cual, cualquier cambio que se realice en la variable nombre sólo afectará a la copia, no al original.

Pero si queremos que el procedimiento pueda modificar el valor recibido como parámetro, tendremos que indicarle al Visual Basic .NET de que lo pase por referencia, para ello habrá que usar la instrucción ByRef en lugar de ByVal.
Veámoslo con un ejemplo:

Sub Saludar3(ByRef nombre As String)
    nombre = "Hola " & nombre
    Console.WriteLine(nombre)
End Sub

Sub PruebaSaludar3()
    Dim elNombre As String
    elNombre = "Guillermo"
    Saludar3(elNombre)
    '
    ' ¿qué valor mostraría esta línea?
    Console.WriteLine(elNombre)
End Sub

En esta ocasión la variable elNombre contendrá "Hola Guillermo".
La explicación es que al pasar la variable por referencia (ByRef), el VB lo que ha hecho es asignar a la variable nombre del procedimiento la misma dirección de memoria que tiene la variable elNombre, de forma que cualquier cambio realizado en nombre afectará a elNombre.
¿Te acuerdas de lo que hablamos sobre los tipos por valor y los tipos por referencia? Pues esto sería algo como lo que ocurre con los tipos por referencia.

Nota:
En Visual Basic .NET, de forma predeterminada, los parámetros serán ByVal (por valor), a diferencia de lo que ocurría con las versiones anteriores de Visual Basic que eran por referencia (ByRef). Es decir, si declaras un parámetro sin indicar si es ByVal o ByRef, el VB.NET lo interpretará como si fuera ByVal.

Resumiendo:
Las variables indicadas con ByVal se pasan por valor, es decir se hace una copia del contenido de la variable o constante y es esa copia la que se pasa al procedimiento.
Por otro lado, los parámetros indicados con ByRef se pasan por referencia, es decir se pasa al procedimiento una referencia a la posición de memoria en la que está el contenido de la variable en cuestión, por tanto cualquier cambio efectuado a la variable dentro del procedimiento afectará a la variable indicada al llamar al procedimiento.

Por supuesto, todo esto es aplicable tanto a los procedimientos de tipo Sub como a los de tipo Function. En el caso de las funciones, el utilizar parámetros ByRef nos permiten devolver más de un valor: el que devuelve la función más los que se puedan devolver en los parámetros declarados con ByRef.

Ni que decir tiene que en un procedimiento se pueden usar indistintamente parámetros por valor como por referencia, es decir, podemos tener tanto parámetros declarados con ByVal como con ByRef, y, por supuesto, sólo los indicados con ByRef podrán cambiar el contenido de las variables indicadas al llamar al procedimiento.

 

Vamos a dejar la cosa aquí.
En la próxima entrega seguiremos con el tema de los parámetros, en esa ocasión veremos cómo indicar parámetros opcionales, es decir, parámetros que no son obligatorios indicar al llamar al procedimiento y si te preguntas porqué dejo ese tema que en realidad estaría relacionado con lo que acabamos de ver, te diré que es porque también te voy a explicar otras cosas relacionadas con la forma de declarar los procedimientos y cómo podemos tener varias "versiones" de un mismo procedimiento que tenga distinto número de parámetros... y como el tema ese será algo extenso... prefiero dejarlo para otra entrega... que esta ya ha dado mucho de sí y no quiero que tu cabecita termine por explotar... je, je... así que, paciencia y a esperar a la Semana Santa que será la fecha en la que seguramente publicaré la entrega número trece... espero que no seas una persona supersticiosa y no te la saltes, ya que seguro que te perderás cosas interesantes...

No quiero que te vayas sin que veamos un pequeño resumen de lo que te he explicado en esta entrega número doce, aunque haya sido por encima o de pasada:
Los elementos de un proyecto de .NET: ensamblados, espacios de nombres, clases, módulos, estructuras; además de los miembros de las clases: campos, propiedades y métodos. Y lo que seguro que no ha sido de pasada es el tema de los procedimientos o métodos, así como los parámetros o argumentos de dichos procedimientos y la forma en que los podemos declarar: ByVal y ByRef.

Nos vemos.
Guillermo
Nerja, 18, 23 de Febrero, 2 y 30 de Marzo de 2003


ir a la entrega anterior ir al índice del curso vb.NET
 
ir al Glosario .NET
ir a la siguiente entrega (o al índice si esta es la última)

la Luna del Guille o... el Guille que está en la Luna... tanto monta...