Buscador

Tipos de tratamiento de error en VB.NET

VB.NET proporciona dos tipos de tratamiento de error: estructurado y no estructurado. El primero se basa en los esquemas de captura de errores de lenguajes como C# y C++; gestionando los errores a través de excepciones, y una estructura de control que se encarga de atrapar aquellas excepciones que se produzcan. 
El segundo es un sistema heredado de versiones anteriores del lenguaje Visual Basic, y está basado en la detección y captura de errores a través de etiquetas de código, mediante saltos no estructurados en el flujo de la ejecución.

Manipuladores de excepciones

Un manipulador de excepción es un bloque de código que proporciona una respuesta al error que se ha producido, y que se incluye en una estructura proporcionada por el lenguaje a tal efecto, es decir, para la captura de excepciones.

Manipulación de errores

Errores y excepciones 

Dentro del esquema de gestión de errores del entorno .NET Framework, encontramos las figuras del error y la excepción. Estos elementos son utilizados indistintamente en muchas ocasiones para hacer referencia genérica a los errores producidos; sin embargo, aunque complementarios, cada uno tiene su propia funcionalidad dentro del proceso de tratamiento de un error. 
  • Error. Un error es un evento que se produce durante el funcionamiento de un programa, provocando una interrupción en su flujo de ejecución. Al producirse esta situación, el error genera un objeto excepción. 
  • Excepción. Una excepción es un objeto generado por un error, que contiene información sobre las características del error que se ha producido.

Arrays multidimensionales

Para recorrer arrays multidimensionales, la clase Array dispone de varios miembros, algunos de los cuales describimos seguidamente. 
• Rank. Devuelve el número de dimensiones del array. 
• GetLength(Dimension). Devuelve el número de elementos de la dimensión del array pasada como parámetro. 
• GetLowerBound(Dimension). Devuelve el número de índice inferior de la dimensión pasada como parámetro. 
• GetUpperBound(Dimension). Devuelve el número de índice superior de la dimensión pasada como parámetro. 
El Código fuente 349 muestra un ejemplo de manipulación de un array multidimensional mediante las propiedades y métodos mencionados.

Sub Main()
' crear array multidimensional y rellenar de valores
Dim iDatos(2, 4) As Integer
iDatos(0, 0) = 1000
iDatos(0, 1) = 2000
iDatos(0, 2) = 3000
iDatos(0, 3) = 4000
iDatos(0, 4) = 5000
iDatos(1, 0) = 25
iDatos(1, 1) = 35
iDatos(1, 2) = 45
iDatos(1, 3) = 55
iDatos(1, 4) = 65
iDatos(2, 0) = 111
iDatos(2, 1) = 222
iDatos(2, 2) = 333
iDatos(2, 3) = 444
iDatos(2, 4) = 555
Dim iContadorDimUno As Integer
Dim iContadorDimDos As Integer
Dim sTextoFila As String
' poner títulos de la fila y columnas del array a mostrar
Console.WriteLine("Fila" & ControlChars.Tab & _
"Col 0" & ControlChars.Tab & "Col 1" & ControlChars.Tab & _
"Col 2" & ControlChars.Tab & "Col 3" & ControlChars.Tab & "Col 4")
' el bucle externo recorre la primera dimensión
For iContadorDimUno = iDatos.GetLowerBound(0) To iDatos.GetUpperBound(0)
' aquí obtenemos el número de fila
' que se está procesando
sTextoFila = iContadorDimUno & ControlChars.Tab
' este bucle recorre la segunda dimensión
For iContadorDimDos = iDatos.GetLowerBound(1) To iDatos.GetUpperBound(1)
sTextoFila = sTextoFila & iDatos(iContadorDimUno, iContadorDimDos) & _
ControlChars.Tab
Next
' mostrar en la consola el contenido
Console.WriteLine(sTextoFila)
sTextoFila = ""
Next
Console.WriteLine()
Console.WriteLine("El número de dimensiones es: {0}", iDatos.Rank)
Console.WriteLine("El número total de elementos es: {0}", iDatos.Length)
Console.ReadLine()
End Sub
Código fuente 349

Búsqueda

Los métodos IndexOf( ) y LastIndexOf( ) de la clase Array, nos permiten buscar un elemento en un array comenzando la búsqueda desde el principio o final respectivamente. Ya que ambos disponen de diferentes implementaciones al estar sobrecargados, consulte el lector la documentación de la plataforma. El Código fuente 348 muestra algunos ejemplos de uso.

Sub Main()
Dim sNombres() As String = {"Alberto", "Juan", "Ana", "Paco", "Miguel", "Ana"}
' buscar una cadena a partir del índice 0 del array
Console.WriteLine("Paco está en la posición {0}", _
Array.IndexOf(sNombres, "Paco"))
' buscar una cadena a partir del índice 3 del array
Console.WriteLine("Ana está en la posición {0}," & _
" comenzando a buscar desde índice 3", _
Array.IndexOf(sNombres, "Ana", 3))
' introducir un valor a buscar en el array,
' si no existe se devuelve -1
Dim iPosicionBuscar As Integer
Console.WriteLine("Introducir nombre a buscar")
iPosicionBuscar = Array.IndexOf(sNombres, _
Console.ReadLine())
If iPosicionBuscar = -1 Then
Console.WriteLine("El nombre no está en el array")
Else
Console.WriteLine("El nombre está en la posición {0} del array", _
iPosicionBuscar)
End If
' buscar comenzando por la última posición
Dim iNumeros() As Integer
Dim iUltPosicionBuscar As Integer
iNumeros = New Integer() {10, 20, 30, 10, 50, 60, 10, 70, 80}
Console.WriteLine("El 10 está en la posición {0} comenzando por el final", _
Array.LastIndexOf(iNumeros, 10))
Console.ReadLine()
End Sub
Código fuente 348

Ordenación

Para ordenar un array disponemos del método Sort( ), que al estar sobrecargado, tiene varias implementaciones; la más básica de ellas es la que ordena la totalidad del array. También podemos ordenar una parte del array, indicando la posición inicial y cantidad de elementos a ordenar, etc. 
El método Reverse( ), invierte la posición de todos o parte de los elementos de un array. En este punto, debemos matizar que no se realiza un orden inverso de los elementos, sino que se cambian las posiciones de los mismos. Ver Código fuente 347.

Sub Main()
' ordenar todo el array
Dim sLetras1() As String = {"z", "a", "g", "m", "w", "i", "c", "b"}
Array.Sort(sLetras1)
Console.WriteLine("Ordenar todos el array")
MostrarArray(sLetras1)
' ordenar parte del array
Dim sLetras2() As String = {"z", "a", "g", "m", "w", "i", "c", "b"}
Array.Sort(sLetras2, 4, 3)
Console.WriteLine("Ordenar parte del array")
MostrarArray(sLetras2)
' invertir valores dentro del array
Dim sLetras3() As String = {"z", "a", "g", "m", "w", "i", "c", "b"}
Array.Reverse(sLetras3, 2, 4)
Console.WriteLine("Invertir valores del array")
MostrarArray(sLetras3)
Console.ReadLine()
End Sub
Private Sub MostrarArray(ByVal sMiLista() As String)
Dim iContador As Integer
For iContador = 0 To sMiLista.Length - 1
Console.WriteLine("Elemento: {0} - Valor: {1}", _
iContador, sMiLista(iContador))
Next
Console.WriteLine()
End Sub
Código fuente 347

Inicialización de valores

Para inicializar o eliminar los valores de los elementos de un array, utilizaremos el método Clear( ), al que pasaremos el array a inicializar, el índice a partir del que comenzaremos, y el número de elementos. 
Los valores serán inicializados en función del tipo de dato del array: cadena vacía en arrays String; cero en arrays numéricos, etc. Veamos el Código fuente 346.

Sub Main()
' array String, asignar valores e inicializar
Dim sLetras(2) As String
sLetras(0) = "a"
sLetras(1) = "b"
sLetras(2) = "c"
' limpiar elementos en un array de tipo String,
' los elementos limpiados quedan como cadena vacía
Array.Clear(sLetras, 0, 1)
Console.WriteLine("Array sLetras")
MostrarArray(sLetras)
' array Integer, asignar valores e inicializar
Dim iNumeros() As Integer = {100, 200, 300, 400, 500, 600}
' limpiar elementos en un array de tipo Integer,
' los elementos limpiados se ponen a 0
Array.Clear(iNumeros, 1, 2)
Console.WriteLine("Array iNumeros")
MostrarArrayNum(iNumeros)
' array Object, asignar valores e inicializar
Dim oVarios(6) As Object
oVarios(0) = "Hola"
oVarios(1) = 456
oVarios(2) = 1200
oVarios(3) = #12/25/2001#
oVarios(4) = 900
oVarios(5) = True
oVarios(6) = "adelante"
' al ser este un array de tipo Object
' los elementos limpiados se establecen a Nothing
Array.Clear(oVarios, 3, 2)
Console.WriteLine("Array oVarios")
MostrarArrayObj(oVarios)
Console.ReadLine()
End Sub
' recorrer un array de cadenas
Private Sub MostrarArray(ByVal sMiLista() As String)
Dim iContador As Integer
For iContador = 0 To sMiLista.Length - 1
Console.WriteLine("Elemento: {0} - Valor: {1}", _
iContador, sMiLista(iContador))
Next
Console.WriteLine()
End Sub
' recorrer un array de números
Private Sub MostrarArrayNum(ByVal iMiLista() As Integer)
Dim iContador As Integer
For iContador = 0 To iMiLista.Length - 1
Console.WriteLine("Elemento: {0} - Valor: {1}", _
iContador, iMiLista(iContador))
Next
Console.WriteLine()
End Sub
' recorrer un array de objetos
Private Sub MostrarArrayObj(ByVal oMiLista() As Object)
Dim iContador As Integer
For iContador = 0 To oMiLista.Length - 1
Console.WriteLine("Elemento: {0} - Valor: {1}", _
iContador, oMiLista(iContador))
Next
Console.WriteLine()
End Sub
Código fuente 346

Copia

Si intentamos copiar un array asignando la variable que contiene un array a otra, el resultado real serán dos variables que apuntan a la misma lista de valores, por lo que en definitiva sólo tendremos un array, al cual podremos acceder usando dos variables. Ello es debido a que como explicamos en un apartado anterior, los arrays son tipos por referencia que apuntan al mismo conjunto de valores. 
Podemos clonar el array, como se ha descrito en el apartado anterior, con lo que obtendremos un nuevo array, que será idéntico al original. O bien, podemos copiar el array utilizando los métodos CopyTo( ) y Copy( ) de la clase array. La diferencia con respecto a la clonación, consiste en que al copiar un array, el array destino ya debe estar creado con el número suficiente de elementos, puesto que los métodos de copia de la clase Array, lo que hacen es traspasar valores de los elementos del array origen al array destino, en función de los parámetros utilizados, copiaremos todos los elementos o un subconjunto. Veamos unos ejemplos en el Código fuente 345.

Sub Main()
Dim sColores(3) As String
sColores(0) = "Azul"
sColores(1) = "Verde"
sColores(2) = "Rosa"
sColores(3) = "Blanco"
MostrarArray(sColores)
' copiar usando el método CopyTo(),
' copiamos en el array sColorDestino,
' y comenzando por su posición 2, los
' valores del array sColores
Dim sColorDestino(6) As String
sColores.CopyTo(sColorDestino, 2)
Console.WriteLine("Array sColorDestino")
MostrarArray(sColorDestino)
' copiar usando el método Copy(),
' copiamos en el array sListaColores,
' a partir de su posición 2,
' 2 elementos del array sColores, comenzando
' desde la posición 1 de sColores
Dim sListaColores(5) As String
Array.Copy(sColores, 1, sListaColores, 2, 2)
Console.WriteLine("Array sListaColores")
MostrarArray(sListaColores)
Console.ReadLine()
End Sub
Private Sub MostrarArray(ByVal sMiLista() As String)
Dim iContador As Integer
For iContador = 0 To sMiLista.Length - 1
Console.WriteLine("Elemento: {0} - Valor: {1}", _
iContador, sMiLista(iContador))
Next
Console.WriteLine()
End Sub
Código fuente 345

Clonación

Para evitar el problema planteado en el apartado anterior, si necesitamos disponer de un array con las mismas características que uno ya existente, y que sea totalmente independiente del primero, utilizaremos el método Clone( ). 
Con esto solucionaremos el problema de que al pasar un array como parámetro, las modificaciones que precisemos realizar, afecten al array original. Veamos un ejemplo en el Código fuente 344.

Sub Main()
' crear un array
Dim iValores() As Integer = {10, 20, 30}
CambiaArray(iValores)
' mostrar el array original,
' en este no se habrán producido cambios
Console.WriteLine("Array original")
MostrarArray(iValores)
Console.ReadLine()
End Sub
Private Sub CambiaArray(ByVal iListaDatos As Integer())
' crear un array clónico,
' cambiarle valores y mostrarlo
Dim iListaClonada As Array
iListaClonada = iListaDatos.Clone()
iListaClonada(0) = 621
iListaClonada(1) = 900
Console.WriteLine("Array clónico")
MostrarArray(iListaClonada)
End Sub
Private Sub MostrarArray(ByVal sMiLista() As Integer)
Dim iContador As Integer
For iContador = 0 To sMiLista.Length - 1
Console.WriteLine("Elemento: {0} - Valor: {1}", _
iContador, sMiLista(iContador))
Next
Console.WriteLine()
End Sub
Código fuente 344

Paso de arrays como parámetros, y devolución desde funciones

Podemos pasar un array como parámetro a una rutina de código, teniendo en cuenta que los cambios que realicemos sobre el array en el procedimiento llamado, se mantendrán al volver el flujo de la ejecución al procedimiento llamador. 
Ello es debido a que los arrays son tipos por referencia del entorno, y por lo tanto, las variables del array que manejamos tanto desde el procedimiento llamador, como desde el procedimiento llamado, son en realidad punteros hacia una misma zona de memoria o referencia, la que contiene el array. En el ejemplo del Código fuente 343, comprobaremos que al pasar un array por valor, los cambios que realicemos sobre sus elementos se mantendrán al volver al procedimiento que hizo la llamada.

Sub Main()
Dim iValores() As Integer = {10, 20, 30}
' en ambos casos, se pasa una referencia del array,
' y al volver de las llamadas a los procedimientos,
' el array ha sido modificado en ambas llamadas,
' independientemente de que haya sido pasado por
' valor o referencia
ManipArrayVal(iValores)
MostrarArray(iValores)
ManipArrayRef(iValores)
MostrarArray(iValores)
Console.ReadLine()
End Sub
' a este procedimiento le pasamos un array por valor
Private Sub ManipArrayVal(ByVal iListaPorValor As Integer())
' cambiar elemento del array
iListaPorValor(0) = 888
End Sub
' a este procedimiento le pasamos un array por referencia
Private Sub ManipArrayRef(ByRef iListaPorReferencia As Integer())
' cambiar elemento del array
iListaPorReferencia(2) = 457
End Sub
Private Sub MostrarArray(ByVal sMiLista() As Integer)
' muestra el array pasado como parámetro
Dim iContador As Integer
For iContador = 0 To sMiLista.Length - 1
Console.WriteLine("Elemento: {0} - Valor: {1}", _
iContador, sMiLista(iContador))
Next
Console.WriteLine()
End Sub
Código fuente 343

Recorrer el contenido - II

La estructura de control utilizada para recorrer el array, puede ser indistintamente un bucle For...Next, For Each...Next, o la novedosa técnica de los objetos enumeradores proporcionados por el objeto array. 
Como muestra de estas funcionalidades, el Código fuente 342 que vemos a continuación, contiene algunos ejemplos de cómo realizar una iteración sobre los elementos de un array.

Sub Main()
' recorrer un array
' =================
Dim sNombres() As String = {"Ana", "Luis", "Pablo"}
Dim iContador As Integer
Dim sUnNombre As String
' modo tradicional
' ----------------
Console.WriteLine("Recorrido del array con LBound() y UBound()")
For iContador = LBound(sNombres) To UBound(sNombres)
Console.WriteLine("Posicion: {0} - Valor: {1}", _
iContador, sNombres(iContador))
Next
Console.WriteLine()
' con bucle For Each
Console.WriteLine("Recorrido del array con bucle For Each")
For Each sUnNombre In sNombres
Console.WriteLine("Nombre actual: {0}", sUnNombre)
Next
Console.WriteLine()
' modo orientado a objeto
' ----------------
' usando la propiedad Length
Console.WriteLine("Recorrido del array con propiedad Length")
For iContador = 0 To (sNombres.Length - 1)
Console.WriteLine("Posicion: {0} - Valor: {1}", _
iContador, sNombres(iContador))
Next
Console.WriteLine()
' usando los métodos GetLowerBound() y GetUpperBound()
Console.WriteLine("Recorrido del array con métodos GetLowerBound() y
GetUpperBound()")
For iContador = sNombres.GetLowerBound(0) To sNombres.GetUpperBound(0)
Console.WriteLine("Posicion: {0} - Valor: {1}", _
iContador, sNombres(iContador))
Next
Console.WriteLine()
' recorrer con un enumerador
Console.WriteLine("Recorrido del array con un enumerador")
Dim sLetras() As String = {"a", "b", "c", "d"}
Dim oEnumerador As System.Collections.IEnumerator
' obtener el enumerador del array
oEnumerador = sLetras.GetEnumerator()
' con un enumerador no es necesario posicionarse
' en el primer elemento ni calcular la cantidad
' de elementos del array, sólo hemos de avanzar
' posiciones con MoveNext() y obtener el valor
' actual con Current
While oEnumerador.MoveNext()
Console.WriteLine("Valor actual: {0}", oEnumerador.Current)
End While
Console.ReadLine()
End Sub
Código fuente 342

Recorrer el contenido - I

Para realizar un recorrido por los elementos de un array, disponemos de las funciones LBound( ) y UBound( ), que devuelven el número de índice inferior y superior respectivamente del array que pasemos como parámetro. Debido a que en .NET todos los arrays deben comenzar obligatoriamente por el índice cero, no será necesario el uso de LBound( ), ya que esta es una función que proviene de versiones anteriores del lenguaje, en las que el primer índice podía ser un número distinto de cero. 
La orientación a objetos proporcionada por el entorno, pone a nuestra disposición el conjunto de características que comentamos seguidamente, y que harán prácticamente innecesario el uso de las funciones de manejo de arrays, a favor de una codificación más orientada a objeto.
  • Length. Esta propiedad de un objeto array devuelve el número de elementos que contiene. 
  • GetLowerBound( ), GetUpperBound( ). Estos métodos de un objeto array, devuelven respectivamente, el número de índice inferior y superior de una dimensión del array. El resultado es el mismo que usando LBound( ) y UBound( ), pero desde una perspectiva orientada a objetos. 
  • Enumeradores. Un objeto enumerador pertenece al interfaz IEnumerator, diseñado para realizar un recorrido o iteración a través de uno de los diferentes tipos de colección (arrays incluidos) existentes en .NET Framework. Mediante el método GetEnumerator( ) de un objeto array, obtenemos un objeto que implementa el interfaz IEnumerator, que sólo puede realizar labores de lectura sobre el array, en ningún caso de modificación.

Asignación y obtención de valores

Para asignar u obtener valores de los elementos de un array, emplearemos la variable que contiene el array haciendo referencia al índice o posición a manipular, o bien, puesto que un array es un objeto, utilizaremos los métodos SetValue( ) y GetValue( ), que asignan y obtienen respectivamente los valores del array. Veamos un ejemplo en el Código fuente 341.

Sub Main()
' asignación de valores a los elementos de un array
' =================================================
Dim sNombres(4) As String
' directamente sobre la variable,
' haciendo referencia al índice
sNombres(0) = "Juan"
sNombres(1) = "Ana"
sNombres(2) = "Luis"
' o con el método SetValue(), asignando el
' valor en el primer parámetro y especificando
' la posición en el segundo
sNombres.SetValue("Elena", 3)
sNombres.SetValue("Miguel", 4)
' obtención de valores de un array
' ================================
Dim sValorA As String
Dim sValorB As String
sValorA = sNombres(2) ' directamente de la variable
sValorB = sNombres.GetValue(3) ' usando el meth GetValue
Console.WriteLine("Contenido de las variables")
Console.WriteLine("==========================")
Console.WriteLine("ValorA: {0} -- ValorB: {1}", sValorA, sValorB)
Console.ReadLine()
End Sub
Código fuente 341

Declaración

Podemos declarar un array en la forma tradicional, explicada en temas anteriores, o bien utilizando la sintaxis orientada a objetos mediante la palabra clave New. El Código fuente 340 muestra algunos ejemplos de las diferentes formas disponibles.

Sub Main()
' formas de declaración de arrays
' ===============================
' 1)
' estableciendo el número de elementos
Dim sNombres(2) As String
' 2)
' asignando valores al array al mismo tiempo que se declara,
' la lista de valores debe ir encerrada entre llaves
Dim sEstaciones() As String = {"Ana", "Pedro", "Luis"}
' 3)
' indicando el tipo de dato pero no el número de elementos,
' de este modo la variable todavía no es considerada un array
' ya que contiene una referencia a Nothing
Dim iValores() As Integer
' 4)
' indicando el tipo de dato y estableciendo una
' lista vacía de elementos,
' a diferencia del caso anterior, la variable ahora sí
' es considerada un array aunque de longitud cero
Dim iDatos() As Integer = {}
' 5)
' instanciando el tipo de dato, estableciendo el número
' de elementos al instanciar, e indicando que se trata de un array
' al situar las llaves
Dim iCantidades() As Integer = New Integer(20) {}
' 6)
' declarar primero la variable que contendrá el array,
' asignar valores al array al mismo tiempo que se instancia
' la lista de valores debe ir encerrada entre llaves
Dim iNumeros() As Integer
iNumeros = New Integer() {10, 20, 30, 10, 50, 60, 10, 70, 80}
End Sub
Código fuente 340

Recomendamos al lector, que en estos ejemplos con arrays, utilice el depurador para ejecutar línea a línea el código, y abra la ventana Locales también del depurador, para ver en cada caso el contenido de los elementos del array.

La clase Array

Esta clase, perteneciente a la jerarquía de clases del sistema, es decir, incluida en el espacio de nombres System, proporciona a través de sus miembros, acceso orientado a objeto para los arrays que manipulemos en nuestras aplicaciones. Esto quiere decir que los arrays, como sucede con otros elementos del lenguaje, son también objetos. 
Al igual que el resto de elementos del entorno, los arrays son tipos pertenecientes al sistema común de tipos de la plataforma o CTS, y se encuentran clasificados como tipos por referencia; esto quiere decir, que durante la ejecución, un array será gestionado en la zona de memoria conocida como montón o heap. 
Aunque podemos trabajar con los arrays como objetos, no será necesario instanciar un objeto de esta clase para poder disponer de un array. Al declarar una variable como array, implícitamente se instancia un objeto de la clase. En sucesivos apartados de este tema, haremos una descripción de los miembros de instancia y compartidos más importantes de la clase Array.

Arrays

Aspectos básicos 

Como ya se explicó en un tema anterior, un array es aquel elemento del lenguaje que nos permite agrupar un conjunto de valores del mismo tipo, y acceder a ellos a través de una misma variable o identificador, especificando la posición o índice en donde se encuentra el dato a recuperar. El Código fuente 339, muestra las operaciones esenciales que podemos realizar con un array. Recomendamos al lector la creación de un nuevo proyecto en el IDE de tipo consola, para realizar las pruebas mostradas en los siguientes apartados.

Sub Main()
' declarar un array de tipo String,
' el número de elementos es el indicado
' en la declaración más uno, porque la primera
' posición de un array es cero
Dim sNombres(3) As String
' asignar valores al array
sNombres(0) = "Ana"
sNombres(1) = "Pedro"
sNombres(2) = "Antonio"
sNombres(3) = "Laura"
' pasar un valor del array a una variable
Dim sValor As String
sValor = sNombres(2)
' mostrar en la consola el valor pasado a una variable
' y un valor directamente desde el array
Console.WriteLine("Valor de la variable sValor: {0}", sValor)
Console.WriteLine("Valor del array, posición 1: {0}", sNombres(1))
Console.ReadLine()
End Sub
Código fuente 339

Enlace dinámico de eventos - II

Como ventaja adicional, el objeto sobre el que vamos a manipular sus eventos podemos declararlo tanto a nivel local como en la zona de declaraciones, a diferencia del enlace estático, que nos obliga a declarar el objeto en la zona de declaraciones del módulo en el que vayamos a utilizarlo. Para establecer un enlace dinámico entre un evento y un manipulador, utilizaremos la instrucción AddHandler. Esta instrucción, recibe como primer parámetro el evento a conectar en el formato NombreObjeto.NombreEvento. Como segundo parámetro, pasaremos la dirección de entrada al procedimiento que deberá ejecutar el evento, y que obtenemos a través de la instrucción AddressOf. El Código fuente 338, muestra el procedimiento Main( ), en el que pedimos al usuario que introduzca un número, y según el valor obtenido, conectamos el evento con uno de los dos procedimientos manipuladores antes descritos.

Module Module1
'....
'....
Sub Main()
' pedir un número al usuario para conectar a uno de los
' dos procedimientos manipuladores de evento que hemos escrito
Dim liTipoManip As Integer
Console.WriteLine("Introduzca el número 1 ó 2," & _
" para seleccionar el manipulador de evento a utilizar")
liTipoManip = Console.ReadLine()
' instanciar un objeto Empleado
Dim loMiEmpleado As New Empleado()
' asignar un manejador de evento en tiempo de ejecución
' en función del número que el usuario ha introducido
Select Case liTipoManip
Case 1
AddHandler loMiEmpleado.LimiteSueldo, AddressOf SobreAsignacionSueldo
Case 2
AddHandler loMiEmpleado.LimiteSueldo, AddressOf SalarioIncorrecto
End Select
loMiEmpleado.Nombre = "ANTONIO"
' esta asignación provoca el evento,
' ello ejecutará uno de los manipuladores
' de evento que hemos conectado
loMiEmpleado.Sueldo = 2500
Console.ReadLine()
End Sub
'....
'....
End Module
Código fuente 338

Enlace dinámico de eventos - I

Siendo un poco más complejo a nivel sintáctico que el enlace estático, el enlace dinámico de eventos a sus correspondientes manipuladores, tiene la ventaja de que nos permite asociar el mismo evento a diferentes procedimientos manipuladores de dicho evento, durante el transcurso de la ejecución del programa. 
Por lo tanto, en el módulo de código donde tenemos a Main( ), vamos a escribir dos procedimientos que asociaremos dinámicamente al evento que hemos creado en la clase Empleado. Ver Código fuente 337

Module Module1
'....
'....
' manipuladores de evento que conectaremos en tiempo de ejecución
Public Sub SobreAsignacionSueldo(ByVal ldbImporte As Double)
Console.WriteLine("Se intentó asignar a un empleado el sueldo {0}" & _
ControlChars.CrLf & "¡ESTO ES INCORRECTO!", ldbImporte)
End Sub
Public Sub SalarioIncorrecto(ByVal ldbImporte As Double)
Console.WriteLine("INFORME DE INCIDENCIAS")
Console.WriteLine("======================")
Console.WriteLine("Error al intentar asignar el salario {0} a un empleado", _
ldbImporte)
End Sub
'....
'....
End Module
Código fuente 337

Enlace estático de eventos - II

Un pequeño truco que tenemos en el editor de código de VS.NET, para facilitar la creación de los procedimientos manipuladores de evento, consiste en abrir la lista Nombre de clase y seleccionar el nombre de la variable que hemos declarado WithEvents. Ver Figura 127.
Figura 127. Seleccionar objeto declarado WithEvents.
Seguidamente pasamos a la lista Nombre de método, y allí elegimos el nombre del evento que vamos a codificar. Ver Figura 128.
Figura 128. Seleccionar el evento a codificar.
Esto nos crea el procedimiento manipulador de evento vacío, en base a una convención de nombres predefinida en el IDE. Ver Código fuente 335.

Public Sub moEmple_LimiteSueldo(ByVal ldbImporte As Double) Handles
moEmple.LimiteSueldo
End Sub
Código fuente 335

Como hemos escrito el manipulador de evento para el objeto Empleado en un módulo, vamos ahora a escribir un procedimiento Main(), instanciando en el mismo, un objeto de esta clase. Asignaremos en primer lugar, un valor correcto a la propiedad Sueldo, y a continuación un valor que provocará el evento en la clase. Recomendamos al lector que ejecute el código línea a línea con el depurador, para observar el efecto cuando se produzca el evento.

Sub Main()
moEmple = New Empleado()
moEmple.Nombre = "Juan"
moEmple.Sueldo = 500 ' esta asignación no provoca el evento
moEmple.Sueldo = 8000 ' esta sí provoca el evento
End Sub
Código fuente 336

Enlace estático de eventos - I

Este es el modo mas sencillo para implementar la conexión entre un evento y un procedimiento manipulador de evento.
 En primer lugar, declaramos una variable del tipo de objeto cuyos eventos queremos capturar, en la zona de declaraciones del módulo, clase, etc., utilizando la palabra clave WithEvents. Veamos el Código fuente 332.

Module Module1
Private WithEvents moEmple As Empleado
'......
'......
Código fuente 332

A continuación, tenemos que escribir el procedimiento manipulador, que será invocado cada vez que se produzca el evento. Dicho procedimiento debe ser de tipo Sub, ya que un evento no puede devolver valores, por lo que no podremos utilizar un Function. También debemos finalizar su declaración con la palabra clave Handles, seguida del nombre de la variable del objeto que hemos declarado en la zona de declaraciones, y el nombre del evento que el procedimiento va a tratar. En el Código fuente 333, el procedimiento moEmple_LimiteSueldo( ), será llamado cada vez que se produzca el evento LimiteSueldo en el objeto Empleado.

Public Sub moEmple_LimiteSueldo(ByVal ldbImporte As Double) _
Handles moEmple.LimiteSueldo
Console.WriteLine("Se ha sobrepasado para {0} el límite" & _
" establecido de sueldo", _
moEmple.Nombre)
Console.WriteLine("El importe {0} no es válido", ldbImporte)
Console.ReadLine()
End Sub
Código fuente 333

El nombre utilizado para el procedimiento puede ser cualquiera, aunque en este caso hemos empleado la convención NombreObjeto_NombreEvento simplemente para facilitar la lectura del código, pero podríamos haber empleado, por ejemplo, el que se muestra en el Código fuente 334.

Public Sub Sobrepasado(ByVal ldbImporte As Double) _
Handles moEmple.LimiteSueldo
' ....
' ....
End Sub
Código fuente 334

Conexión de un emisor de eventos con un manipulador de eventos

Existen dos medios para comunicar un evento con un manipulador de eventos: 
  • En tiempo de compilación, realizando un enlace estático entre la clase y el manipulador mediante las palabras clave WithEvents y Handles. Esta técnica tiene la ventaja de que permite escribir un código mucho más legible, en cuanto a la manipulación de eventos se refiere. 
  • En tiempo de ejecución, realizando un enlace dinámico entre la clase y el manipulador mediante la palabra clave AddHandler. La ventaja en este caso, es que podemos asociar procedimientos manipuladores de evento dinámicamente durante el transcurso de la ejecución del programa.

El receptor de eventos

Un receptor de eventos, también denominado manipulador de eventos (event receiver o event handler), es aquella parte del código cliente, que configuramos para que sea capaz de recibir los eventos generados por un objeto emisor. Para que ambos elementos en este canal de comunicación que es la transmisión de eventos puedan operar, es necesario conectarlos.

El emisor de eventos

Un emisor de eventos, también denominado origen de eventos (event source o event sender), es un objeto capacitado para generar y lanzar eventos al sistema, que puedan ser recuperados por otros objetos preparados para realizar su tratamiento. Para que un objeto pueda desencadenar eventos, en su clase debemos realizar dos tareas: 
  • • Declarar el propio evento usando la palabra clave Event, especificando si es necesario una lista de parámetros que acompañan al evento. 
  • Lanzar el evento mediante la palabra clave RaiseEvent, seguida del nombre del evento a provocar. Si hemos declarado el evento con parámetros, deberemos añadir los valores para cada uno de los parámetros en el mismo orden en el que los hemos declarado. 

Situándonos pues ante el problema planteado por la clase Empleado en un apartado anterior, la solución que proponemos consistirá en generar desde la clase Empleado un evento cuando se produzca un fallo en la validación del sueldo. De esta manera, el código cliente que lo necesite, responderá al evento, y el que no lo precise, hará caso omiso del evento lanzado. 
En primer lugar, declaramos en la zona de declaraciones de la clase el evento LimiteSueldo, que irá acompañado de un parámetro que nos informará del importe erróneo que se intentaba asignar a la propiedad. 
A continuación, en la propiedad Sueldo, cuando detectemos que el sueldo sobrepasa el valor permitido, en lugar de lanzar allí el mensaje a la consola, generaremos el evento LimiteSueldo, que podrá ser recuperado por el código cliente que haga uso de la clase, actuando como necesite en cada ocasión. Observe el lector, que al mismo tiempo que lanzamos el evento, le pasamos el importe del sueldo que se intentaba asignar. Veamos el Código fuente 331.

Public Class Empleado
' declaramos el evento
Public Event LimiteSueldo(ByVal ldbImporte As Double)
Private msNombre As String
Private mdbSueldo As Double
Public Property Nombre() As String
Get
Return msNombre
End Get
Set(ByVal Value As String)
msNombre = Value
End Set
End Property
Public Property Sueldo() As Double
Get
Return mdbSueldo
End Get
Set(ByVal Value As Double)
' si el valor que intentamos asignar
' al sueldo supera el permitido...
If Value > 1000 Then
' ...lanzamos el evento, y le pasamos
' como parámetro informativo el valor
' incorrecto que intentábamos asignar
RaiseEvent LimiteSueldo(Value)
Else
mdbSueldo = Value
End If
End Set
End Property
End Class
Código fuente 331
Con estas modificaciones sobre la clase Empleado, ya tenemos listo nuestro emisor de eventos. Queda ahora por completar la parte que captura los eventos lanzados por el emisor.

Esquema básico de un sistema orientado a eventos

Un sistema conducido por eventos basa su funcionamiento en dos pilares fundamentales: un emisor y un receptor de eventos. El primero genera y lanza el evento al sistema, mientras que el segundo, si está interesado en tratar el evento lanzado, lo captura y le da respuesta. Si un objeto receptor no necesita gestionar eventos, simplemente no lo obtiene. Ver Figura 126.
Figura 126. Esquema de generación y captura de eventos.
Tras una introducción conceptual, a continuación trataremos con más detalle cada uno de los elementos integrantes de la gestión de eventos.

Programación basada en eventos

La aparición de sistemas operativos basados en ventajas trajo consigo un nuevo esquema en el desarrollo de aplicaciones. En un programa que se ejecute dentro de un sistema como Windows se están produciendo constantemente eventos (sucesos), provocados por las acciones del usuario o por el propio sistema. Tan elevado es el número de eventos que se producen, que dar respuesta a todos, es decir, codificar todas aquellas situaciones que acontecen a lo largo de la ejecución de un programa, es algo impensable. 
Por tal motivo, la técnica seguida al escribir código orientado a eventos se basa en codificar sólo los eventos que nos interese tratar, ya que para el resto, será el propio sistema quien proporcione el comportamiento por defecto. En una aplicación Windows típica, todos los elementos que forman parte de la misma, es decir, la propia ventana y los controles contenidos en ella, lanzan eventos en respuesta a las acciones del usuario. 
Un ejemplo típico: al pulsar un control de tipo botón en la ventana se produce su evento clic; si queremos que el programa realice alguna acción al pulsar dicho botón, deberemos escribir código en el procedimiento de evento asociado, para dar respuesta a tal suceso.

Un escenario de trabajo sin eventos

Supongamos que nos encargan desarrollar una clase llamada Empleado, entre cuyos miembros tenemos la propiedad Sueldo. Uno de los requerimientos respecto a esta propiedad es que su valor no debe ser superior a 1000; por ello, en su procedimiento Property, realizamos una validación a tal efecto, emitiendo un mensaje cuando el sueldo que asignemos sea superior. Ver el Código fuente 330.

Public Class Empleado
' variables de propiedad
Private msNombre As String
Private mdbSueldo As Double
' propiedad Nombre
Public Property Nombre() As String
Get
Return msNombre
End Get
Set(ByVal Value As String)
msNombre = Value
End Set
End Property
' propiedad Sueldo
Public Property Sueldo() As Double
Get
Return mdbSueldo
End Get
' al asignar un valor a la propiedad,
' si el valor es superior a 1000
' mostrar un mensaje y no permitir la
' asignación del sueldo
Set(ByVal Value As Double)
If Value > 1000 Then
Console.WriteLine("Asignación de sueldo incorrecta")
Console.ReadLine()
Else
mdbSueldo = Value
End If
End Set
End Property
End Class
Código fuente 330

Una vez finalizado el desarrollo de la clase, la distribuimos a nuestro cliente. Posteriormente, un nuevo cliente nos requiere la clase, pero en esta ocasión, aunque necesita la validación sobre la propiedad Sueldo, no quiere que se muestre el mensaje al sobrepasar el sueldo asignado. 
Se nos plantea en este caso un problema, ya que si escribimos una nueva versión de la clase Empleado, tendremos el trabajo extra de mantener ambas. Para solucionarlo mediante una única versión de la clase recurriremos a los eventos.

Programación estrictamente procedural

Antes de la llegada de los sistemas y lenguajes orientados a eventos, las aplicaciones ejecutaban su código en un orden fijo, ya que estaban basadas en un modelo construido exclusivamente a base de procedimientos: se realizaban llamadas a las rutinas de código en un orden predeterminado, y una vez terminada la ejecución de tales rutinas, finalizaba la aplicación.

Eventos en VB.NET

Ciñéndonos al ámbito de la programación, un evento es, dentro de un programa, una notificación lanzada por un objeto, que podrá ser contestada por aquellos otros objetos interesados en darle respuesta.

Eventos. ¿Qué es un evento?

Un evento es un suceso o situación, que acontece en una ubicación de espacio y tiempo no predecible. 
Cuando una máquina deja de funcionar por una avería, o cuando una persona resbala y cae, estamos en ambos casos, ante ejemplos de eventos, ya que ocurren en momentos inesperados. 
Para que se desencadene un evento, se deben dar determinadas circunstancias, las cuales favorecen el que dicho evento se produzca.

Formateo de valores - III

El Código fuente 328 muestra algunos formatos personalizados, construidos a base de patrones de formato.

Sub Main()
Dim ldtFecha As Date
ldtFecha = Date.Now()
Console.WriteLine(ldtFecha.ToString("ddd, dd-MMM/yyyy"))
Console.WriteLine(ldtFecha.ToString("dddd, a dd \de MMMM ,en el año yyyy"))
Console.WriteLine(ldtFecha.ToString("H:mm:s"))
End Sub
Código fuente 328

En cuanto a los números, si necesitamos aplicar un formato para este tipo de valor, podemos hacerlo mediante los caracteres mostrados en la Tabla 27.
El Código fuente 329 muestra algunos formatos aplicados sobre un tipo numérico.

Sub Main()
Dim ldcMiNum As Decimal
' crear un array con caracteres de formato
Dim lsFormatos() As String = {"c", "e", "f", "g", "n"}
Dim lsNumFormateado As String
ldcMiNum = 850.678 ' asignar valor al número
' recorrer el array de formatos y aplicar cada
' uno de los formatos al número
For Each lsNumFormateado In lsFormatos
Console.WriteLine(ldcMiNum.ToString(lsNumFormateado))
Next
Console.ReadLine()
End Sub
Código fuente 329

Formateo de valores - II

La Tabla 26 por otra parte, muestra algunos caracteres utilizados para crear patrones de formato personalizados, los cuales, se deben combinar entre sí, para componer el formato que necesitemos.
Tabla 26. Caracteres para patrones de formato

Formateo de valores - I

La utilización de un formato sobre un tipo de dato, nos permite mostrar su valor de un modo distinto a como se encuentra almacenado en la aplicación. Por ejemplo, el valor puro de una fecha no muestra el nombre del mes; sin embargo, aplicándole el formato adecuado, podemos hacer que se muestre la fecha en un modo extendido, con el nombre del mes, día de la semana, etc. Todos los tipos de datos del entorno que pueden mostrar información formateada, disponen del método ToString( ), al cuál podemos pasarle una cadena, con la especificación de formato que necesitemos. 
A continuación mostraremos unos ejemplos de formateo para fechas y números, ya que son los tipos de datos que con más frecuencia requieren ser formateado a lo largo del código de un programa.
Respecto a las fechas, el tipo Date, aparte del método ToString( ), tiene algunos miembros que devuelven un tipo de formato fijo. Veamos el Código fuente 326.

Sub Main()
Dim ldtFecha As Date
ldtFecha = Date.Now()
Console.WriteLine("ToLongDateString: {0}", ldtFecha.ToLongDateString())
Console.WriteLine("ToUniversalTime: {0}", ldtFecha.ToUniversalTime())
End Sub
Código fuente 326

Empleando alguna de las sobrecargas del método ToString( ), podemos formatear en los modos mostrados seguidamente. 
La Tabla 25 muestra algunos caracteres asociados a los formatos predefinidos.
En el Código fuente 327 podemos ver un formateo de fechas con caracteres de formato.
Sub Main()
Dim ldtFecha As Date
Dim lsListaFormatos() As String = {"d", "D", "g", "G", "t", "T", "m", "y"}
Dim lsFormato As String
ldtFecha = Date.Now()
For Each lsFormato In lsListaFormatos
Console.WriteLine("Formato: {0}, resultado: {1}", _
lsFormato, ldtFecha.ToString(lsFormato))
Next
End Sub
Código fuente 327

Operaciones aritméticas, la clase Math

La clase Math contiene el conjunto de operaciones aritméticas más habituales. Gracias a que sus miembros son compartidos, es muy fácil su uso, ya que sólo debemos especificar el nombre de la clase, seguido del método a ejecutar.

El Código fuente 325 muestra algunos ejemplos utilizando métodos de la clase Math. Consulte el lector la documentación de .NET Framework para una explicación detallada sobre todos los miembros de esta clase.

Sub Main()
Dim liSigno As Integer
Dim ldbRedondear As Double
' Abs(): devuelve el valor absoluto del número
' pasado como parámetro
Console.WriteLine("Abs --> {0}", Math.Abs(-1867.79))
' Ceiling(): devuelve el número sin precisión decimal,
' más grande o igual que el pasado como parámetro
Console.WriteLine("Ceiling --> {0}", Math.Ceiling(256.7235))
' Floor(): devuelve el número sin precisión decimal,
' más pequeño o igual que el pasado como parámetro
Console.WriteLine("Floor --> {0}", Math.Floor(256.7235))
' Sign(): devuelve un valor informando del signo del número
' pasado como parámetro
Console.WriteLine("Introducir número para averiguar su signo")
liSigno = Console.ReadLine()
Select Case Math.Sign(liSigno)
Case -1
Console.WriteLine("El número es negativo")
Case 0
Console.WriteLine("El número es cero")
Case 1
Console.WriteLine("El número es positivo")
End Select
' Round(): redondea el número pasado como parámetro
ldbRedondear = Math.Round(28.3215)
Console.WriteLine("Redondear 28.3215 --> {0}", ldbRedondear)
ldbRedondear = Math.Round(28.63215)
Console.WriteLine("Redondear 28.63215 --> {0}", ldbRedondear)
Console.ReadLine()
End Sub
Código fuente 325