Buscador

Manipulación de directorios mediante las clases Directory y DirectoryInfo - I

Las clases Directory y DirectoryInfo contienen métodos y propiedades para crear, borrar, copiar y mover directorios, así como otra serie de tareas para su manejo y obtención de información. 
Al igual que sucedía con las clases del anterior apartado, los miembros de Directory son compartidos, mientras que los de DirectoryInfo son de instancia; esta es su principal diferencia. 
En el ejemplo del Código fuente 378, el método Exists( ) comprueba la existencia de un directorio, y en caso afirmativo, obtenemos su última fecha de uso mediante GetLastAccessTime( ). Seguidamente obtenemos un array String con su lista de archivos mediante GetFiles( ), y creamos un subdirectorio de respaldo con CreateSubdirectory( ). En caso de que el directorio no exista, lo creamos con CreateDirectory( ).

Dim sNombreDir As String
Dim Archivos() As String
Dim Archivo As String
Dim oDirInfo As DirectoryInfo
Console.WriteLine("Introducir un nombre de directorio")
sNombreDir = Console.ReadLine()
If Directory.Exists(sNombreDir) Then
Console.WriteLine("Fecha último acceso: {0}", _
Directory.GetLastAccessTime(sNombreDir))
Console.WriteLine("Archivos del directorio {0}", sNombreDir)
Archivos = Directory.GetFiles(sNombreDir)
For Each Archivo In Archivos
Console.WriteLine(Archivo)
Next
oDirInfo = New DirectoryInfo(sNombreDir)
oDirInfo.CreateSubdirectory("bak")
Else
Directory.CreateDirectory(sNombreDir)
Console.WriteLine("No existía el directorio, se acaba de crear")
End If
Código fuente 378

Manipulación de archivos mediante las clases File y FileInfo - III

Además de esta excepción, el espacio de nombres IO proporciona algunas clases de excepción adicionales para tratar otras diversas circunstancias de error. Consulte el lector la documentación de la plataforma referente a IO. 

Los métodos Copy( ), Move( ) y Delete( ), nos permiten copiar, mover y borrar respectivamente el nombre de archivo que pasemos como parámetro. El método GetCreationTime( ) nos devuelve un tipo Date con la fecha de creación del archivo. 

Por otro lado, si queremos obtener información adicional sobre un archivo, como su nombre, extensión, ruta, etc., instanciaremos un objeto FileInfo( ), pasando al constructor una cadena con el nombre del archivo, y utilizaremos algunas de sus propiedades como Name, Extensión, DirectoryName. Veamos una muestra de todo esto en el Código fuente 377.

Dim sNombreFich As String
Dim iOperacion As Integer
Dim oFInfo As FileInfo
Console.WriteLine("Introducir ruta y archivo")
sNombreFich = Console.ReadLine()
Console.WriteLine("Fecha creación archivo: {0}", _
File.GetCreationTime(sNombreFich))
oFInfo = New FileInfo(sNombreFich)
Console.WriteLine("Introducir el número de operación a realizar:")
Console.WriteLine("1 - Copiar")
Console.WriteLine("2 - Mover")
Console.WriteLine("3 - Borrar")
iOperacion = Console.ReadLine()
Select Case iOperacion
Case 1
File.Copy(sNombreFich, "\pruebas\distinto" & oFInfo.Extension)
Case 2
Console.WriteLine("Vamos a mover el archivo {0}", oFInfo.Name)
Console.WriteLine("que está en la ruta {0}", oFInfo.DirectoryName)
File.Move(sNombreFich, "\pruebas\" & oFInfo.Name)
Console.WriteLine("Completado")
Console.ReadLine()
Case 3
File.Delete(sNombreFich)
End Select
Código fuente 377

Manipulación de archivos mediante las clases File y FileInfo - II

Para obtener los atributos de un archivo, disponemos del método GetAttributes( ), al que pasamos la ruta de un archivo, y devuelve un valor de la enumeración FileAttributes con la información sobre los atributos. En el caso de que al intentar acceder a un archivo, este no exista, se producirá una excepción de tipo FileNotFoundException, que podemos tratar en una estructura de manejo de excepciones. 

Ver el Código fuente 376.

Dim sNombreFich As String
Dim oAtributos As FileAttributes
Try
Console.WriteLine("Introducir ruta y archivo")
sNombreFich = Console.ReadLine()
oAtributos = File.GetAttributes(sNombreFich)
Console.WriteLine("Atributos del archivo: {0}", oAtributos.ToString())
Catch oExcep As FileNotFoundException
Console.WriteLine("Se ha producido un error {0}{1}", _
ControlChars.CrLf, oExcep.Message)
Finally
Console.WriteLine("Proceso finalizado")
Console.ReadLine()
End Try
Código fuente 376

Manipulación de archivos mediante las clases File y FileInfo - I

Las clases File y FileInfo, proporcionan a través de sus miembros, el conjunto de operaciones comunes que podemos realizar con archivos en cuanto a su creación, copia, borrado, etc. 
La diferencia principal entre ambas radica en que los miembros de File son todos compartidos, con lo cual se facilita en gran medida su uso, al no tener que crear una instancia previa de la clase; mientras que en FileInfo deberemos crear un objeto para poder utilizarla, ya que sus miembros son de instancia. FileInfo dispone de algunos métodos adicionales que no se encuentran en File. 
Comenzando por la clase File, los métodos CreateText( ) y OpenText( ), devuelven respectivamente un objeto StreamWriter y StreamReader, que utilizaremos para escribir y leer en el archivo pasado como parámetro a estos métodos. Con el método Exists( ), comprobamos si existe un determinado archivo. 
Veamos un ejemplo en el Código fuente 375.

Dim sNombreFich As String
Dim srLector As StreamReader
Dim swEscritor As StreamWriter
Console.WriteLine("Introducir ruta y archivo")
sNombreFich = Console.ReadLine()
If File.Exists(sNombreFich) Then
srLector = File.OpenText(sNombreFich)
Console.WriteLine("El archivo contiene:{0}{1}", _
ControlChars.CrLf, srLector.ReadToEnd())
srLector.Close()
Else
swEscritor = File.CreateText(sNombreFich)
swEscritor.WriteLine("este es")
swEscritor.WriteLine("un nuevo archivo")
swEscritor.Close()
End If
Console.WriteLine("Proceso finalizado")
Console.ReadLine()
Código fuente 375

La clase FileStream - II

Para las operaciones de lectura tenemos los siguientes métodos: 
  • ReadByte( ). Devuelve el valor sobre el que esté posicionado el objeto en ese momento. 
  • Read( ). Traspasa valores a un array de bytes. 
Si queremos desplazarnos por los elementos del Stream, podemos utilizar el método Seek( ), pasando la cantidad de posiciones a movernos, y el punto desde el que queremos realizar dicho desplazamiento, mediante los valores de la enumeración SeekOrigin. Para averiguar el elemento del Stream en el que estamos situados, disponemos de la propiedad Position.

Veamos algunos ejemplos de lectura sobre este tipo de objetos, en el Código fuente 374.

' lectura con FileStream
Dim oFileStream As FileStream
oFileStream = New FileStream("\pruebas\apuntes.dtt", FileMode.Open)
Dim Valor As Byte
Valor = oFileStream.ReadByte() ' obtener un valor
Console.WriteLine("Se ha leido el valor: {0}", Valor)
Console.WriteLine("Nos desplazamos dos bytes en el stream")
oFileStream.Seek(2, SeekOrigin.Begin)
Valor = oFileStream.ReadByte()
Console.WriteLine("Se ha leido el valor: {0}", Valor)
Console.WriteLine("La posición actual del stream es: {0}", _
oFileStream.Position)
' leer varios valores, pasándolos a un array
' previamente dimensionado
Dim VariosValores(3) As Byte
oFileStream.Read(VariosValores, 0, 4)
Console.WriteLine("Leer bloque de valores del stream")
Dim Enumerador As IEnumerator
Enumerador = VariosValores.GetEnumerator()
While Enumerador.MoveNext
Console.WriteLine("Valor: {0}", Enumerador.Current)
End While
Console.ReadLine()
Código fuente 374

La clase FileStream - I

Realiza escritura y lectura de bytes sobre un archivo; en el caso de que el archivo no exista, lo crearíamos al mismo tiempo que instanciamos este objeto. Uno de los constructores de esta clase, nos permite especificar una cadena con la ruta del archivo a utilizar, mientras que en el segundo parámetro utilizaremos un valor de la enumeración FileMode, mediante la que indicamos el modo de trabajo sobre el archivo: añadir, abrir, crear, etc. Las propiedades CanRead, CanWrite y CanSeek, devuelven un valor lógico que nos informa de si en el objeto podemos realizar operaciones de lectura, escritura y desplazamiento por los bytes que contiene. Para escribir datos, disponemos de los siguientes métodos: • WriteByte( ). Escribe un byte en el archivo • Write( ). Escribe, de un array de bytes pasado como parámetro, una cantidad de elementos determinada, empezando por una de las posiciones de dicho array. 

Veamos en el Código fuente 373, un ejemplo de escritura con esta clase.

' escrituras con Filestream
Dim oFileStream As FileStream
oFileStream = New FileStream("\pruebas\apuntes.dtt", FileMode.CreateNew)
oFileStream.Write(New Byte() {15, 160, 88, 40, 67, 24, 37, 50, 21}, 0, 6)
oFileStream.WriteByte(75)
Console.WriteLine("Opciones en el FileStream")
Console.WriteLine("Podemos leer: {0}", IIf(oFileStream.CanRead, "SI", "NO"))
Console.WriteLine("Podemos escribir: {0}", IIf(oFileStream.CanWrite, "SI", "NO"))
Console.WriteLine("Podemos movernos: {0}", IIf(oFileStream.CanSeek, "SI", "NO"))
oFileStream.Close()
oFileStream = Nothing
Código fuente 373

La clase StreamReader - II

Otro de los métodos de lectura es ReadBlock( ), que recibe como parámetro un array de tipo Char, sobre el que se depositarán una cierta cantidad de caracteres leídos del archivo. En el segundo parámetro de este método indicamos la posición del array desde la que se comenzarán a guardar los caracteres, y en el tercer parámetro, el número de caracteres a leer. El método Read( ) utilizado sin parámetros, devuelve un valor numérico correspondiente al código del carácter que se acaba de leer. Cuando tras sucesivas operaciones de lectura lleguemos al final del Stream, este método devolverá -1. 
Para convertir de nuevo a carácter los valores que devuelve Read( ), debemos pasarlos a un array de tipo Byte, y después, utilizando un objeto ASCIIEncoding, llamaremos a su método GetString( ), que convertirá el array en una cadena. Veamos unos ejemplos de estos métodos en el Código fuente 372. Observe el lector, que para poder utilizar en el programa un objeto ASCIIEnconding, debemos importar el espacio de nombres System.Text.

Imports System.IO
Imports System.Text
Module Module1
Sub Main()
' crear un Stream de lectura
Dim srLector As StreamReader = New StreamReader("\pruebas\NOTAS.txt")
' obtener valores del Stream con el método ReadBlock()
' ----------------------------------------------------
' crear un array Char que contendrá los caracteres leídos
Dim Caracteres(15) As Char
' leemos 16 caracteres del archivo y los pasamos al array
' comenzando a grabarlos a partir de su posición 0
srLector.ReadBlock(Caracteres, 0, 16)
' pasamos el array de valores Char a String mediante
' el constructor de la clase String que recibe como
' parámetro un array Char
Dim Parte1 As String = New String(Caracteres)
Console.WriteLine("Resultado de la lectura con ReadBlock()")
Console.WriteLine(Parte1)
Console.WriteLine()
' obtener valores del stream con el método Read()
' -----------------------------------------------
Dim Valor As Integer
Dim Codigos() As Byte
' vamos a ir volcando en un bucle los códigos de carácter
' leidos desde el archivo a un array Byte
Valor = srLector.Read()
While (Valor <> -1) ' cuando lleguemos al final, obtendremos -1
If Codigos Is Nothing Then
ReDim Codigos(0)
Else
ReDim Preserve Codigos(Codigos.GetUpperBound(0) + 1)
End If
Codigos(Codigos.GetUpperBound(0)) = Valor
Valor = srLector.Read()
End While
Dim Codificador As New ASCIIEncoding()
Dim Parte2 As String
' con el objeto ASCIIEncoding, método GetString(),
' obtenemos una cadena, pasando como parámetro un array
' de tipos Byte
Parte2 = Codificador.GetString(Codigos)
Console.WriteLine("Resultado de la lectura con ReadBlock()")
Console.WriteLine(Parte2)
Console.ReadLine()
End Sub
End Module
Código fuente 372

Finalmente, el método Peek( ), al igual que Read( ), devuelve el siguiente valor disponible del Stream, pero sin extraerlo del búfer, con lo que deberemos utilizar alguno de los métodos anteriormente descritos para realizar una lectura real.

La clase StreamReader - I

Un objeto StreamReader realiza operaciones de lectura de texto sobre un archivo. El proceso que debemos llevar a cabo para leer el contenido de un Stream de lectura es muy similar al de escritura: instanciar el objeto con uno de sus constructores, abriendo un archivo para leer; ejecutar alguno de los métodos de lectura del StreamReader, y cerrar el objeto con Close( ). 
Entre los métodos de lectura de este objeto, tenemos ReadLine( ), que devuelve una línea del archivo; y ReadToEnd( ), que devuelve el resto del contenido del archivo, desde el punto en el que se encontrara el Stream al realizar la última lectura. Veamos unos ejemplos en el Código fuente 371

Dim srLector As StreamReader = New StreamReader("\pruebas\NOTAS.txt")
Console.WriteLine("**Leer una primera línea**")
Dim Linea As String
Linea = srLector.ReadLine()
Console.WriteLine("La línea contiene --> {0}", Linea)
Console.WriteLine()
Console.WriteLine("**Ahora leemos el resto del archivo**")
Dim Texto As String
Texto = srLector.ReadToEnd()
Console.WriteLine("El texto restante contiene --> {0}", Texto)
srLector.Close()
' ***********************************************
' leer línea a línea mediante un bucle
Dim srLector As StreamReader = New StreamReader("\pruebas\Datos.txt")
Dim Linea As String
Dim ContadorLin As Integer = 1
Linea = srLector.ReadLine()
Do While Not (Linea Is Nothing)
Console.WriteLine("Línea: {0} - Contenido: {1}", ContadorLin, Linea)
ContadorLin += 1
Linea = srLector.ReadLine()
Loop
Código fuente 371

La clase StreamWriter - II

En el caso de que el archivo sobre el que vamos a escribir ya exista, podemos utilizar, de los métodos constructores de StreamWriter, aquel que nos permite especificar en el segundo parámetro si vamos a añadir texto al archivo, o vamos a sobrescribir el texto previamente existente. Veamos un ejemplo en el Código fuente 369.

' abre el archivo y se sitúa al final del texto para añadir
swEscritor = New StreamWriter("\pruebas\NOTAS.txt", True)
' se elimina el contenido previo del archivo
swEscritor = New StreamWriter("\pruebas\NOTAS.txt", False)
Código fuente 369

Después de crear un objeto de este tipo, y escribir algunas líneas de texto sin cerrar el Stream, si abrimos su archivo de texto correspondiente (con el Bloc de Notas por ejemplo), nos encontraremos con que no hay texto dentro del archivo. Ello es debido a que todavía no se ha volcado el contenido del búfer del objeto sobre el archivo. Para forzar dicho volcado, deberemos llamar al método Flush( ), que se encarga de traspasar el búfer al archivo asociado al Stream. Veamos el Código fuente 370.

Dim swEscritor As StreamWriter
' creamos un stream de escritura
swEscritor = New StreamWriter("\pruebas\NOTAS.txt", False)
' escribir líneas
swEscritor.WriteLine("la primera línea")
swEscritor.WriteLine("un poco más de texto")
' si abrimos el archivo antes de la siguiente, estará vacío
swEscritor.Flush()
' ahora el archivo ya contendrá texto
' cerrar el stream y el archivo asociado
swEscritor.Close()
Código fuente 370

La clase StreamWriter - I

Un objeto StreamWriter realiza operaciones de escritura de texto sobre un archivo. El proceso típico de escritura de datos mediante un StreamWriter, comprende los siguientes pasos: 
  • Instanciar un objeto de esta clase mediante alguno de los constructores disponibles. Aquí creamos un nuevo archivo para escribir datos sobre él, o abrimos uno existente. 
  • Escritura de texto mediante los métodos WriteLine( ) y Write( ). El primero escribe el texto pasado como parámetro, y añade los caracteres especiales de retorno de carro y nueva línea. El segundo escribe el texto pasado y deja el puntero de escritura a partir del último carácter escrito, con lo que no produce un cambio automático de línea. Deberemos utilizar la propiedad NewLine para introducir manualmente un salto de línea. 
  • Cierre del Stream con el método Close( ). Esta acción vuelca el contenido del búfer del objeto en el archivo.

 El Código fuente 368 muestra el proceso básico que acabamos de describir.

Imports System.IO
Module Module1
Sub Main()
Dim swEscritor As StreamWriter
' creamos un stream de escritura, y al mismo tiempo un
' nuevo archivo para escribir texto sobre él
swEscritor = New StreamWriter("\pruebas\NOTAS.txt")
' escribir líneas
swEscritor.WriteLine("esta es la primera línea")
swEscritor.WriteLine("segunda línea de texto")
' ahora escribimos texto pero sin provocar un salto de línea
swEscritor.Write("Juan y Luna ")
swEscritor.Write("van de paseo")
swEscritor.Write(swEscritor.NewLine) ' esto introduce el salto de línea
swEscritor.WriteLine("con esta línea cerramos")
' cerrar el stream y el archivo asociado
swEscritor.Close()
End Sub
End Module
Código fuente 368

Objetos Stream

Un objeto Stream representa un flujo o corriente de datos, es decir, un conjunto de información guardada en formato de texto o binario, que podremos leer y escribir sobre un soporte físico, también denominado almacén de respaldo (backing store), en la plataforma .NET. 

Algunos tipos de Stream, para optimizar el flujo de transferencia de datos entre el objeto y su medio físico de almacenamiento, disponen de una característica denominada almacenamiento intermedio (buffering), que consiste en mantener un búfer intermedio con los datos. En el caso, por ejemplo, de tareas de escritura, todas las operaciones se realizarían en el búfer, mientras este dispusiera de capacidad. Una vez terminado el proceso de escritura, o cuando el búfer estuviera lleno, su contenido pasaría al archivo físico. Podemos también, alterar el comportamiento por defecto del búfer a través de diversas propiedades y métodos del objeto Stream correspondiente.

System.IO, el punto de partida para el manejo de archivos

Mediante VB.NET podemos hacer uso de las clases de la plataforma .NET situadas en el espacio de nombres System.IO, para todos los aspectos relacionados con el manejo de archivos.
Operaciones tales como lectura y escritura de información en archivos, y la gestión de los mismos en operaciones de borrado, copia, etc., entre directorios y unidades pueden ser llevadas a cabo a través de las clases contenidas en IO. 
Los propios directorios y unidades pueden ser igualmente tratados como objetos, lo que facilita enormemente el trabajo del programador. A lo largo de este tema realizaremos una descripción, con ejemplos de uso, de algunas de las clases contenidas en IO. Por lo que en todos los ejemplos utilizados aquí, tendremos que importar este espacio de nombres.

Manipulación de archivos

¿Qué es un archivo? 

En los ejemplos realizados hasta el momento, cuando necesitamos guardar un conjunto de valores en algún lugar empleamos un array. Sin embargo, existen situaciones en las que debemos almacenar cantidades muy elevadas de datos, no constituyendo los arrays el elemento adecuado para este fin. Adicionalmente, el array es manipulado en memoria durante la ejecución del programa, por lo que su contenido se pierde al finalizar el mismo. Ante este tipo de casos debemos recurrir a los archivos.

Un archivo es un medio de almacenamiento separado del programa, que nos permite guardar en el mismo una gran cantidad de información. 

Al tratarse de un soporte de almacenamiento permanente, podemos utilizarlo para grabar datos que puedan ser consultados en posteriores ejecuciones del programa que lo creó, o de otros programas que accedan a ese archivo. 

El uso de un archivo para guardar datos de forma externa es recomendable en procesos que no necesiten una organización muy compleja de los datos a manipular, ya que en tal caso, lo mejor sería utilizar un programa gestor de base de datos, que ya incorpora, de modo mucho más optimizado, los algoritmos y procesos específicos para el tratamiento de la información.

On Error Goto 0

Este uso de la instrucción On Error, desactiva el manejador de errores que hubiera activado hasta el momento; de modo, que a no ser que activemos otro manejador, los errores que se produzcan a partir de esa línea, provocarán un error fatal, cancelando la ejecución del programa. Ver el Código fuente 367.

Public Sub Main()
On Error Goto ControlErrores
Dim iValor As Integer
Console.WriteLine("Introducir un número")
iValor = Console.ReadLine()
On Error Goto 0
Console.WriteLine("Introducir otro número")
iValor = Console.ReadLine()
Console.ReadLine()
Exit Sub
' ------------
' etiqueta de control de errores
ControlErrores:
Console.WriteLine("Error: {0} - {1}", Err.Number, Err.Description)
Console.ReadLine()
Resume Next
End Sub
Código fuente 367

Creación de errores con el objeto Err

El método Raise( ), del objeto Err, nos permite generar nuestros propios errores, que se comportarán igual que los errores del sistema. Veamos un ejemplo en el Código fuente 366.

Public Sub Main()
On Error Goto ControlErrores
Dim iValor As Integer
Console.WriteLine("Introducir un número")
iValor = Console.ReadLine()
If iValor > 500 Then
Err.Raise(5100, , "El número debe ser menor de 500")
End If
Console.WriteLine("Esta línea se ejecuta después de un posible error")
Console.ReadLine()
Exit Sub
' ------------
' etiqueta de control de errores
ControlErrores:
Console.WriteLine("Error: {0} - {1}", Err.Number, Err.Description)
Console.ReadLine()
Resume Next
End Sub
Código fuente 366

On Error Resume Next

Esta variante de la instrucción On Error, hace que al producirse un error, la ejecución continúe con la siguiente línea de código, por lo que no utiliza etiquetas de control para desviar la ejecución. Debido a sus características, en este tipo de captura de errores, tras cada línea susceptible de provocar un error, debemos consultar los valores del objeto Err, para comprobar si existe un error, y actuar en consecuencia. En este tipo de situaciones, después de comprobar un error, debemos inicializar el objeto Err, llamando a su método Clear( ). 
Veamos pues, un ejemplo de este tipo de gestión de errores en el Código fuente 365.

Public Sub Main()
On Error Resume Next
Dim dtFecha As Date
Console.WriteLine("Introducir una fecha")
dtFecha = Console.ReadLine()
If Err.Number > 0 Then
Console.WriteLine("Error: {0} - {1}", Err.Number, Err.Description)
Console.ReadLine()
Err.Clear()
End If
Console.WriteLine("Esta línea se ejecuta después de un posible error")
Console.ReadLine()
End Sub
Código fuente 365

On Error Goto Etiqueta

Activa una etiqueta de control de errores, que consiste en un bloque de código, al que se desviará el flujo del programa cuando se produzca un error. El Código fuente 363 muestra un ejemplo.

Public Sub Main()
On Error Goto ControlErrores
Dim dtFecha As Date
dtFecha = "valor incorrecto"
Exit Sub
' ------------
' etiqueta de control de errores
ControlErrores:
Console.WriteLine("Error: {0} - {1}", Err.Number, Err.Description)
Console.ReadLine()
End Sub
Código fuente 363

Si queremos reintentar la ejecución de la línea que produjo el error, debemos utilizar en la etiqueta de control de errores la instrucción Resume, como muestra el Código fuente 364.

Public Sub Main()
On Error Goto ControlErrores
Dim dtFecha As Date
Console.WriteLine("Introducir una fecha")
dtFecha = Console.ReadLine()
Console.WriteLine("Esta línea se ejecuta después del error")
Console.ReadLine()
Exit Sub
' ------------
' etiqueta de control de errores
ControlErrores:
Console.WriteLine("Error: {0} - {1}", Err.Number, Err.Description)
Console.ReadLine()
Resume
End Sub
Código fuente 364

De esta forma, en el ejemplo anterior, damos una nueva oportunidad al usuario, en el caso de que haya introducido una fecha incorrecta. Si no queremos volver a reintentar la línea del error, usaremos la instrucción Resume Next, que después de ejecutar la etiqueta de control de errores, seguirá la ejecución en la siguiente línea a la que provocó el error. También podemos utilizar el formato Resume Etiqueta, en donde Etiqueta representa a otra etiqueta de control, a la que saltará el código después de finalizar la ejecución de la actual.

Manipulación no estructurada de errores

En este tipo de gestión de errores, cada vez que se produce un error, consultaremos el objeto del sistema Err. Este objeto contiene, a través de sus propiedades, la información sobre el error producido. Para poder capturar los errores mediante esta técnica, utilizaremos la instrucción On Error, que nos permitirá seleccionar el controlador de errores a ejecutar. 

El objeto Err 

Este objeto se crea automáticamente al iniciarse la aplicación, y proporciona al usuario información sobre los errores producidos en el transcurso de la aplicación. Tiene ámbito público, por lo que podremos usarlo desde cualquier punto del programa. Cuando se produzca un error, la propiedad Number de este objeto tendrá un valor mayor de cero, mientras que la propiedad Description, nos dará una información textual del error producido.

On Error 

Esta instrucción activa o desactiva una rutina de manejo de errores. Tiene diversos modos de empleo, que describimos en los siguientes apartados.

Generación manual de excepciones

Además de las excepciones provocadas por errores del programa, VB.NET nos permite la notificación de problemas a través de la creación y envío manual de excepciones. Para ello, en aquel punto de código en el que determinemos que será necesario generar una excepción, instanciaremos un objeto de la clase Exception o alguna de sus clases heredadas, y lo notificaremos mediante la instrucción Throw, como muestra el ejemplo del Código fuente 362.

Public Sub Main()
Dim Valor As Integer
Dim Resultado As Integer
Try
Console.WriteLine("Introducir un número")
Valor = Console.ReadLine()
Resultado = Calcular(Valor)
Console.WriteLine("El resultado es: {0}", Resultado)
Catch oExcep As Exception
' si se produce un error y se genera una
' excepción en la función Calcular(),
' el flujo de la ejecución saltará hasta
' este manipulador de excepciones
Console.WriteLine("Error")
Console.WriteLine(oExcep.Message)
End Try
Console.ReadLine()
End Sub
Public Function Calcular(ByVal Dato As Integer) As Integer
' en esta rutina generamos la excepción si es necesario
Dim Importe As Integer
Dim oExcepcion As Exception
Importe = Dato * 2
If Importe > 100 Then
' si debido a los valores introducidos por el
' usuario, la variable tiene un valor no permitido
' crear una excepción
oExcepcion = New Exception("El importe es incorrecto")
' lanzar la excepción
Throw oExcepcion
End If
' si no se producen errores, devolver el valor calculado
Return Importe
End Function
Código fuente 362

Como vemos en el anterior ejemplo, el esquema de captura y tratamiento de excepciones no es obligatorio utilizarlo exclusivamente cuando se produzcan errores por un funcionamiento incorrecto del programa, sino que podemos emplearlo también para tratar otras situaciones, por ejemplo, cuando el usuario de la aplicación no proporciona los valores adecuados a la misma.

Forzar la salida de un controlador de errores mediante Exit Try

A través de esta sentencia de la estructura Try...End Try, obligamos al flujo del programa a salir de la estructura de control de errores, desde cualquier punto de la misma en que nos encontremos. En el Código fuente 361, y retomando parte del código del anterior ejemplo, vemos como en el bloque de código del controlador de errores, forzamos la salida de la estructura sin haber finalizado de ejecutar todo el código propenso a errores.

' ....
Try
' comienza el control de errores
Console.WriteLine("Introducir un número")
' si no es un número Byte se produce error
byMiNum = Console.ReadLine()
' salimos de controlador de errores
' sin finalizarlo
Exit Try
' esta línea produce error siempre, ya
' que no existe el índice 5 en el array
aValores(5) = "d"
Catch oExcep As OverflowException
' ....
Catch oExcep As Exception
' ....
' ....
End Try
' ....
Código fuente 361

La influencia del orden de los manipuladores de excepciones

El orden en el que situemos las sentencias Catch dentro de una estructura Try...End Try, es determinante, a la hora de que ciertas excepciones puedan ser capturadas. Por este motivo, al escribir un controlador de errores, se recomienda situar en primer lugar, los manipuladores más específicos, y dejar para el final, los más genéricos. 
En el ejemplo que muestra el Código fuente 360 se pueden producir dos tipos de excepción: por desbordamiento, y por acceso a índice no existente en un array. El problema que tenemos en dicha construcción de código, reside en que el manipulador de excepciones de desbordamiento nunca se ejecutará, ya que en primer lugar hemos situado uno más genérico que captura todo tipo de excepciones, incluidas las que se produzcan por desbordamiento.

Public Sub Main()
Dim byMiNum As Byte
Dim aValores() As String = {"a", "b", "c"}
Try
' comienza el control de errores
Console.WriteLine("Introducir un número")
' si no es un número Byte se produce error
byMiNum = Console.ReadLine()
' esta línea produce error siempre, ya
' que no existe el índice 5 en el array
aValores(5) = "d"
Catch oExcep As Exception
' manipulador genérico de excepciones,
' captura todo tipo de excepciones, por lo que si
' también se produce una excepción OverflowException,
' se ejecutará este manipulador, por estar situado primero
Console.WriteLine("Se ha producido un error")
Catch oExcep As OverflowException
' captura de errores de desbordamiento,
' este manipulador nunca se ejecutará, por estar
' uno más genérico antes
Console.WriteLine("El número introducido " & _
"no se encuentra en el rango adecuado")
Finally
Console.WriteLine("El controlador de errores ha finalizado")
End Try
Console.ReadLine()
End Sub
Código fuente 360

En este caso que acabamos de ver, situaremos en primer lugar el manejador de excepciones de desbordamiento, y por último, el genérico.

Establecer una condición para un manipulador de excepciones - IV

El manipulador genérico de excepciones de este último ejemplo tiene un problema, ya que aunque las captura correctamente, no proporciona suficiente información, por lo que no podremos saber si el error se produjo por asignar un valor incorrecto a la variable Byte o a la fecha. Este problema tiene una fácil solución: al ser una excepción un objeto, y por lo tanto, un tipo del sistema, mediante su método GetType( ) obtendremos el tipo de excepción producida, mostrándola en el mensaje del manipulador de excepciones. Ver el Código fuente 359.

' ....
' ....
Catch oExcep As Exception
' manipulador genérico de excepciones
Dim oTipo As Type
oTipo = oExcep.GetType()
Console.WriteLine("Se ha producido un error de tipo {0}", oTipo.Name)
' ....
' ....
Código fuente 359

Establecer una condición para un manipulador de excepciones - III

• Añadir un manipulador de excepciones genérico. Con esto evitaremos el mensaje de error no controlado, generado por el IDE. Si por ejemplo, además de las operaciones con el tipo Byte, nos encontramos manipulando fechas, podremos capturar todas las excepciones producidas. Veamos este caso en el Código fuente 358.

Public Sub Main()
Dim byMiNum As Byte
Dim dtFecha As Date
Dim dtFHActual As Date
' obtener la fecha actual
dtFHActual = System.DateTime.Today()
Try
' comienza el control de errores
Console.WriteLine("Introducir un número")
' si introducimos un número no incluido
' en el rango de Byte, según el mes actual iremos
' a uno de los manipuladores de excepción existentes
byMiNum = Console.ReadLine()
' si introducimos un valor incorrecto para la fecha,
' iremos al controlador de errores genérico
Console.WriteLine("Introducir una fecha")
dtFecha = Console.ReadLine()
Catch oExcep As OverflowException When (dtFHActual.Month = 3)
' manipulador de excepciones sólo
' cuando las excepciones de desbordamiento
' se produzcan en el mes de Marzo
Console.WriteLine("El número introducido " & _
"no se encuentra en el rango adecuado")
Catch oExcep As Exception
' manipulador genérico de excepciones
Console.WriteLine("Se ha producido un error")
Finally
Console.WriteLine("El controlador de errores ha finalizado")
End Try
Console.ReadLine()
End Sub
Código fuente 358

Establecer una condición para un manipulador de excepciones - II

Si queremos capturar también el resto de excepciones de desbordamiento, u otro tipo de excepciones, tenemos varias alternativas que describimos seguidamente. 
• Quitar la condición de filtro al manipulador de excepciones actual. De este modo, capturaremos todas las excepciones de desbordamiento, pero no podremos filtrar por un mes determinado. 
• Añadir un nuevo manipulador a la estructura de control, para todas las excepciones de desbordamiento. En esta situación, si se produce un error de desbordamiento, y no estamos en el mes definido por el anterior manipulador, se ejecutará este nuevo manipulador. Ver el Código fuente 357.

' ....
Catch oExcep As OverflowException When (dtFHActual.Month = 3)
' ...saltará este manipulador de excepciones, pero sólo
' cuando las excepciones de desbordamiento
' se produzcan en el mes de Marzo
Console.WriteLine("El número introducido " & _
"no se encuentra en el rango adecuado")
Catch oExcep As OverflowException
Console.WriteLine("Error de desbordamiento")
' ....
Código fuente 357

Establecer una condición para un manipulador de excepciones - I

Mediante la cláusula When, de la sentencia Catch, podemos situar una expresión que sirva como filtro o condición, para que dicho manipulador de excepciones se ejecute, en función de que el resultado de la expresión devuelva Verdadero o Falso. 
En el siguiente ejemplo, definimos un manipulador de excepciones, para cuando se produzcan errores de desbordamiento al asignar un valor a una variable de tipo Byte. Sin embargo, mediante When, establecemos que dicho manipulador sólo se ejecute cuando sea un determinado mes; lo que provoca, que en el caso de que no se cumpla tal condición, saltará el mensaje de excepción predeterminado del IDE. Veamos el Código fuente 356.

Public Sub Main()
Dim byMiNum As Byte
Dim dtFHActual As Date
' obtener la fecha actual
dtFHActual = System.DateTime.Today()
Try
' comienza el control de errores
Console.WriteLine("Introducir un número")
' si introducimos un número no incluido
' en el rango de Byte...
byMiNum = Console.ReadLine()
Catch oExcep As OverflowException When (dtFHActual.Month = 3)
' ...saltará este manipulador de excepciones, pero sólo
' cuando las excepciones de desbordamiento
' se produzcan en el mes de Marzo
Console.WriteLine("El número introducido " & _
"no se encuentra en el rango adecuado")
Finally
Console.WriteLine("El controlador de errores ha finalizado")
End Try
Console.ReadLine()
End Sub
Código fuente 356

Captura de excepciones de diferente tipo en el mismo controlador de errores

Cuando en el código de un controlador de errores puedan producirse errores de distintos tipos de excepción, debemos situar tantas sentencias Catch como excepciones queramos controlar. En el Código fuente 355, hasta el momento, hemos controlado los errores por conversión de tipos. Ahora vamos a añadir varias líneas más, que obtienen un valor, y lo asignan a un índice de un array. 
Dado que el índice a manipular lo pedimos al usuario, y es posible que dicho elemento no exista en el array, añadiremos un nuevo manipulador para este tipo de excepción, mediante la sentencia Catch correspondiente.

Public Sub Main()
Dim sValor As String
Dim iNumero As Integer
Dim sLetras() As String = {"a", "b", "c", "d"}
Try
' comienza el control de errores
Console.WriteLine("Introducir un número")
sValor = Console.ReadLine()
' si no hemos introducido un número...
iNumero = sValor ' ...aquí se producirá un error...
' ...y no llegaremos a esta parte del código
iNumero = iNumero + 1000
' introducir una letra y asignarla a una
' posición del array
Dim sNuevaLetra As String
Dim iPosicion As Integer
Console.WriteLine("Introducir una letra")
sNuevaLetra = Console.ReadLine()
Console.WriteLine("Introducir posición del array para la letra")
iPosicion = Console.ReadLine()
' si al asignar la letra al array no existe
' el índice, se producirá un error
sLetras(iPosicion) = sNuevaLetra
Catch oExcep As System.InvalidCastException
' excepción producida por un error al intentar
' realizar una conversión de tipos
Console.WriteLine(oExcep.ToString())
Catch oExcep As System.IndexOutOfRangeException
' excepción producida por un error
' al intentar usar un índice inexistente
' de array, o índice fuera de rango
Console.WriteLine(oExcep.ToString())
Finally
' si se produce un error, después de Catch se ejecuta este bloque;
' si no se produce error, después de Try también se ejecuta
Console.WriteLine("El controlador de errores ha finalizado")
End Try
Console.ReadLine()
End Sub
Código fuente 355

La clase Exception - III

El Código fuente 354 contiene una pequeña muestra de los valores obtenidos a partir de las propiedades Message, Source y StackTrace, tras la ejecución del fuente anterior.

Message: Cast from String ('hola') to Integer is not valid.
Source: Microsoft.VisualBasic
StackTrace: at Microsoft.VisualBasic.Helpers.IntegerType.FromString(String Value)
at ErroresPru.Module1.Main() in
K:\CursoVBNET\Texto\t16Errores\ErroresPru\Module1.vb:line 12
Código fuente 354

Exception representa la clase base en la jerarquía de tipos de excepción que se pueden producir dentro del entorno de ejecución. Partiendo de Exception, disponemos de un conjunto de clases derivadas, que nos permiten un tratamiento más particular del error producido, como ApplicationException, IOException, SystemException, etc. Cada una de ellas puede tener, además de los miembros generales de Exception, una serie de métodos y propiedades particulares de su tipo de excepción. Por ello, lo más conveniente será utilizar estas clases, a través de las que podremos averiguar más detalles sobre el problema producido.

La clase Exception - II

El Código fuente 353 muestra la captura de la excepción en el ejemplo anterior, dentro de la sentencia Catch, pero en este caso utilizando un objeto Exception. El resto del código es igual que el anterior ejemplo.

' ....
Try
' ....
' ....
Catch oExcep As Exception
' si se produce un error, se crea un objeto excepción
' que capturamos volcándolo a un identificador
' de tipo Exception
Console.WriteLine("Se produjo un error. Información de la excepción")
Console.WriteLine("================================================")
Console.WriteLine("Message: {0}", oExcep.Message)
Console.WriteLine()
Console.WriteLine("Source: {0}", oExcep.Source)
Console.WriteLine()
Console.WriteLine("StackTrace: {0}", oExcep.StackTrace)
Console.WriteLine()
Console.WriteLine(oExcep.ToString())
Finally
' ....
' ....
End Try
' ....
Código fuente 353

La clase Exception - I

Como hemos explicado en anteriores apartados, cada vez que se produce un error, el entorno de ejecución genera una excepción con la información del error acaecido. Para facilitarnos la tarea de manipulación de la excepción producida, en un controlador de excepciones obtenemos un objeto de la clase Exception, o de alguna de sus derivadas, de forma que, a través de sus miembros, podemos saber qué ha pasado. Entre las propiedades y métodos que podemos utilizar, se encuentran las siguientes. 
  • Message. Descripción del error. 
  • Source. Nombre del objeto o aplicación que provocó el error. 
  • StackTrace. Ruta o traza del código en la que se produjo el error. 
  • ToString( ). Devuelve una cadena con información detallada del error. En esta cadena podemos encontrar también, los valores obtenidos de las propiedades anteriores; por lo que el uso de este método, en muchas ocasiones será el modo más recomendable para obtener los datos de la excepción.
 Podemos obtener el objeto de excepción creado a partir de un error, utilizando la sentencia Catch de la estructura Try. Para ello, a continuación de Catch, escribimos el nombre de un identificador, tipificándolo como Exception o alguno de los tipos de su jerarquía.

Manipulación estructurada de errores - III

Tanto si se produce un error como si no, la sentencia Finally de la estructura Try...End Try, nos permite escribir un bloque de código que será ejecutado al darse una condición de error, o bajo ejecución normal del procedimiento. 
El Código fuente 352 muestra el mismo ejemplo anterior, pero introduciendo un bloque Finally. Pruebe el lector alternativamente, a forzar un error, y a ejecutar sin errores este fuente; en ambos casos verá que el bloque Finally es ejecutado. Para completar el ejemplo, tras la estructura Try...End Try se han escrito varias líneas de código potencialmente problemáticas; en el caso de que se produzca un error, la ejecución será cancelada, al no estar dichas líneas situadas en un controlador de errores.

Public Sub Main()
Dim sValor As String
Dim iNumero As Integer
Try
' comienza el control de errores
Console.WriteLine("Introducir un número")
sValor = Console.ReadLine()
' si no hemos introducido un número...
iNumero = sValor ' ...aquí se producirá un error...
' ...y no llegaremos a esta parte del código
iNumero = iNumero + 1000
Catch
' si se produce un error, se genera una excepción
' que capturamos en este bloque de código
' manipulador de excepción, definido por Catch
Console.WriteLine("Error al introducir el número" & _
ControlChars.CrLf & _
"El valor {0} es incorrecto", _
sValor)
Finally
' si se produce un error, después de Catch se ejecuta este bloque;
' si no se produce error, después de Try también se ejecuta
Console.WriteLine("El controlador de errores ha finalizado")
End Try
' resto del código del procedimiento
Dim dtFecha As Date
Console.WriteLine("Introducir una fecha")
' si ahora se produce un error,
' al no disponer de una estructura para controlarlo
' se cancelará la ejecución
dtFecha = Console.ReadLine()
Console.WriteLine("La fecha es {0}", dtFecha)
Console.ReadLine()
End Sub
Código fuente 352

Manipulación estructurada de errores - II

Analicemos con detalle los principales elementos de esta estructura. 
En primer lugar nos encontramos con su declaración mediante la palabra clave Try. Todo el código que escribimos a partir de dicha palabra clave, y hasta la primera sentencia Catch, es el código que definimos como sensible a errores, o dicho de otro modo, el bloque de instrucciones sobre las que deseamos que se active el control de errores cuando se produzca algún fallo en su ejecución. 
A continuación, establecemos cada uno de los manipuladores de excepción mediante la palabra clave Catch. Junto a esta palabra clave, situaremos de forma opcional, un identificador que contendrá el objeto con la excepción generada. Finalmente, y también de modo opcional, con la palabra clave When, especificaremos una condición para la captura del objeto de excepción. Podemos escribir uno o varios manipuladores Catch dentro de una estructura de control Try...End Try. 
Cada vez que se produzca un error, el flujo de la ejecución saltará a la sentencia Catch más acorde con el tipo de excepción generada por el error, siempre y cuando hayamos situado varios manipuladores de excepciones en el controlador de errores. 
Tal y como acaba de ver el lector en la sintaxis de la estructura Try...End Try, es posible utilizar Catch de un modo genérico, es decir, sin establecer qué tipo de excepción se ha producido. Este es el tipo de control de errores más sencillo que podemos implementar, aunque también el más limitado, ya que sólo podemos tener un manipulador de excepciones. Veamos un ejemplo en el Código fuente 351.

Public Sub Main()
Dim sValor As String
Dim iNumero As Integer
Try
' comienza el control de errores
Console.WriteLine("Introducir un número")
sValor = Console.ReadLine()
' si no hemos introducido un número...
iNumero = sValor ' ...aquí se producirá un error...
' ...y no llegaremos a esta parte del código
iNumero = iNumero + 1000
Catch
' si se produce un error, se genera una excepción
' que capturamos en este bloque de código
' manipulador de excepción, definido por Catch
Console.WriteLine("Error al introducir el número" & _
ControlChars.CrLf & _
"El valor {0} es incorrecto", _
sValor)
End Try
' resto del código del procedimiento
' ....
Console.ReadLine()
End Sub
Código fuente 351

Manipulación estructurada de errores - I

En este tipo de tratamiento, cada vez que se produce un error, se genera un objeto de la clase Exception o alguna de sus derivadas, conteniendo la información del error ocurrido. La manera de capturar este tipo de objetos pasa por utilizar una estructura de control del lenguaje, proporcionada para esta finalidad. 

La estructura Try...End Try

Esta estructura de control del lenguaje, proporciona el medio para definir un bloque de código sensible a errores, y los correspondientes manipuladores de excepciones, en función del tipo de error producido. El Código fuente 350 muestra su sintaxis.

Try
' código que puede provocar errores
' ....
' ....
[Catch [Excepcion [As TipoExcepcionA]] [When Expresión]
' respuesta a error de tipo A
' ....
' ....
[Exit Try]
]
[Catch [Excepcion [As TipoExcepcionN]] [When Expresión]
' respuesta a error de tipo N
' ....
' ....
[Exit Try]
]
[Finally
' código posterior al control de errores
' ....
' ....
]
End Try
Código fuente 350