Curso Sobre Visual
Curso Sobre Visual
VISUAL
BASIC.NET
GLOSARIO
Palabra Descripción
A
Básicamente un ensamblado es un
programa (EXE) o un componente (DLL),
Assenbly en el que además del código compilado
(ensamblado) tiene un manifiesto o definición de que es
lo que contiene dicho ensamblado: tipos
de datos, dependencias, versión, etc.
B
C
Introducción:
Debido a que la nueva versión de Visual Basic no es sólo una mejora con respecto a
las versiones anteriores, sino que cambia mucho, tanto como si de otro lenguaje de
programación se tratara, creo que se merece que se explique de forma más o
menos fácil de comprender para que cualquiera que se decida a elegirlo como su
lenguaje de programación lo tenga, valga la redundancia, fácil.
Tan fácil como permitan las circunstancias, y además, (para que esto de estudiar
no resulte algo tedioso), tan ameno como me sea posible, ya que las cosas se
pueden explicar de muchas formas y, a pesar de parecer que peco de falta de
modestia, estoy seguro que este curso de iniciación a la programación con Visual
Basic .NET te va a resultar ameno y fácil de comprender... ¡seguro!
Pero no sólo vas a aprender a programar con VB.NET, sino que al estar "basado" en
el .NET Framework, conocerás lo suficiente de este marco de desarrollo que
podrás atreverte con otros lenguajes .NET, tales como c#, ya que al fin y al cabo,
el corazón de los lenguajes .NET es el .NET Framework.
Para ir aclarando ideas, veamos algunos conceptos que habrá que tener claros
desde el principio:
Visual Basic .NET usa una jerarquía de clases que están incluidas en el .NET
Framework, por tanto conocer el .NET Framework nos ayudará a conocer al propio
Visual Basic .NET, aunque también necesitarás conocer la forma de usar y de hacer
del VB ya que, aunque en el fondo sea lo mismo, el aspecto sintáctico es diferente
para cada uno de los lenguajes basados en .NET Framework, si no fuese así, ¡sólo
existiría un solo lenguaje!
Voy a intentar dar una respuesta que sea fácil de "asimilar", a ver si lo consigo...
Primer intento, lo que se dice en el eBook Microsoft .NET Framework, cuya
versión en Castellano puedes conseguir usando este link: (este link está
actualizado, al menos a fecha de hoy 10 de noviembre de 2002)
Aunque dicho libro está basado en la Beta1 es válido para aclarar conceptos sobre
lo que es el .NET Framework además de otros conceptos como el Common
Language Runtime (CLR), Common Language Specification (CLS), Common Type
System (CTS), Microsoft Intermediate Language (MSIL), los ensamblados o
assemblies, así como sobre ASP.NET, conceptos que si bien no son imprescindibles
para poder usar Visual Basic .NET, es conveniente leer un poco sobre ellos, para no
estar totalmente perdidos cuando nos encontremos con esos conceptos...
Antes de empezar a ver el código, un par de aclaraciones, que aunque ahora puede
ser que te suenen a chino, (si eres chino o conoces ese idioma, sólo decirte que es
una frase hecha: "me suena a chino" es como decir: "no sé de que me estás
hablando"), pronto serán tan usuales que acabarás por asimilarlas como si toda tu
vida las hubieras estado usando... o casi...
Tipos de ejecutables.
Con Visual Basic .NET puedes crear básicamente estos dos tipos de ejecutables:
de consola, no gráfico, al estilo del viejo MS-DOS, y
gráficos, como los que normalmente estamos acostumbrados a ver en Windows.
Existen otros tipos de aplicaciones que se pueden crear con Visual Basic .NET:
aplicaciones ASP.NET, (realmente no es una aplicación o ejecutable, sino un
compendio de distintos tipos de elementos...), servicios Web, servicios Windows,
etc.
Para ir calentando motores, creo que lo mejor es empezar creando una pequeña
aplicación con VB.NET, después iremos aclarando los distintos conceptos usados...
así te resultará menos complicado todo lo que tengo preparado para ti.
Inicia el Visual Studio .NET, por defecto te mostrará la "página de inicio" desde la
cual pueden crearse nuevos proyectos o bien abrir alguno de los más recientemente
abiertos. Pulsa en Nuevo proyecto
Te mostrará los diferentes tipos de proyectos que se pueden crear, en el panel
izquierdo selecciona Proyectos de Visual Basic (Visual Basic Projects) y de los que
muestra en el panel de la derecha, selecciona Console Application
Para que nos entendamos, podríamos decir que un assembly es una librería
dinámica (DLL) en la cual pueden existir distintos espacios de nombres. Aunque
esto es simplificar mucho, por ahora nos vale.
Un ensamblado o assembly puede estar formado por varios ficheros DLLs y EXEs,
pero lo más importante es que todos los ensamblados contienen un manifiesto (o
manifest), gracias al cual se evitan muchos de los quebraderos de cabeza a los que
Windows nos tiene acostumbrados, al menos en lo referente a las distintas
versiones de las librerías y ejecutables, seguramente habrás oído hablar de las DLL
Hell (o librerías del demonio) expresión que se usa cuando hay incompatibilidad de
versiones entre varias librerías que están relacionadas entre si.
Por ejemplo, supongamos que tenemos una librería DLL que en su primera versión
contenía X funciones. Al tiempo, se crea la segunda versión de dicha librería en la
que se cambian algunas funciones y se añaden otras nuevas, para mejorar el
rendimiento de las funciones contenidas en esa librería se usa otra DLL que es
usada por algunas de las funciones contenidas en esa segunda versión. Esa otra
librería puede ser una librería del sistema, la cual a su vez se actualiza con nueva
funcionalidad y puede que dicha funcionalidad dependa a su vez de una tercera
librería.
Resulta que instalamos un programa que usa las últimas versiones de todas estas
librerías. Todo va bien, el programa funciona a las mil maravillas y nosotros
estamos "supersatisfechos" de ese programa que no se cuelga ni una sola vez...
(¿quién habrá hecho ese programa tan maravilloso?, sin comentarios...)
Ahora llega a nuestras manos otra aplicación que necesitamos instalar y la
instalamos, pero resulta que esa aplicación usa la primera versión de nuestra
famosa librería. Si el programa de instalación está bien hecho, no ocurrirá nada
malo, ya que al descubrir que tenemos una versión más reciente de la librería, deja
la que ya está instalada. Probamos el programilla de marras y todo funciona bien.
Probamos el maravilloso programa anterior y también funciona bien. ¿Cual es el
problema? Por ahora ninguno, pero espera...
Después instalamos un programa que usa una de las librerías del sistema u otra
que también usa nuestra "flamante" librería, pero ese programa se ha instalado de
"mala manera", bien porque el programa de instalación sea una caca o bien porque
simplemente se ha instalado mal... como quiera que ha instalado una librería
anterior a la que nuestros dos maravillosos ejecutables usan, se puede dar el caso
de que ninguno de los dos programas funcionen correctamente... esto ocurrió
cuando salió el Internet Explorer 4 y a más de uno nos trajo de cabeza, aunque
también ha ocurrido con otros programas que no han tenido en cuenta a la hora de
instalar que ya existe una versión más reciente de la librería. Por suerte, esto ya es
menos común que hace unos años, sobre todo si los programas de instalación están
creados con el Windows Installer o estamos usando el Windows 2000/XP.
Pero es que .NET mejora aún esa "imposibilidad" de meter la pata ya que cada
assembly contiene un manifiesto en el cual se indica:
-el nombre y la versión del assembly,
-si este assembly depende de otros ensamblados, con lo cual se indica hasta la
versión de dichos ensamblados,
-los tipos expuestos por el assembly (clases, etc.),
-permisos de seguridad para los distintos tipos contenidos en el assembly.
También se incluyen en los assemblies los datos del copyright, etc.
Ejemplo de ensamblados compartidos son los que definen las clases (tipos) usados
por el propio .NET Framework.
CAPITULO 2
Para que se muestre el formulario (Form1), haz doble click en dicho "elemento" del
explorador de soluciones.
Lo que vamos a hacer ahora es cambiar la separación de los puntos que se
muestran en el formulario, ese "grid" o grilla, servirá para ajustar los controles (ya
sabrás de qué hablo) que se añadan a dicho formulario.
Por defecto la separación es de 8x8 puntos o pixels... y vamos a ponerlo en 4x4.
Selecciona el formulario, (el cual se supone que ya si puedes verlo), simplemente
con hacer un click en él es suficiente. verás que tiene este aspecto:
Figura 3
Realmente no tendrá ese aspecto, salvo que tengas el Windows XP, pero al aspecto
que me refiero es al de la separación de los puntos.
Figura 4
Fíjate que después de haber cambiado esos valores, los mismos se muestran en
"negrita", indicándonos de esta forma que son valores que nosotros hemos
asignado, los que tiene por defecto. También te habrás fijado que ahora el "grid"
tiene los puntos más juntos. Si no te gusta así como está ahora, ponlos a tu gusto.
Yo los configuro a 4x4, pero tu eres libre de ponerlos como mejor te plazca...
El aspecto habrá cambiado a este otro, te muestro el aspecto de Windows XP y el
del Windows 2000 (clásico), aunque en resumidas cuentas, lo que hay que ver es
que los punticos esos están más arrejuntaos...
Figura 5
Figura 6
Para añadir controles al formulario, hay que usar la barra de herramientas que está
situada en la parte izquierda del IDE de Visual Studio .NET, por ejemplo para añadir
una etiqueta (Label) y una caja de texto (TextBox), simplemente haz doble-click
sobre esos elementos de la barra de herramientas y se añadirán al formulario.
Para poder situarlos en el sitio que más te apetezca, simplemente pulsa en ellos y
manteniendo el ratón pulsado, ponlos donde más te guste... todo esto deberías
saber hacerlo, ya que son cosas básicas de Windows, así que en próximas
ocasiones no esperes tantas explicaciones... ¿vale?
Añade ahora un botón (Button) y sitúalo debajo del textbox. Para cambiarle el texto
que muestra el botón, que por defecto será el nombre que el IDE le ha asignado, en
esta ocasión será Button1, hay que usar la ventana de propiedades, en esta ocasión
el elemento que nos interesa de esa ventana de propiedades es Text, escribe
Mostrar y cuando pulses Intro, verás que el texto del botón también ha cambiado.
Si antes has trabajado con el Visual Basic "clásico", esa propiedad se llamaba
Caption. Decirte que ya la propiedad Caption no existe en ningún control, ahora se
llama Text.
Haz lo mismo con la etiqueta, recuerda que tienes que seleccionarla (un click) para
que se muestren las propiedades de la etiqueta, escribe Nombre: y pulsa intro.
Ahora vamos a escribir código para que se ejecute cada vez que se haga click en el
botón que hemos añadido.
Para ello, selecciona el botón Mostrar y haz doble click en él, se mostrará una
nueva ventana, en este caso la ventana de código asociada con el formulario que
tenemos en nuestro proyecto.
Te mostrará esto: (realmente te mostrará más cosas, pero por ahora centrate sólo
en este código)
End Sub
Un detalle: el nombre Button1 es porque hemos dejado el nombre que por defecto
el IDE de Visual Studio asigna a los controles que se añaden a los formularios. Si
quieres que se llame de otra forma, simplemente muestra el formulario, selecciona
el control al que quieres cambiarle el nombre, busca la propiedad Name en la
ventana de propiedades y cambia el nombre que allí se sugiere por el que tu
quieras... o casi, ya que para los nombres de los controles, así como para otras
cosas que usemos en Visual Basic hay que seguir ciertas normas:
- El nombre debe empezar por una letra o un guión bajo.
- El nombre sólo puede contener letras, números y el guión bajo.
Por tanto, si quieres cambiarle el nombre al evento que se produce cuando se hace
click en el botón, escribe ese nombre después de Private Sub, aunque no es
necesario cambiar el nombre del evento, ya que, al menos por ahora, nos sirve tal
y como está.
Antes de explicarte que es lo que estamos haciendo, pulsa F5 para que se ejecute
el código que hemos escrito o pulsa en el botón "play" que está en la barra de
botones.
Escribe algo en la caja de textos, que por defecto tendrá "TextBox1", (que es el
valor que por defecto Visual Basic le asigna a la propiedad Text de dicho control),
pulsa en el botón Mostrar y verás que se muestra un cuadro de diálogo diciéndote
Hola y a continuación lo que hayas escrito en el TextBox.
Algo así:
Figura 7
Pues sí: ¡esta es tu primera aplicación para Windows creada con el Visual Basic
.NET!
(pfiuuuuu, pfiiiiuuuu, bang! (sonido de cohetes y esas cosas))
Ahora vamos a añadir otro botón, el cual se llamará cmdCerrar y el texto que debe
mostrar es: Cerrar.
Y este es el código que debe ejecutar cuando se haga click en el... ¿te atreves a
hacerlo sin ayuda? Si no te atreves, tendrás que hacerlo sin mi ayuda... creo que
ya sabes cómo hacerlo... venga, no me seas holgazán... (u holgazana,
puntualicemos).
Me.Close()
Ahora veamos con detalle el código que hemos usado en los dos eventos:
En el método del evento Click del botón cerrar hemos escrito: Me.Close()
Me representa al objeto o clase Form1 (el formulario) y el método Close lo que
hace es cerrar el formulario, igual que cuando pulsamos en el botón cerrar del
formulario.
Vamos a ver ahora el código completo del evento Click del botón cerrar, así como
los pasos que se supone que has realizado para poder añadirlo al formulario, tal y
como te dije un poco antes:
End Sub
Nota: Para que tengas más fácil buscar las propiedades en la ventana de
propiedades, puedes hacer que se muestren por orden alfabético, simplemente
pulsando en el botón AZ:
Figura 8
Para terminar esta segunda entrega, vamos a crear un proyecto igual al que hasta
ahora hemos usado, pero con el lenguaje C# (c sharp), para que veas que en
algunas cosas es igual de sencillo usarlo, aunque en algunos aspectos es más
estricto que el Visual Basic y así de camino te demuestro que no era falso eso que
te dije de que en el entorno integrado del Visual Studio .NET podíamos tener varios
proyectos en varios de los lenguajes soportados por .NET.
Figura 9
Figura 10
Esto mismo también es válido si queremos usar esa función desde Visual Basic.
Te resumo lo que hasta ahora hemos aprendido: (dice hemos, porque él también
va experimentando mientras te explica, que conste, no sea que te creas que el
Guille ha nacido sabiendo...)
Hacer esto con las versiones anteriores de Visual Basic era un trabajo duro y
algunas veces bastante engorroso, que requería bastante código y casi nunca se
lograba lo que queríamos... ¿que no sabes de qué estoy hablando? bueno, no te
preocupes que, aunque ahora no sepas la utilidad de todo lo que te voy a explicar
pueda tener, en algún momento lo necesitarás y aquí tendrás la explicación de
cómo hacerlo.
Figura 1
comprobaremos que la ventana se ha agrandado, pero los controles que hay en ella
siguen teniendo el mismo tamaño y la misma posición que en la ventana anterior.
Figura 3
Para que esto sea posible de forma automática, hay que hacer unas cuantas
asignaciones a los controles, de forma que podamos indicarle qué tienen que hacer
cuando el tamaño de la ventana varíe.
En este ejemplo, lo correcto sería que:
- La caja de texto superior se agrandase hacia el lado derecho.
- El botón Añadir se moviese hacia el extremo derecho del formulario.
- La lista se ajustara al ancho y también al alto de la ventana.
Todo esto lo podemos hacer en tiempo de diseño, es decir cuando tenemos el
formulario en el entorno integrado o bien lo podemos codificar dentro del propio
formulario, dónde hacerlo queda a tu criterio, yo te voy a explicar cómo hacerlo en
los dos casos y después tu decides cómo hacerlo.
Figura 4
Por último vamos a anclar el listbox... ¿cómo? ¿que quieres intentarlo por tu
cuenta? vale... me parece bien...
Sólo decirte que el listbox debe hacerse grande tanto hacia la derecha como hacia
la izquierda e incluso cuando se estira el formulario desde la parte inferior, pero en
la parte superior debe mantenerse en la misma posición.
Vamos a complicar un poco más la cosa y vamos a añadirle otro botón. En este
caso, dicho botón estará en la parte inferior derecha del formulario, será el botón
cerrar y al pulsarlo hay que cerrar el formulario... ¿recuerdas cómo se hace?
¡Exacto! usando Me.Close en el evento Click de dicho botón, el cual yo voy a llamar
cmdCerrar.
Como te decía, este botón se debería anclar en la parte inferior derecha, por tanto
los valores que hay que asignar en Anchor son precisamente esos: Right y Botton
(derecha y abajo).
Como habrás notado, con el Label1 no hay que hacer nada, ya que por defecto el
tamaño se ajusta por la derecha y por abajo, por tanto se quedará en la misma
posición... aunque realmente está anclada arriba y a la izquierda, que son los
valores por defecto de la propiedad Anchor, por eso no es necesario asignarle nada.
Ahora vamos a ver cómo hacerlo mediante código... ¿que qué sentido tiene hacerlo
por código? pues... esto... bueno, porque puede ser que quieras hacerlo... (je, je,
¡tan pillao guille!)
Haz que se muestre el formulario y haz doble-click en él, (no hagas doble-click en
ninguno de los controles que tiene el formulario, sino en el propio formulario).
Cuando estamos en el IDE y hacemos doble-click en el formulario se muestra el
evento Form_Load que es el que se ejecuta cuando el formulario "se carga" en la
memoria, justo antes de mostrarse, por tanto aquí es un buen sitio para hacer
algunas "inicializaciones" o asignaciones que nuestro formulario necesite.
Por ejemplo, podemos limpiar el contenido de la lista, el de la caja de texto, etc. e
incluso hacer las asignaciones para que los controles se queden "anclados" en la
posición que nosotros le indiquemos.
Lo primero que hay que notar es que Handles es la palabra que le indica al
compilador de Visual Basic .NET qué evento es el que "manipula" o maneja este
procedimiento. Siempre lo digo... o lo pienso, que los anglosajones (los que hablan
inglés) lo tienen muchísimo más fácil que los que no hablamos la lengua de
Shakespeare, ya que para ellos eso de Handles es una palabra que tiene sentido y
precisamente quiere decir eso "maneja", manipula, se encarga de , etc. con lo cual
tienen superclaro que es lo que quiere decir esa palabreja... Esto, (la declaración
del Sub), se encarga de manejar el evento Load del objeto MyBase.
Aunque también hay que decirlo... algunos de esos "hablantes" de inglés, aún a
pesar de tener el lenguaje (idioma) a su favor... no tienen ni repajolera idea de
Basic... en fin... ese consuelo nos queda... así que, no te desanimes y tira "pa
lante", que "pa trás" no hay que ir ni para coger carrerilla...
El objeto MyBase se refiere al objeto base del que se deriva el formulario, recuerda
que en .NET todo está basado en objetos y en programación orientada a objetos y
todo objeto se deriva de un objeto básico o que está más bajo en la escala de las
clases... es decir, un formulario se basa en la clase
System.Windows.Forms.Form y a esa clase es a la que hace referencia el objeto
MyBase, mientras que Me se refiere a la clase actual, la que se ha derivado de
dicha clase Form o por extensión a cualquier clase, como veremos en futuras
ocasiones.
¿Cómo? ¿que has acabado por liarte más? ¿que no has captado lo que acabo de
decir?
Pues lo siento por ti... pero no te lo voy a explicar mejor... simplemente déjalo
estar y poco a poco acabarás por comprenderlo... (je, je, ¡que malo que soy
algunas veces!)
Veamos ahora el código prometido para hacer que los controles se anclen al
formulario de forma que se adapten al nuevo tamaño del mismo:
La propiedad que hace eso posible es AutoScale, esta propiedad sólo está
disponible en los formularios y por defecto tiene el valor True (verdadero), por
tanto los formularios, sin necesidad de que hagamos nada, se auto ajustarán al
tamaño de las fuentes.
Esto no lo he comprobado, pero me fío de lo que dice la documentación, (aunque
esté en inglés)
Figura 5
Si quieres probarlo, quita el código que hemos añadido, o coméntalo todo, para ello
selecciona todas las líneas que quieres comentar, las que asignan los valores a la
propiedad Anchor y en el menú Edición, selecciona Avanzado y Comentar Selección,
también puedes usar las teclas: Ctrl+K seguidas de Ctrl+C, (yo tengo las opciones
de Comentar Selección y Quitar los comentarios puestas en la barra de
herramientas, ya que de vez en cuando las utilizo para hacer alguna que otra
prueba y no tener que borrar el texto que quiero quitar)
Figura 6
Todas estas cosas ya nos hubiese gustado tenerlas en las versiones anteriores de
Visual Basic, ya que para hacerlo o bien te tenías que "comer" el coco o bien te
tenías que crear un control que hiciera ese trabajo...
Sólo comentarte que los Ors que se están utilizando sirven para "sumar" y el
resultado sería el mismo que si usáramos el signo de suma, pero la razón de usar
Or es porque lo que queremos hacer es una suma de bits... realmente da lo mismo
usar la suma que Or en este caso, pero... dejemos el Or que es lo apropiado... y no
me preguntes porqué... ya que, aunque no te hayas enterado, te lo acabo de
explicar... je, je.
¡UF! vaya entrega más larga y en resumidas cuentas ¿qué es lo que has aprendido?
Sí... claro que no ha sido en vano... ¿te crees que iba a gastar yo tanto tiempo para
explicarte algo que no sirve para nada...
De todas formas, vamos a ver algo de código para que no se te quede mal sabor de
boca.
Lo que te voy a explicar es lo que está dentro del evento Click, ya que lo de
Handles te lo he explicado hace un rato.
Si te has fijado en el código que te mostré del evento Form_Load, seguramente
habrás visto que teníamos:
Me.ListBox1.Items.Clear()
Fíjate que en este caso no es el evento Click, como era de esperar (y como es en
las versiones anteriores de VB, incluso como era en la Beta 1 de vb.NET), sino que
el evento en cuestión es SelectedIndexChanged.
Ya te he comentado que lo que podemos añadir a los elementos del listbox son
objetos, pero lo que a nosotros nos interesa mostrar es el "texto" de dicho
elemento, ya que no nos interesa otra cosa, más que nada porque lo que hemos
añadido son textos y no objetos... aunque, como ya te he comentado en otras
ocasiones TODO lo que se maneja en .NET son objetos, incluso las cadenas de
textos son objetos... pero... en fin... dejemos las cosas así por ahora.
Lo que en este evento hacemos es asignar a la caja de textos el texto del elemento
seleccionado: la propiedad SelectedItem representa al elemento seleccionado y
GetItemText es una función, (o método), que devuelve el texto (o la
representación en formato texto del elemento indicado dentro de los paréntesis).
Figura 7
End Sub
Este sería el código a usar para eliminar el elemento que está seleccionado:
Pero como no estoy dispuesto a terminar aún esta tercera entrega... para que te
empaches y no te entre el mono cuando veas que tardo en publicar la siguiente
entrega... vamos a permitir múltiple selección de elementos y vamos a ver cómo
borraríamos los elementos que estén seleccionados.
¿Cómo hacer que un ListBox permita múltiple selección? ya que por defecto
sólo se puede seleccionar un elemento a un mismo tiempo.
Para que un ListBox permita múltiple selección de elementos, hay que asignar a la
propiedad SelectionMode el valor MultiExtended, por tanto selecciona el ListBox
y en la ventana de propiedades, asigna dicho valor a la propiedad SelectionMode.
Ahora tenemos que hacer más cosas cuando se detecta la pulsación de la tecla
suprimir en el evento KeyDown, ya que tenemos que saber qué elementos están
seleccionados para poder borrarlos.
Lo primero que tenemos que hacer es recorrer todos los elementos del ListBox para
saber si está o no seleccionado, pero ese recorrido hay que hacerlo desde atrás
hacia adelante... ¿por qué? porque si lo hiciéramos desde el principio de la lista, al
eliminar un elemento de dicha lista, el número de elementos variaría y tendríamos
problemas cuando llegásemos al final, ya que no será el mismo número de
elementos después de haber borrado alguno... mientras que al recorrer los
elementos desde el final hacia adelante, no importará que borremos alguno del
final, ya que el siguiente que comprobaremos estará más al principio que el recién
borrado y no tendremos problemas... sé que no te has enterado, pero no importa,
confía en mi, (¡que remedio te queda!), y ya tendrás tiempo de comprobarlo por tu
cuenta.
Dim i As Integer esto le indica al VB que vamos a usar un número y que ese
número lo guarde en la variable i, de esto también tendrás ocasión de enterarte
mejor, por ahora dejémoslo así.
With ListBox1 el With se utiliza para simplificarnos las cosas y lo que viene a
significar es que donde se tendría que usar el objeto ListBox1, se ponga un punto...
más o menos... ahora veremos cómo se escribiría el código sin usar el With
ListBox1.
¿Qué? ¿te estás liando? ¿Sí? Pues no desesperes, que aunque todo esto sea un
follón, dentro de un par de años acabarás por comprenderlo... je, je... no, que no...
que no será tanto tiempo, confía en el Guille...
Next indica que continúe el bucle o la cuenta que estamos llevando con la variable
i.
De esta forma, al haber usado el Step -1, lo que hacemos es contar hacia atrás y si
por ejemplo i valía 3, al llegar aquí, valdrá 2, es decir restamos 1: el valor indicado
en Step
End With indica hasta dónde llega el tema ese del With ListBox1
End If le dice que hasta aquí llega la comprobación que hicimos de si la tecla
pulsada era la de suprimir.
¡Menudo embrollo!
¿De verdad piensas que no acabarás nunca aprendiendo?
Date una oportunidad y de paso dámela a mi también... y confía, que dentro de
poco todo esto te parecerá una tontería.
Es decir, tendríamos que haber escrito el nombre del objeto en cada una de las
partes correspondientes.
Por tanto, si usamos With Objeto, podemos sustituir a Objeto por el punto,
siempre y cuando ese punto, (y la propiedad o método correspondiente), esté
dentro del par With... End With.
Para que un botón intercepte la tecla Intro, hay que decirle al VB que ese botón es
el botón de aceptar, lo mismo ocurre con la "captura" de la tecla ESC, pero en lugar
de ser el botón por defecto, será el botón de cancelación.
Selecciona el formulario y en la ventana de propiedades busca la propiedad
AcceptButton, habrá una lista desplegable con los botones disponibles en el
formulario, (tal y como te muestro en la figura 8), selecciona cmdAdd y ya está.
Ahora al pulsar Intro es lo mismo que si pulsaras en el botón Añadir.
Figura 8
Las variables son "nombres" que pueden contener un valor, ya sea de tipo
numérico como de cualquier otro tipo.
Esos nombres son convenciones que nosotros usamos para facilitarnos las cosas, ya
que para los ordenadores, (o computadores, según te guste o estés acostumbrado
a llamarlos), una variable es una dirección de memoria en la que se guarda un
valor o un objeto, te vuelvo a recordar por enésima vez que en .NET todo es un
objeto.
Existen distintos tipos de valores que se pueden asignar a una variable, por
ejemplo, se puede tener un valor numérico o se puede tener un valor de tipo
alfanumérico o de cadena, (string para los que inventaron esto de los lenguajes de
programación), pero en cualquier caso, la forma de hacerlo siempre es de la misma
forma... al menos en .NET ya no hay las distinciones que antes había en las
versiones anteriores de Visual Basic... no voy a entrar en detalles, pero si has
trabajado anteriormente en VB con objetos, sabrás de que estoy hablando.
i = 10
En este caso i es la variable, mientras que 10 sería una constante, (10 siempre vale
10), la cual se asigna a esa "posición" de memoria a la que llamamos i, para
facilitarnos las cosas... ya que, realmente no nos interesa saber dónde se guarda
ese valor, lo único que nos interesa es saber que se guarda en algún lado para en
cualquier ocasión poder volver a usarlo.
Pensarás, que tal y como están las cosas, i también vale 10, por tanto ¿por qué no
es una constante? por la sencilla razón de que podemos alterar su valor, por
ejemplo, si en cualquier ocasión posterior hacemos esto: i = 25, el valor de la
variable i cambiará, de forma que el valor anterior se esfumará y el que se
almacenará será el nuevo.
Para que vayas entrando en calor, te diré que las cadenas de caracteres (o valores
alfanuméricos) se representan por algo que está contenido dentro de comillas
dobles: "hola" sería una constante de cadena, ya que "hola" será siempre "hola", lo
mismo que el número 10 siempre vale 10.
Para asignar esa constante de caracteres a una variable, se haría algo como esto:
s = "Hola"
De esta forma, la variable s contiene el valor constante "Hola".
Podemos cambiar el valor de s, asignándole un nuevo valor: s = "adiós", pero no
podemos cambiar el valor de "Hola", ya que si lo cambiamos dejará de ser "Hola" y
se convertirá en otra cosa...
Como ya te he dicho, existen distintos tipos de datos que vb.NET maneja, para que
podamos usar una variable para almacenar cualquiera de esos tipos, tenemos que
decirle al VB que "reserve" espacio en la memoria para poder guardarlo.
Esto se consigue mediante la "declaración de variables", es necesario, aunque
no obligatorio, declarar las variables según el tipo de datos que va a almacenar.
Por ejemplo, en el caso anterior, la variable i era de tipo numérico y la variable s
era de tipo cadena. Esas variables habría que declararlas de la siguiente forma:
(después veremos otras formas de declarar las variables numéricas)
Dim i As Integer
Dim s As String
Con esto le estamos diciendo al vb.NET que reserve espacio en su memoria para
guardar un valor de tipo Integer, (numérico), en la variable i y que en la variable s
vamos a guardar valores de cadena de caracteres.
Antes de seguir con esta "retahíla" de conceptos, vamos a ver cuales son los tipos
de datos que .NET soporta y esas cosillas, así veremos los tipos de variables que
podemos tener en nuestros programas.
La siguiente tabla te muestra algunos de ellos y los valores mínimos y máximos que
puede contener, así como el tamaño que ocupa en memoria; también te comento
algunas otras cosas, que aunque ahora no te parezcan "aclaratorios", en un futuro
si que lo serán, como por ejemplo a que tipo de datos se puede convertir sin recibir
un mensaje overflow, (o los que se aceptan usando Option Strict), que signo se
puede usar para "aclarar" el tipo de datos que representa, e incluso que signo se
puede usar con variables para que el VB sepa el tipo de datos que es... (aunque
esto último no es recomendable, lo muestro para que lo sepas):
En la tabla anterior tienes los tipos de datos que podemos usar en vb.NET y por
tanto, de los que podemos declarar variables.
Por ejemplo, si queremos tener una variable en la que guardaremos números
enteros, (sin decimales), los cuales sabemos que no serán mayores de 32767 ni
menores de -32768, podemos usar el tipo Short:
Dim unShort As Short
Después podemos asignar el valor correspondiente:
unShort = 15000
He de aclarar que el Visual Basic no "obliga" a que se declaren todas las variables
que vayamos a usar.
Existe una instrucción, (Option Explicit), que gracias a las fuerzas de la
Naturaleza, (por no decir gracias a Dios, que hay mucho ateo por ahí suelto), ahora
viene puesta por defecto; la cual si que obliga a que declaremos las variables, pero
si quitamos esa instrucción, entonces podemos hacer perrerías y declarar las
variables si nos da la gana, etc...
¡Que bien! estarás diciendo... ¡la libertad total!
¡Pues no! tanto anarquismo no es bueno... porque después te acostumbras y ¿que
pasa? palos vienen, palos van... y no me refiero a palos físicos... sino a los palos
que te dará el programa por no haber sido un poco más "conservador"... (espero
que tantos símiles políticos no te alteren...)
La cuestión es que siempre debes declarar las variables, e incluso te diría más:
siempre debes declarar las variables del tipo que quieres que dicha
variable contenga, lo resalto porque esto te evitará quebraderos de cabeza...
¡créeme!
Ya que, puedes declarar variables sin tipo específico:
Dim unaVariable
que en realidad es como si la hubieses declarado del tipo Object, (As Object), por
tanto aceptará cualquier tipo de datos, pero esto, acuérdate de lo que te digo, no
es una buena práctica.
De todas formas, voy a decirte cómo hacer que no tengas que declarar las
variables, por si eres masoquista y te gusta sufrir... de camino, también te voy a
decir dónde está la otra opción que te pido encarecidamente que siempre uses, me
refiero a la opción Option Strict, ésta opción, si se activa, se indica con Option
Strict On, obligará a que los tipos de datos que uses sean del tipo adecuado, de
esta forma, aunque es un verdadero "peñazo", (por no decir coñazo, ya que a
algunos les puede parecer improcedente), hará que las cosas las hagas o las
programes cómo debes... seguramente acabarás dejando esa opción en el valor
que el vb.NET trae por defecto... valor que no creo que cambien en la versión
definitiva del Visual Studio .NET, cosa que me agradaría un montón... (en este caso
no se si el masoca soy yo, ya que usando Option Strict On es más complicado hacer
las conversiones entre tipos diferentes de datos, pero...)
Por ejemplo, con el Option Strict On no podemos hacer esto:
Dim unChar As Char = "N", ya que "N" es una constante del tipo String.
El compilador de Visual Basic nos diría algo así:
Option Strict no permite la conversión entre Char y String
Estos son los valores que yo te recomiendo. También te los recomiendan otros
muchos programadores, a los cuales parece ser que Microsoft no tiene tan en
cuenta como dice... en fin...
Después de las recomendaciones y súplicas, a las que espero que hagas caso,
sigamos con esto de las declaraciones de las variables, e incluso de las constantes,
ya que también podemos declarar constantes.
Por defecto, cuando no se asigna un valor a una variable, éstas contendrán los
siguientes valores, dependiendo del tipo de datos que sea:
- Las variables numéricas tendrán un valor CERO.
- Las cadenas de caracteres una cadena vacía: ""
- Las variables Boolean un valor False (recuerda que False y CERO es lo mismo)
- Las variable de tipo Objeto tendrán un valor Nothing, es decir nada, un valor nulo.
Por ejemplo:
Dim i As Integer
Tendrá un valor inicial de 0
2.)
Dim i As Integer = 15
De igual manera, para declarar una variable de tipo String y que contenga un valor,
lo haremos de esta forma:
Dim Nombre As String = "Guillermo"
Es decir, en las variables usaremos la palabra DIM, mientras que en las constantes
usaremos CONST.
Después veremos algunas variantes de esto, aunque para declarar constantes,
siempre hay que usar Const.
Por ejemplo:
Dim x As Integer = 25
Dim i As Integer
i=x*2
En este caso, se evalúa el resultado de la expresión, (lo que hay a la derecha del
signo igual), y el resultado de la misma, se asigna a la variable que estará a la
izquierda del signo igual.
Incluso podemos hacer cosas como esta:
i = i + 15
Esto último se llama incrementar una variable, y el vb.NET tiene su propio operador
para estos casos, es decir cuando lo que asignamos a una variable es lo que ya
había antes más el resultado de una expresión:
i += 15
Ya que "10 * 25" es una constante de tipo cadena, no una expresión que
multiplica 10 por 25.
Al estar entre comillas dobles se convierte automáticamente en una constante de
cadena y deja de ser una expresión numérica.
Y si tenemos Option Stric On, tampoco podríamos usar números que no fuesen del
tipo Integer:
i += 25 * 3.1416
No vamos a profundizar, pero para que sepas que haciendo las cosas como se
deben hacer... casi todo es posible, aunque lo que esté escrito dentro de comillas
dobles o esté contenido en una variable de cadena no se evalúa... lo más que
podemos hacer es convertir esa cadena en un valor numérico, en el caso de "10 *
25", el resultado de convertirlo en valor numérico será 10, ya que todo lo que hay
después del 10, no se evalúa... simplemente ¡porque no es un número! son letras,
que tienen el "aspecto" de operadores, pero que no es el operador de multiplicar,
sino el símbolo *.
Pero cuidado con los valores que se evalúan, ya que si el valor que se quiere
asignar no "cabe" en la variable a la que lo asignamos, nos dará un error de
overflow... es decir que el número que queremos asignar es más grande de los
que ese tipo de datos puede soportar... para solucionar esto, habrá que usar un
tipo de datos que soporte valores mayores... a eso es a lo que me refería con lo de
la conversión a otros tipos sin producir overflow de la tabla anterior.
Por ejemplo:
i = CInt(Val("25987278547875"))
dará error, porque el número ese que está dentro de las comillas es demasiado
grande para almacenarlo en una variable de tipo Integer.
Quiero hacer hincapié en las dos últimas funciones, sobre todo si ya has usado
anteriormente el Visual Basic e incluso el Basic de MS-DOS, ya que por tradición
esas dos funciones devolvían valores enteros de tipo Integer.
Ahora, a pesar de lo que la ayuda de VB.NET pueda decir, ya que en un sitio dice
una cosa y en otro dice otra, Int y Fix devuelve un valor del mismo tipo que el
que se indica en el parámetro o expresión, pero sin decimales.
Aunque si esos números son negativos, Fix devuelve el siguiente valor igual o
mayor que el número indicado, mientras que Int lo hace con el primer número
menor o igual...
Por ejemplo: Fix(-8.4) devuelve -8, mientras que Int(-8.4) devolverá -9.
En caso de que sean positivos, las dos funciones devuelven el mismo valor: Int(8.4)
devuelve 8, lo mismo que Fix(8.4).
Haz pruebas por tu cuenta usando Option Strict On y Off, para que veas porqué
algunas veces es conveniente dejarlo en On para que nos avise de que algunas
operaciones que hacemos pueden no dar los resultados esperados.
Evaluar expresiones lógicas.
El siguiente tema del que vamos a tratar son las expresiones lógicas.
Es decir evaluar expresiones cuyo resultado pueda ser un valor verdadero o falso.
Hay ocasiones en las que necesitaremos decidir que hacer dependiendo de algún
condicionante, por ejemplo, en la entrega anterior teníamos que comprobar si la
tecla que se había pulsado era la tecla Suprimir y si era así, hacer algo, en aquella
ocasión, eliminar elementos de un ListBox.
If <expresión a evaluar> Then <Lo que haya que hacer si la expresión evaluada
devuelve Verdadero>
Esta es la forma más simple, ya que aquí lo que se hace es evaluar la expresión
que se indica después de IF y si esa expresión devuelve un valor verdadero, (es
decir es verdad), se ejecutan los comandos que haya después de THEN y si esa
expresión no es cierta, se ejecuta lo que haya en la siguiente línea.
If <expresión a evaluar> Then <Lo que haya que hacer si la expresión evaluada
devuelve Verdadero> Else <Lo que haya que hacer si no se cumple> (todo en una
misma línea)
O mejor aún de esta otra forma, que además queda más claro y evidente lo que
queremos hacer:
Después de Else podemos usar otro IF si así lo creemos conveniente, esto es útil
cuando queremos comprobar más de una cosa y dependiendo del valor, hacer una
cosa u otra:
If a = 10 Then
' Lo que sea que haya que hacer cuando a vale 10
ElseIf a = 15 Then
' Lo que haya que
hacer cuando
vale a 15
Else
' Lo que haya que hacer en caso de que a no valga ni 10 ni 15
End If
' Esto se ejecuta siempre después de haberse comprobado todo lo anterior.
Los comentarios también se pueden hacer con la palabra reservada Rem, pero eso
es algo que ya nadie usa.
De todas formas, cuando el Visual Basic se encuentra con algo como esto:
If i > 25 Then
Lo que hace es evaluar la expresión y al comprobar si el valor de i es mayor de 25
y en caso de que así sea, devolverá un valor True y si resulta que i no es mayor de
25, devolverá False.
A continuación se comprueba ese valor devuelto por la expresión y si es verdadero
(True) se hace lo que esté después del Then y si es falso (False), se hará lo que
esté después del Else, (si es que hay algún Else...)
Pero si lo que queremos es que el valor de i sea mayor que 200 y menor de 500,
habría que usar AND:
If i > 200 And i < 500 Then
Por último, si queremos que la condición se cumpla cuando i NO sea igual a 100:
If Not i = 100 Then
Aunque esto mismo podríamos haberlo hecho de esta otra forma:
If i <> 100 Then
Con AND se cumple la verdad si las dos expresiones son verdaderas.
Con Or se cumple si cualquiera de las expresiones es verdadera.
Por supuesto, podemos usar expresiones en las que se mezclen AND y OR, aunque
en estos casos es recomendable el uso de paréntesis para separar las expresiones
"dudosas".
Por ejemplo:
If A = 100 Or B > 50 And x = n * 2 Then
Aunque si nuestra intención era otra, podíamos haberlo escrito de esta otra forma:
If (A = 100 Or B > 50) And x = n * 2 Then
En cuyo caso sólo se cumplirá cuando A sea 100 o B mayor de 50, pero SIEMPRE x
debe ser igual a n * 2
Ya que estamos, decirte que los símbolos que podemos usar para efectuar
comparaciones, son estos:
= igual
< menor que
> mayor que
<= menor o igual
>= mayor o igual
<> distinto
Seguramente ya lo sabías, pero... nunca está de más.
Para terminar, decirte que las expresiones que se pueden usar con el IF pueden ser
tanto numéricas como alfanuméricas o de cadena, ya que también puede ser
conveniente saber si el contendido de la cadena s es mayor que la palabra "hola",
aunque en este tipo de expresiones, se evalúa tal y como si se fuese a clasificar...
es decir "ahora" será menor que "hola", ya que si lo clasificáramos, tendríamos que
la letra A está antes que la H.
Aquí también juega un poco el Option Compare Binary ya que, como te dije, se
hacen distinciones de mayúsculas y minúsculas, aunque a la hora de clasificar (u
ordenar), las minúsculas están después de las mayúsculas, por tanto "Amigo" será
menor que "amigo" si es que tenemos puesto el Option Compare Binary.
Pero serán iguales si está puesto el Option Compare Text.
Si quieres hacer algunas pruebas, recuerda que puedes crear un proyecto del tipo
Consola y usar el Console.WriteLine para mostrar cosas en la pantalla, aunque
debes acordarte al final de poner un Console.ReadLine para que la pantalla se
quede visible hasta que pulses Intro.
'
Dim unByte As Byte = 129
Dim unBoolean As Boolean = True
Dim unChar As Char = "N"c
'Dim unChar2 As Char = "B" ' (Con Option Strict On da error)
Dim unaFecha As Date = #10/27/2001#
Dim unDecimal As Decimal = 99912581258.125D
Dim unDecimal2 As Decimal = 9876543210123456@
Dim unDoble As Double = 125897.12045R
Dim unDoble2 As Double = 2457998778745.4512#
'Dim unInt As Integer = 24579987787456 ' (Con Option Strict On
da error)
Dim unEntero As Integer = 250009I
Dim unEntero2 As Integer = 652000%
Dim unLong As Long = 123456789L
Dim unLong2 As Long = 987654&
Dim unShort As Short = 32000S
'
Const n As Integer = 15
'
Dim i As Integer
i = 10
i += 25 + (n * 2)
Console.WriteLine("(i=10, n=15), 'i += 25 + (n * 2)' es igual
a: " & CStr(i))
i += CType(Val("10 * 25"), Integer)
'i = CInt(Val("25987278547875")) ' (dará error)
i = CInt(Val("25000"))
Console.WriteLine(i)
'
unDoble = Fix(unDoble2)
Console.WriteLine("unDoble = Fix(" & unDoble2.ToString & ") : "
& unDoble.ToString)
unDoble2 = 2457998778745.665#
unDoble = CInt(unDoble2)
Console.WriteLine("unDoble = Int(" & unDoble2.ToString & ") : "
& unDoble.ToString)
'unDoble = CInt(unDoble2)
'Console.WriteLine("unDoble = CInt(" & unDoble2.ToString & ") :
" & unDoble.ToString)
'
unDoble = Fix(8.9)
Console.WriteLine("unDoble = Fix(8.9) : " & unDoble.ToString)
'
unDecimal = Fix(8.9D)
Console.WriteLine("unDecimal = Fix(8.9D) : " &
unDecimal.ToString)
'
Console.WriteLine("i vale: " & CStr(i))
If i > 1500 Then
Console.WriteLine("i es mayor de 1500")
End If
'
i = 200
Console.WriteLine("Se asigna el valor " & CStr(i) & " a i")
If i > 15 + n Then
Console.WriteLine("i es mayor de 15 + n")
Else
Console.WriteLine("i NO es mayor de 15 + n")
End If
'
If i > 200 Or i < 500 Then
Console.WriteLine("el valor de i es mayor de 200 O menor de
500")
End If
If i > 100 And i < 500 Then
Console.WriteLine("el valor de i es mayor de 100 Y menor de
500")
End If
If Not i = 100 Then
Console.WriteLine("i NO vale 100")
End If
Dim a As Integer = 100
Dim b As Integer = 50
Dim x As Integer = n * 2 + 10
If a = 100 Or (b > 50 And x = n * 2) Then
Console.WriteLine("SI: If a = 100 Or b > 50 And x = n * 2
Then")
End If
Console.ReadLine()
Por ejemplo, esta línea declara dos variables del tipo Integer:
Dim a, b As Integer
¿Cual es la diferencia?
Evidentemente que en la segunda línea indicamos dos veces el tipo de datos de
cada una de las variables, mientras que en la primera sólo lo especificamos una
vez.
Por supuesto que esta no es la única forma de declarar varias variables en una
misma línea, ya que puede ser que queramos declarar variables de distintos tipos.
En ese caso, hay que indicar junto a cada variable el tipo de datos que queramos
que tenga.
En este caso, tenemos dos variables de dos tipos distintos, cada una con su As tipo
correspondiente, pero separadas por una coma.
Pero si lo que pretendemos es que el que lea nuestro código se "caliente" el coco
intentando entenderlo... (por decir algo), podemos complicar aún más la cosa:
En esta ocasión, las variables j y k son del tipo Integer, las variables s1 y
Nombre del tipo String y por último la variable d1 es de tipo Decimal.
Tipo de dato por defecto de las variables:
Si has tratado con alguna versión anterior de Visual Basic, incluso del BASIC de MS-
DOS, también estaba permitido hacer las declaraciones de esa forma, pero en el
caso de que no se indicara explícitamente el tipo de datos, el Basic le asignaba el
tipo de datos por defecto.
En el caso de las versiones de MS-DOS y el VB1, el tipo de datos era Single, en las
versiones anteriores a vb.NET el tipo de datos predeterminado era Variant y, si la
cosa siguiera igual que en las versiones anteriores, (por suerte ya no es así), en
vb.NET, el tipo de datos sería Object. Aunque... si sólo declaras una variable con
Dim y no especificas el tipo (y tampoco sigues mis recomendaciones), el tipo
asignado será ese: Object.
En las declaraciones con las versiones anteriores al vb.NET existían unas
excepciones para que el tipo de datos predeterminado no fuese el que el
"compilador" imponía, pero no vamos a entrar en detalles, ya que no vienen al caso
y lo único que conseguiría sería complicarte la vida... aunque si quieres saber a qué
me refiero... échale un vistazo al Curso Básico de VB que tengo en mis páginas (el
Guille siempre aprovecha cualquier ocasión para hacer publicidad).
Sigamos viendo cómo actuaría el vb.NET en el caso de que hagamos algo como
esto:
Dim z
Si no me has hecho caso... o se te ha olvidado poner lo de Option Strict On, esa
declaración sería válida y el tipo de datos de la variable z sería Object, es decir,
seria lo mismo que hacer esto otro:
Dim z As Object
Pero si, por otro lado, tienes puesto el Option Strict en On, el vb.NET te diría que
"nones", que eso no está permitido.
Realmente nos mostraría un mensaje como este:
Option Strict requiere que todas las declaraciones de variables tengan una
cláusula 'As'.
Con lo cual tendríamos que especificar el tipo de datos que "realmente" queremos
que tenga esa variable.
Como puedes comprobar... nos puede venir bien que de vez en cuando nos avisen
de que no estamos haciendo las cosas como debiéramos...
Una cosa que no está permitida al declarar varias variables usando sólo un As Tipo,
es la asignación de un valor predeterminado.
Ya vimos en la entrega anterior de que podíamos hacer esto para asignar el valor
15 a la variable N:
Dim N As Integer = 15
Pero lo que no podemos hacer es declarar, por ejemplo, dos variables de tipo
Integer y "pretender" asignarle a una de ellas un valor predeterminado (o inicial),
por ejemplo:
Dim p, q As Integer = 1
Ahora vamos a pasar a otro de los temas propuestos para esta entrega:
A la última pregunta, la respuesta es: NO... salvo que escribas con tinta invisible...
8-)
A la primera pregunta... es a la que debo dar respuesta, ya que de eso se trata...
¿o no? pues eso...
Si tienes el Visual Studio y has creado algún proyecto, por ejemplo una aplicación
de consola. Habrás visto que el código que se crea es el siguiente:
Module Module1
Sub Main()
End Sub
End Module
Es decir, se crea un módulo llamado Module1 y un procedimiento llamado Sub
Main, que por otro lado es el que sirve como punto de entrada al programa...
aunque de esto aún no debes preocuparte y dejarlo estar... ya te explicaré de que
va todo esto... Por ahora sólo debes saber que los procedimientos Sub son como
instrucciones y cuando se usan en otras partes del programa, se ejecuta el código
que haya en su interior...
Lo que nos interesa es saber que hay dos "espacios" diferentes en los que poder
insertar las declaraciones de las variables:
Después de Module Module1 o después de Sub Main.
El código insertado entre Module y End Module, se dice que es código a nivel de
módulo, en este caso el único código posible es el de declaraciones de variables o
procedimientos.
El código insertado dentro del Sub y End Sub, se dice que es código a nivel de
procedimiento, en esta ocasión podemos poner lo que queramos, además de
declaraciones de variables.
Pues bien.
Option Strict On
Module Module1
' Variable declarada a nivel de módulo
Dim n As Integer = 15
Sub Main()
' Variable declarada a nivel de procedimiento
Dim i As Long = 10
'
' Esto mostrará que n vale 15
Console.WriteLine("El valor de n es: {0}", n)
Console.WriteLine("El valor de i es: {0}", i)
Console.ReadLine()
End Sub
Sub Prueba()
' Esto mostrará que n vale 15
Console.WriteLine("El valor de n es: {0}", n)
Console.ReadLine()
End Sub
End Module
Te explico un poco:
Este módulo contiene dos procedimientos de tipo Sub, (los SUBs son como las
instrucciones, realizan una tarea, pero no devuelven un valor como lo harían las
funciones y las propiedades) (Guille, eso ya está explicado en el Glosario, ¿es que
pretendes que la entrega sea más larga?).
La variable n la hemos declarado a nivel de módulo, por tanto estará visible en todo
el módulo y por todos los procedimientos de ese módulo.
Dentro del procedimiento Main, hemos declarado la variable i, ésta sólo estará
disponible dentro de ese procedimiento. Por eso al intentar usarla en el
procedimiento Prueba, el vb.NET nos da un error diciendo que la variable no está
declarada.
Y a pesar de que está declarada, lo está pero sólo para lo que haya dentro del
procedimiento Main, por tanto en Prueba no se sabe que existe una variable
llamada i.
(je, je... seguro que ni tú, Guille, te has enterado de lo que has explicado... Que sí,
que es mu fácil... si la variable i... ¡VALEEEE! que yo si que me he enterado... así
que calla y sigue, que ya queda poco)
Option Strict On
Module Module1
' Variable declarada a nivel de módulo
Dim n As Integer = 15
Sub Main()
'
Console.WriteLine("El valor de n Main es: {0}", n)
'
Console.ReadLine()
End Sub
Sub Prueba()
Dim n As Long = 9547
'
Console.WriteLine("El valor de n en Prueba es: {0}", n)
'
Console.ReadLine()
End Sub
End Module
Este ejemplo me sirve para que compruebes que una variable de nivel "superior"
puede ser eclipsada por otra de un nivel "inferior".
La variable n declarada a nivel de módulo estará visible en todos los procedimientos
del módulo, pero al declarar otra variable, también llamada n, dentro del
procedimiento Prueba, ésta última "eclipsa" a la variable que se declaró a nivel de
módulo y se usa la variable "local" en lugar de la "global". Aunque sólo dentro del
procedimiento Prueba, ya que en el procedimiento Main si que se ve el valor de la
variable declarada a nivel de módulo, por la sencilla razón de que no hay ninguna
variable declarada dentro de Main con el mismo nombre. (elemental mi querido
Guille, algunas veces me sorprende tu lógica aplastante)
En otra ocasión veremos más cosas sobre la visibilidad de las variables y cómo
podemos ampliar la "cobertura" o el campo de visibilidad, para que puedan ser
vistas en otros sitios... pero eso en otra ocasión. (sí, déjalo ya, que es tarde y los
Reyes Magos nos van a pillar levantados...)
En la próxima entrega veremos más cosas, pero será referente a lo que ya vimos
en la entrega anterior, respecto a las decisiones lógicas con IF/THEN y
seguramente veremos cómo hacer bucles (o lo que es lo mismo, hacer que una
variable tome valores de forma automática y por un número determinado de veces)
y otras cosillas que a la larga nos serán de utilidad en la mayoría de los programas.
Sólo me queda hacerte, (como espero que sea la norma en todas las entregas), un
"pequeño" resumen de lo que hemos visto en esta entrega:
En la entrega anterior te dije que no iba a dejar que pasara un año para publicar la
siguiente, pero... casi, casi... si me descuido, si que pasa un año... en fin... Aunque
tengo que decirte que ha sido por causas ajenas y por otras en las que no voy a
entrar en detalles... lo importante, al menos para mí, es que aquí tienes la sexta
entrega de este curso de iniciación a Visual Basic .NET
Lo que si es importante, es que en las fechas en las que estoy escribiendo estas
líneas, la versión definitiva de Visual Basic .NET ya es algo real... y también tengo
que decirte que todo lo dicho anteriormente es aplicable a esta versión, que según
Microsoft es Visual Studio .NET 2002, (te recuerdo que anteriormente estaba
trabajando con la Beta 2 de Visual Studio .NET)
Bien, empecemos.
Según te comenté hace casi un año... en esta entrega vamos a seguir viendo cosas
relacionadas con las comparaciones (IF / THEN) y también veremos cómo hacer
bucles, etc.
Aunque esto de los bucles ya vimos algo en la tercera entrega, aquí lo veremos con
algo de más detalle, es decir, te lo voy a explicar.
Esto en teoría no sería problemático, ya que Visual Basic no tarda mucho tiempo en
comprobar la segunda expresión, al menos en este ejemplo, ya que si en lugar de
ser una "simple" comparación, en la segunda parte del AND tuviéramos algún tipo
de expresión que se tomara su tiempo... sería un desperdicio inútil tener que
esperar ese tiempo, sobre todo sabiendo que no es necesario hacer esa segunda
comprobación.
En las versiones anteriores de Visual Basic, e incluso en la época del BASIC de MS-
DOS, este tipo de expresiones se solían "partir" en dos comparaciones IF, ya que, si
la primera no se cumple, no es necesario hacer la segunda, por tanto esa
comparación se podría hacer de la siguiente forma:
IF N = 3 THEN
IF X > 10 THEN
Es decir, sólo comprobamos la segunda expresión si se cumple la primera.
Ahora, con Visual Basic .NET no es necesario tener que llegar a este
"desdoblamiento" y podemos hacer la comparación "casi" como lo que hemos visto,
pero usando una instrucción (u operador) especial: AndAlso.
Lo mismo, (o casi), ocurre con OR, aunque en este caso, sabemos que si el
resultado de cualquiera de las expresiones usadas con ese operador es verdadero,
la expresión completa se da por buena.
Por ejemplo, si tenemos:
IF N = 3 OR X > 10 THEN
En el caso de que N valga 3, la expresión (N = 3 OR X > 10) sería verdadera, pero,
al igual de lo que pasaba con AND, aunque N sea igual a 3, también se comprobará
si X vale más de 10, aunque no sea estrictamente necesario.
Para que estos casos no se den, Visual Basic .NET pone a nuestra disposición del
operador OrElse.
Otra cosa que debemos tener en cuenta cuando evaluamos expresiones lógicas, o
cuando simplemente evaluamos expresiones, por ejemplo para asignar el resultado
a una variable, hay que tener presente que Visual Basic .NET sigue un orden a la
hora de evaluar las distintas partes de la expresión.
Esto es lo que se llama:
Exponenciación (^)
Negación (-)
Multiplicación y división (*, /)
División de números enteros (\)
Módulo aritmético (Mod)
Suma y resta (+, -)
Concatenación de cadenas (&)
Operadores de comparación:
Igualdad (=)
Desigualdad (<>)
Menor o mayor que (<, >)
Mayor o igual que (>=)
Menor o igual que (<=)
Operadores lógicos:
Negación (Not)
Conjunción (And, AndAlso)
Disyunción (Or, OrElse, Xor)
For / Next, con este tipo de bucle podemos repetir un código un número
determinado de veces.
Dim i As Integer
'
For i = 1 To 10
' contará de 1 hasta 10
' la variable i tomará los valores 1, 2, 3, etc.
Next
'
For i = 1 To 100 Step 2
' contará desde 1 hasta 100 (realmente 99) de 2 en 2
' la variable i tomará los valores 1, 3, 5, etc.
Next
'
For i = 10 To 1 Step -1
' contará desde 10 hasta 1
' la variable i tomará los valores 10, 9, 8, etc.
Next
'
For i = 100 To 1 Step -10
' contará desde 100 hasta 1, (realmente hasta 10)
' la variable i tomará los valores 100, 90, 80, etc.
Next
'
For i = 10 To 1
' este bucle no se repetirá ninguna vez
Next
'
For i = 1 To 20 Step 50
' esto sólo se repetirá una vez
Next
En algunos casos, hay que tener en cuenta que el valor final del bucle puede que no
sea el indicado, todo dependerá del incremento que hayamos especificado. Por
ejemplo, en el tercer bucle, le indicamos que cuente desde 1 hasta 100 de dos en
dos, el valor final será 99.
En otros casos puede incluso que no se repita ninguna vez... este es el caso del
penúltimo bucle, ya que le decimos que cuente de 10 a 1, pero al no indicar Step
con un valor diferente, Visual Basic "supone" que será 1 y en cuanto le suma uno a
10, se da cuenta de que 11 es mayor que 1 y como le decimos que queremos
contar desde 10 hasta 1, pues... sabe que no debe continuar.
For Each, este bucle repetirá o iterará por cada uno de los elementos
contenidos en una colección.
Dim s As String
'
For Each s In "Hola Mundo"
Console.WriteLine(s)
Next
Console.ReadLine()
Dim i As Integer
'
While i < 10
Console.WriteLine(i)
i = i + 1
End While
Console.ReadLine()
'
'
Dim n As Integer = 3
i = 1
While i = 10 * n
' no se repetirá ninguna vez
End While
En el primer caso, el bucle se repetirá mientras i sea menor que 10, fíjate que el
valor de i se incrementa después de mostrarlo, por tanto se mostrarán los valores
desde 0 hasta 9, ya que cuando i vale 10, no se cumple la condición y se sale del
bucle.
En el segundo ejemplo no se repetirá ninguna vez, ya que la condición es que i sea
igual a 10 multiplicado por n, cosa que no ocurre, ya que i vale 1 y n vale 3 y como
sabemos 1 no es igual a 30.
Do / Loop, este tipo de bucle es muy parecido al anterior, aunque algo más
flexible.
Si se utiliza sólo con esas dos instrucciones, este tipo de bucle no acabará nunca y
repetirá todo lo que haya entre Do y Loop.
Pero la flexibilidad a la que me refería es que este tipo de bucle se puede usar con
dos instrucciones que nos permitirán evaluar expresiones lógicas:
While y Until
Pero no debemos confundir este While con el While/End While que acabamos de ver
anteriormente, aunque la forma de usarlo es prácticamente como acabamos de ver.
La ventaja de usar While o Until con los bucles Do/Loop es que estas dos
instrucciones podemos usarlas tanto junto a Do como junto a Loop, la diferencia
está en que si los usamos con Do, la evaluación se hará antes de empezar el bucle,
mientras que si se usan con Loop, la evaluación se hará después de que el bucle se
repita al menos una vez.
Veamos cómo usar este tipo de bucle con las dos "variantes":
Do While <expresión>
'
Loop
Do
'
Loop While <expresión>
Do Until <expresión>
'
Loop
Do
'
Loop Until <expresión>
Usando Do con While no se diferencia en nada con lo que vimos anteriormente, con
la excepción de que se use junto a Loop, pero como te he comentado antes, en ese
caso la evaluación de la expresión se hace después de que se repita como mínimo
una vez.
i = 0
Do Until i > 9
Console.WriteLine(i)
i = i + 1
Loop
i = 0
Do While Not (i > 9)
Console.WriteLine(i)
i = i + 1
Loop
Con esto entenderás mejor a lo que me refería con negar la expresión con While.
Lo que nos queda por ve, es que cuando estamos dentro de un bucle, podemos
abandonarlo de dos formas:
1- Esperando a que el bucle termine.
2- Saliendo "abruptamente" o abandonándolo antes de que termine de forma lógica
o por los valores que le hemos indicado (al menos en el caso de los bucles For)
Para poder abandonar un bucle, esto veremos que es ampliable a otros temas), hay
que usar la instrucción Exit seguida del tipo de bucle que queremos abandonar:
Exit For
Exit While
Exit Do
En la próxima entrega veremos otra forma de tomar decisiones, hasta ahora sólo
hemos visto que podemos tomarlas con If / Then.
Sólo me queda hacer el resumen de lo que hemos visto en esta sexta entrega:
- Otra forma de evaluar expresiones lógicas de una forma más "lógica", usando
-Los diferentes tipos de bucles que podemos usar con Visual Basic .NET
CAPITULO 7
En esta entrega veremos otra forma con la que podemos escoger entre varias
opciones. Hasta ahora hemos usado las instrucciones IF / Then / Else, pero Visual
Basic pone a nuestra disposición la instrucción Select Case con la que podemos
elegir entre varias opciones y en la versión .NET nos facilita un poco las cosas, al
menos con respecto a como se usaba en las versiones anteriores de Visual Basic,
esto lo comprobaremos en cuanto veamos una nueva forma de crear constantes.
If i = 3 Then
'
ElseIf i > 5 AndAlso i < 12 Then
'
ElseIf i = 14 OrElse i = 17 Then
'
ElseIf i > 25 Then
'
Else
'
End If
Esto mismo, con Select Case lo haríamos de esta forma:
Select Case i
Case 3
'
Case 6 To 11
'
Case 14, 17
'
Case Is > 25
'
Case Else
'
End Select
Como vemos, funciona casi igual que con If... Then, pero de una forma algo más
ordenada, la única pega es que sólo podemos evaluar una expresión, mientras que
con If podemos usar tantas como queramos... precisamente por eso existen estas
dos posibilidades... cuando una no es válida, podemos usar la otra.
Enum colores
rojo = 1
azul
verde
End Enum
Los valores que pueden tener los miembros de una enumeración, pueden ser
cualquiera de los que un tipo numérico de tipo entero pueda tener. Por defecto el
tipo es Integer, pero las enumeraciones también pueden ser de tipo Byte, Long o
Short, para poder especificar un tipo diferente a Integer, lo indicaremos usando As
Tipo después del nombre de la enumeración.
Por defecto, el primer valor que tendrá un elemento de una enumeración será cero
y los siguientes elementos, salvo que se indique lo contrario, tendrán uno más que
el anterior.
En el ejemplo mostrado, el elemento rojo, valdrá 1, azul valdrá 2 y verde tendrá un
valor 3.
Para poder cambiar esos valores automáticos, podemos indicarlo usando una
asignación como la usada para indicar que rojo vale 1: rojo = 1
En caso de que no indiquemos ningún valor, el primero será cero y los siguientes
valdrán uno más que el anterior, por ejemplo, si la declaración anterior la hacemos
de esta forma:
Enum colores
rojo
azul
verde
End Enum
Enum colores
rojo
azul = 3
verde
End Enum
Por supuesto, los valores que podemos asignar a los elementos (o miembros) de
una enumeración serán valores que estén de acuerdo con el tipo de datos,
recordemos que si no indicamos nada, serán de tipo Integer, pero si especificamos
el tipo de datos, por ejemplo, de tipo Byte, el cual si recordamos la tabla vista en la
cuarta entrega, sólo podrá contener valores enteros comprendidos entre 1 y 255.
Sabiendo esto, no podríamos declarar la siguiente enumeración sin recibir un
mensaje de error:
¿Por qué? te preguntarás, ¿si el valor está dentro de los valores permitidos?
Por la sencilla razón de que azul tiene un valor adecuado, pero tanto rojo como
verde, tendrán un valor 256 y 257 respectivamente, los cuales están fuera del
rango permitido por el tipo Byte.
Otra cosa con la que tendremos que andarnos con cuidado al usar las
enumeraciones, es que una variable declarada del tipo de una enumeración, en
teoría no debería admitir ningún valor que no esté incluido en dicha enumeración,
aunque esta restricción es sólo "recomendable", no es algo "obligatorio", aunque si
tenemos activado Option Strict On, el IDE de Visual Studio .NET nos lo recordará,
con lo cual nos obligará a hacer una conversión de datos entre la variable (o el
valor usado) y el tipo "correcto" al que corresponde la enumeración.
Para entenderlo mejor, veamos un ejemplo:
Aunque el valor 1, sea un valor correcto, si tenemos Option Strict On, nos indicaría
que no se permite la conversión implícita entre Integer y el tipo colores. Si no
tuviéramos activada la comprobación estricta, (cosa que yo te recomiendo y si
pudiera, hasta te obligaría a usar, más que nada porque así aprenderás a hacer las
cosas bien, que siempre habrá tiempo de hacerlas mal), a lo que iba, si no tuvieras
esa opción activada, no te mostraría ningún error, pero si sabemos que para un
buen uso de los tipos de datos, deberíamos hacer la conversión correspondiente,
¿por qué no hacerla?
Si te portas como el Guille quiere, tendrías que hacer lo siguiente:
Es decir, usamos CType para hacer la conversión entre el número y el tipo correcto
de datos.
De todas formas, te diré que si usas el IDE de Visual Studio .NET, éste te mostrará
los valores que puedes asignarle a la variable declarada del tipo colores:
Al igual que ocurre con las variables de tipo Boolean, (que sólo pueden tomar los
valores True o False), cuando vamos a asignar un valor, se nos muestra los valores
posibles, esto mismo también ocurre si queremos usar esa variable en en una
comparación o en un Select Case, a esto es a lo que me refería al principio de esta
entrega.
Veamos lo que nos mostraría el IDE al usar la variable unColor en los casos antes
mencionados:
En las versiones anteriores de Visual Basic, podíamos usar los miembros de las
enumeraciones sin indicar la enumeración a la que pertenecen, por ejemplo,
podíamos asignar el valor azul a una variable del tipo colores, pero en Visual
Basic .NET esto ya no es posible, si queremos usar el miembro azul, tenemos que
indicar la enumeración a la que pertenece, por ejemplo con unColor =
colores.azul, esto más que un inconveniente es una ventaja, ya que así no habrá
posibilidades de "mal interpretación" o mal uso de los miembros de una
enumeración.
Esto está bien, ya que, a pesar de que usemos CType para convertir un valor en la
enumeración, no evita que que se "cuele" un valor erróneo:
pruebaColores(CType(12, colores))
Esta llamada sería correcta, pero el valor no estaría dentro de los valores válidos,
aunque no produciría ningún tipo de error, ya que, aunque 12 no sea un valor
válido del tipo colores, si que es un valor válido del tipo Integer, que al fin y al cabo
es el tipo de valores que admiten las variables declaradas como colores.
En el procedimiento, se comprueba si el valor pasado en el parámetro es del tipo
adecuado y se actuaría de la forma que nosotros quisiéramos. El problema vendría
si la cantidad de miembros de la enumeración fuese muy extensa y esos valores no
fuesen consecutivos, ya que nos obligaría a hacer muchas "comparaciones" para
poder saber si el valor indicado es uno de los valores correctos.
Para poder solventar este "problemilla", tendremos que echar mano del propio .NET
Framework, en particular de la clase Enum, esta clase tiene una serie de métodos
"estáticos" que podemos usar para distintas circunstancias, en nuestro caso
particular, podemos usar el método IsDefined que devolverá un valor verdadero o
falso, según el valor indicado esté o no definido en la enumeración.
Además de IsDefined, la clase Enum tiene otros métodos que pueden sernos útiles.
Pero en lugar de enumerártelos aquí, dejaré que le eches un vistazo a la
documentación del .NET Framework (o de Visual Studio .NET), para que practiques
en el uso de dicha documentación...
(Guille, no seas malo, anda, dale aunque sea un par de ejemplos, bueno, pero es
que si no, no leen la documentación y así... pues no aprenden...)
Valeee, está bien, veamos algunos de los métodos de la clase Enum: (que hay que
usar con el System. delante para que el VB no se confunda con el "intento" de una
declaración del tipo Enum)
GetNames, devuelve un array de tipo String con los nombres de todos los
miembros de la enumeración.
Lo que es un array lo veremos en detalle en otra ocasión, pero espero que puedas
comprender qué es lo que hace el siguiente código sin que te de un "dolor" de
cabeza.
Por último vamos a ver un método que, casi con toda seguridad veremos en más
de una ocasión:
Parse, devuelve un valor de tipo Object con el valor de la representación de la
cadena indicada en el segundo parámetro. Esa cadena puede ser un valor numérico
o una cadena que representa a un miembro de la enumeración.
System.Enum.Parse(unColor.GetType, "1")
System.Enum.Parse(unColor.GetType, "azul")
Hay más métodos, pero creo que estos que acabo de enumerar son los más
interesantes, de todas formas, te invito a seguir investigando por tu cuenta... cosa
que, aunque yo no te lo dijera, deberías acostumbrarte a hacer.
Pero ya que estamos con esto de Parse y para ir terminando esta entrega, veamos
cómo podemos usar ese método para los tipos de datos que podemos usar en .NET
Framework (los cuales vimos en la cuarta entrega).
El método Parse se utiliza para convertir una cadena en un valor numérico, el tipo
de número devuelto dependerá del tipo desde el que hemos usado ese método, por
ejemplo si hacemos lo siguiente:
s = "129.33"
i = Integer.Parse(s)
CAPITULO 8
Cuando en el código de nuestra aplicación se produce un error sintáctico, es decir,
porque hayamos escrito mal alguna instrucción de Visual Basic .NET, será el propio
entorno de desarrollo el que nos avise de que hay algo que no es correcto; a este
tipo de errores se suele llamar errores sintáctico o en tiempo de diseño. Pero si lo
que ocurre es que hemos asignado un valor erróneo a una variable o hemos
realizado una división por cero o estamos intentado acceder a un archivo que no
existe, entonces, se producirá un error en tiempo de ejecución, es decir sólo
sabremos que hay algo mal cuando el ejecutable esté funcionando.
Try
' el código que puede producir error
Catch [tipo de error a capturar]
' código cuando se produzca un error
Finally
' código se produzca o no un error
End Try
En el caso de que sólo usemos Finally, tendremos que tener en cuenta de que si se
produce un error, el programa se detendrá de la misma forma que si no
hubiésemos usado la detección de errores, por tanto, aunque usemos Finally (y no
estemos obligados a usar Catch), es más que recomendable que siempre usemos
una cláusula Catch, aunque en ese bloque no hagamos nada, pero aunque no
"tratemos" correctamente el error, al menos no se detendrá porque se haya
producido el error.
Aclaremos este punto, ya que puede parecer extraño.
Si decidimos "prevenir" que se produzca un error, pero simplemente queremos que
el programa continúe su ejecución, podemos usar un bloque Catch que esté vacío,
con lo cual el error simplemente se ignorará... si has usado o has leído sobre cómo
funciona On Error Resume Next, pensarás que esto es algo parecido... si se produce
un error, se ignora y se continúa el programa como si nada. Pero no te confundas
que aunque lo parezca... no es igual. (guille, no te enrolles más y explícalo)
Si tenemos el siguiente código, se producirá una excepción (o error), ya que al
dividir i por j, se producirá un error de división por cero.
Dim i, j As Integer
Try
i = 10
j = 0
i = i \ j
Catch
' nada que hacer si se produce un error
End Try
' se continúa después del bloque de detección de errores
Dim i, j As Integer
On Error Resume Next
i = 10
j = 0
i = i \ j
' se continúa después del bloque de detección de errores
Dim i, j As Integer
Try
i = 10
j = 0
i = i \ j
Console.WriteLine("el nuevo valor de i es: {0}", i)
Catch
' nada que hacer si se produce un error
End Try
' se continúa después del bloque de detección de errores
Console.WriteLine("después del bloque de detección de errores")
Aquí tenemos prácticamente el mismo código que antes, con la diferencia de que
tenemos dos "instrucciones" nuevas, una se ejecuta después de la línea que
"sabemos" que produce el error, (sí, la línea i = i \ j es la que produce el error,
pero el que sepamos qué línea es la que produce el error no es lo habitual, y si en
este caso lo sabemos con total certeza, es sólo es para que comprendamos mejor
todo esto...), y la otra después del bloque Try... Catch.
Cuando se produce el error, el Visual Basic .NET, o mejor dicho, el runtime del .NET
Framework, deja de ejecutar las líneas de código que hay en el bloque Try, (las que
hay después de que se produzca la excepción), y continúa por el código del bloque
Catch, que en este caso no hay nada, así que, busca un bloque Finally, y si lo
hubiera, ejecutaría ese código, pero como no hay bloque Finally, continúa por lo
que haya después de End Try.
Dim i, j As Integer
On Error Resume Next
i = 10
j = 0
i = i \ j
Console.WriteLine("el nuevo valor de i es: {0}", i)
' se continúa después del bloque de detección de errores
Console.WriteLine("después del bloque de detección de errores")
Veamos un ejemplo, que no será con código real, sino con "pseudo" código, el que
abrimos un archivo (o fichero) de texto y en el que vamos a escribir el contenido de
una o varias variables. Si no se produce error, todo irá bien, pero si se produce un
error justamente cuando vamos a abrir el archivo, (por ejemplo, porque el disco
esté lleno o no tengamos acceso de escritura o cualquier otra cosa), el archivo no
estará abierto y por tanto lo que guardemos en él, realmente no se guardará.
El seudo código sería algo como esto:
Abrir el archivo
Guardar el contenido de la variable
repetir la línea anterior mientras haya algo que guardar
Cerrar el archivo
Avisar al usuario de que todo ha ido bien, que ya se puede ir a su casa a descansar
porque todo se ha guardado.
Si en lugar de usar On Error Resume Next (e incluso On Error Goto nnn), hemos
optado por usar Try... Catch, todo el seudo código ese, lo escribiríamos dentro del
bloque Try y si se produce un error, avisaríamos al usuario de que se ha olvidado
de poner el disquete, (por ejemplo), y le podríamos dar la oportunidad de volver a
intentarlo...
Sí, ya se que eso mismo podríamos hacerlo con el On Error... pero hacerlo con Try
resulta más elegante y no tendríamos la necesidad de usar un salto a una línea
anterior usando una instrucción que... en fin... no sé si debería decirte que
instrucción usarías... pero sí, es GoTo, la terrible instrucción que ha hecho del
lenguaje Basic un lenguaje al que nadie ha tratado con "seriedad" y por culpa de la
que este querido lenguaje de programación ha sido tan "blasfemado"... o casi, que
tampoco es "pa" tanto, pero en fin...
De todas formas, hasta aquí hemos llegado con esto del Goto, que ni te voy a
explicar para que sirve y también hemos llegado al final de todo lo que a
tratamiento de errores no estructurados se refiere... por tanto, si quieres saber más
de esas instrucciones... ya sabes, o te das una vuelta por algún "viejo" curso del VB
o te lees lo que la documentación de Visual Basic .NET te diga, porque yo, no te voy
a decir nada más de esas instrucciones...
Como te he comentado antes, el bloque Catch sirve para detectar errores, incluso
para detectar distintos tipos de errores, con idea de que el "runtime" de .NET
Framework (el CLR), pueda ejecutar el que convenga según el error que se
produzca.
Cuando queremos hacerlo de esta forma, lo más lógico es que usemos un Catch
para cada uno de los errores que queremos interceptar, y lo haríamos de la
siguiente forma:
Dim i, j As Integer
Dim s As String
'
Try
Console.Write("Escribe un número (y pulsa Intro) ")
s = Console.ReadLine
i = CInt(s)
Console.Write("Escribe otro número ")
s = Console.ReadLine
j = CInt(s)
'
Console.WriteLine("El resultado de dividir {0} por {1} es {2}",
i, j, i \ j)
'
Catch ex As DivideByZeroException
Console.WriteLine("ERROR: división por cero")
Catch ex As OverflowException
Console.WriteLine("ERROR: de desbordamiento (número demasiado
grande)")
Catch ex As Exception
Console.WriteLine("Se ha producido el error: {0}", ex.Message)
End Try
'
Console.ReadLine()
Si usamos esta forma de detectar varios errores, te comentaré que debes tener
cuidado de poner el tipo genérico al final, (o el que no tenga ningún tipo de "error a
capturar" después de Catch), ya que el CLR siempre evalúa los tipos de errores a
detectar empezando por el primer Catch y si no se amolda al error producido,
comprueba el siguiente, así hasta que llegue a uno que sea adecuado al error
producido, y si da la casualidad de que el primer Catch es de tipo genérico, el resto
no se comprobará, ya que ese tipo es adecuado al error que se produzca, por la
sencilla razón de que Exception es el tipo de error más genérico que puede haber,
por tanto se adecua a cualquier error.
Nota:
Realmente el tipo Exception es la clase de la que se derivan (o en la que se basan)
todas las clases que manejan algún tipo de excepción o error.
Si te fijas, verás que todos los tipos de excepciones que podemos usar con Catch,
terminan con la palabra Exception, esto, además de ser una "norma" o
recomendación nos sirve para saber que ese objeto es válido para su uso con
Catch. Esto lo deberíamos tener en cuenta cuando avancemos en nuestro
aprendizaje y sepamos crear nuestras propias excepciones.
Por otro lado, si sólo usamos tipos específicos de excepciones y se produce un error
que no es adecuado a los tipos que queremos interceptar, se producirá una
excepción "no interceptada" y el programa finalizará.
Dim i, j As Integer
Dim s As String
'
Try
Console.Write("Escribe un número (y pulsa Intro) ")
s = Console.ReadLine
i = CInt(s)
Console.Write("Escribe otro número ")
s = Console.ReadLine
j = CInt(s)
'
Console.WriteLine("El resultado de dividir {0} por {1} es {2}",
i, j, i \ j)
'
Catch ex As DivideByZeroException
Console.WriteLine("ERROR: división por cero")
Catch ex As OverflowException
Console.WriteLine("ERROR: de desbordamiento (número demasiado
grande)")
End Try
Para lanzar (o crear) excepciones tendremos que usar la instrucción Throw seguida
de un objeto derivado del tipo Exception.
Normalmente se hace de la siguiente forma:
Con este código estamos indicándole al Visual Basic .NET que queremos "lanzar"
(Throw) una nueva excepción (New Exception) y que el mensaje que se mostrará,
será el que le indiquemos dentro de los paréntesis.
Sub Main()
Dim n, m As Integer
'
Try
n = 10
m = 15
Dim k As Integer = n + m
'
' llamanos a un procedimiento
Prueba()
'
'... más código...
'
'
Catch ex As DivideByZeroException
Console.WriteLine("ERROR: división por cero")
Catch ex As OverflowException
Console.WriteLine("ERROR: de desbordamiento (número demasiado
grande)")
Catch ex As InvalidCastException
Console.WriteLine("ERROR: lo escrito no es un número.")
Catch ex As Exception
Console.WriteLine("Se ha producido el error: {0} {1} {2}",
ex.Message, vbCrLf, ex.ToString)
End Try
'
Console.WriteLine("Pulsa Intro para terminar")
Console.ReadLine()
End Sub
Sub Prueba()
Dim i, j As Integer
Dim s As String
Console.Write("Escribe un número (y pulsa Intro) ")
s = Console.ReadLine
i = CInt(s)
Console.Write("Escribe otro número ")
s = Console.ReadLine
j = CInt(s)
Console.WriteLine("El resultado de dividir {0} por {1} es {2}",
i, j, i \ j)
End Sub
¿Te enteras?
¿No?
Pues no te preocupes, que algún día puede que te resulte claro... je, je... por
ahora, simplemente compruébalo y experimenta... y sigue leyendo, que aún queda
un poco más...
Sub Main()
Dim n, m As Integer
'
Try
n = 10
m = 15
Dim k As Integer = n + m
'
'... más código...
'
'
Catch ex As DivideByZeroException
Console.WriteLine("ERROR: división por cero")
Catch ex As OverflowException
Console.WriteLine("ERROR: de desbordamiento (número demasiado
grande)")
Catch ex As InvalidCastException
Console.WriteLine("ERROR: lo escrito no es un número.")
Catch ex As Exception
Console.WriteLine("Se ha producido el error: {0} {1} {2}",
ex.Message, vbCrLf, ex.ToString)
End Try
'
' llamanos a un procedimiento
Prueba()
'
'
Console.WriteLine("Pulsa Intro para terminar")
Console.ReadLine()
End Sub
Sub Prueba()
Dim i, j As Integer
Dim s As String
Console.Write("Escribe un número (y pulsa Intro) ")
s = Console.ReadLine
i = CInt(s)
Console.Write("Escribe otro número ")
s = Console.ReadLine
j = CInt(s)
Console.WriteLine("El resultado de dividir {0} por {1} es {2}",
i, j, i \ j)
End Sub
Pues eso... no quiero parecer "mal educado", pero..., se acabó lo que se daba y
hasta aquí hemos llegado en esta octava entrega dedicada exclusivamente al
tratamiento de errores... al final no he tratado de los arrays... esperemos que sea
en la próxima entrega... cosa que descubrirás en cuanto esté publicada, así que,
ahora dedícate a "indagar" por tu cuenta y riesgo y sigue probando con esto de las
excepciones y los bloques Try... Catch.
CAPITULO 9
Que el personal esté tranquilo, que me refería al tema de los Arrays.
Antes de entrar en detalles sobre los arrays, creo que, ya que lo he mencionado,
deberíamos ver en qué se diferencian los tipos de datos por valor y los tipos de
datos por referencia.
¿Qué? ¿Te aclaras? A que no... pues no me extraña... porque esto no es para
aclararse... pero, confía en mí y aunque no te enteres, sigue leyendo, no
abandones ahora... (lo que no te dice el Guille es que todavía quedan cosas más
complicadas y difíciles de digerir... je, je, ¡no te quea ná!)
Vamos a ver un par de ejemplos para aclarar esto de los tipos por valor y los tipos
por referencia.
Sub Main()
' creamos una variable de tipo Integer
Dim i As Integer
' le asignamos un valor
i = 15
' mostramos el valor de i
Console.WriteLine("i vale {0}", i)
'
' creamos otra variable
Dim j As Integer
' le asignamos el valor de i
j = i
Console.WriteLine("hacemos esta asignación: j = i")
'
' mostramos cuanto contienen las variables
Console.WriteLine("i vale {0} y j vale {1}", i, j)
'
' cambiamos el valor de i
i = 25
Console.WriteLine("hacemos esta asignación: i = 25")
' mostramos nuevamente los valores
Console.WriteLine("i vale {0} y j vale {1}", i, j)
'
Console.WriteLine("Pulsa Intro para finalizar")
Console.ReadLine()
End Sub
Como puedes comprobar, cada variable tiene un valor independiente del otro.
Esto está claro.
Ahora vamos a ver qué es lo que pasa con los tipos por referencia.
Para el siguiente ejemplo, vamos a crear una clase con una sola propiedad, ya que
las clases a diferencia de los tipos por valor, deben tener propiedades a las que
asignarles algún valor.
No te preocupes si no te enteras, que de todo esto hablaremos en otra ocasión,
pero por ahora sólo es para ver, prácticamente, esto de los tipos por referencia.
Class prueba
Public Nombre As String
End Class
Sub Main()
' creamos una variable de tipo prueba
Dim a As prueba
' creamos (instanciamos) el objeto en memoria
a = New prueba()
' le asignamos un valor
a.Nombre = "hola"
' mostramos el contenido de a
Console.WriteLine("a vale {0}", a.Nombre)
'
' dimensionamos otra variable
Dim b As prueba
'
' asignamos a la nueva el valor de a
b = a
Console.WriteLine("hacemos esta asignación: b = a")
'
' mostramos el contenido de las dos
Console.WriteLine("a vale {0} y b vale {1}", a.Nombre,
b.Nombre)
' cambiamos el valor de la anterior
a.Nombre = "adios"
'
Console.WriteLine("hacemos una nueva asignación a
a.Nombre")
'
' mostramos nuevamente los valores
Console.WriteLine("a vale {0} y b vale {1}", a.Nombre,
b.Nombre)
'
Console.WriteLine("Pulsa Intro para finalizar")
Console.ReadLine()
End Sub
La clase prueba es una clase muy simple, pero como para tratar de los tipos por
referencia necesitamos una clase, he preferido usar una creada por nosotros que
cualquiera de las clases que el .NET Framework nos ofrece.
Dimensionamos una variable de ese tipo y después creamos un nuevo objeto del
tipo prueba, el cual asignamos a la variable a.
Una vez que tenemos "instanciado" (o creado) el objeto al que hace referencia la
variable a, le asignamos a la propiedad Nombre de dicho objeto un valor.
Lo siguiente que hacemos es declarar otra variable del tipo prueba y le asignamos
lo que contiene la primera variable.
Hasta aquí, es casi lo mismo que hicimos anteriormente con las variables de tipo
Integer. La única diferencia es la forma de manipular las clases, ya que no
podemos usarlas "directamente", porque tenemos que crearlas (mediante New) y
asignar el valor a una de las propiedades que dicha clase contenga. Esta es la
primera diferencia entre los tipos por valor y los tipos por referencia, pero no es lo
que queríamos comprobar, así que sigamos con la explicación del código mostrado.
Para simplificar, los tipos por valor son los tipos de datos que para usarlos no
tenemos que usar New, mientras que los tipos por referencia son tipos (clases) que
para crear un nuevo objeto hay que usar la instrucción New.
Los tipos por valor son los tipos "básicos" (o elementales), tales como Integer,
Double, Decimal, Boolean, etcétera. En la primera tabla de la entrega cuatro tienes
una relación de los tipos por valor.
Los tipos por referencia son todos los demás tipos.
Sin querer parecer que quiero confundirte más, te diré que en .NET Framework,
realmente todos los tipos son clases, aunque esos que están relacionados en la
tabla mencionada, son los que actúan como tipos por valor y el .NET Framework los
trata de una forma especial... ¡vale! lo dejo, no voy a "calentarte" más la cabeza,
que seguramente ya la tendrás en ebullición...
Los Arrays
Las variables que hemos estado usando hasta ahora, eran de tipo escalar: sólo
pueden contener un valor a la vez, (no te asustes por la palabra esa que ha usado
el Guille: escalar, es que lo ha leído en la ayuda del VS y se las quiere dar de
entendido). Pero resulta que en ocasiones nos podemos ver en la necesidad de
querer tener en una misma variable, valores que de alguna forma están
relacionados. Por ejemplo, si tenemos una colección de discos, nos podría interesar
tener esa discografía incluida en una misma variable para poder acceder a
cualquiera de esos discos sin necesidad de tener que crear una variable distinta
para cada uno de los discos, ya que sería totalmente ineficiente si, por ejemplo,
quisiéramos imprimir una relación de los mismos.
Realmente para el ejemplo este que estoy poniendo, hay otros tipos de datos que
serían más prácticos, pero... es eso, sólo un ejemplo, y cuando veamos esos otros
tipos de datos, serás tú el que decida cual utilizar.
Una de las formas en las que podemos agrupar varios datos es mediante los arrays
(o matrices).
Usando un array, podemos acceder a cualquiera de los valores que tenemos
almacenado mediante un índice numérico. Por ejemplo, si tenemos la variable
discografía y queremos acceder al tercer disco, podríamos hacerlo de la siguiente
forma: discografía(3).
Sabiendo esto, podemos comprobar que sería fácil recorrer el contenido de los
arrays mediante un bucle For.
Los tipos de datos de las variables usadas como array, pueden ser de cualquier
tipo, dependiendo de lo que queramos guardar. Por ejemplo, si queremos guardar
los nombres de los discos que tenemos, podemos usar un array del tipo String, que
lo que nos interesa es saber el porcentaje de goles por partido de nuestro equipo
de fútbol favorito, el tipo de datos del array podía ser Decimal. Incluso si queremos,
también podemos crear un array de un tipo que nosotros hayamos definido o de
cualquier clase que exista en el .NET Framework.
Vale, muy bien, pero ¿cómo narices le digo al Visual Basic que una variable es un
array?
Para poder indicarle al VB que nuestra intención es crear un array podemos hacerlo
de dos formas distintas, para este ejemplo crearemos un array de tipo Integer:
Nota:
Aunque este curso es de Visual Basic .NET te diré que en C#,
cuando declaramos un array con el equivalente a Dim a(5) As
Integer, que sería algo como:
int[] a = new int[5], lo que estamos creando es un array de tipo
int (el Integer de C#) con 5 elementos, de 0 a 4.
De igual forma, si queremos utilizar ese elemento que está en la posición 3 para
una operación, podemos hacerlo como con el resto de las variables, pero siempre
usando el paréntesis y el número de elemento al que queremos acceder:
i = b * a(3)
El índice para poder acceder al elemento del array puede ser cualquier expresión
numérica, una variable o como hemos estado viendo en estos ejemplos, una
constante. La única condición es que el resultado sea un número entero.
Por tanto, podríamos acceder a la posición indicada entre paréntesis, siempre que
el resultado sea un valor entero y, por supuesto, esté dentro del rango que
hayamos dado al declarar el array:
x = a(i + k)
Nota aclaratoria:
Cuando digo el elemento que está en la posición 3, no me refiero al tercer elemento
del array, ya que si un array empieza por el elemento 0, el tercer elemento será el
que esté en la posición 2, ya que el primer elemento será el que ocupe la posición
cero.
For i = 0 To a.Length - 1
Console.WriteLine(a(i))
Next
Al igual que las variables normales se pueden declarar y al mismo tiempo asignarle
un valor inicial, con los arrays también podemos hacerlo, pero de una forma
diferente, ya que no es lo mismo asignar un valor que varios.
Aunque hay que tener presente que si inicializamos un array al declararla, no
podemos indicar el número de elementos que tendrá, ya que el número de
elementos estará supeditado a los valores asignados.
Para inicializar un array debemos declarar ese array sin indicar el número de
elementos que contendrá, seguida de un signo igual y a continuación los valores
encerrados en llaves. Veamos un ejemplo:
En todos estos ejemplos estamos usando valores de tipo Integer, pero podríamos
hacer lo mismo si fuesen de tipo String o cualquier otro.
En el caso de que sean datos de tipo String, los valores a asignar deberán estar
entre comillas dobles o ser variables de tipo String. Por ejemplo:
s(3) = "saludamos"
Dim i As Integer
For i = 0 To s.Length - 1
Console.WriteLine(s(i))
Next
El tipo de bucle For Each es muy útil para recorrer los elementos de un array,
además de ser una de las pocas, por no decir la única, formas de poder acceder a
un elemento de un array sin indicar el índice.
Sub Main()
Dim a() As Integer = {1, 42, 15, 90, 2}
'
Console.WriteLine("Elementos del array a(): {0}", a.Length)
'
Dim i As Integer
For Each i In a
Console.WriteLine(i)
Next
'
Console.WriteLine()
'
Array.Sort(a)
'
For i = 0 To a.Length - 1
Console.WriteLine(a(i))
Next
'
Console.WriteLine("Pulsa Intro para finalizar")
Console.ReadLine()
End Sub
Si quieres saber más cosas de los arrays, te recomiendo que le eches un vistazo a
la documentación de Visual Studio .NET, pero no te preocupes, que no es que
quiera "quitarte" de en medio, simplemente es para que te acostumbres a usar la
documentación, ya que allí encontrarás más cosas y explicadas de una forma algo
más extensa y sobre todo formal... que aquí parece que nos lo tomamos a
"cachondeo"... je, je.
Para terminar con esta entrega, vamos a ver lo que te comentaba al principio: que
los arrays son tipos por referencia en lugar de tipos por valor.
Dim m As Integer = 7
Dim n As Integer
'
n = m
'
m = 9
'
Console.WriteLine("m = {0}, n = {1}", m, n)
Sin embargo, si hacemos algo parecido con dos arrays, veremos que la cosa no es
igual:
En este caso, al cambiar el contenido del índice 3 del array a, también cambiamos
el contenido del mismo índice del array b, esto es así porque sólo existe una copia
en la memoria del array creado y cuando asignamos al array b el contenido de a,
realmente le estamos asignando la dirección de memoria en la que se encuentran
los valores, no estamos haciendo una nueva copia de esos valores, por tanto, al
modificar el elemento 3 del array a, estamos modificando lo que tenemos
"guardado" en la memoria y como resulta que el array b está apuntando (o hace
referencia) a los mismos valores... pues pasa lo que pasa... que tanto a(3) como
b(3) devuelven el mismo valor.
Para poder tener arrays con valores independientes, tendríamos que realizar una
copia de todos los elementos del array a en el array b.
La única forma de tener copias independientes de dos arrays que contengan los
mismos elementos es haciendo una copia de un array a otro.
Esto lo podemos hacer mediante el método CopyTo, al cual habrá que indicarle el
array de destino y el índice de inicio a partir del cual se hará la copia. Sólo aclarar
que el destino debe tener espacio suficiente para recibir los elementos indicados,
por tanto deberá estar inicializado con los índices necesarios.
Aclaremos todo esto con un ejemplo:
Con esto, no está todo dicho sobre los arrays, aún quedan más cosas, pero habrá
que dejarlo para otra entrega, que esta ya ha dado bastante de sí.
Así que, disfruta, si puedes o te apetece, de estos días de fiesta y prepara el cuerpo
para lo que seguirá, casi con toda seguridad el próximo año. Si no quieres esperar
tanto, ya sabes, abres el Visual Studio .NET, y busca en la ayuda arrays o matrices
(esto último si la ayuda está en español) y verás que es lo que te encontrarás en la
próxima entrega... bueno, todo no, que es mucho, sólo una parte... lo que yo
considere más necesario, que del resto ya te encargarás tú de investigarlo por tu
cuenta (eso espero).
CAPITULO 10
En la entrega anterior vimos los arrays de una sola dimensión, que, por cierto, son
las que con mayor asiduidad usaremos en nuestras aplicaciones, pero el tema que
vamos a tratar hoy sigue siendo también los arrays, pero con un matiz especial, ya
que son las llamadas arrays (o matrices) multidimensionales, es decir arrays que
no sólo tienen un índice simple, sino que serán como tablas, es decir, arrays que se
pueden representarse por medio de filas y columnas, además de, si se le echa un
poco de imaginación, algún otro nivel dimensional... vale, espera y sabrás a que me
refiero.
Tal y como vimos en la entrega anterior, la diferencia entre las variables "normales"
y los arrays era que se utilizaba un índice para acceder al contenido de cada una de
las variables normales contenidas en un array. Por ejemplo, para acceder al tercer
elemento de un array llamado nombres, tendríamos que hacer algo como esto:
nombres(2). Recuerda que los arrays empiezan con un índice cero, por tanto el
índice 2 será el tercero.
Para declarar un array multidimensional, lo podemos hacer, (al igual que con las
unidimensionales), de varias formas, dependiendo de que simplemente declaremos
el array, que le indiquemos (o reservemos) el número de elementos que tendrá o
de que le asignemos los valores al mismo tiempo que la declaramos, veamos
ejemplos de estos tres casos:
Veamos cómo haríamos para averiguar cuantos elementos tiene cada una de las
tres dimensiones del array c3:
Una cosa es saber cuantos elementos tiene un array (o una de las dimensiones del
array) y otra cosa es saber cuantas dimensiones tiene dicho array.
Para saber el número de dimensiones del array, usaremos la propiedad Rank.
Por ejemplo, (si usamos la declaración hecha anteriormente), el siguiente código
nos indicará que el array c3 tiene tres dimensiones:
Console.WriteLine("EL array c3 tiene {0} dimensiones.", c3.Rank)
Como siempre, el valor devuelto por Rank será el número total de dimensiones del
array, pero ese número de dimensiones será desde cero hasta Rank - 1.
Veamos ahora un ejemplo de cómo recorrer todos los elementos del array c3, los
cuales se mostrarán en la consola. Para saber cuantos elementos hay en cada una
de las dimensiones, utilizaremos la propiedad GetUpperBound para indicar hasta
qué valor debe contar el bucle For, el valor o índice menor sabemos que siempre
será cero, aunque se podría usar la propiedad GetLowerBound.
Nota:
Esto es curioso, si todos los arrays empiezan por cero, ¿qué sentido tiene poder
averiguar el valor del índice menor? ya que, según sabemos siempre debe ser
cero. Lo mismo es que han dejado la puerta abierta a un posible cambio en esta
"concepción" del índice menor de los arrays... en fin... el tiempo (y las nuevas
versiones de .NET Framework) lo dirá.
Si has trabajado anteriormente con Visual Basic clásico, sabrás que en VB6
podemos indicar "libremente" el valor inferior así como el superior de un array y el
uso del equivalente a GetLowerBound si que tenía sentido.
Dim i, j, k As Integer
For i = 0 To c3.GetUpperBound(0)
For j = 0 To c3.GetUpperBound(1)
For k = 0 To c3.GetUpperBound(2)
Console.WriteLine("El valor de c3({0}, {1}, {2}) es {3}",
i, j, k, c3(i, j, k))
Next
Next
Next
Seguramente te estarás preguntando qué son todos esas llaves con "numericos"
que está usando el Guille en los ejemplos... no te preocupes que dentro de poco te
lo explico con un poco de detalle, aunque espero que sepas "deducir" para qué
sirven... al menos si estás probando estos "trozos" de ejemplos.
Esto es algo que debería haber explicado en la entrega anterior, pero seguramente
se me "escapó", (venga va, perdonemos al despistado del Guille, que demasiado
hace... pa lo despistao que es...), así que, para que la próxima sección no te suene
a "chino", voy a explicarte cómo poder cambiar el tamaño de un array sin perder
los valores que tuviese antes.
Para poder conseguirlo, debemos usar ReDim seguida de la palabra clave
Preserve, por tanto si tenemos la siguiente declaración:
Dim a() As Integer = {1, 2, 3, 4, 5}
Y queremos que en lugar de 5 elementos (de 0 a 4) tenga, por ejemplo 10 y no
perder los otros valores, usaremos la siguiente instrucción:
ReDim Preserve a(10)
A partir de ese momento, el array tendrá 11 elementos (de 0 a 10), los 5 primeros
con los valores que antes tenía y los nuevos elementos tendrán un valor cero, que
es el valor por defecto de los valores numéricos.
El único problema con el que nos podemos encontrar, al menos si queremos usar
Preserve para conservar los valores previos, es que sólo podemos cambiar el
número de elementos de la última dimensión del array. Si has usado el VB6 esto es
algo que te "sonará", ya que con el VB clásico tenemos el mismo inconveniente,
pero a diferencia de aquél, con ReDim podemos cambiar el número del resto de las
dimensiones, al menos la cantidad de elementos de cada dimensión, ya que si un
array es de 3 dimensiones siempre será de tres dimensiones.
Con esto último hay que tener cuidado, ya que si bien será el propio IDE el que nos
avise de que no podemos cambiar "la cantidad" de dimensiones de un array, es
decir, si tiene tres dimensiones, siempre debería tener tres dimensiones, por
ejemplo, siguiendo con el ejemplo del array c3, si hacemos esto:
ReDim c3(1, 4)
Será el IDE de Visual Studio .NET el que nos avise indicando con los dientes de
sierra que hay algo que no está bien.
Pero si cambiamos el número de elementos de las dimensiones (usando Preserve),
hasta que no estemos en tiempo de ejecución, es decir, cuando el programa llegue
a la línea que cambia el número de elementos de cada dimensión, no se nos avisará
de que no podemos hacerlo.
Aclarando temas:
Podemos usar ReDim para cambiar el número de elementos de cada una de las
dimensiones, pero no podemos cambiar el número de dimensiones.
Podemos usar ReDim Preserve para cambiar el número de elementos de la última
dimensión sin perder los valores que previamente hubiera.
En ningún caso podemos cambiar el número de dimensiones de un array.
Eliminar un array de la memoria.
Nota:
Que no te sorprenda que, en el IDE, al mostrar los miembros (métodos y
propiedades) de un array multidimensional se muestren métodos no válidos para
los arrays multidimensionales, así que, acostúmbrate a "leer" el ToolTip (mensaje
de ayuda emergente) que se muestra o, mejor aún, leer la documentación de
Visual Studio .NET.
Para usar el método Copy de la clase Array, debemos indicar el array de origen, el
de destino y el número de elementos a copiar. Siguiendo con el ejemplo del array
c3, podríamos copiar el contenido en otro array de la siguiente forma:
Para entenderlo mejor, veamos varios casos que se podrían dar. Usaremos el
contenido del array c3 que, como sabemos, está definido de esta forma: c3(3, 2, 1)
y como destino un array llamado c31.
Para copiar los elementos haremos algo así:
Array.Copy(c3, c31, c3.Length)
Espero que todo lo que hemos tratado sobre los arrays o matrices que hemos visto
en esta dos últimas entregas sea suficiente para que sepas cómo usarlas, pero te
repito y a pesar de que pienses que pueda ser un "pesado", te recomiendo que
practiques, practiques y sigas practicando, eso unido a que leas la documentación
de Visual Studio .NET, hará que te enteres bien de cómo usarlas.
También te digo que, seguramente, te encontrarás con algunas cosas en la ayuda
que, salvo que lo hayas aprendido por tu cuenta, te resultarán extrañas y puede
que no las entiendas... paciencia te pido, ya que aún quedan muchos conceptos
que debemos aprender y, como es natural no se pueden resumir en 10 entregas.
No te voy a decir que es lo que puedes encontrarte en la documentación que no
entiendas... puede que sean muchas... pero, ciñéndonos en el tema de los arrays,
hay algunos conceptos referidos a la herencia y otras cosas relacionadas con la
programación orientada a objetos que de seguro no te serán familiares.
Así que, puede ser que en la próxima entrega veamos el tema de la programación
orientada a objetos, así que... ve preparando el cuerpo y, sobre todo, la mente
para que cuando llegue ese día, que con total seguridad será el próximo mes de
febrero (siempre que estés leyendo esto cuando se publica), no te de ningún tipo
de "espasmo" o ataque "cerebral".
De todas formas, aún puedes descansar la mente unos minutos para que veamos
de que va eso de las llaves con los números ({0}) que he usado en algunos de los
ejemplos de esta y otras entregas anteriores.
Nota:
El formato especificado en la cadena usada en los métodos Write y WriteLine será
el mismo que se puede usar con el método Format de la clase String, por tanto, lo
aquí explicado será aplicable a cualquier cadena.
Los marcadores se indican con un número encerrado entre llaves, los números
indicarán el número u orden del parámetro que siga a la cadena en el que está ese
marcador. Como es habitual, el primer parámetro estará representado por el cero y
así sucesivamente.
Por ejemplo, para mostrar los valores de dos parámetros usaremos algo como esto:
Console.WriteLine("{0} {1}", i, j)
En este ejemplo, el {0} se sustituirá por el valor de la variable i, y el {1} por el
valor de la variable j.
Nota:
Los parámetros pueden ser numéricos, de cadena, e incluso objetos basados en
una clase. En todos los casos se usará la "versión" de tipo cadena de esos
parámetros, de esto sabrás un poco más cuando toquemos el tema de las clases...
sólo te lo comento para que no te extrañe ver algo "raro" si haces tus propias
pruebas.
Console.WriteLine("---------- ----------")
Console.WriteLine("{0,10} {1,-10} {2}", 10, 15, 23)
'
Console.WriteLine("---------- ----------")
Console.WriteLine("{0,10:#,###.00} {1,10}", 10.476, 15.355)
---------- ----------
10 15 23
---------- ----------
10,48 15,355
--------------------
Esta es una cadena larga de más de 20 caracteres
Cadena más corta
--------------------
Tal como podemos comprobar, la primera cadena excede los 20 caracteres (los
indicados con las rayas), por tanto se muestra todo el contenido, sin embargo la
segunda cadena mostrada si se posiciona correctamente con espacios a la izquierda
hasta completar los 20 especificados.
Pues creo que con esto acabamos esta décima entrega en la que además de ver
cómo se trabaja con arrays multidimensionales, hemos visto algunos de los
métodos usados con la clase Array, así como la forma de dar formatos a las
cadenas usadas con los métodos Write y WriteLine de la clase Console.
En la próxima entrega, salvo que cambie de opinión a última hora, cuando vaya a
escribirla, veremos o empezaremos a ver temas relacionados con la programación
orientada a objetos, al menos en sus conceptos más básicos y elementales, los
cuales iremos ampliando en otras entregas.
Creo que es muy importante saber sobre esos temas, ya que todo en .NET
Framework y por tanto en Visual Basic .NET, está estrechamente relacionado con la
programación orientada a objetos, así que... lo que te dije, ve dejando espacio libre
en tu "cabecita" para que pueda entrar todo lo referente a ese tema. Aunque no te
asustes si te parece que puede que sea algo demasiado "sofisticado" o avanzado,
ya que, como lo trataremos bastante en todo este curso de iniciación, al final
acabarás aprendiéndolo, ¡aunque no quieras!
CAPITULO 11
Como te comenté en la entrega anterior, en esta ocasión vamos a ver un tema del
que acabarás hasta la coronilla, entre otras cosas porque es algo que "tienes" que
saber y si lo dominas, cosa que pretendo y espero conseguir, te será mucho más
fácil hacer cualquier tipo de programa o utilidad con Visual Basic .NET o con
cualquier otro lenguaje de la familia .NET, como puede ser C#.
Lo que si te pido es paciencia y mucha mente "en blanco", es decir, déjate llevar y
no desesperes si de algo no te enteras, ya que intentaré que todo esto que
empezaremos en esta entrega llegue a formar parte de ti... no, no te voy a
implantar ningún chip, así que no te asustes... pero este tema es algo de lo que los
programadores de Visual Basic debemos estar "orgullosos" (por decir algo) de que
al fin... se nos permita tener, de forma más o menos completa, ya que hasta el
momento sólo teníamos acceso a ello de forma limitada... Bueno, menos rollo y
vamos al tema, que es ni más ni menos que:
Nota:
Seguramente, en esta y en las próximas entregas, utilice las siglas OOP cuando me
refiera a la programación orientada a objetos, esto es así porque éstas son las
siglas de Object Oriented Programming, que es como se dice en inglés y como
estoy acostumbrado a usar OOP, pues...
Aunque no soy muy dado a esto de las cosas teóricas, vamos a ver de que va todo
esto de la programación orientada a objetos y cuales son las "condiciones" que
cualquier lenguaje que se precie de serlo debe cumplir.
Según se dice por ahí, cualquier lenguaje basado en objetos debe cumplir estos tres
requisitos:
1. Herencia
2. Encapsulación
3. Polimorfismo
Nota:
Algunos autores añaden un cuarto requisito: la abstracción, pero este último está
estrechamente ligado con la encapsulación, ya que, de echo, es prácticamente lo
mismo, cuando te lo explique, lo comprobarás y podrás decidir por tu cuenta si son
tres o cuatro los pilares de la OOP...
Herencia
Esto es lo que la documentación de Visual Studio .NET nos dice de la herencia:
Una relación de herencia es una relación en la que un tipo (el tipo derivado) se
deriva de otro (el tipo base), de tal forma que el espacio de declaración del tipo
derivado contiene implícitamente todos los miembros de tipo no constructor del
tipo base.
(ms-help://MS.VSCC.2003/MS.MSDNVS.3082/vbls7/html/vblrfVBSpec4_2.htm)
Para que te quede un poco más claro, pero no esperes milagros, aquí tienes otra
definición de herencia:
Encapsulación
Cuando usamos las clases, éstas tienen una serie de características (los datos que
manipula) así como una serie de comportamientos (las acciones a realizar con esos
datos). Pues la encapsulación es esa capacidad de la clase de ocultarnos sus
interioridades para que sólo veamos lo que tenemos que ver, sin tener que
preocuparnos de cómo está codificada para que haga lo que hace... simplemente
nos debe importar que lo hace.
Si tomamos el ejemplo de la clase Gato, sabemos que araña, come, se mueve, etc.,
pero el cómo lo hace no es algo que deba preocuparnos, salvo que se lance sobre
nosotros... aunque, en ese caso, lo que deberíamos tener es una clase "espanta-
gatos" para quitárnoslo de encima lo antes posible...
Polimorfismo
Pues no sé que decirte... si en los dos casos anteriores la cosa estaba complicada...
En fin... a ver que es lo que nos dice la documentación de VS.NET sobre el
polimorfismo:
Vale, que a ti te ha dejado igual que hace un rato... pues... no se yo que decirte...
es que, si no lo entiendes, la verdad es que podrías dedicarte a otra cosa... je, je,
tranqui colega, que ya te he comentado antes que había que tener la mente en
blanco... y lo que no te dije es que seguramente la ibas a seguir teniendo en
blanco...
Y para que podamos ver ejemplos antes debemos tener claro una serie de
conceptos, como por ejemplo:
Las clases
Ya hemos visto antes lo que son las clases. Ya que todo lo que tiene el .NET
Framework, en realidad son clases.
Una clase no es ni más ni menos que código. Aunque dicho de esta forma, cualquier
programa sería una clase.
Cuando definimos una clase, realmente estamos definiendo dos cosas diferentes:
los datos que dicha clase puede manipular o contener y la forma de acceder a esos
datos.
Por ejemplo, si tenemos una clase de tipo Cliente, por un lado tendremos los datos
de dicho cliente y por otro la forma de acceder o modificar esos datos. En el primer
caso, los datos del Cliente, como por ejemplo el nombre, domicilio etc., estarán
representados por una serie de campos o propiedades, mientras que la forma de
modificar o acceder a esa información del Cliente se hará por medio de métodos.
Esas propiedades o características y las acciones a realizar son las que definen a
una clase.
Los Objetos
Por un lado tenemos una clase que es la que define un "algo" con lo que podemos
trabajar. Pero para que ese "algo" no sea un "nada", tendremos que poder
convertirlo en "algo tangible", es decir, tendremos que tener la posibilidad de que
exista. Aquí es cuando entran en juego los objetos, ya que un objeto es una clase
que tiene información real.
Por ejemplo, el nombre de un cliente sería una propiedad de la clase Cliente. Ese
nombre lo almacenaremos en una variable de tipo String, de dicha variable
podemos decir que es el "campo" de la clase que representa al nombre del cliente.
Por otro lado, si queremos mostrar el contenido de los campos que contiene la clase
Cliente, usaremos un procedimiento que nos permita mostrarlos, ese procedimiento
será un método de la clase Cliente.
Por tanto, los miembros de una clase son las propiedades (los datos) y los métodos
las acciones a realizar con esos datos.
Seguro que dirás: Vale, todo esto está muy bien, pero... ¿cómo hago para que
todo eso sea posible?
¿Cómo creo o defino una clase? y ya que estamos, ¿cómo defino las propiedades y
los métodos de una clase?
Para crear una clase debemos usar la instrucción Class seguida del nombre que
tendrá dicha clase, por ejemplo:
Class Cliente
End Class
Por tanto, todo lo que esté entre Class <nombre> y End Class será la definición de
dicha clase.
Para definir los miembros de una clase, escribiremos dentro del "bloque" de
definición de la clase, las declaraciones y procedimientos que creamos
convenientes. Veamos un ejemplo:
Class Cliente
Sub Mostrar()
Console.WriteLine("El nombre del cliente: {0}", Nombre)
End Sub
End Class
En este caso, la línea Public Nombre As String, estaría definiendo una propiedad
o "campo" público de la clase Cliente.
Por otro lado, el procedimiento Mostrar sería un método de dicha clase, en esta
caso, nos permitiría mostrar la información contenida en la clase Cliente.
Pero no sólo de clases vive el Visual Basic... o lo que es lo mismo, ¿para que nos
sirve una clase si no sabemos crear un objeto basado en esa clase...? Así que,
sepamos cómo crearlos.
Definimos una variable capaz de contener un objeto del tipo de la clase, esto lo
haremos como con cualquier variable:
Pero, a diferencia de las variables basadas en los tipos visto hasta ahora, para
poder crear un objeto basado en una clase, necesitamos algo más de código que
nos permita "crear" ese objeto en la memoria, ya que con el código usado en la
línea anterior, simplemente estaríamos definiendo una variable que es capaz de
contener un objeto de ese tipo, pero aún no existe ningún objeto en la memoria,
para ello tendremos que usar el siguiente código:
Con esto le estamos diciendo al Visual Basic: crea un nuevo objeto en la memoria
del tipo Cliente.
Nota:
En las versiones anteriores de Visual Basic no era recomendable usar esta forma de
instanciar un nuevo objeto en la memoria, porque, aunque de forma transparente
para nosotros, el compilador añadía código extra cada vez que se utilizaba esa
variable, pero en la versión .NET no existe ese problema y por tanto no deteriora el
rendimiento de la aplicación.
cli.Nombre = "Guillermo"
Es decir, de la misma forma que haríamos con cualquier otra variable, pero
indicando el objeto al que pertenece dicha variable.
Como podrás comprobar, esto ya lo hemos estado usando anteriormente tanto en
la clase Console como en las otras clases que hemos usado en entregas anteriores,
incluyendo los arrays.
cli.Mostrar()
Pues así es todo lo demás en .NET Framework, así que... ¿para que seguir
explicándote?
Espero que con lo dicho y lo que ya llevamos visto hasta ahora, tengas claro que
son las propiedades y los métodos y si aún tienes dudas, no te preocupes ya que
ahondaremos más en estos conceptos y sobre todo los utilizaremos hasta la
saciedad, con lo cual puedes tener la tranquilidad de que acabarás enterándote al
100% de que va todo esto...
A pesar de que aún no estés plenamente informado de todo, vamos a ver un par de
ejemplos de cómo usar las características de la programación orientada a objetos,
después, en próximas entregas, profundizaremos más.
Ya sabemos cómo definir una clase, aunque para este ejemplo, usaremos la clase
Cliente que vimos anteriormente, después crearemos otra, llamada ClienteMoroso
la cual heredará todas las características de la clase Cliente además de añadirle una
propiedad a esa clase derivada de Cliente.
Class Cliente
Public Nombre As String
Sub Mostrar()
Console.WriteLine("El nombre del cliente: {0}", Nombre)
End Sub
End Class
Class ClienteMoroso
Inherits Cliente
End Class
Como puedes comprobar, para que la clase ClienteMoroso herede la clase Cliente,
he usado Inherits Cliente, con esta línea, (la cual debería ser la primera línea de
código después de la declaración de la clase), le estamos indicando al VB que
nuestra intención es poder tener todas las características que la clase Cliente tiene.
Haciendo esto, añadiremos a la clase ClienteMoroso la propiedad Nombre y el
método Mostrar, aunque también tendremos la nueva propiedad que hemos
añadido: Deuda.
Ahora vamos a ver cómo podemos usar estas clases, para ello vamos a añadir
código en el procedimiento Main del módulo del proyecto:
Module Module1
Sub Main()
Dim cli As New Cliente()
Dim cliM As New ClienteMoroso()
'
cli.Nombre = "Guille"
cliM.Nombre = "Pepe"
cliM.Deuda = 2000
'
Console.WriteLine("Usando Mostrar de la clase Cliente")
cli.Mostrar()
'
Console.WriteLine("Usando Mostrar de la clase
ClienteMoroso")
cliM.Mostrar()
'
Console.WriteLine("La deuda del moroso es: {0}",
cliM.Deuda)
'
Console.ReadLine()
End Sub
End Module
Lo que hemos hecho es crear un objeto basado en la clase Cliente y otro basado en
ClienteMoroso.
Ahora veamos cómo podríamos hacer uso del polimorfismo o al menos una de las
formas que nos permite el .NET Framework.
Teniendo ese mismo código que define las dos clases, podríamos hacer lo siguiente:
Sub Main()
Dim cli As Cliente
Dim cliM As New ClienteMoroso()
'
cliM.Nombre = "Pepe"
cliM.Deuda = 2000
cli = cliM
'
Console.WriteLine("Usando Mostrar de la clase Cliente")
cli.Mostrar()
'
Console.WriteLine("Usando Mostrar de la clase
ClienteMoroso")
cliM.Mostrar()
'
Console.WriteLine("La deuda del moroso es: {0}",
cliM.Deuda)
'
Console.ReadLine()
End Sub
En este caso, la variable cli simplemente se ha declarado como del tipo Cliente,
pero no se ha creado un nuevo objeto, simplemente hemos asignado a esa variable
el contenido de la variable cliM.
Realmente las dos variables apuntan a un mismo objeto, por eso al usar el método
Mostrar se muestra lo mismo. Además de que si hacemos cualquier cambio a la
propiedad Nombre, al existir sólo un objeto en la memoria, ese cambio afectará a
ambas variables.
Console.WriteLine()
cli.Nombre = "Juan"
Console.WriteLine("Después de asignar un nuevo nombre a
cli.Nombre")
cli.Mostrar()
cliM.Mostrar()
A este tipo de variables se las llama variables por referencia, ya que hacen
referencia o apuntan a un objeto que está en la memoria. A las variables que antes
hemos estado viendo se las llama variables por valor, ya que cada una de esas
variables tienen asociado un valor que es independiente de los demás.
Recuerda que estas definiciones ya las vimos en la entrega número nueve.
Pues, vamos a dejarlo aquí... no sin resumir un poco lo que hemos tratado en esta
entrega, de esta forma, si eres de los que empiezan a leer por el final, lo mismo te
decides a leer desde el principio.
Hemos visto las condiciones que un lenguaje debe cumplir para que sea
considerado orientado a objetos: herencia, encapsulación y polimorfismo. También
hemos aprendido a definir una clase y a derivar una clase a partir de otra por
medio de la instrucción Inherits. Ya sabemos cómo definir variables cuyo tipo sea
una clase y cómo crear nuevos objetos en memoria a partir de una clase. También
hemos comprobado que el que exista más de una variable del tipo de una clase no
significa que exista un objeto independiente para cada una de esas variables y por
último hemos comprobado que son las variables por referencia y cómo afectan a los
objetos referenciados por ellas.
CAPITULO 12
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...
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
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.
Te resumiré los distintos miembros de las clases y por extensión de los módulos y
las estructuras:
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
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.
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).
Saludar("Guillermo")
Saludar("Hello", "Guille")
Sub PruebaSaludar2()
Dim elNombre As String
elNombre = "Guillermo"
Saludar2(elNombre)
'
' ¿qué valor mostraría esta línea?
Console.WriteLine(elNombre)
End Sub
Sub PruebaSaludar3()
Dim elNombre As String
elNombre = "Guillermo"
Saludar3(elNombre)
'
' ¿qué valor mostraría esta línea?
Console.WriteLine(elNombre)
End Sub
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.
CAPITULO 13
Parámetros opcionales
Como vimos, en un procedimiento podemos indicar parámetros, en el caso de que
el procedimiento necesite más de un parámetro, tendremos que indicarlos todos y
cada uno de ellos, además hay que hacerlo en el mismo orden en el que se han
declarado dichos parámetros. Por ejemplo, si tenemos la siguiente declaración de
un procedimiento:
Sub Prueba(ByVal uno As Integer, ByVal dos As Integer)
Para poder usarlo hay que hacerlo de esta forma:
Prueba(10, 20)
El parámetro "uno" recibirá el valor 10, mientras que el parámetro "dos" recibirá el
valor 20. Es decir, el primer valor indicado para el primer parámetro, el segundo
valor indicado será para el segundo parámetro y así sucesivamente.
Por otro lado, si sólo quisiéramos indicar el primer parámetro, pero no el segundo,
podríamos intentar hacer algo como esto:
Prueba(10)
Pero... ¡no se puede hacer! ya que produciría un error por la sencilla razón de que
hay que indicar los dos parámetros "forzosamente".
Entonces... ¿cómo lo hago si en ocasiones quiero que sólo se indique un
parámetro?
La respuesta es: indicando parámetros opcionales.
Para poder indicarle al Visual Basic .NET que un parámetro es opcional debemos
indicarlo usando la instrucción Optional antes de la declaración del parámetro en
cuestión, aunque la cosa no acaba aquí, ya que además tenemos que indicar el
valor que tendrá por defecto, es decir, si no se indica ese parámetro, éste debe
tener un valor predeterminado. Pero tampoco debemos creernos que haciendo esto
de la forma que queramos podemos indicar que un parámetro es opcional. Bueno,
si, lo dicho hasta ahora es válido, pero también debemos tener en cuenta que sólo
podemos especificar parámetros opcionales después de todos los parámetros
obligatorios.
Dicho esto, veamos cómo declarar el procedimiento Prueba para indicar que el
segundo parámetro es opcional y que el valor predeterminado (si no se indica) es
cinco:
Sub Prueba(ByVal uno As Integer, Optional ByVal dos As Integer = 5)
Con esta declaración podemos usar este procedimiento de estas dos formas:
Prueba(10, 20)
En este caso se indicará un 10 para el parámetro uno y 20 para el parámetro dos.
Prueba(10)
Si no indicamos el segundo parámetro, el valor que se usará dentro del
procedimiento será el valor indicado en la declaración, es decir: 5.
Si quisiéramos que los dos parámetros fueran opcionales, tendríamos que declarar
el procedimiento de esta forma:
Sub Prueba(Optional ByVal uno As Integer = 3, Optional ByVal dos As
Integer = 5)
En este caso podemos llamar al procedimiento de estas formas:
1- Prueba()
2- Prueba(10)
3- Prueba(10, 20)
4- Prueba(,20)
5- Prueba(uno:=4)
6- Prueba(dos:=7)
7- Prueba(dos:=8, uno:=6)
8- Prueba(6, dos:=8)
En la cuarta sólo se indica el segundo parámetro, en este caso para "no" indicar el
primero, dejamos el hueco, por eso se pone la coma y a continuación el segundo
parámetro.
Esto está bien, y es la forma que "antes" se solía usar, pero en el caso de que haya
muchos parámetros opcionales, puede ser algo "lioso" eso de tener que usar la
coma para separar los parámetros que no se indican, ya que en el caso de que
alguno de los parámetros opcionales no se vaya a indicar hay que "dejar" el
hueco... imagínate que tenemos un procedimiento con 5 parámetros opcionales y
sólo queremos indicar el tercero y el quinto, usando este mismo sistema lo
haríamos así:
Prueba5(,,3,,5)
Y como puedes comprobar, es fácil olvidarse de alguna coma intermedia.
La octava forma es como para rizar el rizo, ya que sería lo mismo que: Prueba(6,
8), pero nos sirve para que sepamos que también podemos hacerlo así, aunque
esto sólo sería efectivo si en lugar de sólo dos parámetros tenemos más de dos
parámetros. En estos casos en los que se mezclan valores "por posición" con
valores "por variable" hay que tener presente que si no se indica el nombre de la
variable del parámetro, los parámetros indicados explícitamente deben estar en el
mismo orden en el que están declarados en el procedimiento.
Por ejemplo, esto daría error: Prueba(uno:=6, 8) por la sencilla razón de que si
se indica un valor usando el nombre de uno de los parámetros los siguientes deben
también indicarse usando el nombre del parámetro.
Sobrecarga de procedimientos
Pero aunque esto de usar Optional está muy bien, hay que tener en cuenta de que
esta instrucción existe "por compatibilidad" con las versiones anteriores de Visual
Basic... o casi.
¿Por qué digo esto? Porque ahora, gracias a que Visual Basic .NET permite la
sobrecarga de procedimientos, podemos hacer lo mismo de una forma más intuitiva
y menos "liante" al menos de cara al que vea nuestro código (en ese "alguien"
también nos incluimos nosotros, los autores del código).
Sub Prueba2()
' Si no se indica ninguno de los dos parámetros,
' usar los valores "por defecto"
Prueba2(3, 5)
End Sub
Sub Prueba2(ByVal uno As Integer)
' Si no se indica el segundo parámetro,
' usar el valor "por defecto"
Prueba2(uno, 5)
End Sub
Sub Prueba2(ByVal uno As Integer, ByVal dos As Integer)
Console.WriteLine("uno = {0}, dos = {1}", uno, dos)
Console.WriteLine()
End Sub
Nota:
Cuando se usan procedimientos sobrecargados, es el propio compilador de Visual
Basic .NET el que decide cual es el procedimiento que mejor se adecua a los
parámetros que se han indicado al llamar a ese procedimiento.
Sí, ya se que no te he explicado de que va eso del constructor de una clase, así que
aquí tienes un pequeño adelanto:
Nota:
De esto del constructor de las clases hablaremos más en otra ocasión, por ahora
sólo quiero que sepas que existe y para que se usa.
Como te decía, esto es útil si queremos que al crear un objeto (o instancia) de una
clase podamos hacerlo de varias formas, por ejemplo, sin indicar ningún parámetro
o bien indicando algo que nuestra clase necesite a la hora de crear una nueva
instancia de dicha clase.
Por ejemplo, si tenemos una clase llamada Cliente, puede sernos útil crear nuevos
objetos indicando el nombre del cliente que contendrá dicha clase.
Veámoslo con un ejemplo:
Class Cliente
Public Nombre As String
Public email As String
'
Sub New()
'
End Sub
Esta clase nos permite crear nuevos objetos del tipo Cliente de dos formas.
Por ejemplo si tenemos una variable llamada cli, declarada de esta forma:
Dim cli As Cliente
podemos crear nuevas instancias sin indicar ningún parámetro:
cli = New Cliente()
o indicando un parámetro, el cual se asignará a la propiedad Nombre de la clase:
cli = New Cliente("Guillermo")
Nota:
Como te dije en la nota anterior, de esto del constructor hablaremos en otra
ocasión con algo de más detalle o profundidad.
Lo único que debemos tener presente es que el array de parámetros indicados con
ParamArray debe ser el último parámetro indicado en la declaración del
procedimiento y que no es necesario usar la palabra Optional, ya que estos
parámetros son opcionales de forma predeterminada.
Nota:
Cuando digo miembro de una clase, me refiero a cualquier campo, propiedad,
enumeración, método o evento.
Te aclaro que además del modificador Private y Public hay otros, los cuales
veremos en esta o en otra entrega posterior.
Cuando declaramos un campo con el modificador Public, estamos haciendo que ese
campo (o variable) sea accesible desde cualquier sitio, por otro lado, si lo
declaramos como Private, sólo estará accesible en la propia clase.
Module Module1
Sub Main()
' creamos una nueva instancia de la clase
Dim p As New Prueba14()
'
' asigmos los valores a los campos públicos
p.Nombre = "Guillermo"
p.Apellidos = "Som"
' usamos el método para mostrar la información en la
consola
p.Mostrar()
'
' esto dará error
'Console.WriteLine(p.elNombreCompleto)
'
Console.WriteLine("Pulsa Intro")
Console.ReadLine()
End Sub
End Module
Si ya has trabajado antes con Visual Basic, a estos campos públicos también se les
podían llamar propiedades, en Visual Basic .NET esto no ha cambiado y se pueden
considerar a los campos públicos como si fuesen propiedades de una clase.
Pero, realmente, las propiedades son otra cosa diferente, al menos así deberíamos
planteárnoslo y, tanto en Visual Basic .NET como en las versiones anteriores,
además de declarar una propiedad usando la declaración de un campo público,
también podemos usar la instrucción Property para que quede más claro cual es
nuestra intención.
Como veremos a continuación, el uso de Property nos dará algo de más trabajo,
pero también nos proporcionará mayor control sobre lo que se asigne a esa
propiedad.
Por ejemplo, imagínate que no se debería permitir que se asigne una cadena vacía
al al Nombre o al Apellido, en el ejemplo anterior, esto no sería posible de
"controlar", ya que al ser "variables" públicas no tenemos ningún control sobre
cómo ni cuando se asigna el valor, por tanto, salvo que creemos unos métodos
específicos para asignar y recuperar los valores de esas dos "propiedades", no
tendríamos control sobre la asignación de una cadena vacía.
Vamos a ver cómo se podría "controlar" esto que acabo de comentar, para
simplificar, sólo vamos a comprobar si al nombre se le asigna una cadena vacía,
para ello, vamos a crear un procedimiento que se encargue de asignar el valor al
nombre y otro que se encargue de recuperar el contenido de esa "propiedad".
Estos métodos se llamarán AsignaNombre y RecuperaNombre.
El primero nos permitirá asignar un nuevo nombre y el segundo nos permitirá
recuperar el contenido de esa "propiedad".
Module Module1
Sub Main()
' creamos una nueva instancia de la clase
Dim p As New Prueba14_2()
'
' asigmos los valores a los campos públicos
p.AsignarNombre("Guillermo")
p.Apellidos = "Som"
' usamos el método para mostrar la información en la
consola
p.Mostrar()
'
Console.WriteLine("El nombre es: " & p.RecuperarNombre)
'
Console.WriteLine()
Console.WriteLine("Pulsa Intro")
Console.ReadLine()
End Sub
End Module
Declaramos una variable privada (un campo privado) llamado elNombre, este
campo nos permitirá almacenar el nombre que se asigne usando el método
AsignarNombre, el cual recibirá un parámetro, antes de asignar el contenido
indicado en ese parámetro, comprobamos si es una cadena vacía, de ser así, no
hacemos nada, ya que sólo se asigna el nuevo valor si el contenido del parámetro
es distinto de una cadena vacía, con esto conseguimos lo que nos proponíamos: no
asignar al nombre una cadena vacía.
Por otro lado, si queremos acceder al contenido del "nombre", tendremos que usar
el método RecuperarNombre, esta función simplemente devolverá el contenido
del campo privado elNombre.
En cuanto al campo Apellidos, (el cual actuará como una propiedad, es decir,
representa a un dato de la clase), éste permanece sin cambios, por tanto no se
hace ninguna comprobación y podemos asignarle lo que queramos, incluso una
cadena vacía.
Esto está muy bien, pero no es "intuitivo", lo lógico sería poder usar la propiedad
Nombre tal como la usamos en el primer ejemplo, pero de forma que no permita
asignarle una cadena vacía.
Esto lo podemos conseguir si declaramos Nombre como un procedimiento de tipo
Property.
La forma de usarlo sería como en el segundo ejemplo, pero en lugar de usar
AsignarNombre o RecuperarNombre, usaremos Nombre, que es más intuitivo,
aunque, como comprobarás, "internamente" es como si usáramos los dos
procedimientos que acabamos de ver.
Vale, esto está muy bien, pero... ¿cómo se declara un procedimiento Property?
Tranqui... que ya llegamos a eso...
Si queremos que Nombre sea realmente una propiedad (un procedimiento del tipo
Property) para que podamos hacer ciertas comprobaciones tanto al asignar un
nuevo valor como al recuperar el que ya tiene asignado, tendremos que crear un
procedimiento como el que te muestro a continuación:
Es decir, declaramos un procedimiento del tipo Property, el cual tiene dos bloques
internos:
El primero es el bloque Get, que será el código que se utilice cuando queramos
recuperar el valor de la propiedad, por ejemplo para usarlo en la parte derecha de
una asignación o en una expresión.
El segundo es el bloque Set, que será el código que se utilice cuando queramos
asignar un nuevo valor a la propiedad, tal sería el caso de que esa propiedad
estuviera en la parte izquierda de una asignación.
Como puedes comprobar, el bloque Set recibe un parámetro llamado Value que es
del mismo tipo que la propiedad, en este caso de tipo String. Value representa el
valor que queremos asignar a la propiedad y representará lo que esté a la derecha
del signo igual de la asignación. Por ejemplo, si tenemos esto: p.Nombre =
"Guillermo", "Guillermo" será lo que Value contenga.
Nota:
En el caso de Visual Basic .NET, el parámetro indicado en el bloque Set se puede
llamar como queramos. En C#, siempre se llamará value, además de que no se
indica el parámetro en el bloque set.
Por esa razón, se recomienda dejar el nombre Value, que es el que el VB .NET
utiliza automáticamente cuando declaramos un procedimiento de tipo Property.
Fíjate que cuando creamos un procedimiento Property siempre será necesario tener
un campo (o variable) privado que sea el que contenga el valor de la propiedad.
Ese campo privado lo usaremos para devolver en el bloque Get el valor de nuestra
propiedad y es el que usaremos en el bloque Set para conservar el nuevo valor
asignado.
Ni que decir tiene que el tipo de datos del campo privado debe ser del mismo
tipo que el de la propiedad.
Nota:
De todas formas, no es recomendable hacer mucho "trabajo" dentro de una
propiedad, sólo lo justo y necesario.
Si nuestra intención es que dentro de una propiedad se ejecute un código que
pueda consumir mucho tiempo o recursos, deberíamos plantearnos crear un
método, ya que las propiedades deberían asignar o devolver los valores de forma
rápida.
Debido a que en Visual Basic .NET los campos públicos son tratados como
propiedades, no habría demasiada diferencia en crear una propiedad declarando
una variable pública o usando un procedimiento Property, pero deberíamos
acostumbrarnos a crear procedimientos del tipo Property si nuestra intención es
crear una propiedad, además de que el uso de procedimientos Property nos da más
juego que simplemente declarando una variable pública.
Nota:
En las versiones de Visual Basic anteriores a .NET la forma de declarar una
propiedad es mediante tres tipos de procedimientos Property:
Property Get para devolver el valor de la propiedad,
Property Let para asignar el nuevo valor a la propiedad y
Property Set para asignar un objeto a esa propiedad.
En Visual Basic .NET sólo existen dos bloques: Get y Set, el segundo sería el
equivalente a los dos últimos de Visual Basic 6.0
Para poder conseguir que una propiedad sea de sólo lectura, tendremos que
indicárselo al Visual Basic .NET de la siguiente forma:
Nota:
En las versiones anteriores de Visual Basic si sólo queríamos una propiedad de sólo
lectura, simplemente se declaraba el procedimiento Property Get y no era
necesario indicar ReadOnly. El mero hecho de no declarar el procedimiento
Property Let indica que es de sólo lectura. Esto mismo también es aplicable a C#,
en ese lenguaje no es necesario indicar explícitamente si es o no de sólo lectura.
Por tanto, si ya has programado con C# y quieres hacer algo en VB .NET tendrás
que tener esto presente.
Como te comenté hace unas líneas, la forma de asignar el valor que tendrá un
campo de sólo lectura, sería asignándolo en el constructor de la clase. Por tanto,
podríamos tener un constructor (Sub New) que reciba como parámetro el valor que
tendrá ese campo de sólo lectura.
En el siguiente código vamos a declarar una clase que tendrá un campo de sólo
lectura, el cual se asigna al crear una nueva instancia de la clase.
Esta clase tiene definidos dos constructores: uno sin parámetros y otro que recibe
un valor de tipo Integer, ese valor será el que se use para el campo de sólo lectura.
En el Sub Main, el cual está declarado como Shared para que se pueda usar como
punto de entrada del ejecutable, declaramos dos objetos del tipo de la clase, el
primero se instancia usando New sin ningún parámetro, mientras que el segundo se
crea la nueva instancia indicando un valor en el constructor, ese valor será el que
se utilice para darle valor al campo de sólo lectura, cosa que se demuestra en la
salida del programa:
p.LongitudNombre = 50
p1.LongitudNombre = 25
Y para que queden las cosas claras, decirte que una vez que hemos asignado el
valor al campo de sólo lectura, ya no podemos modificar dicho valor, salvo que esa
modificación se haga en el constructor. Por tanto, sólo podemos asignar un nuevo
valor a un campo de sólo lectura en el constructor de la clase.
Dicho esto, si nuestras mentes fuesen tan retorcidas como para hacer esto, sería
factible y no produciría ningún error:
Public Sub New(ByVal nuevaLongitud As Integer)
LongitudNombre = nuevaLongitud
LongitudNombre = LongitudNombre + 10
End Sub
Por la sencilla razón de que "dentro" del constructor podemos asignar un nuevo
valor al campo de sólo lectura.
Aquí te dejo algunos links de la ayuda de Visual Studio .NET que te ayudarán a
comprender mejor lo que hemos tratado en esta entrega:
Instrucciones ms-help://.../cpgenref/html/cpconfieldusageguidelines.htm
de uso de
campos
Comparación ms-
de help://.../vbcn7/html/vbconPropertyProceduresVsPublicVariables.ht
procedimiento m
s de
propiedades y
campos
Agregar ms-help://.../vbcn7/html/vbconProperties.htm
campos y
propiedades a
una clase
Propiedades y ms-help://.../vbcn7/html/vaconpropertyprocedures.htm
procedimiento
s de propiedad
Property ms-help://.../vblr7/html/vastmproperty.htm
(Instrucción)
Get ms-help://.../vblr7/html/vastmget.htm
(Instrucción)
Set ms-help://.../vblr7/html/vastmset.htm
(Instrucción)
En la próxima entrega, si no cambio de idea, veremos algo más del ámbito de los
miembros de una clase, es decir, más cosas sobre Public, Private y otros
modificadores de ámbito. También intentaré explicarte algo más sobre la
sobrecarga de procedimientos... pero eso será dentro de un tiempo... ¿un mes?
esperemos que como mucho sea dentro de un mes...
CAPITULO 15
En esta clase hemos definido dos campos: elNombre y Apellidos, el primero está
declarado como Private, por tanto sólo será visible dentro de la clase, es decir: no
podemos acceder a ese campo desde fuera de la clase, ahora lo comprobaremos. El
segundo está declarado como Public, por tanto será accesible (o visible) tanto
desde dentro de la clase como desde fuera de ella.
Además de esos dos campos, también tenemos una propiedad y un método, ambos
declarados como Public.
El método Mostrar, que es un procedimiento de tipo Sub, puede acceder tanto a la
variable declarada como Private como a la que hemos declarado como Public, ya
que ambas variables (en este caso también campos de la clase) tienen la
"cobertura" suficiente para que sean vistas (o accesibles) desde cualquier sitio
"dentro" de la propia clase.
La propiedad Nombre, tiene dos bloques de código, uno será la parte Get (la que
devuelve el valor de la propiedad) y la otra es el bloque Set (el que asigna el valor
a la variable privada). Desde los dos bloques podemos acceder a la variable privada
elNombre, ya que el ámbito de dicha variable es: toda la clase.
Pero si te fijas, en el bloque Set de la propiedad se ha declarado una variable, esa
variable sólo será "visible" dentro de ese bloque y no podrá ser accedida desde
ningún otro sitio, en cuanto acabe ese bloque, la variable Value "desaparece" y
deja de existir... (si no te aclaras, no te preocupes por ahora, que esto lo
aclararemos).
Antes de seguir con más explicaciones, veamos el código desde el que usaremos
esa clase.
Module Module1
Sub Main()
Dim n As New Prueba15()
'
n.Nombre = "Guille"
n.Apellidos = "Som"
n.Mostrar()
End Sub
End Module
Figura 1
Nota:
Fíjate que en Visual Basic, todos los "bloques" o niveles (que siempre están
encerrados entre un inicio y un final), suelen terminar con End <tipo de nivel>. Por
ejemplo un bloque If... Then ocupará desde el If... Then hasta el End If, (aclaro que
salvo que esté definido como si no fuese un bloque). Las excepciones son For que
llegará hasta el Next y Do que lo hará hasta Loop.
Los tipos de niveles están enumerados de menor a mayor, es decir, una variable
declarada en un nivel de módulo, tendrá más cobertura que otra declarada en un
bloque If.
Lo cual quiere decir que si declaramos una variable a nivel de módulo, esa variable
será visible desde cualquier otro "nivel" inferior. Cosa que hemos comprobado en el
código de la clase anterior. La variable elNombre declarada a nivel de módulo (en
este caso el módulo es uno del tipo Class) es visible en un nivel inferior, en ese
ejemplo podemos acceder a ella desde los dos procedimientos que tenemos.
Como te comentaba al principio de esta sección, las variables las podemos declarar
de varias formas, dependiendo del "nivel" en el que la declaremos, salvo a nivel de
Namespace, ya que en un Namespace no podemos declarar una variable.
Como hemos visto desde el principio de este curso, lo habitual para declarar una
variable es usando Dim. De hecho, usar Dim es la única forma de declarar una
variable en los dos niveles inferiores (bloque y procedimiento). Pero en el nivel de
módulo podemos usar tanto Dim como las instrucciones Private, Public (y las que
ahora veremos). Cuando declaramos una variable con Dim en un nivel de módulo
es lo mismo que declararla con Private.
Pero aún hay más, si el parámetro de un procedimiento recibe una variable que se
llama como otra declarada a nivel superior, esa variable también ocultaría a la de
nivel superior.
Por ejemplo, si tenemos este procedimiento:
Nota:
Para aclarar esto, seguramente tendría que dar explicaciones de cómo funciona la
pila (stack) y cómo se almacenan las variables locales en la pila, pero creo que esto
es un tema que lo que podría hacer es complicarte aún más las cosas... de todas
formas, sin que lo tomes como "doctrina", te diré que cada vez que se usan las
variables locales a un procedimiento (incluidos los parámetros declarados con
ByVal), estos valores (o referencias en el caso de que sean tipos por referencia), se
almacenan en una memoria especial llamada "pila", cuando se sale del
procedimiento se "limpia" la pila y por tanto esas variables se pierden.
Pero, habrá ocasiones en que no queramos que esos valores de las variables locales
se pierdan, en Visual Basic podemos hacerlo declarando las variables usando la
instrucción Static en lugar de Dim.
Static le indica al compilador que esa variable debe mantener el valor entre
distintas llamadas al procedimiento, para que de esa forma no se pierda el valor
que tuviera.
Las variables declaradas con Static siguen siendo locales al procedimiento y
también "ocultarán" a variables declaradas a nivel de módulo que se llamen de
igual forma. Es decir, funcionan como las declaradas con Dim, pero mantienen el
valor, además de que Static sólo se puede usar para declarar variables dentro de
procedimientos.
Veamos un ejemplo que lo aclare.
Si escribimos este código (el cual no tendrá nada que ver con la clase y módulo que
hemos usado antes):
Module Modulo2
Sub Main()
Dim i As Integer
For i = 1 To 3
Prueba()
Next
End Sub
'
Sub Prueba()
Dim numDim As Integer
Static numStatic As Integer
'
Dim i As Integer
For i = 1 To 10
' incrementamos las variables
numDim += 1
numStatic += 1
Next
Console.WriteLine("El valor de numDim = {0}, " & _
"el valor de numStatic = {1}", _
numDim, numStatic)
End Sub
End Module
En los bloques podemos declarar variables "privadas" a esos bloques, pero en esta
ocasión sólo pueden declararse con Dim.
Las variables declaradas en un bloque sólo serán visibles "dentro" de ese bloque,
por tanto no podrán ser accedidas desde fuera del bloque.
Las limitaciones que tenemos en Visual Basic .NET respecto a las variables "locales"
declaradas en un bloque, es que no pueden llamarse de igual forma que una
declarada en el mismo "nivel" en el que se usa el bloque, normalmente en un
procedimiento, ya que no podemos usar un bloque fuera de un procedimiento.
Cuando digo bloque, me estoy refiriendo a un "bloque" IF, DO, FOR, CASE, etc.
Una variable declarada en un bloque si se puede llamar como otra declarada a nivel
de módulo, en ese caso, la variable "local" ocultará a la declarada a nivel de
módulo.
Module Modulo3
Sub Main()
Dim i As Integer
For i = 1 To 2
Console.WriteLine("Llamando a Prueba3, cuando i
vale {0}", i)
Prueba3()
Next
End Sub
'
Sub Prueba3()
Dim i As Integer
For i = 1 To 5
If i > 1 Then
Dim j As Integer
Dim k As Integer = 2
'
' incrementamos las variables
k += 1
j += 1
Console.WriteLine("j = {0}, k = {1}", j, k)
End If
Next
End Sub
End Module
En este ejemplo, dentro del bloque If i > 1 Then... tenemos dos variables locales al
bloque. La variable j se ha declarado de forma "normal", mientras que la variable k
se ha declarado usando un valor inicial. Cada vez que llamemos al procedimiento
Prueba3, el bucle se repetirá 5 veces y la condición para entrar en el bloque se
ejecutará 4 de esas 5 veces, y como podemos comprobar la variable j va tomando
valores desde 1 hasta 4, mientras que k siempre muestra el valor 3, (dos que le
asignamos al declarar y uno del incremento).
Cuando declaramos una variable a nivel de módulo, ésta será visible en todo el
módulo (clase) en el que se ha declarado, además, dependiendo del modificador de
nivel de visibilidad que tenga, podrá ser "vista" (o estará accesible) desde otros
sitios. Recuerda que las variables declaradas en los módulos se les llama "campos"
y si son accesibles desde fuera de la clase, pueden llegar a confundirse con las
propiedades, al menos en la forma de usarla.
Hay que tener en cuenta que el ámbito que tendrán los elementos declarados, (por
elemento entenderemos que es una variable, un procedimiento, una clase, módulo,
estructura o una enumeración), dependerán del nivel en el que se encuentran. Por
ejemplo, cuando declaramos un elemento con el modificador Public dentro de un
nivel que ha sido declarado como Private, el elemento declarado como público no
podrá "sobrepasar" el nivel privado. Por otro lado, si se declara un elemento como
Private, será visible desde cualquier otro elemento que esté en el mismo nivel o en
un nivel inferior.
Nota:
En la documentación de Visual Studio .NET, ámbito es el nivel de visibilidad que
puede tener, ya sea a nivel de bloque, procedimiento, módulo o espacio de
nombres.
Y la accesibilidad es la "visibilidad" de dicho elemento, si es público, privado, etc.
Te aclaro esto, ya que yo suelo usar indistintamente ámbito y accesibilidad para
referirme a cómo podemos acceder (o desde dónde) a un elemento, ya sea una
variable, procedimiento o clase. Espero no confundirte demasiado...
Otra cosa a tener en cuenta es que cuando declaramos en Visual Basic .NET una
clase, una estructura, una enumeración o un procedimiento sin especificar el
ámbito, (ver los ejemplos anteriores), de forma predeterminada será Public. En C#
el ámbito predeterminado es private.
Que porqué te digo también lo de C#... porque hoy me ha dado por ahí... además
de que algunos me lo habéis pedido y como casi me cuesta el mismo trabajo... pero
no sé si esto será una tónica general en futuras entregas, todo dependerá de la
"aceptación" que tenga... si no te parece bien dímelo, igualmente si te parece una
buena idea, dímelo también. ¿Cómo decírmelo? Enviándome un mensaje que en el
asunto diga algo como: Me parece bien que digas lo de C# en el curso de VB o
como esto otro: Guille passa de decir las cosas de C# en el curso de VB ¡que me
lías! (pero digas lo que digas, el Guille hará lo que le parezca... je, je...) bueno,
vale, a lo mejor tengo en cuenta tu opinión... pero, por favor, opina. Gracias.
Ya que estamos con esto de los Module, voy a explicártelo mejor o con más detalle.
Por regla general, se usarán las clases de tipo Class cuando "realmente" queramos
crear una clase. Para poder acceder a los miembros de una clase, tendremos que
crear una nueva instancia en la memoria.
Por otro lado, usaremos las clases de tipo Module cuando queramos tener
"miembros" que puedan ser usados en cualquier momento, sin necesidad de crear
una nueva instancia de la clase, de hecho no podemos crear una instancia de un
tipo declarado como Module. El que podamos acceder a los miembros de un Module
sin necesidad de crear una nueva instancia, es por el hecho de que esos miembros
son miembros compartidos.
Pero los Module no son los únicos "poseedores" de la exclusiva de los miembros
compartidos, ya que podemos declarar clases, (del tipo Class), que tengan
miembros compartidos. De hecho, esta es la única forma de hacerlo en C#.
Veamos cómo podemos crear estas clases con miembros compartidos, y lo que es
más importante (o casi), cómo decirle al Visual Basic .NET que un miembro de una
clase es un miembro compartido.
Para que un miembro de una clase esté compartido, hay que usar el modificador
Shared (static en C#).
Esta instrucción le indicará al runtime de .NET que ese miembro puede ser accedido
sin necesidad de crear una nueva instancia de la clase. Para simplificar, te diré que
es como si el CLR hubiese creado una instancia de esa clase y cada vez que
queramos acceder a un miembro compartido, usara esa instancia para acceder a
ese miembro, (seguramente no será así, pero esa es la impresión que da).
La única "pega" con los miembros compartidos es que no se pueden usar en ellos
variables u otros miembros que no estén compartidos.
Nota:
A los miembros de una clase que no están compartidos se les llama miembros de
instancia, ya que sólo podemos acceder a ellos desde una instancia creada en la
memoria por medio de New.
Otra nota:
A los miembros compartidos también se les llama miembros estáticos, así que, si
en algún sitio te dicen que este o aquel miembro es estático, ya sabes que están
diciendo que son compartidos... seguramente esa es la razón de que en C# se use
static en lugar de Shared. Además, en la documentación de Visual Studio .NET
(versión en castellano), cuando se tratan temas de Visual Basic, se dice que son
miembros compartidos, mientras que al tratar temas de C# dicen que son
miembros estáticos. Así que... ¡que no te confundan! (que ya me encargo yo de
confundirte... je, je).
Class Prueba15_5
' miembros compartidos
Public Shared elNombreShared As String
Public Shared Sub MostrarCompartido()
Console.WriteLine("Este procedimiento está declarado
como compartido (Shared)")
Console.WriteLine("Sólo podemos acceder a miembros
compartidos: {0}", elNombreShared)
End Sub
'
' miembros de instancia
Public elNombreInstancia As String
Public Sub MostrarInstancia()
Console.WriteLine("Este procedimiento es de
instancia.")
Console.WriteLine("Podemos acceder a miembros de
instancia: {0}", elNombreInstancia)
Console.WriteLine("y también a miembros compartidos:
{0}", elNombreShared)
End Sub
'
End Class
En esta clase, hemos declarado un "campo" y un procedimiento compartido
(usando Shared) y otro campo y otro procedimiento no compartido (de instancia).
Desde el procedimiento compartido sólo podemos acceder al campo compartido.
Pero desde el procedimiento de instancia podemos acceder tanto al miembro
compartido como al de instancia.
Recuerda que una instancia es un nuevo objeto creado en la memoria. Por tanto
cada instancia (u objeto en la memoria) tendrá su propia copia de los miembros de
instancia; mientras que de los miembros compartidos sólo existe una copia en
memoria que será común para todos y cada uno de los objetos instanciados.
Module Modulo5
Sub Main()
' para acceder a miembros compartidos
' usaremos el nombre de la clase:
Prueba15_5.elNombreShared = "Shared"
Prueba15_5.MostrarCompartido()
'
Console.WriteLine()
'
' creamos una instancia de la clase
Dim o As New Prueba15_5()
'
o.elNombreInstancia = "Instancia"
o.MostrarCompartido()
o.elNombreShared = "Shared cambiado"
o.MostrarInstancia()
'
Console.WriteLine()
'
' creamos otro objeto del mismo tipo (otra instancia)
Dim o2 As New Prueba15_5()
'
o2.elNombreInstancia = "Instancia 2"
o2.MostrarInstancia()
'o2.MostrarCompartido()
Prueba15_5.MostrarCompartido()
End Sub
End Module
La salida de este código será la siguiente:
Como puedes comprobar, para acceder a los miembros compartidos, hemos usado
el nombre de la clase.
De esta forma sólo podemos acceder a los miembros compartidos.
Por otro lado, al crear una instancia de esa clase, podemos acceder tanto a los
miembros compartidos como a los de instancia, en este ejemplo, se asigna un valor
al campo de instancia, se llama al procedimiento compartido y después cambiamos
el contenido del campo compartido y llamamos al procedimiento de instancia.
En la segunda clase, al mostrar el contenido del campo compartido, mostrará el
valor que el objeto (de instancia) le asignó, ya que ese campo está compartido por
todas las instancias.
En la última línea de código he usado el método compartido usando la clase, pero el
resultado sería el mismo si se usara el objeto para llamar al método compartido.
Pues tal como te dije en la entrega anterior, en esta veremos cosas relacionadas
con los espacios de nombres (Namespace), que de alguna forma está también
está de alguna forma dentro del mismo tema de la visibilidad y ámbito, es decir
desde dónde podemos acceder a los distintos elementos de nuestro programa y
esas cosas.
¿Qué es un Namespace?
Introducción:
Cada vez que creamos un proyecto con Visual Studio .NET de forma automática se
crea un espacio de nombres para nuestra aplicación. Por tanto todos los tipos
(clases, estructuras, enumeraciones, etc.) que definamos en dicho proyecto estarán
incluidos dentro de ese espacio de nombres.
Aunque el editor de Visual Studio no nos muestra que todo esto es así, ya que para
crear o definir un espacio de nombres debemos usar la instrucción Namespace y
marcar el final de dicha definición usando End Namespace, por tanto es como si el
IDE añadiera esas instrucciones por nosotros. Por eso, si has creado algún proyecto
de C#, habrás visto que en ese lenguaje siempre se añade por defecto la definición
del espacio de nombres, por tanto es importante saber que, aunque no lo veamos,
todos nuestros proyectos estarán dentro de un espacio de nombres. Dicho
Namespace se llamará de la misma forma que nuestro proyecto, salvo si tiene
espacios, en ese caso se cambiarán los espacios (o caracteres no válidos en un
nombre de .NET) por un guión bajo.
El escribir nuestro código dentro de un bloque Namespace tiene por finalidad, tal
como te he comentado al principio, el poder mantener una especie de jerarquía.
Para que nos entendamos mejor, podríamos comparar los espacios de nombres con
los directorios de un disco. En cada directorio tendremos ficheros que de alguna
forma están relacionados, de esta forma no mezclaremos los ficheros de música
con los de imágenes ni con los proyectos de Visual Basic, por poner algunos
ejemplos.
Pues lo mismo ocurre con las jerarquías creadas con los Namespace, de forma que
podamos tener de alguna forma separados unos tipos de datos (clases, etc.) de
otros.
Namespace CursoGuille
Namespace Entrega16
Module Module1
Sub Main()
End Sub
End Module
End Namespace
End Namespace
De igual forma que para acceder a los ficheros de un directorio debemos indicar el
"path" en el que se encuentran, con los espacios de nombres ocurre lo mismo,
aunque como veremos (en breve, supongo) existen formas abreviadas de realizar
ese acceso.
Por ejemplo, si queremos usar la clase StringBuilder que se encuentra declarada en
el espacio de nombres System.Text, tendremos que indicar el espacio de nombres
en el que se encuentra:
Es decir, debemos indicar el "path" completo de la clase, para poder acceder a ella.
Namespace CursoGuille
Namespace Entrega16
Module Module1
Sub Main()
CursoGuille.Entrega16.Module1.Prueba16()
End Sub
End Namespace
End Namespace
Por tanto, para acceder a los miembros declarados dentro de un espacio de
nombres, debemos usar la sintaxis: Espacio_de_nombres Punto
Nombre_de_la_clase Punto Nombre_Del_Método
Si dicha clase está dentro de un espacio de nombres anidado, también debemos
indicar dicho espacio de nombres.
Esto es tan habitual que seguramente ni te parecerá interesante, (al menos cuando
pase algún tiempo y estés algo más habituado a usarlos), ya que incluso para
acceder a las propias clases de .NET usamos esta forma sintaxis, tal como se
mostró antes para declarar la variable del tipo StringBuilder. En dicha declaración
vimos que había dos espacios de nombres: System y dentro de éste está el espacio
de nombres Text, el cual contiene la definición de la clase StringBuilder.
Aunque existen ciertas formas de hacer que esto no siempre tenga que ser así, ya
que el código se puede convertir en algo más engorroso de leer si siempre tenemos
que indicar los espacios de nombres que contienen las clases.
Pues además de no tener que especificar los espacios de nombres cuando la clase
(o tipo) está definido en el mismo en el que queremos usarla, (Guille, esto ya lo has
dicho antes, ya lo se, pero así se enteran bien), podemos usar lo que se llama
importaciones de espacios de nombres, de esa forma podemos acortar el código
que tenemos que escribir.
Esto quiere decir que podemos acceder a cualquiera de las clases de esos espacios
de nombres sin necesidad de tener que indicarlos.
Estas importaciones serán efectivas (o válidas) en todos los ficheros que tengamos
dentro del mismo proyecto, de esa forma si añadimos aquí algún espacio de
nombres, dicho Namespace se usará en todo el proyecto.
Pero si queremos usarlo sólo en un fichero en concreto, podemos usar Imports,
seguido del espacio de nombres, al principio de dicho fichero.
Nota:
Una cosa que no hay que confundir son las importaciones de
espacios de nombres y las referencias.
Las referencias son las que le indican al compilador dónde
encontrar las clases (y espacios de nombres) que queremos usar
en nuestro proyecto, mientras que las importaciones simplemente
hacen que no tengamos que escribir los espacios de nombres
importados.
Por supuesto, las importaciones (Imports) las podemos realizar no sólo con los
espacios de nombres definidos en .NET Framework, sino que podemos hacerlas
para importar nuestros propios espacios de nombres.
Nota:
En C# el código equivalente daría error, indicando que no se encuentra el espacio
de nombres Text, es decir, en C# sólo se pueden usar los tipos definidos en un
espacio de nombres importado (por medio de using), pero no se puede hacer
referencia a los espacios de nombres anidados.
Una cosa que también deberías saber, es que las importaciones automáticas sólo
son válidas si compilamos desde el propio Visual Studio, por tanto no serían
efectivas si compilamos desde la línea de comandos. En ese caso, tendremos que
indicar las importaciones usadas en cada uno de los ficheros o bien indicarlos
usando el parámetro /imports: al llamar al compilador de Visual Basic .NET.
A partir de ese momento podemos acceder a los tipos definidos en dicho espacio de
nombres usando el alias que hemos creado: fic.
Por ejemplo, si escribimos un punto después de fic, el IDE de Visual Studio nos
mostrará las clases que podemos usar (las incluidas en ese espacio de nombres),
tal como vemos en la figura 4.
Nota:
Los alias también se pueden usar en C#, (using fic = System.IO;), en los que
puede ser incluso más prácticos que en VB, ya que en VB, por ejemplo no
necesitaríamos crear un alias a System.IO, ya que podríamos usar directamente IO
seguido de un punto para acceder a las clases que contiene.
Y ahora que hemos visto toda la "teoría" de los espacios de nombres (Namespace)
vamos a verlo y aplicarlo de forma práctica (¡por fin!, ya creía que el Guille se iba a
ir, pues he estado a punto de dar por terminada la entrega, así que no me toques
las "narices", vayamos a liarla...).
Namespace Entrega16
Module Module1
Sub Main()
' declarar una clase del espacio de nombres
ClaseUno
Dim c1 As New ClasesUno.PruebaA("Pepe", "Ruiz")
'
' declarar una clase del espacio de nombres
ClaseDos
Dim c2 As New ClasesDos.PruebaB("Maria", "Sánchez")
'
Console.WriteLine("c1 = {0}", c1)
Console.WriteLine("c2 = {0}", c2)
'
Console.ReadLine()
End Sub
End Module
End Namespace
'
Namespace ClasesUno
Public Class PruebaA
Private _nombre As String
Private _apellidos As String
'
Public Sub New()
End Sub
Public Sub New(ByVal nombre As String, ByVal apellidos
As String)
Me.Nombre = nombre
Me.Apellidos = apellidos
End Sub
'
Public Property Nombre() As String
Get
Return _nombre
End Get
Set(ByVal value As String)
_nombre = value
End Set
End Property
Public Property Apellidos() As String
Get
Return _apellidos
End Get
Set(ByVal value As String)
_apellidos = value
End Set
End Property
'
Public Overrides Function ToString() As String
Return Apellidos & ", " & Nombre
End Function
End Class
End Namespace
'
Namespace ClasesDos
Public Class PruebaB
Inherits ClasesUno.PruebaA
'
Public Sub New()
End Sub
Public Sub New(ByVal nombre As String, ByVal apellidos
As String)
Me.Nombre = nombre
Me.Apellidos = apellidos
End Sub
End Class
End Namespace
Algunas cosas que debes saber sobre los Namespace "automáticos" y los
definidos por tí
Para ir finalizando esta entrega quiero decirte que si quitas el espacio de nombres
predeterminado que utiliza el Visual Studio y añades al código tu propios espacios
de nombres, debo decirte que hay dos cosas que debes tener en cuenta:
la clase que usará como punto de inicio, debido a que ahora está en otro espacio de
nombres distinto, por tanto tendrás que mostrar las propiedades del proyecto (ver
que "contenga" tus clases (o tipos), por tanto, si en un fichero indicas tu propio
Todo esto viene al caso porque en un mismo proyecto podemos definir varios
espacios de nombres y si no somos precisos con la ubicación de cada tipo que
tenemos en el proyecto, puede que el compilador se queje y nos diga que no
encuentra tal o cual clase (o tipo).
También debes saber que si nuestro proyecto usa librerías creadas por nosotros,
pero que simplemente las tenemos referenciadas, (es decir, podemos usar otras
DLLs creadas por nosotros y ya compiladas), esos otros ensamblados (assemblies)
pueden tener también un espacio de nombres que se llame igual que el que
tenemos en nuestro proyecto, en ese caso no será necesario indicar el espacio de
nombres de la otra librería, ya que podemos tener un mismo espacio de nombres
"repartido" por varios ensamblados.
Esto es muy útil si queremos definir una serie de clases, enumeraciones, etc., en un
proyecto, crear un ensamblado DLL a partir de ese proyecto y posteriormente
usarlo en otro proyecto diferente, pero sin cambiar el espacio de nombres en el que
tenemos definidos todos los tipos. Con la ventaja adicional de que esa DLL la
podemos usar en proyectos de otros lenguajes de .NET, por tanto podemos crear
con VB una librería de clases y usarla desde C#.
CAPITULO 17
Pues no quería que acabara el año sin una nueva entrega del Curso de iniciación a
la programación con Visual Basic .NET, o ¿deberíamos llamarlo simplemente Visual
Basic? Ya que a estas alturas de la vida, la versión 2005 de este lenguaje ya está
disponible, oficialmente Microsoft hizo pública todas las versiones de los lenguajes
incorporados en Visual Studio 2005 y por extensión el .NET Framework 2.0, el 7 de
Noviembre de 2005.
Pero para no confundir al personal, lo seguiremos llamando Visual Basic .NET o
simplemente VB.NET, por ahora seguiremos viendo las características de las
versiones 2002 y 2003, y cuando llegue el caso, empezaremos a ver las nuevas
características de Visual Basic 2005, aunque al ritmo con el que publico las
entregas, es posible que cuando quiera hablar de VB2005, ya esté en la calle el
VB2007... En fin... esperemos que no.
Lo que si me gustaría aclarar es que todo lo que estamos viendo en este curso, es
totalmente válido para el VB2005, si bien, las capturas de las pantallas serían
diferentes, pero en lo que al lenguaje se refiere, prácticamente no tendrás que
hacer cambios, o si debes hacerlos, serán mínimos, sobre todo porque aún no
hemos tocado nada "especial" que en realidad indique que haya que hacer grandes
cambios.
Y, digo yo, ya que estamos hablando de VB2005, ¿por qué no vemos algunas
cosillas que han cambiado con respecto a las versiones anteriores? al menos en lo
concerniente a el diseño de formularios, que ya tocamos algo en la entrega 2 y en
la entrega 3.
¿Te parece bien? (¿qué va a decir? si el que escribe eres tú... ¡tendrán que
aguantarse con lo que se te ocurra!)
Pues eso, como dice el "otro" Guille, esto es de lo que trataremos en esta entrega:
Para acceder a las configuraciones debes seleccionar la opción Options del menú de
herramientas (Tools), cuando lo hagas te mostrará una pantalla similar a la de la
figura 2 (siempre que no hayas hecho ya algunos cambios, que después seguro que
me escribes diciendo: pues mi cuadro de diálogo se ve de otra forma...)
Figura 2. Opciones de configuración de Visual Basic 2005
Environment
o Autorecover
VB2005 guarde copia de tu trabajo, por defecto será cada 5 minutos, pero puedes
cambiarlo. Esto servirá por si se produce algún error y se tiene que cerrar el IDE,
o Help>Genertal
inglés, que esas siempre se muestran, salvo que quites la marca que hay debajo de
esa lista, pero mejor déjala marcada, sobre todo porque por ahora hay más
información en inglés.
En la lista desplegable que hay en la parte superior, puedes indicar que se muestre
la ayuda dentro del propio IDE de VB, en vez de como una ventana aparte, que es
o Help>Online
De las opciones mostradas aquí, hay que destacar las tres
que la búsqueda se haga primero en internet y después en la ayuda local (la que
que solo quieres que busque en local, (deberías seleccionarla si no tienes conexión
español (Spanish), el sitio del Guille (¿ese quién es?) también estará en la lista de
los sitios asociados a Codezone, es decir, que si buscas algo y está publicado en mi
o Startup
inicio y que es lo que quieres que se muestre en el panel derecho de esa página de
inicio, que quieres poner las novedades de mi sitio, tendrás que poner lo siguiente
en la casilla indicada con Start Page news channel:
http://www.elguille.info/elGuille.rss.xml
De esa forma se mostrarán las novedades de mi sitio cada vez que cargues el VB.
Por supuesto siempre que de la lista desplegable que hay arriba no hayas indicado
otra cosa, como cargar el último proyecto o que te pregunte para crear un nuevo
proyecto.
o General
después quieres que se mantengan en el disco, pues le dices que lo guarde, pero si
o VB Defaults
esa opción, entre otras cosas, te obligará a programar bien y sabiendo exactamente
agradecerá)
o General
grilla, como prefieras llamarlo que en cada país tienen una forma de llamar al
tendrás que seleccionar SnapToGrid de la lista que hay junto a LayoutMode (la
Por supuesto que hay muchas más opciones, pero del resto ya te encargas tú de
investigar para que sirven y esas cosillas. Estas que te he mostrado con las que yo
normalmente modifico en Visual Basic 2005.
Nota:
Si echas en falta el control de usuario, decirte que por medio de la librería de
clases lo puedes crear.
Crea un nuevo proyecto de tipo Class Library, mediante el menú Project
añade un nuevo elemento y selecciona User control, o bien añádelo
directamente seleccionando Add User Control desde el menú de proyecto.
Agregar controles al formulario
Esto no ha cambiado, ya que se hace de la misma forma que con las versiones
anteriores, lo que si ha cambiado es que ahora tenemos muchos más controles, y,
según se mire, están mejor organizados que en las versiones anteriores.
Las líneas azules sirven para alinear los controles, tanto entre ellos como entre los
controles y los bordes del formulario o el contenedor. La línea morada (o roja si lo
prefieres, pero en realidad no es roja), sirve para alinear la base del texto de los
controles, de esa forma sabremos que el texto de la etiqueta está a la misma altura
que el texto de la caja de textos... ¡una maravilla!, ya que si tienes una versión
anterior de VB, (o Visual Studio), verás que es una lata el tener que alinearlos
correctamente... sí, estoy emocionado... ¡¡¡no sabes lo que te ahorras con estas
dichosas líneas!!!
Especificar los márgenes entre los controles
Además, otra de las novedades de los controles de .NET Framework 2.0 es que
también nos permite indicar cuantos píxeles queremos de separación en los bordes,
es decir, cual es el valor mínimo de puntos que queremos tener para que aparezcan
las líneas azules. Ese valor lo podemos indicar mediante la propiedad Margin, que
como vemos en la figura 10, el valor predeterminado (en la mayoría de controles)
es de 3 puntos por cada lado, pero que podemos modificar a nuestro antojo (o
según nuestras preferencias).
Introducción:
La programación orientada a objetos (POO) nos permite escribir código menos
propenso a fallos además de permitirnos la reutilización de código de forma más
conveniente.
En este artículo veremos las características de la POO desde el punto de vista de los
lenguajes de .NET Framework y cómo utilizar los distintos elementos que nos
permitirán crear código que sea más fácil de escribir y mantener.
Encapsulación
Herencia
Polimorfismo
Veamos una pequeña descripción de cada una de ellas y después las ampliaremos
para comprender mejor su significado y cómo puede ayudarnos a crear aplicaciones
que aprovechen todas las posibilidades que nos da la POO.
Pero antes de ver algo de código concreto, creo que es importante que aprendamos
otros conceptos relacionados también con la POO, pero esta vez desde un punto de
vista del programador, es decir, vamos a dejar en parte la teoría y vamos a ser
algo más prácticos, ya que los siguientes conceptos serán con los que tendremos
que "bregar" a diario. Además nos interesa conocerlos para aprovechar lo que un
lenguaje de programación orientado a objetos nos ofrece, si bien, es posible que, al
menos de forma genérica, no todos los lenguajes dispongan de ellos.
Por eso, aunque lo que se ha dicho y se diga a continuación será válido para
cualquier lenguaje orientado a objetos, lo vamos a enfocar desde el punto de vista
de .NET Framework, más concretamente desde el punto de vista del programador
de Visual Basic .NET y C#.
Una clase es una pieza de código en la que podemos definir una serie de datos y al
mismo tiempo unos métodos (funciones o procedimientos) que nos permitirán
acceder a esos datos.
Cuando definimos una clase, lo que estamos haciendo es definir una plantilla, a
partir de la cual podemos crear objetos en la memoria. Por tanto, la clase es el
molde con el cual podemos crear nuevos objetos. Para poder crear algo "tangible" a
partir de una clase, tenemos que crear en la memoria un nuevo objeto del tipo de
la clase, en estos casos lo que decimos es que instanciamos un nuevo objeto de la
clase. A partir de ese momento tendremos algo real con lo que podemos trabajar:
una instancia de la clase, es decir, la definición realizada en la clase se ha
convertido en un objeto al que podemos acceder y que podemos empezar a utilizar,
dándole nuevos valores a los datos que manipula y usando las funciones que nos
permiten manipular dichos datos.
La diferencia principal entre una clase y una estructura es la forma en que se crean
los objetos que representan a esas "ideas". Los objetos creados a partir de las
clases son objetos por referencia, es decir, si declaramos una variable para
manipular ese objeto, lo que tendremos será una referencia (o puntero) a una
dirección de memoria en la que realmente está el objeto. Mientras que los objetos
creados a partir de una estructura se almacenan de forma diferente, en lugar de
"apuntar" a una dirección de memoria en la que se encuentra el objeto, es como si
las variables declaradas como estructuras fuesen realmente el objeto
permitiéndonos hacer ciertas operaciones y manipulaciones que los objetos
obtenidos a partir de una clase no pueden realizar de la misma forma. Esto lo
veremos después con más detalle.
NOTA: Clases
En .NET siempre usamos una clase para escribir cualquier tipo de código. Por
tanto, hagamos lo que hagamos en .NET Framework, debemos hacerlo dentro
de una clase. Esto no quiere decir que siempre tengamos que usar las
características de la POO, ya que si simplemente queremos hacer una
aplicación que muestre un mensaje en la consola, el código no tiene porqué
usar la herencia, el polimorfismo o la encapsulación, simplemente escribimos el
código que muestre el mensaje y asunto arreglado, pero lo que si podremos
hacer es usar algunas de las "otras" ventajas que nos aporta la programación
orienta a objetos.
INTERFACES
Cuando hablamos de polimorfismo, ineludiblemente tenemos que hablar de las
interfaces, ya que, principalmente, nos posibilita utilizar esta característica de la
POO. La pregunta es: ¿qué es una interfaz? Aquí no hablamos de "interfaces de
usuario", es decir, lo que se mostrará al usuario de nuestra aplicación, sino a una
clase especial en la que solamente se definen los métodos y propiedades que una
clase que la implemente debe codificar. Las interfaces representan un contrato, de
forma que cualquier clase que la implemente debe utilizar los miembros de la
interfaz usando la misma forma en que ésta la ha descrito: mismo número de
argumentos, mismo tipo de datos devuelto, etc.
Supongamos que queremos hacer una función que realice cualquier tipo de
operación sobre dos valores numéricos, sería lógico pensar que si esos valores son
de tipo entero, el resultado que devuelva la función también debería ser de tipo
entero, en caso de que los valores a usar en la operación son de tipo flotante, el
resultado podría devolverlo de ese mismo tipo.
En los lenguajes no orientado a objetos, tendríamos que crear dos funciones con
nombres diferentes, por ejemplo: sumaInt y sumaFloat. Pero la sobrecarga nos
permite crear dos funciones que se llamen suma y el compilador utilizará la
adecuada según el tipo de datos que pasemos como argumentos.
El único requisito para poder crear sobrecargas de métodos es que las diferentes
versiones se diferencien en los argumentos, ya sea porque sean de diferentes tipos
de datos o porque el número de argumentos usados sea diferente, de esa forma el
compilador no tendrá ningún problema en saber cual debe usar en cada ocasión. La
sobrecarga la podemos aplicar tanto a los constructores como a cualquier otro
método de la clase.
NOTA: Sobrecarga
No existirá la posibilidad de crear métodos sobrecargados si solamente se
diferencian en el tipo de datos devuelto, ya que en esos casos el compilador no
podrá decidir correctamente qué método debe utilizar.
Si queremos exponer los datos, podemos usar las propiedades. Las propiedades
son funciones especiales que nos permiten acceder a esos datos, aunque para ser
más precisos, las propiedades realmente representan a los datos que una clase
contiene, al menos de forma pública. De esa forma podemos "controlar" la forma
en que se leen o asignan esos datos, ya que las propiedades realmente son
funciones en las que podemos escribir código para controlar los valores asignados o
leídos.
Los métodos nos permitirán realizar acciones sobre los datos, por ejemplo devolver
un rango de valores o simplemente una representación amigable de la información
contenida. Debido a que algunas veces los métodos devolverán algo y otras no,
podemos usar tanto funciones que devuelvan o no un valor.
NOTA: Métodos
En C# los métodos siempre son funciones, que devolverán un tipo concreto o el
valor especial void, que se usa para indicar que una función no devolverá
ningún valor.
En Visual Basic .NET existen dos tipos de métodos distintos, las funciones
(Function) que siempre devuelven un valor y los procedimientos (Sub) que no
devuelven ningún valor.
Además de los campos, métodos y propiedades, las clases tienen otros miembros
como los eventos y las enumeraciones. Éstos nos permitirán recibir notificaciones
de cuando algo ocurra (eventos) o declarar ciertos valores constantes que podemos
usar para restringir algunos valores asignados a las propiedades o que nos
permitan seleccionar de forma coherente la información que queremos obtener
(enumeraciones).
privado sólo lo podremos acceder desde la propia clase. Este es el más restrictivo y
clase derivada.
(proyecto).
es decir solo accesible desde las clases derivadas o desde el mismo proyecto.
NOTA: Ámbito
Los miembros de una clase los podemos declarar sin especificar el ámbito,
dependiendo del lenguaje de programación que usemos se aplicará un
modificador de ámbito u otro. En C#, si no indicamos el ámbito, las
declaraciones se consideran privadas, mientras que en Visual Basic .NET el
ámbito predeterminado es Friend.
Como hemos comentado, cuando una clase hereda a otra podemos modificar el
comportamiento de los miembros heredados, pero estos solamente se podrán
modificar si la clase base así lo contempla o lo permite. De forma predeterminada,
al menos en .NET, cuando declaramos un método o una propiedad en una clase,
solo podremos acceder a él desde una instancia creada (un objeto) en memoria,
desde donde podemos usarlos dependerá del ámbito que le hayamos aplicado. De
igual forma, el que una clase que se base en otra, pueda crear su propia versión de
ese método o propiedad dependerá de que la clase base lo haya declarado como
virtual (Overridable en VB .NET). Los métodos virtuales serán los que podamos
sobrescribir en las clases derivadas, de forma que podamos crear nuestras propias
versiones. En .NET los miembros de una clase no son virtuales de forma
predeterminada. Por tanto, si queremos que la clase derivada pueda crear su propia
versión de un método, debemos declararlo como virtual o "redefinible".
Pero también se nos puede presentar el caso contrario, en el que queremos que un
método forzosamente haya que redefinirlo en las clases derivadas, en esos casos la
clase base que lo define no incluye ninguna implementación, es decir, el método no
contiene código ejecutable, solo la definición, (como ocurre con las interfaces). Se
dice que estos métodos son abstractos porque solo se ha definido en la forma y no
se ha implementado ningún código ejecutable. En Visual Basic se definen usando el
modificador MustOverride (asbtract en C#). Estos métodos abstractos solo se
pueden declarar en clases abstractas (MustInherit en Visual Basic, abstract en
C#) y por la necesidad de tener que redefinirlos, son implícitamente virtuales.
Las instrucciones o modificadores que nos permiten crear estos tipos de miembros
son:
Overridable (virtual). Los miembros virtuales son los que las clases
derivadas puedes sobrescribir para crear su propia versión. Para indicar en una
(override en C#).
derivada y que solo se define en la clase base sin ningún código ejecutable. Los
métodos abstractos son virtuales por defecto y solo se pueden declarar en clases
abstractas.
NOTA: STATIC
En Visual Basic existe también la instrucción Static, (que no tiene equivalencia
en C#), en este caso se utiliza con variables declaradas en un procedimiento y
sirven para indicar que esa variable debe mantener el valor entre distintas
llamadas a dicho procedimiento, a diferencia del resto de variables que solo
existen mientras se ejecuta el código del procedimiento y cuyos valores se
pierden al finaliza la ejecución del mismo.
Como hemos comentado, las clases de .NET pueden usarse para crear nuevas
clases derivadas de ellas, esta es la funcionalidad predeterminada, pero no
obligatoria, es decir, si queremos podemos usar una clase por si misma o como
base de otras.
Pero también podemos hacer que una clase solamente se use como clase base de
otras, pero no se puedan usar para crear nuevas instancias en memoria, este es el
caso de las clases abstractas. Una clase abstracta puede contener miembros
abstractos, miembros normales o virtuales. Para indicar que una clase es abstracta,
se usa el modificador MustInherit en Visual Basic o abstract en C#.
La contrapartida de las clases abstractas son las clases selladas o clases que no se
pueden usar como clases base, en estos casos las clases las definiremos como
NotInheritable en Visual Basic o sealed en C#. Como es lógico, las clases no
heredables se pueden usar en ocasiones en las que no nos interese que nadie
cambie el comportamiento que tiene, por tanto no se podrán declarar miembros
virtuales ni abstractos, ya que no tendría ningún sentido.
Introducción:
En la entrega anterior vimos algunos conceptos teóricos de la POO (Programación
Orientada a Objetos) desde el punto de vista de los lenguajes de .NET Framework,
en esta ocasión veremos con ejemplos prácticos cómo utilizar las características de
la POO en Visual Basic .NET, (en el ZIP se incluye también el código para C#), de
forma que tengamos claro cómo usar la herencia, el polimorfismo y la
encapsulación, pero con código.
Nota:
Este artículo se publicó en Octubre de 2004, y por tanto todo lo aquí explicado
está relacionado con la versión 1.1 de .NET Framework.
La versión de .NET Framework que hay actualmente (a la hora de escribir esta
nota) es la 3.0, (que en el fondo es la misma que la 2.0), y se han introducido
ciertos cambios o mejoras, pero básicamente lo aquí explicado sigue siendo tan
válido ahora como hace dos años.
Public Class A
Private _prop2 As Integer
Private _prop1 As String
'
Public Property Prop1() As String
Get
Return _prop1
End Get
Set(ByVal value As String)
If value <> "" Then
_prop1 = value
End If
End Set
End Property
Public Property Prop2() As Integer
Get
Return _prop2
End Get
Set(ByVal value As Integer)
_prop2 = value
End Set
End Property
'
Public Sub Mostrar()
Console.WriteLine("{0}, {1}", _prop1, _prop2)
End Sub
End Class
Listado 1
Tal como podemos ver en el listado 1, tenemos una clase llamada A que define dos
campos, dos propiedades y un método. Los dos campos, declarados como privados,
se usan para mantener "internamente" la información que se expone mediante las
dos propiedades públicas, de esta forma protegemos los datos y esta sería una
forma de encapsular la información.
De las dos propiedades definidas para acceder a esos datos, solo la propiedad
Prop1 hace una comprobación de que no se asigne una cadena vacía al campo que
mantiene internamente la información, aunque en este ejemplo por su simplicidad
no hacemos más comprobaciones, en una clase algo más compleja, se podrían
realizar otras comprobaciones, por ejemplo si el valor a almacenar es una cuenta
de email, podríamos comprobar que es una cadena correctamente formada.
Las propiedades suelen definir dos bloques de código, uno, el bloque Get se utiliza
cuando queremos acceder al valor devuelto por la propiedad, el otro es el bloque
Set, el cual se utilizará cuando asignemos un valor a la propiedad.
El método Mostrar se usará para mostrar el contenido de las dos propiedades por
la consola y está definido como Sub (void en C#) porque no devuelve ningún
valor.
En nuestra clase podemos redefinirlo para que nos devuelva el contenido de los dos
datos que la clase mantiene:
Para crear una clase que se derive de la clase A definida en el listado 1, tendríamos
que hacer lo siguiente:
Public Class B
Inherits A
End Class
Sub Main()
Dim objB As New B
objB.Prop1 = "guille"
objB.Prop2 = 47
objB.Mostrar()
Console.WriteLine("{0}", objB.ToString)
End Sub
Listado 2
Esa advertencia nos informa que deberíamos indicar que la declaración "oculta" a la
definida en la clase A y por tanto deberíamos usar la instrucción Shadows (new
en C#). Aunque usemos Shadows, el problema real sigue existiendo: el método
declarado en la clase B oculta al declarado (y heredado) en la clase A.
Si después de definir este método de la clase B volvemos a ejecutar el código del
listado 2, comprobaremos que se utiliza el nuevo método.
Public Class C
Inherits B
End Class
Sub Main()
Dim objC As New C
objC.Prop1 = "guille"
objC.Prop2 = 47
objC.Mostrar()
Console.WriteLine("{0}", objC.ToString)
End Sub
Listado 3
Esta nueva definición del método Mostrar ya no tiene nada que ver con la definida
por la clase A y por tanto no existe ninguna relación "polimórfica" entre ambos
métodos.
Para ser más precisos, tanto la clase B como la clase C tienen dos definiciones del
método Mostrar: el inicialmente heredado de la clase A y el nuevo definido por la
clase B, aunque siempre prevalecerá el definido expresamente en la clase derivada
frente al heredado de la clase base.
Dim objA As A
objA = objB
NOTA:
Cuando declaramos una variable y la instanciamos, (creando un nuevo objeto a
partir de una clase), dicha variable simplemente tiene una referencia al objeto
creado en la memoria, (la variable simplemente tiene un puntero al objeto
real), por tanto, ese objeto existe y puede ser referenciado por otras variables,
aunque esas otras variables deben ser de tipos "incluidos" en la clase usada
para crear dicho objeto.
Al instanciar un nuevo objeto del tipo B, (tal como se muestra en la figura 2),
podemos acceder a él mediante variables de tipo Object, de tipo A y, por
supuesto, de tipo B.
Para que esto sea así, utilizaremos la instrucción Overridable (virtual en C#). De
esta forma le indicaremos al compilador que el método se puede redefinir, es decir,
que en la clase derivada se puede crear una nueva versión "personalizada" de dicho
miembro.
Con esto logramos que en sucesivas derivaciones de la clase solamente exista un
mismo miembro polimórfico.
La ventaja principal es que si en otra clase decidimos crear una nueva versión de,
por ejemplo, un método, cuando se cree un objeto en la memoria, solo existirá ese
método, no varios métodos con el mismo nombre, pero sin ninguna relación entre
ellos, exceptuando el hecho de que se llamen de la misma forma.
DEFINIENDO INTERFACES
Una interfaz realmente es la definición de los miembros públicos de una clase. Pero
en los lenguajes de programación de .NET también podemos definir clases
especiales que simplemente definan cómo deben ser los miembros que una clase
implemente. Es decir que características deben tener. De esta forma podemos
garantizar que si varias clases implementan los miembros definidos en una interfaz,
podemos usarlos de manera anónima, es decir, sin necesidad de saber si estamos
usando un objeto de una clase o de otra, ya que si ambas clases implementan la
interfaz, tendremos la certeza de que dichas clases tienen los miembros definidos
en dicha interfaz.
Una interfaz representa un contrato, si una clase implementa una interfaz, está
suscribiendo dicho contrato, es más, está obligada a cumplirlo, por tanto, la clase
tendrá que definir todos los miembros que la interfaz contenga.
Antes de ver cómo usar las interfaces en nuestras clases, veamos cómo definir una
interfaz.
En este caso hemos definido una interfaz llamada IPrueba2 (por convención los
nombres de las interfaces siempre empiezan con la letra I mayúscula), en la que se
define una propiedad y un método.
Los miembros de las interfaces siempre son públicos y no deben implementar
código, solamente la definición propiamente dicha.
Para "implementar" en una clase los miembros definidos en una interfaz tendremos
que usar la instrucción Implements seguida del nombre de la interfaz. Además
tendremos que definir los métodos y propiedades que dicha interfaz contiene,
aunque en Visual Basic además hay que indicarlo expresamente, de forma que se
sepa con seguridad de que cada uno de esos miembros equivale a los definidos en
la interfaz.
En el listado 4 vemos cómo definir una clase que utilice la interfaz que acabamos de
ver en la sección anterior.
Listado 4
El método y las dos propiedades deben tener el mismo nombre y parámetros (si los
hubiera) que los definidos en la interfaz.
Cuando trabajamos con Visual Basic además debemos indicar expresamente que
dicho método o propiedad está "ligado" con el definido en la interfaz, cuando
trabajamos con C# no es necesario indicarlo.
La ventaja de esta "redundancia" de VB es que podemos dar un nombre diferente al
miembro implementado, pero "internamente" el compilador sabrá que nos estamos
refiriendo al que implementa la interfaz.
Listado 5
Es más, muchas de las clases de .NET además permiten que demos nueva
funcionalidad a nuestras propias clases si implementamos ciertas interfaces.
Por ejemplo, si queremos que nuestra clase sea "clasificable", es decir, que se
pueda usar en una colección que clasifique los elementos que contiene, nuestra
clase debe implementar la interfaz IComparable.
Si definimos una clase en la que queremos que el método ToString actúe de forma
que podamos especificar ciertos formatos a la hora de mostrar el contenido,
nuestra clase debe implementar IFormattable.
Que queremos que nuestra clase actúe como una colección, en la que se pueda
enumerar o recorrer el contenido de la misma, debemos implementar la interfaz
IEnumerable.
Pero esas interfaces propias del .NET Framework lo que harán será darle una nueva
funcionalidad a nuestras clases, por tanto, lo importante es saber de que forma
actúan los objetos creados en la memoria.
Tal como hemos comentado antes, solo existe un objeto en la memoria y cuando
accedemos a él lo podemos hacer bien usando alguna de las clases de las que se
deriva o bien mediante alguna de las interfaces que implementa.
Cuando accedemos a dicho objeto mediante algunas de estas clases o interfaces
simplemente estamos accediendo a la parte "conocida" por dicho tipo.
En el código del listado 4 y 5 la interfaz IPrueba2 "sabe" cómo acceder al método
Mostrar y a la propiedad Prop1, independientemente del objeto que la haya
implementado, por tanto si una clase implementa dicha interfaz podemos acceder a
esos miembros mediante una variable del tipo IPrueba2, tal como se demuestra
en el listado 5 en el que accedemos al método Mostrar definido en la interfaz e
implementado por las dos clases.
Listado 6
Nota:
Por la extensión del listado, el mismo se incluye en el ZIP con el código de los
ejemplos (tanto para Visual Basic como para C#), en el listado 7 puedes ver
cómo usar esas clases.
S mostrará el saldo
Sub Main()
Dim acli(6) As Cliente
'
acli(0) = New Cliente("Jose", "Sanchez", 125.5D)
acli(1) = New ClienteOro("Luis", "Rebelde", 2500.75D)
acli(2) = New ClienteMoroso("Antonio", "Perez", -500.25D)
acli(3) = New Cliente("Miguel", "Rodriguez", 200)
acli(4) = New ClienteMoroso("Juan", "Ruiz", -310)
acli(5) = New ClienteOro("Mariano", "Alvarez", 500.33D)
acli(6) = New Cliente("Carlos", "Bueno", 975)
'
Console.WriteLine("Antes de clasificar:")
For Each c As Cliente In acli
Console.WriteLine("{0}, saldo= {1}", c, c.MostrarSaldo())
Next
Array.Sort(acli)
'
Console.WriteLine()
Console.WriteLine("Después de clasificar:")
For Each c As Cliente In acli
Console.Write("{0}, saldo= {1}", c, c.MostrarSaldo())
If TypeOf c Is ClienteOro Then
Console.WriteLine(" -> $$$ es cliente ORO $$$")
ElseIf TypeOf c Is ClienteMoroso Then
Console.WriteLine(" -> OJO que es un cliente moroso")
Else
Console.WriteLine()
End If
Next
'
Console.WriteLine()
Console.WriteLine("Mostrar usando formatos:")
For Each c As Cliente In acli
Console.WriteLine("Usando NAS= {0:NAS}", c)
Console.WriteLine("Usando AN= {0:AN}", c)
Console.WriteLine("Usando S= {0:S}", c)
Next
'
Console.ReadLine()
End Sub
Listado 7
CONSTRUCTORES Y SOBRECARGA DE
CONSTRUCTORES
El punto de inicio de cualquier clase, cuando se crea una instancia en la memoria,
es un método especial al que se le conoce como constructor.
Un constructor no devuelve ningún valor, por tanto en Visual Basic sería un método
de tipo Sub llamado New (en C# no se declara como void, simplemente tendrá el
mismo nombre de la clase).
Listado 8
En estos casos, en Visual Basic es fácil hacerlo, como sabemos que los
constructores realmente son métodos llamados New, los podemos usar de la
misma forma que haríamos con cualquier otro método.
Me.New(elNombre, losApellidos)
_saldo = elSaldo
End Sub
De forma que desde el constructor que recibe tres parámetros llamemos al que
recibe dos.
Por ejemplo, podríamos tener una clase Animal, la cual no tendría sentido si a
partir de ella se pudiesen crear nuevos objetos, ya que el concepto de animal es
demasiado abstracto para poder crear un objeto a partir de él. Pero si la podríamos
utilizar para derivar de ella otras clases que bien podrían ser a la vez abstractas o
bien clases "normales".
.NET Framework nos permite crear clases abstractas, las cuales son como las
interfaces, pero las que pueden tener métodos y otros miembros que tengan no
solo la definición de esos miembros sino también código funcional. Además, debido
a que las clases abstractas están pensadas para usarse como clases base de otras,
todos los miembros son virtuales de forma predeterminada, por tanto no es
necesario indicarlos usando el modificador Overridable (virtual en C#).
MIEMBROS ABSTRACTOS
Cuando comentamos que las clases abstractas son como las interfaces no solo nos
referíamos a que se podrían usar para proporcionar polimorfismo, sino porque en
las clases abstractas también podemos definir miembros que a su vez sean
abstractos, es decir, que en las clases abstractas solamente se defina el método o
propiedad, pero que no tenga ninguna funcionalidad, es más, si declaramos un
miembro como abstracto la clase que se derive de la clase abstracta estará
obligada a definirlo.
Esto nos ofrece la ventaja de poder definir miembros funcionales y miembros que
solo tengan sentido en las clases derivadas y por tanto serán las clases derivadas
las que deban definir el código que los haga funcionales y le den la utilidad
adecuada.
Este tipo de clases no permitirán que se usen como clases base de otras nuevas
clases.
La existencia de las clases normales y las abstractas nos permiten derivar nuevas
clases a partir de ellas, eso tiene sentido, pero, ¿qué sentido puede tener una clase
de la que no se puedan derivar nuevas clases?
Esto es así, porque al estar "sellada" tampoco podremos definir miembros virtuales,
por la sencilla razón de que nadie podrá derivar nuevas clases y por tanto tampoco
podrá reemplazar el comportamiento de los mismos.
Debido a que el comportamiento normal de una clase es que sea heredable, para
poder crear clases que estén selladas, y por tanto hacerlas no heredables, debemos
usar la instrucción o modificador NotInheritable (sealed en C#).
CAPITULO 20
Esa importación la añado al principio de cada fichero de los proyectos que creo
(además de Option Strict On):
Imports vb = Microsoft.VisualBasic
De esta forma, si quiero usar la función Left de Visual Basic, lo haré usando el
"alias" que he definido:
vb.Left(...)
cadena1 = Right(cadena, n)
cadena1 = Mid(cadena, p, n)
cadena1 = Mid(cadena, p)
Mid Asigna la cadena que hay después del signo igual al trozo
indicado desde la posición p contando n caracteres, si n no
se indica, asigna desde la posición p.
Mid(cadena1, p, n) = cadena2
Mid(cadena1, p) = cadena2
cadena1 = LTrim(cadena)
cadena1 = RTrim(cadena)
cadena1 = Trim(cadena)
entero = Len(cadena)
cadena1 = LCase(cadena)
cadena1 = UCase(cadena)
cadena1 = Space(n)
entero = Asc(cadena)
entero = AscW(cadena)
caracter = Chr(entero)
caracter = ChrW(entero)
Pero... como siempre existe esa diferencia de que unas funciones tienen en cuenta
el valor de Option Compare y otras no, además de que ese valor no afecta al
resto de funciones y clases de .NET, yo siempre la dejo en Binary (que es como
está por defecto en Visual Basic, y cuando quiero hacer una comparación sin que se
diferencie las mayúsculas de las minúsculas, uso el parámetro apropiado con el
valor adecuado de CompareMethod (que casi siempre suele ser Text para que no
diferencie las mayúsculas de las minúsculas).
Algo MUY IMPORTANTE que hay que tener en cuenta con las funciones de
manipulación de cadenas de Visual Basic, es que esas funciones siempre cuentan
que la primera posición de la cadena es la posición uno, mientras que en las
funciones de la clase String (y por extensión de .NET Framework), el primer
carácter está en la posición cero.
El que Visual Basic tenga en cuenta que la primera posición es la 1, es... hasta
lógica, ya que si quieres usar Mid para extraer 3 caracteres que empiezan en la
posición 7, es intuitivo usar:
Mid(cadena, 7, 3),
pero si eso mismo lo quieres hacer con el método equivalente de la clase String de
.NET, en este caso, el método Substring, debes tener en cuenta que la primera
posición es la cero, por tanto, para extraer 3 caracteres desde la posición 7 de una
cadena, tendrás que hacer esto:
cadena.Substring(6, 3).
Para practicar estos ejemplos, debes crear un nuevo proyecto de tipo Consola, y
añadir al principio del fichero la importación del alias para vb además de usar
Option Strict On.
Nota:
Aunque en el código que te voy a mostrar no usaré el "vb", más que nada para
que no te confundas, aunque yo lo suelo usar casi siempre, al menos en las
aplicaciones de Windows Forms, en las que Left puede ser la propiedad Left del
formulario actual o la función Left de Visual Basic.
Option Strict On
Imports vb = Microsoft.VisualBasic
Dentro del Sub Main define ciertas variables que vamos a usar, además de la
cadena usada para hacer las pruebas:
Module Module1
Sub Main()
Dim entero, n, p As Integer
Dim cadena1, cadena2 As String
Dim cadenas() As String
Console.ReadKey()
End Sub
End Module
Si estás usando una versión de Visual Basic .NET anterior a Visual Basic 2005
tendrás que cambiar ReadKey por ReadLine.
Una advertencia:
Cuando manipulamos cadenas, hay que tener en cuenta que si indicamos una
posición que no existe en la cadena, puede que se produzca un error... lo
mismo ocurre si la cadena no tiene nada, sobre todo en los métodos de la clase
String.
cadena1 = "mundo"
Console.WriteLine("La posición de '{0}'", cadena1)
' InStr / IndexOf
entero = InStr(cadena, cadena1, CompareMethod.Text)
Console.WriteLine("Con InStr, está en {0}", entero)
Console.WriteLine()
Como puedes ver, las funciones de la clase String se usan directamente en las
cadenas, mientras que las funciones de Visual Basic hay que indicar en que cadena
se debe hacer la búsqueda. Esto mismo es aplicable al resto de funciones.
Una vez que sabemos en que posición está la cadena buscada, podemos tomar los
caracteres a partir de esa posición o los que estén antes... veamos cómo.
p = cadena.IndexOf(cadena1, StringComparison.OrdinalIgnoreCase)
cadena2 = cadena.Substring(p)
Console.WriteLine(cadena2)
' Left
' Los primeros N caracteres
Los últimos caracteres con Substring es complicado, frente a los fácil que resulta
con Right, ya que debemos contar cuantos caracteres hay y después tomar a partir
de la posición calculada...
' Right
' Los N últimos caracteres
cadena1 = Right(cadena, n)
Console.WriteLine("con Right: '{0}'", cadena1)
cadena1 = cadena.Substring(cadena.Length - n)
Console.WriteLine("con Substring: '{0}'", cadena1)
Puede que pienses que tampoco es tanto, pero debes tener en cuenta que en una
aplicación normal debes comprobar que "cadena" tenga algo antes de hacer la
manipulación, ya que si la cadena está vacía, cadena.Length - n dará un número
negativo, lo que producirá un error, mientras que si la función Right quiere tomar
n caracteres de algo que está vacío, simplemente devuelve una cadena vacía.
Por otro lado, tomar los caracteres desde una posición cualquiera es igual de fácil
de las dos formas, solo que en el caso de Substring debemos tener en cuenta que
la primera posición es la cero.
' Mid
' Los N caracteres desde la posición p
n = 5
p = 6
cadena1 = Mid(cadena, p, n)
Console.WriteLine("con Mid: '{0}'", cadena1)
cadena1 = cadena.Substring(p, n)
Console.WriteLine("con Substring: '{0}'", cadena1)
cadena1 = cadena.Substring(p - 1, n)
Console.WriteLine("con Substring: '{0}'", cadena1)
Visual Basic define también Mid como una instrucción, que nos permite insertar
una cadena en otra a partir de una posición, (es como reemplazar los caracteres en
la posición indicada por los nuevos caracteres). Esto no tiene equivalencia en la
clase String, por tanto, debemos manipularla de forma "manual". Veamos cómo:
cadena1 = cadena
cadena1 = cadena1.Substring(0, p - 1) & "GUILLE" & cadena1.Substring(p + n -
1)
Console.WriteLine("con Substring: '{0}'", cadena1)
Fíjate que la instrucción Mid de Visual Basic modifica la cadena que se pasa en el
primer parámetro, (por eso hago una copia antes), sin embargo la función
Substring devuelve una nueva cadena.
cadena1 = cadena
cadena1 = cadena1.Substring(0, p - 1) & _
"GUILLE".Substring(0, n) & _
cadena1.Substring(p + n - 1)
Console.WriteLine("con Substring: '{0}'", cadena1)
¡Efectivamente!
Que las funciones (métodos) de la clase String las podemos usar en las constantes
literales o en cualquier variable o constante que sea de tipo cadena.
¿Cómo? Que lo que te parecía raro eran los guiones bajo... ¿es que no lo he
explicado antes?
Como no es plan de buscar en las 19 entregas anteriores, te lo explico ahora:
El continuador de líneas
El guión bajo lo podemos usar para continuar líneas (ya sabes que en Visual Basic
una instrucción no puede ocupar más de una línea), lo único que no se puede
continuar son los comentarios (en Visual Basic 6.0 si se podían).
Antes del guión bajo debes poner un espacio y si "cortas" una cadena, ésta debe
tener los caracteres de comillas dobles antes y después del "corte", además de que
debes indicar que se quiere "unir" la cadena... por ejemplo:
Si te fijas bien, verás que he puesto un espacio antes de cerrar las comillas de las
dos primeras partes, esto es así porque quiero que estén esos espacios,
independientemente de que esté troceada en varias líneas.
Te lo digo porque a muchos nos pasa que al cambiar de línea olvidamos que
estamos creando una sola línea y no nos damos cuenta de esos espacios... de
hecho al escribir el ejemplo me ha pasado, je, je.
Sustituir caracteres con Replace
La función Replace tiene su equivalente en el método Replace de la clase String,
aunque en el caso del método Replace, no hay forma de indicar que no queremos
tener en cuenta las mayúsculas o minúsculas, algo que si podemos hacer con la
función Replace de Visual Basic.
Por eso el siguiente código funcionará para la función Replace pero no para el
método de la clase String:
' Replace
cadena2 = "mundo"
Console.WriteLine("Replace, buscando {0}", cadena2)
Para solucionarlo de forma fácil, yo lo que suelo hacer es convertir las cadenas en
minúsculas, de esa forma, se buscará sin importar como esté el original y lo que yo
quiero buscar:
' A mayúsculas
cadena1 = UCase(cadena)
Console.WriteLine(cadena1)
cadena1 = cadena.ToUpper
Console.WriteLine(cadena1)
' A minúsculas
cadena1 = LCase(cadena)
Console.WriteLine(cadena1)
cadena1 = cadena.ToLower
Console.WriteLine(cadena1)
' Trim...
cadena1 = " Hola "
cadena2 = LTrim(cadena1)
Console.WriteLine("LTrim: '{0}'", cadena2)
cadena2 = cadena1.TrimStart
Console.WriteLine("TrimStart: '{0}'", cadena2)
cadena2 = RTrim(cadena1)
Console.WriteLine("RTrim: '{0}'", cadena2)
cadena2 = cadena1.TrimEnd
Console.WriteLine("TrimEnd: '{0}'", cadena2)
cadena2 = Trim(cadena1)
Console.WriteLine("Trim: '{0}'", cadena2)
cadena2 = cadena1.Trim
Console.WriteLine("Trim: '{0}'", cadena2)
La diferencia, entre otras cosas, es que las funciones de Visual Basic solo quitan los
espacios en blanco, mientras que los métodos de la clase String quitarán el primer
espacio en blanco que encuentre, considerando "espacio en blanco" como algo no
imprimible, por ejemplo un tabulador o un cambio de línea.
cadena2 = cadena1.Trim(ChrW(34))
Console.WriteLine("Trim: '{0}'", cadena2)
En este caso, Visual Basic maneja bien el carácter de las comillas dobles, y no es
necesario convertir en un array de tipo Char, (que es lo que se espera como
argumento del método), pero si queremos quitar más de un carácter, para
convertir esos caracteres en un array de tipo Char podemos usar el método
ToCharArray de la clase String:
cadena1 = "!@Hola!@"
cadena2 = cadena1.Trim("!@".ToCharArray)
Console.WriteLine("Trim: '{0}'", cadena2)
En este ejemplo, se supone que tienes un fichero de texto con varias líneas, ese
fichero se llama Prueba.txt y está en el directorio raíz de la unidad E, es que tengo
el Vista y no me deja crear de forma fácil ficheros en la unidad C, así que acuérdate
de cambiar la unidad.
El contenido de ese fichero es "Hola Mundo de las cadenas" poniendo cada palabra
en una línea.
Console.WriteLine()
cadenas = cadena1.Split(vbCrLf.ToCharArray)
For Each s As String In cadenas
Console.WriteLine("'{0}'", s)
Next
En mi caso, que el fichero acaba con una línea al final, usando la función de Visual
Basic me habrá mostrado una línea en blanco al final. Pero al usar el método de la
clase String, se habrá mostrado dos líneas en blanco al final además de una entre
cada palabra.
Por suerte, en la versión 2.0 de .NET Framework, que es la que usa el Visual Basic
2005, ese método tiene un parámetro con el que podemos indicar que se eliminen
los elementos que estén vacíos.
El problema "colateral" es que si ese fichero tiene líneas en blanco que no están al
final, también las elimina.
Aunque si trabajamos con "caracteres normales", las dos funciones van bien, al
menos si tenemos Visual Basic 2005 o superior.
cadena2 = "la"
Fíjate que en el caso del método Split de la clase String, he usado una sobrecarga
que espera un array de tipo String para indicar lo que queremos usar para trocear,
esa sobrecarga solo está disponible en .NET Framework 2.0 o superior, por eso solo
vale para el Visual Basic 2005 o superior.
Nota:
Esto que acabamos de ver funciona, porque en la cadena hay caracteres que
coinciden con lo que hemos indicado, pero si en lugar de buscar, por ejemplo
"la", hubiéramos dividido con "LA", la cosa no hubiera funcionado, ya que esa
palabra no está en la cadena.
Para solucionarlo, en la función de Visual Basic tendríamos que haber indicado
el valor CompareMethod.Text como cuarto argumento, en el método de la clase
String no se puede hacer nada...
Nota:
En caso de que la cadena indicada no exista, lo que se devuelve es un array de
un elemento con la cadena completa.
Console.WriteLine()
Yo suelo usar la función Format que define la clase String, ya que me permite
usar los formatos que se pueden usar con Console.WriteLine, y a los que me he
acostumbrado, sobre todo al escribir ejemplos de Visual Basic, je, je. En serio, los
formatos que usa el método Format de la clase String me gustan porque son los
mismos que puedo usar con el método AppendFormat de la clase StringBuilder,
que es la clase que te recomiendo cuando quieras "ir uniendo" trozos de cadenas
por medio de código, es decir, si tienes que unir dos cadenas, en Visual Basic lo
puedes hacer de esta forma:
cadena2 = "mundo"
cadena1 = "hola" & cadena2
En lugar de usar & puedes usar el signo +, pero yo no te lo recomiendo, ya que con
el signo & no hay dudas de que lo que quieres hacer es unir dos cadenas, mientras
que con el signo +, puede parecer que quieras "sumar" dos cosas, por ejemplo
cadena = "1" + "2".
Bueno, pues cuando quieras unir (o concatenar) varios trozos de cadenas, lo mejor
es usar la clase StringBuilder (definida en System.Text), de forma que por
medio del método Append o AppendFormat puedas ir "agregando" trozos de
cadenas:
sb.Append("hola")
sb.Append("mundo")
cadena1 = sb.ToString
Porque unir cadenas en .NET es muy costoso, cada vez que unimos una cadena a
otra existente, se crea un nuevo objeto en la memoria y ese nuevo objeto es el que
se asigna, y si estamos uniendo muchas cosas, pues...
sb = New System.Text.StringBuilder
sb.AppendFormat("hola{0}mundo", vbCrLf)
Que no es que sea más complicado que lo anterior, pero te puede dar una idea de
que puedes hacer con esos "formatos" que usa el método AppendFormat.
Pues bien, el método Format de la clase String también usa esos formatos, por
tanto, el ejemplo anterior lo podríamos escribir de esta forma:
Con la función Format de Visual Basic solo podemos usar formatos para aplicarlos
a una expresión, por ejemplo:
Esto mostrará el número con este formato: 12.345,68 (los separadores dependerá
de la configuración regional, en mi caso está con el formato de España, donde el
separador de decimales es la coma y el de miles es el punto).
La forma más fácil de hacer eso mismo, es con el método ToString de la variable
"hoy":
cadena1 = hoy.ToString("dd/MMM/yyyy")
Console.WriteLine(cadena1)
Pero si quieres usar el método Format de la clase String (recuerda que ese
método es un método compartido, por tanto debes usarlo desde la propia clase que
lo define), tendrás que hacerlo de esta forma: