Buscador

El tipo Date (fecha)

Este tipo de dato, que utilizamos para trabajar con fechas, hace uso de la estructura DateTime, por lo que cuando tipificamos una variable como Date, los miembros que realmente manipulamos son los de un tipo DateTime. Consulte el lector, el apartado dedicado a la estructura DateTime para una mayor información.

La estructura Char - II

Para asignar un valor de manera explícita a una variable, parámetro, etc., de tipo Char, es recomendable situar el carácter c junto a dicho valor. Veamos el Código fuente 323.

Dim lcCaracter As Char
' ambas asignaciones son equivalentes, pero se recomienda la primera
lcCaracter = "H"c
lcCaracter = "H"
Código fuente 323

En el anterior ejemplo este aspecto es opcional, sin embargo, si queremos asignar un valor Char a una variable tipificada como Object, debemos utilizar irremisiblemente el indicador c junto al valor, o de otro modo, el subtipo almacenado en la variable Object lo tomará como String en lugar de Char. El mejor modo de comprobarlo, es abriendo la ventana Locales en modo de depuración. Veamos un ejemplo en el Código fuente 324.

Dim loValor As Object
loValor = "H" ' objeto de subtipo String
loValor = "H"c ' objeto de subtipo Char
Código fuente 324

La estructura Char - I

Mediante el uso de esta estructura podemos manejar tipos de datos simples de carácter. Los métodos compartidos de Char nos informarán del tipo de carácter que estamos manejando, además de poder realizar determinadas operaciones sobre dicho carácter. 
El Código fuente 322 muestra un ejemplo de uso de la estructura Char. Cada uno de los miembros de Char empleados se encuentra con un pequeño comentario aclaratorio de su funcionalidad.

Public Sub Main()
Dim lcCaracter As Char
Dim lsResultado As String
Dim lcConvertido As Char
Do
Console.WriteLine("Introducir un carácter o cero para salir")
lcCaracter = Convert.ToChar(Console.ReadLine())
lsResultado = ""
lcConvertido = Nothing
' IsDigit() indica si el carácter es un dígito decimal
If Char.IsDigit(lcCaracter) Then
lsResultado = "dígito"
End If
' IsLetter() indica si el carácter es una letra
If Char.IsLetter(lcCaracter) Then
lsResultado = "letra"
End If
' IsWhiteSpace() indica si el carácter es un espacio en blanco
If Char.IsWhiteSpace(lcCaracter) Then
lsResultado = "espacio"
End If
' IsPunctuation() indica si el carácter es un signo de puntuación
If Char.IsPunctuation(lcCaracter) Then
lsResultado &= "puntuación"
End If
' IsUpper() comprueba si el carácter está en mayúscula
If Char.IsUpper(lcCaracter) Then
lsResultado &= " mayúscula"
' ToLower() convierte el carácter a minúscula
lcConvertido = Char.ToLower(lcCaracter)
End If
' IsLower() comprueba si el carácter está en minúscula
If Char.IsLower(lcCaracter) Then
lsResultado &= " minúscula"
' ToUpper() convierte el carácter a mayúscula
lcConvertido = Char.ToUpper(lcCaracter)
End If
' mostramos una cadena con el tipo de carácter
Console.WriteLine("El carácter es: {0}", lsResultado)
' si hemos convertido el caracter a mayúscula/minúscula,
' lo mostramos
If Char.IsLetter(lcConvertido) Then
Console.WriteLine("El carácter se ha convertido: {0}", lcConvertido)
End If
Console.WriteLine()
' no salimos hasta que no se introduzca un 0
Loop Until lcCaracter = "0"c
End Sub
Código fuente 322

Conversión de tipos con la clase Convert

Esta clase nos permite convertir el contenido de una variable perteneciente a un tipo base del sistema a otro tipo base. Su uso es muy sencillo a través de los métodos compartidos que proporciona. 
El Código fuente 321 convierte un número a cadena, y después esa cadena a un número utilizando los métodos de esta clase.

Dim lsCadena As String
lsCadena = Convert.ToString(150) ' "150"
Dim liNum As Integer
liNum = Convert.ToInt32(lsCadena) ' 150
Código fuente 321

Manipulación de cadenas con la clase String - V

• Copy( ). Crea un nuevo objeto String, aunque el medio más sencillo consiste en asignar una cadena a la variable. Ver el Código fuente 318.

Dim lsCadA As String
Dim lsCadB As String
lsCadA = "uno"
lsCadB = String.Copy("OTRO")
Console.WriteLine("CadenaA --> {0}", lsCadA)
Console.WriteLine("CadenaB --> {0}", lsCadB)
Código fuente 318

• Compare( ). Este método compartido compara dos cadenas, y devuelve un valor menor de cero, si la primera cadena es menor que la segunda; cero si ambas cadenas son iguales; y mayor de cero, si la primera cadena es mayor. Ver el Código fuente 319.

Dim lsCompara1 As String
Dim lsCompara2 As String
Dim liResultaComp As Integer
Console.WriteLine("Introducir primera cadena a comparar")
lsCompara1 = Console.ReadLine()
Console.WriteLine("Introducir segunda cadena a comparar")
lsCompara2 = Console.ReadLine()
liResultaComp = String.Compare(lsCompara1, lsCompara2)
Select Case liResultaComp
Case Is < 0
Console.WriteLine("Primera cadena es menor")
Case 0
Console.WriteLine("Las cadenas son iguales")
Case Is > 0
Console.WriteLine("Primera cadena es mayor")
End Select
Código fuente 319

• Equals( ). Compara el objeto con una cadena pasada como parámetro, y devuelve un valor lógico, que indica si las cadenas son o no iguales. Ver el Código fuente 320.

Dim lsCadInicial As String
Dim lsCadComparar As String
lsCadInicial = "Prueba"
Console.WriteLine("Introducir una cadena a comparar con la cadena inicial")
lsCadComparar = Console.ReadLine()
If lsCadInicial.Equals(lsCadComparar) Then
Console.WriteLine("Las cadenas son iguales")
Else
Console.WriteLine("Las cadenas son diferentes")
End If
Código fuente 320

Manipulación de cadenas con la clase String - IV

• SubString( ). Obtiene una subcadena comenzando por una posición de la cadena, y extrayendo un número de caracteres. 
• IndexOf( ), LastIndexOf( ). Buscan una subcadena pasada como parámetro, comenzando por el principio y el fin respectivamente; y devuelven la posición de comienzo de dicha subcadena. Ver el Código fuente 315.

Dim lsCadCompleta As String
lsCadCompleta = "En el bosque se alza el castillo negro"
Console.WriteLine("Substring --> {0}", lsCadCompleta.Substring(6, 5)) '
"bosqu"
Console.WriteLine("IndexOf --> {0}", lsCadCompleta.IndexOf("el")) ' 3
Console.WriteLine("LastIndexOf --> {0}", lsCadCompleta.LastIndexOf("el")) '
21
Código fuente 315

• ToUpper( ), ToLower( ). Cambian la cadena a mayúsculas y minúsculas respectivamente. Ver el Código fuente 316.

Dim lsCadMayMin As String
lsCadMayMin = "CambIaNDO A mayúsCUlAs Y MINúscULAS"
Console.WriteLine("Pasar a may. --> {0}", lsCadMayMin.ToUpper())
Console.WriteLine("Pasar a min. --> {0}", lsCadMayMin.ToLower())
Código fuente 316

• Concat( ). Concatena dos cadenas pasadas como parámetro. Este es un método compartido de la clase String, por lo que no se requiere una instancia previa de la clase. El modo, sin embargo más rápido y sencillo para concatenar, sigue siendo el operador específico de concatenación: &. Ver el Código fuente 317.

Dim lsConcatenar As String
lsConcatenar = String.Concat("Hola ", "a todos")
lsConcatenar = "ahora usamos" & " el operador para concatenar"
Código fuente 317

Manipulación de cadenas con la clase String - III

• Insert( ). Inserta en la cadena, una subcadena a partir de una posición determinada. Ver el Código fuente 311.

Dim lsCadena As String
Dim lsAgregar As String
lsCadena = "Estamos programando"
lsAgregar = lsCadena.Insert(2, "HOLA") ' "EsHOLAtamos programando"
Código fuente 311.

• Remove( ). Elimina de la cadena un número determinado de caracteres, comenzando por una posición específica. Ver el Código fuente 312.

Dim lsCadena As String
Dim lsQuitar As String
lsCadena = "Estamos programando"
lsQuitar = lsCadena.Remove(5, 3) ' "Estamprogramando"
Código fuente 312

• Replace( ). Cambia dentro de la cadena, todas las ocurrencias de una subcadena por otra. Ver el Código fuente 313.

Dim lsCadCompleta As String
lsCadCompleta = "En el bosque se alza el castillo negro"
Console.WriteLine("Replace --> {0}", lsCadCompleta.Replace("el", "la"))
Código fuente 313

• StartsWith( ), EndsWith( ). Comprueban que en la cadena exista una subcadena al principio o final respectivamente. Ver el Código fuente 314.

Dim lsCadena As String
lsCadena = "veinte"
Console.WriteLine(lsCadena.StartsWith("ve")) ' True
Console.WriteLine(lsCadena.EndsWith("TE")) ' False
Código fuente 314

Manipulación de cadenas con la clase String - II

Debido al elevado número de miembros que contienen la mayoría de los tipos de la plataforma .NET, tanto clases, como estructuras, tipos de datos, etc.; y a que muchos de ellos disponen de versiones sobrecargadas; en la descripción de cada tipo haremos un repaso de sus miembros principales, remitiendo al lector a la documentación de referencia que sobre los tipos existe en la ayuda de la plataforma .NET, en donde encontrará toda la información detallada. 
Antes de comenzar a describir los métodos de esta clase, y puesto que una cadena es un array de tipos Char, es importante tener en cuenta que la primera posición corresponde al cero. Esta aclaración la realizamos fundamentalmente, de cara a los métodos que requieran el manejo de posiciones concretas de la cadena. 
• Trim( ), TrimStart( ), TrimEnd( ). Eliminan los espacios a ambos lados de una cadena, al comienzo o al final. Ver el Código fuente 309.

Dim lsCadena As String
lsCadena = " Hola .NET "
Dim lsQuitar As String
lsQuitar = lsCadena.TrimEnd() ' " Hola .NET"
lsQuitar = lsCadena.TrimStart() ' "Hola .NET "
lsQuitar = lsCadena.Trim() ' "Hola .NET"
Código fuente 309

• PadLeft( ), PadRight( ). Rellenan una cadena por la izquierda o derecha utilizando un determinado carácter de relleno. Debemos especificar la longitud total de la cadena resultante. Como el carácter de relleno es un tipo Char, podemos especificar que se trata de este tipo, situando junto al carácter de relleno, la letra c. Ver el Código fuente 310.

Dim lsCadena As String
Dim lsRellena As String
lsCadena = "Hola"
lsRellena = lsCadena.PadLeft(10) ' " Hola"
lsRellena = lsCadena.PadRight(10, "W"c) ' "HolaWWWWWW"
Código fuente 310

Manipulación de cadenas con la clase String - I

La clase String nos provee de un amplio abanico de métodos para realizar las más diversas operaciones. Observemos el Código fuente 307, y comparemos con el fuente del ejemplo anterior.

Dim lsCadena As String
Dim liLongitud As Integer
Dim lsCambiada As String
lsCadena = "esto es una prueba"
liLongitud = lsCadena.Length ' 18
lsCambiada = lsCadena.ToUpper() ' ESTO ES UNA PRUEBA
Código fuente 307

Al ser una cadena, tanto un tipo de dato como un objeto de la clase String, podemos manipularlo como cualquier otro objeto de la jerarquía de la plataforma. En esta ocasión, hemos recuperado la longitud de la cadena mediante su propiedad Length, y la hemos convertido a mayúsculas ejecutando su método ToUpper( ), asignando el resultado a otra variable. Para comprobar la versatilidad que ahora nos proporcionan los tipos de datos, cuando declaramos una variable String, podemos hacerlo en la forma tradicional de declaración, o al estilo OOP. Si consultamos la ayuda de .NET Framework, encontraremos una entrada con el título String Class, que describe este tipo como una clase más del sistema. Veamos el Código fuente 308.

Sub Main()
' modo tradicional
Dim lsCad1 As String
lsCad1 = "mesa"
' instanciar un objeto String y asignar valor
Dim loCad2 As New String("silla")
' declarar variable e instanciar un objeto
' String en la misma línea
Dim loCad3 As String = New String("coche")
' declarar variable e instanciar un objeto
' String en la misma línea; el constructor
' utilizado en este caso requiere un array
' de objetos Char; observe el lector la forma
' de crear un array, asignando valores al
' mismo tiempo
Dim loCad4 As String = New String(New Char() {"t", "r", "e", "n"})
Console.WriteLine("lsCad1 --> {0}", lsCad1)
Console.WriteLine("loCad2 --> {0}", loCad2)
Console.WriteLine("loCad3 --> {0}", loCad3)
Console.WriteLine("loCad4 --> {0}", loCad4)
Console.ReadLine()
End Sub
Código fuente 308

Una vez visto el fuente anterior, debemos realizar algunas aclaraciones. 
Podemos comprobar, utilizando el constructor de la clase String que recibe como parámetro un array Char, que el tipo String no pertenece puramente al conjunto de tipos primitivos de la plataforma, ya que internamente, el entorno manipula una cadena como un array de tipos Char; aunque para nuestra comodidad, este es un proceso transparente, que gestiona la plataforma por nosotros. 
En segundo lugar, y este también es un trabajo realizado transparentemente por el entorno, cada vez que creamos o instanciamos un tipo String, obtenemos lo que se denomina una cadena inalterable. Internamente, cuando realizamos una operación sobre la cadena: convertirla a mayúsculas, extraer una subcadena, etc., .NET crea una nueva instancia de String, asignándola a la misma variable. 
En apariencia realizamos modificaciones sobre la misma cadena, pero en realidad, cada operación genera nuevos objetos String. En este apartado realizaremos una revisión de los métodos de esta clase, a través de un conjunto de ejemplos, que a modo ilustrativo, nos permitan familiarizarnos con el modo en que se manejan cadenas en VB.NET.

Tipos de datos como objetos. Eventos

Los tipos de datos también son objetos 

En la mayoría de los lenguajes, Visual Basic incluido, cuando necesitemos un proceso que implique la manipulación de cadenas de caracteres, fechas, cálculos aritméticos, etc., podemos recurrir a un conjunto de funciones de soporte existentes en el lenguaje, para obtener, por ejemplo, la longitud de una cadena, subcadenas, conversiones, formatos, fecha actual, partes de una fecha, etc. El Código fuente 306, muestra cómo obtenemos la longitud de una cadena con la función Len( ).

Dim lsCadena As String
Dim liLongitud As Integer
lsCadena = "esto es una prueba"
liLongitud = Len(lsCadena) ' 18
Código fuente 306

A pesar de disponer de este conjunto de funciones de utilidad, en el caso particular de VB.NET veremos en los siguientes apartados que su uso no será necesario, aunque se proporcionan por compatibilidad con anteriores versiones de este lenguaje. 
Dentro de la plataforma .NET Framework, todos los elementos del lenguaje se consideran tipos: los propios tipos de datos, clases, estructuras, enumeraciones, etc., componen lo que se denomina el CTS, o sistema común de tipos, una enorme jerarquía que parte del tipo base Object, y del que heredan el resto de tipos de la plataforma. Por dicho motivo, utilizaremos el término tipo para referirnos tanto a clases, como estructuras, etc. 
Al ser los tipos de datos, uno de los muchos tipos existentes dentro del esquema del CTS, podemos manipularlos de la forma tradicional o como si fueran objetos; aspecto este, que trataremos en el siguiente apartado.

Enumeraciones - III

Para utilizar una enumeración definida en nuestra aplicación, debemos declarar una variable, a la que daremos como tipo de dato el mismo de la enumeración. Una vez creada, la forma de asignar un valor es muy sencilla, ya que en cuanto escribamos el operador de asignación, el editor de código nos abrirá una lista con los posibles valores que admite la variable, que corresponderán, evidentemente, sólo a los de la enumeración. De esta forma, facilitamos enormemente la escritura de código, ya que se reducen las posibilidades de error en la asignación de valores a la variable enumerada. Ver Figura 125.
Figura 125. Asignación de valor a una variable de tipo enumerado.
El valor almacenado en una variable de enumeración corresponderá al número de la constante que hayamos seleccionado. Al declarar la variable, su valor inicial será cero. 
No obstante, la manipulación de una enumeración va mucho más allá de la asignación y recuperación simple de las constantes que componen la enumeración. Cuando declaramos una variable de una enumeración, el contenido real de dicha variable es un objeto de la clase Enum; por lo tanto, podemos utilizar los métodos de dicho objeto, para realizar diversas operaciones. Para tareas genéricas, la clase Enum también dispone de métodos compartidos que podremos ejecutar directamente, sin necesidad de crear una enumeración previa. El Código fuente 305 muestra algunos ejemplos.
Module Module1
Public Enum Musicas As Integer
Rock
Blues
NewAge
Funky
End Enum
Sub Main()
' creamos una variable de enumeración
' y le asignamos valor
Dim lxMusic As Musicas
lxMusic = Musicas.NewAge
' el contenido de la variable es el número asignado
' a la constante
Console.WriteLine(lxMusic)
' el método ToString() permite mostrar el nombre
' de la constante elegida de la lista que tiene
' la enumeración
Console.WriteLine(lxMusic.ToString("G"))
' obtener los valores de las constantes de la enumeración
' con GetValues(), y los nombres con GetNames(); estos métodos
' son compartidos de la clase Enum, y reciben como parámetro una
' variable de enumeración de la que debe obtenerse el tipo ejecutando
' su método GetType(); devuelven un array con los datos pedidos
Dim liValores() As Integer
Dim lsNombres() As String
Dim liContador As Integer
liValores = System.Enum.GetValues(lxMusic.GetType())
lsNombres = System.Enum.GetNames(lxMusic.GetType())
Console.WriteLine()
Console.WriteLine("Valor - Nombre")
' recorrer los arrays y mostrar su contenido
For liContador = 0 To UBound(liValores)
Console.WriteLine(liValores(liContador) & Space(7) & _
lsNombres(liContador))
Next
' comprobar si un nombre introducido por el
' usuario está entre los nombres de las
' constantes en la enumeración
Dim lsNombreMusica As String
Console.WriteLine("Introducir nombre de constante")
lsNombreMusica = Console.ReadLine()
If System.Enum.IsDefined(lxMusic.GetType(), lsNombreMusica) Then
Console.WriteLine("El tipo de música sí está en la enumeración")
Else
Console.WriteLine("El tipo de música no está en la enumeración")
End If
Console.ReadLine()
End Sub
End Module
Código fuente 305

Enumeraciones - II

Si bien el uso de constantes mejora la situación, su proliferación provocará la aparición de un nuevo problema: la organización y clasificación de todas las constantes del programa. 
Aquí es donde entran en escena las enumeraciones, ya que con ellas, podemos crear conjuntos de constantes relacionadas por una cualidad común, agrupando cada conjunto bajo un identificador genérico. 
Para crear una enumeración debemos utilizar las palabras clave Enum...End Enum, situando junto a Enum el nombre que vamos a dar a la enumeración, y a continuación, la lista de constantes que agrupará. Por lo tanto, si queremos reunir bajo una enumeración, las constantes de los estilos musicales, lo haremos del modo mostrado en el Código fuente 303.

Public Enum Musicas
Rock
Blues
NewAge
Funky
End Enum
Código fuente 303
Una enumeración debe tener un tipo de dato. Los tipos que podemos asignar a una enumeración deben ser los numéricos enteros soportados por el lenguaje que estemos utilizando. En el caso de VB.NET, los tipos de datos admisibles son Byte, Integer, Long y Short. En el caso de que no especifiquemos el tipo, tomará Integer por defecto. 
El hecho de tipificar una enumeración está relacionado con los valores que podemos asignar a cada una de las constantes que contiene. De ello se deduce, que sólo vamos a poder asignar valores numéricos a estas constantes. Cuando creamos una enumeración, si no asignamos valores a sus constantes, el entorno asigna automáticamente los valores, comenzando por cero y en incrementos de uno. Podemos en cualquier momento, asignar manualmente valores, no siendo obligatorio tener que asignar a todas las constantes. 
Cuando dejemos de asignar valores, el entorno seguirá asignando los valores utilizando como valor de continuación, el de la última constante asignada. Veamos unos ejemplos en el Código fuente 304.

Public Enum Musicas As Integer
Rock ' 0
Blues ' 1
NewAge ' 2
Funky ' 3
End Enum
Public Enum DiasSemana As Integer
Lunes ' 0
Martes ' 1
Miercoles = 278
Jueves ' 279
Viernes ' 280
Sabado ' 281
Domingo ' 282
End Enum
Código fuente 304

Enumeraciones - I

Una enumeración consiste en un conjunto de constantes relacionadas. A cada constante se le asigna un nombre, mientras que la agrupación de tales constantes, es decir, la propia enumeración recibe también un nombre identificativo. 
Supongamos por ejemplo, que en un programa debemos realizar clasificaciones por estilos musicales: Rock, Blues, New Age, Funky, etc. El modo más sencillo de manipular estos valores en el código es identificarlos mediante números, de forma que cuando en un procedimiento tengamos que saber la selección del estilo que ha realizado un usuario, empleemos el número identificativo en lugar de cadenas de caracteres. Veamos el Código fuente 301.

Public Sub SelecEstilo(ByVal liEstiloMusical As Integer)
Select Case liEstiloMusical
Case 1
' se ha seleccionado Rock
' ....
Case 2
' se ha seleccionado Blues
' ....
Case 3
' se ha seleccionado New Age
' ....
Case 4
' se ha seleccionado Funky
' ....
End Select
End Sub
Código fuente 301

Sin embargo, la sencillez que supone el uso de números para identificar determinadas características en nuestra aplicación, tiene el efecto adverso de dificultar la lectura del código, ya que, en todos los procedimientos donde debamos trabajar con estilos musicales, deberemos de añadir un comentario de código junto al número de estilo, para saber de cuál se trata. 
Podemos solucionar parcialmente el problema utilizando constantes, de modo que por cada estilo musical, crearemos una constante a la que asignaremos el número de un estilo. Veamos el Código fuente 302.
Public Const ROCK As Integer = 1
Public Const BLUES As Integer = 2
Public Const NEWAGE As Integer = 3
Public Const FUNKY As Integer = 4
Public Sub SelecEstilo(ByVal liEstiloMusical As Integer)
Select Case liEstiloMusical
Case ROCK
' ....
Case BLUES
' ....
Case NEWAGE
' ....
Case FUNKY
' ....
End Select
End Sub
Código fuente 302

La estructura del sistema DateTime

El entorno de .NET Framework proporciona, al igual que ocurre con las clases, una serie de estructuras del sistema, con funcionalidades diseñadas para ayudar al programador en las más variadas situaciones. 
Como ejemplo de este tipo de estructura encontramos a DateTime, en la que a través de sus miembros compartidos y de instancia, nos provee de diversas operaciones para el manejo de fechas. 
El Código fuente 300 muestra algunos ejemplos de uso de esta estructura. Consulte el lector la documentación de la plataforma para una descripción detallada sobre cada uno de sus miembros.

Module Module1
Sub Main()
' ejemplos con la estructura DateTime
' ===================================
' miembros compartidos
Dim ldtFechaActual As Date
Dim ldtFechaA, ldtFechaB As Date
' la propiedad Today devuelve la fecha actual
ldtFechaActual = DateTime.Today
Console.WriteLine("La fecha de hoy es {0}", ldtFechaActual)
' el método DaysInMonth() devuelve el número
' de días que tiene un mes
Console.WriteLine("El mes de Febrero de 2002 tiene {0} días", _
DateTime.DaysInMonth(2002, 2))
' el método Compare() compara dos fechas
Console.WriteLine("Introducir primera fecha")
ldtFechaA = Console.ReadLine()
Console.WriteLine("Introducir segunda fecha")
ldtFechaB = Console.ReadLine()
Select Case DateTime.Compare(ldtFechaA, ldtFechaB)
Case -1
Console.WriteLine("La primera fecha es menor")
Case 0
Console.WriteLine("Las fechas son iguales")
Case 1
Console.WriteLine("La primera fecha es mayor")
End Select
' miembros de instancia
Dim loMiFecha As DateTime
Dim ldtFDias As Date
Dim ldtFMeses As Date
Dim lsFHFormateada As String
' usar el constructor de la estructura
' para crear una fecha
loMiFecha = New DateTime(2002, 5, 12)
' agregar días a la fecha
ldtFDias = loMiFecha.AddDays(36)
' restar meses a la fecha
ldtFMeses = loMiFecha.AddMonths(-7)
' formatear la fecha
lsFHFormateada = loMiFecha.ToLongDateString()
Console.WriteLine("Uso de métodos de instancia de DateTime")
Console.WriteLine("Fecha creada: {0} - Agregar días: {1}" & _
" - Restar meses: {2} - Fecha formateada: {3}", _
loMiFecha, ldtFDias, ldtFMeses, lsFHFormateada)
Console.ReadLine()
End Sub
End Module
Código fuente 300

Estructuras o clases, ¿cuál debemos utilizar?

Llegados a este punto, el lector puede estar preguntándose cuál sería la mejor manera de abordar la aplicación: con clases o estructuras. Bien, no podemos decantarnos totalmente por un modo u otro de trabajo. Aún guardando muchas similitudes con las clases, las estructuras mantienen ciertas diferencias respecto a las primeras, que harán conveniente su uso en determinados momentos, siendo menos adecuadas en otras situaciones. 
Las estructuras no soportan herencia, por lo que el medio más parecido que tenemos de extender sus funcionalidades es a través de interfaces. Para crear un manejador de evento dentro de una estructura, el procedimiento que actúe como manejador deberá ser un miembro compartido, no podrá ser un miembro de instancia. El manejo de eventos será tratado posteriormente. 
Una estructura es un tipo por valor, lo que quiere decir que los datos que contiene se manejan en la pila (stack) de la memoria. Si asignamos una variable que contiene una estructura a otra variable, se realizará una copia de la estructura, y obtendremos dos estructuras cuyos datos serán totalmente independientes. 
Esto último contrasta claramente con las clases, que son tipos por referencia, y sus datos se manejan en el montón (heap) de la memoria. Lo que realmente contiene una variable de objeto no es el objeto en sí, sino un puntero de cuatro bytes, con la referencia hacia la zona de memoria en la que reside el objeto. Por lo tanto, si asignamos una variable de objeto a otra variable, se realiza lo que se denomina una copia superficial (shallow copy) de una variable a otra. 
Esto quiere decir que sólo se copia el puntero de cuatro bytes que contiene la referencia hacia el objeto. El efecto conseguido son dos variables que apuntan al mismo objeto y no dos variables con copias independientes del objeto. 
Observemos el Código fuente 299, en el que se crean dos variables de estructura y una se asigna a otra. Si hacemos un cambio en la segunda, la primera estructura permanecerá inalterada. Sin embargo, a continuación creamos dos variables de objeto, y asignamos la primera a la segunda. Cuando hagamos un cambio en la segunda, se reflejará en la primera; esto es debido a que son dos variables que apuntan al mismo objeto.

Sub Main()
Dim lDBanco1 As DatosBanco
Dim lDBanco2 As DatosBanco
lDBanco1.IDCuenta = 55
lDBanco2 = lDBanco1
lDBanco2.IDCuenta = 188
Console.WriteLine("lDBanco1.IDCuenta --> {0}", lDBanco1.IDCuenta) ' 55
Console.WriteLine("lDBanco2.IDCuenta --> {0}", lDBanco2.IDCuenta) ' 188
Console.WriteLine()
Dim loEmpleado1 As Empleado
Dim loEmpleado2 As Empleado
loEmpleado1 = New Empleado()
loEmpleado1.piIdentificador = 55
loEmpleado2 = loEmpleado1
loEmpleado2.piIdentificador = 188
Console.WriteLine("loEmpleado1.piIdentificador --> {0}", _
loEmpleado1.piIdentificador) ' 188
Console.WriteLine("loEmpleado2.piIdentificador --> {0}", _
loEmpleado2.piIdentificador) ' 188
Console.ReadLine()
End Sub
Código fuente 299

Si vamos a tratar la estructura mayormente como un objeto, se recomienda que utilicemos mejor una clase.

Creación y manipulación de estructuras - II

Como hemos comentado antes, una estructura admite también métodos y propiedades, de instancia y compartidos, al estilo de una clase. Por lo que podemos añadir este tipo de elementos a nuestra estructura, para dotarla de mayor funcionalidad. El Código fuente 298 muestra un ejemplo más completo de la estructura DatosBanco.

Module Module1
Sub Main()
' declarar una variable de la estructura
' y manipularla directamente
Dim lDBanco1 As DatosBanco
lDBanco1.IDCuenta = 958
lDBanco1.Titular = "Carlos Perea"
lDBanco1.DNI = "112233"
lDBanco1.Saldo = 900
lDBanco1.Informacion()
Console.WriteLine()
' declaramos una variable de la estructura
' pero aquí la instanciamos antes de
' comenzar a trabajar con ella, para que
' se ejecute su método constructor
Dim lDBanco2 As DatosBanco
lDBanco2 = New DatosBanco(450)
lDBanco2.IDCuenta = 580
lDBanco2.Titular = "Alfredo Peral"
lDBanco2.DNI = "556677"
lDBanco2.Informacion()
Console.WriteLine()
' en esta ocasión trabajamos con los
' miembros compartidos de la estructura,
' por lo que no es necesario una variable
' con el tipo de la estructura, sino que
' usamos directamente los miembros compartidos
Console.WriteLine("La fecha de apertura de la cuenta es {0}", _
DatosBanco.FHApertura)
DatosBanco.SaldoMin()
Console.ReadLine()
End Sub
End Module
Public Structure DatosBanco
Public IDCuenta As Integer
Public Titular As String
Public Saldo As Integer
Private mDNI As String
Shared FHApertura As Date
Shared SaldoMinimo As Integer
' el constructor de una estructura debe definirse con
' parámetros; si no ponemos parámetros hay que declararlo
' como compartido
Public Sub New(ByVal liSaldo As Integer)
Saldo = liSaldo
End Sub
Public Property DNI() As String
Get
Return mDNI
End Get
Set(ByVal Value As String)
mDNI = Value
End Set
End Property
Public Sub Informacion()
Console.WriteLine("Información sobre la cuenta")
Console.WriteLine("Código: {0} - Titular: {1} - DNI: {2} - Saldo: {3}", _
IDCuenta, Titular, Me.DNI, Saldo)
End Sub
' miembros compartidos de la estructura
' =====================================
' si el constructor no tiene parámetros
' debe ser compartido (shared), y además,
' los miembros que maneje deben ser también compartidos
Shared Sub New()
FHApertura = Now
SaldoMinimo = 200
End Sub
' método compartido que devuelve el saldo mínimo
Shared Sub SaldoMin()
Console.WriteLine("El saldo mínimo necesario debe ser de {0}", _
SaldoMinimo)
End Sub
End Structure
Código fuente 298

Creación y manipulación de estructuras - I

Para crear una estructura, tenemos que utilizar la palabra clave Structure junto al nombre de la estructura, situando a continuación los miembros de la estructura, y finalizándola con la palabra clave End Structure, como muestra el Código fuente 296.

Public Structure DatosBanco
Public IDCuenta As Integer
Public Titular As String
Public Saldo As Integer
End Structure
Código fuente 296

El modo de utilizar una estructura desde el código cliente, consiste en declarar una variable del tipo correspondiente a la estructura, y manipular sus miembros de forma similar a un objeto. En el Código fuente 297, manejamos de esta forma, una variable de la estructura DatosBanco.

Sub Main()
Dim lDBanco As DatosBanco
lDBanco.IDCuenta = 958
lDBanco.Titular = "Carlos Perea"
lDBanco.Saldo = 900
End Sub
Código fuente 297

Estructuras

Una estructura consiste en un conjunto de datos que se unen para formar un tipo de dato compuesto. Este elemento del lenguaje se conoce también como tipo definido por el usuario (UDT o User Defined Type), y nos permite agrupar bajo un único identificador, una serie de datos relacionados. 
En el lenguaje VB.NET, los miembros de una estructura pueden ser, además de los propios campos que almacenan los valores, métodos que ejecuten operaciones, por lo cual, su aspecto y modo de manejo es muy parecido al de una clase. 
Por ejemplo, si disponemos de la información bancaria de una persona, como pueda ser su código de cuenta, titular, saldo, etc., podemos manejar dichos datos mediante variables aisladas, o podemos crear una estructura que contenga toda esa información, simplificando la forma en que accedemos a tales datos.

Interfaces - V

Los interfaces pueden heredar de otros interfaces que hayamos creado. De esta manera, si creamos el interfaz IGestion, podemos hacer que herede las características de ICadena y agregue más declaraciones de miembros. Ver Código fuente 294.

Public Interface IGestion
Inherits ICadena
Sub Calcular()
End Interface
Código fuente 294

También es posible en un interfaz heredar de múltiples interfaces. Ver el Código fuente 295.

Public Interface IGestion
Inherits ICadena, ICalculo
End Interface
Código fuente 295

Interfaces - IV

El Código fuente 293 muestra el código al completo de este ejemplo, incluyendo el interfaz, las clases que lo implementan y el procedimiento Main( ) que instancia objetos de las clases.

Module Module1
Sub Main()
Dim loEmple As Empleado = New Empleado()
loEmple.Nombre = "Raquel Rodrigo"
Console.WriteLine("El nombre del empleado tiene {0} caracteres", _
loEmple.Longitud)
Console.WriteLine("Valor del empleado: {0}", loEmple.ObtenerValor())
Dim loCuenta As Cuenta = New Cuenta()
loCuenta.Codigo = 700256
Console.WriteLine("El código de cuenta {0} tiene una longitud de {1}", _
loCuenta.Codigo, loCuenta.Longitud)
Console.WriteLine("Información de la cuenta: {0}", loCuenta.ObtenerValor())
Console.Read()
End Sub
End Module
' las clases que implementen este interfaz
' deberán crear la propiedad Longitud y el
' método ObtenerValor(); la codificación de
' dichos miembros será particular a cada clase
Public Interface ICadena
ReadOnly Property Longitud() As Integer
Function ObtenerValor() As String
End Interface
Public Class Empleado
Implements ICadena
Private msNombre As String
Public Property Nombre() As String
Get
Return msNombre
End Get
Set(ByVal Value As String)
msNombre = Value
End Set
End Property
' devolvemos la longitud de la propiedad Nombre,
' al especificar el interfaz que se implementa,
' se puede poner todo el espacio de nombres
Public ReadOnly Property Longitud() As Integer _
Implements InterfacesPrueba.ICadena.Longitud
Get
Return Len(Me.Nombre)
End Get
End Property
' devolvemos el nombre en mayúscula;
' no es necesario poner todo el espacio
' de nombres calificados, basta con el
' nombre de interfaz y el miembro a implementar
Public Function ObtenerValor() As String Implements ICadena.ObtenerValor
Return UCase(Me.Nombre)
End Function
End Class
Public Class Cuenta
Implements ICadena
Private miCodigo As Integer
Public Property Codigo() As Integer
Get
Return miCodigo
End Get
Set(ByVal Value As Integer)
miCodigo = Value
End Set
End Property
' en esta implementación del miembro, devolvemos
' el número de caracteres que tiene el campo
' miCodigo de la clase
Public ReadOnly Property Longitud() As Integer _
Implements InterfacesPrueba.ICadena.Longitud
Get
Return Len(CStr(miCodigo))
End Get
End Property
' en este método del interfaz, devolvemos valores
' diferentes en función del contenido de la
' propiedad Codigo
Public Function ObtenerValor() As String _
Implements InterfacesPrueba.ICadena.ObtenerValor
Select Case Me.Codigo
Case 0 To 1000
Return "Código en intervalo hasta 1000"
Case 1001 To 2000
Return "El código es: " & Me.Codigo
Case Else
Return "El código no está en el intervalo"
End Select
End Function
End Class
Código fuente 293

Interfaces - III

Un medio muy sencillo de crear un método vacío que implemente un interfaz, consiste en abrir la lista Nombre de clase del editor de código y allí, seleccionar en la clase, el interfaz que implementa. Ver Figura 123.
Figura 123. Selección del interfaz implementado por una clase en el editor de código.
Después pasaremos a la lista Nombre de método y elegiremos el miembro a implementar. Ver Figura 124.
Figura 124. Selección del miembro del interfaz a implementar.
Estas acciones crearán el método vacío con la implementación del interfaz. Como podemos observar en el Código fuente 292, en la declaración del método se incluye el nombre calificado al completo.

Public ReadOnly Property Longitud() As Integer _
Implements InterfacesPrueba.ICadena.Longitud
Get
End Get
End Property
Código fuente 292

En la implementación del interfaz ICadena para la clase Empleado, devolvemos el nombre en mayúsculas del empleado y la longitud de dicho nombre en los dos miembros correspondientes a dicho interfaz. 
Naturalmente, aparte de los miembros del interfaz, una clase puede tener todos los demás que necesite. Posteriormente creamos una segunda clase en nuestro proyecto con el nombre 
Cuenta, en la que también implementamos el interfaz ICadena, pero en los miembros implementados sobre esta clase las operaciones realizadas no serán exactamente iguales, ya que como hemos indicado, la implementación que hagamos de los miembros de un interfaz en una clase es totalmente libre para el programador.

Interfaces - II

Seguidamente crearemos la clase Empleado. Para que dicha clase pueda implementar (utilizar) las definiciones de un interfaz, emplearemos después de la declaración de la clase, la palabra clave Implements junto al nombre del interfaz que deseamos que implemente la clase. Veamos el Código fuente 289.

Public Class Empleado
Implements Cadena
'....
'....
End Class
Código fuente 289
Esta acción obligará a la clase a crear, como mínimo, tantos miembros como indica el interfaz que implementa, es decir, debemos escribir una propiedad Longitud y un método ObtenerValor( ), o en caso contrario, se producirá un error al intentar ejecutar el programa. Observe el lector, que el editor de código sitúa una marca sobre el nombre del interfaz en la clase mientras que no se hayan implementado todos sus miembros. Ver Figura 122.
Figura 122. Mensaje del interfaz que indica que no se han implementado todos sus miembros.
Para implementar un miembro de un interfaz, en el momento de escribir su declaración, utilizaremos la palabra clave Implements, seguida del interfaz y miembro que implementa. Veamos en el Código fuente 290, la implementación del método ObtenerValor( ) en la clase Empleado, con su código correspodiente.

Public Function ObtenerValor() As String Implements ICadena.ObtenerValor
Return UCase(Me.Nombre)
End Function
Código fuente 290

La necesidad de especificar el interfaz y miembro del mismo que implementa, tiene como ventaja, el que para el nombre del método podemos utilizar uno distinto del que indica el interfaz. Por ejemplo, el anterior método podríamos haberlo escrito como muestra el Código fuente 291.

Public Function DameDatos() As String Implements ICadena.ObtenerValor
Return UCase(Me.Nombre)
End Function
Código fuente 291

Interfaces - I

Un interfaz proporciona, a modo de declaración, una lista de propiedades y métodos, que posteriormente serán codificados en una o varias clases. Debido a su naturaleza declarativa, un interfaz no contiene el código de los miembros que expresa; dicho código será escrito en las clases que implementen el interfaz. 
El concepto de interfaz es análogo al de contrato, las partes integrantes son el propio interfaz y la clase que lo implementa. Mientras que el interfaz no puede ser cambiado desde el momento en que sea implementado, la clase que lo implementa se compromete a crear la lista de miembros en la misma forma que indica el interfaz 
Los interfaces nos permiten definir conjuntos reducidos de funcionalidades, constituyendo una útil herramienta de cara al polimorfismo. El mismo interfaz, implementado en distintas clases, podrá tener a su vez código distinto, con lo que los objetos de diferentes clases que implementen un interfaz común, pueden tener un comportamiento diferente. Supongamos por ejemplo, que necesitamos que algunas clases dispongan de ciertos miembros para la manipulación de cadenas, pero no queremos que estas características sean algo rígido, es decir, cada clase debe de cumplir con un conjunto de nombres y parámetros para los miembros que se encargarán de manipular cadenas, pero la implementación del código que haga cada clase para gestionarlas es libre. 
Ante esta situación podemos definir un interfaz e implementarlo en cada clase.  El ejemplo desarrollado al completo para este caso se encuentra en el proyecto InterfacesPrueba (hacer clic aquí para acceder al ejemplo). 
En primer lugar crearemos un nuevo proyecto de tipo aplicación de consola. A continuación, para crear un interfaz, utilizaremos la palabra clave Interface junto con el nombre que asignemos al interfaz. Para indicar el final del interfaz usaremos la palabra clave End Interface, situando dentro del interfaz las declaraciones de miembros que necesitemos. En nuestro ejemplo vamos a crear el interfaz ICadena que declara la propiedad Longitud y el método ObtenerValor. Aunque no es en absoluto necesario, se recomienda que utilicemos la letra I como prefijo para los nombres de interfaces, de cara a facilitar la lectura del código, como vemos en el Código fuente 288.

' las clases que implementen este interfaz
' deberán crear la propiedad Longitud y el
' método ObtenerValor(); la codificación de
' dichos miembros será particular a cada clase
Public Interface ICadena
ReadOnly Property Longitud() As Integer
Function ObtenerValor() As String
End Interface
Código fuente 288

Definir una clase como punto de entrada de la aplicación

Podemos crear una clase en el proyecto, que contenga un método Main( ) declarado como Shared, de forma que dicho método constituya el punto del programa. Ver Código fuente 287.

Public Class Comienzo
Public Shared Sub Main()
Console.WriteLine("Está comenzando la aplicación")
Console.ReadLine()
End Sub
End Class
Código fuente 287

Además de crear la clase con este método, deberemos modificar las propiedades del proyecto, definiendo como objeto inicial el nombre de la clase o directamente Sub Main. Como habrá podido adivinar el lector, ello hace innecesario el uso de módulos de código dentro del proyecto, pudiendo de esta manera, codificar la aplicación completamente basada en clases. 
Como detalle interesante, debemos destacar el hecho de que al utilizar el modo tradicional de inicio de una aplicación, es decir, a través de un procedimiento Main( ) en un módulo, el compilador convierte internamente dicho módulo en una clase y al procedimiento en un método compartido.

Miembros compartidos (shared) de una clase - II

En el caso de variables de clase declaradas como miembros compartidos, este tipo de variable sólo es creado una vez, manteniendo su valor para todas las instancias de la clase. Esto contrasta con los miembros de instancia, de los que se crea una copia particular para cada objeto. 
El efecto de miembro compartido se hace más patente cuando se aplica sobre variables, por ello, en el ejemplo del Código fuente 286, creamos dos campos compartidos para la clase Empleado; uno de ellos actuará como contador de los objetos creados de la clase, usando el método constructor para ser incrementado. El otro nos servirá para comprobar que siendo compartido no se inicializa, y mantiene el valor asignado previamente.

Module General
Sub Main()
' accedemos a la variable compartida
' y le asignamos valor
Empleado.psApellidos = "Naranjo"
' instanciamos un primer objeto Empleado
Dim loEmp1 As New Empleado()
' asignamos valor a su variable de instancia
loEmp1.psNombre = "Luis"
' mostramos las dos variables del objeto
Console.WriteLine("Objeto loEmp1 - valores de sus variables")
Console.WriteLine("psNombre: {0} - psApellidos: {1}", _
loEmp1.psNombre, loEmp1.psApellidos)
Console.WriteLine()
' instanciamos un segundo objeto Empleado
Dim loEmp2 As New Empleado()
' asignamos valor a su variable de instancia
loEmp2.psNombre = "Juan"
' mostramos las dos variables del objeto
Console.WriteLine("Objeto loEmp2 - valores de sus variables")
Console.WriteLine("psNombre: {0} - psApellidos: {1}", _
loEmp2.psNombre, loEmp2.psApellidos)
Console.WriteLine()
' ahora mostramos el valor de
' la variable compartida miContar
Console.WriteLine("Se han instanciado {0} objetos de la clase Empleado", _
Empleado.piContar)
Console.ReadLine()
End Sub
End Module
Public Class Empleado
Public psNombre As String ' miembro de instancia
Public Shared psApellidos As String ' miembro compartido
Public Shared piContar As Integer ' miembro compartido
Public Sub New()
' por cada instancia de la clase creada,
' incrementar este campo compartido
Me.piContar += 1
End Sub
End Class
Código fuente 286

Miembros compartidos (shared) de una clase - I

Los miembros compartidos o shared son aquellos que no precisan de una instancia previa de un objeto de la clase para poder ser utilizados, aunque pueden también ser usados por una instancia de la clase. Dentro de este contexto, podemos pues clasificar los miembros de una clase en dos categorías: 
  • Miembros de instancia (instance members). Son aquellos a los que accedemos a través de un objeto instanciado previamente de la clase. 
  • Miembros compartidos (shared members). Son aquellos a los que podemos acceder sin necesidad de que exista un objeto creado de la clase. 
Podemos declarar como compartidos los métodos, propiedades y campos de una clase. Para ello deberemos emplear la palabra clave Shared en la declaración. Para utilizar desde el código cliente un miembro compartido, tan sólo debemos poner el nombre de la clase a la que pertenece, el punto y el nombre del miembro a utilizar. El ejemplo del Código fuente 285 demuestra como podemos ejecutar un método compartido sin haber instanciado antes un objeto de la clase a la que pertenece dicho método.

Module General
Sub Main()
Dim lsValor As String
' aunque no hemos instanciado objetos
' de la clase Empleado, podemos llamar
' a este método compartido
Console.WriteLine("Nombre del mes: {0}", Empleado.VerNombreMes())
' ahora creamos una instancia de la clase
Dim loEmpleado1 As New Empleado()
lsValor = loEmpleado1.VerNombreDia()
Console.WriteLine("Nombre del día: {0}", lsValor)
Console.ReadLine()
End Sub
End Module
Public Class Empleado
Public Shared Function VerNombreMes() As String
' este método puede ser llamado
' directamente empleando el nombre
' de la clase como calificador
Return Format(Now(), "MMMM")
End Function
Public Function VerNombreDia() As String
' este método precisa de una instancia
' para ser llamado
Return Format(Now(), "dddd")
End Function
End Class
Código fuente 285

Elementos compartidos e interfaces - III

En el caso de clases heredadas, y con métodos sobrescritos, CType( ) discierne la implementación de clase a la que debe dirigirse. 
Si añadimos la clase Administrativo como hija de Empleado, y sobrescribimos el método VerDatos( ), cuando pasemos al procedimiento ManipularVarios( ) un objeto Administrativo, dado que esta clase hereda de Empleado, con un sencillo cambio en la estructura Select...Case que comprueba el tipo de objeto, ejecutaremos la implementación sobrescrita del método VerDatos( ) en la clase Administrativo. Veamos el Código fuente 283.

Module Module1
Public Sub Main()
Dim loEmple As New Empleado()
loEmple.piID = 58
loEmple.psNombre = "Elena Peral"
ManipularVarios(loEmple)
Dim loAdmin As New Administrativo()
loAdmin.piID = 80
loAdmin.psNombre = "Alfredo Iglesias"
ManipularVarios(loAdmin)
Dim loFac As New Factura()
loFac.pdtFecha = "25/2/2002"
loFac.piImporte = 475
ManipularVarios(loFac)
Console.Read()
End Sub
Public Sub ManipularVarios(ByVal loUnObjeto As Object)
' obtenemos información sobre el tipo del objeto
Dim loTipoObj As Type
loTipoObj = loUnObjeto.GetType()
' comprobamos qué tipo de objeto es,
' y en función de eso, ejecutamos el
' método adecuado
Select Case loTipoObj.Name
Case "Empleado", "Administrativo"
CType(loUnObjeto, Empleado).VerDatos()
Case "Factura"
CType(loUnObjeto, Factura).Emitir()
End Select
End Sub
End Module
Public Class Administrativo
Inherits Empleado
Public pdtFHAlta As Date
Public Overrides Sub VerDatos()
Console.WriteLine("DATOS DEL ADMINISTRATIVO")
Console.WriteLine("==================")
Console.WriteLine("Código: {0}", Me.piID)
Console.WriteLine("Nombre: {0}", Me.psNombre)
End Sub
Public Function MesAlta()
Return Format(Me.pdtFHAlta, "MMMM")
End Function
End Class
Código fuente 283

Pero ¿cómo podríamos ejecutar el método particular MesAlta( ) de Administrativo, que no se encuentra en Empleado?, pues creando en la estructura Select...Case, un caso particular que compruebe cuándo estamos tratando con un objeto Administrativo. Veámoslo en el Código fuente 284.
Public Sub ManipularVarios(ByVal loUnObjeto As Object)
' obtenemos información sobre el tipo del objeto
Dim loTipoObj As Type
loTipoObj = loUnObjeto.GetType()
' comprobamos qué tipo de objeto es,
' y en función de eso, ejecutamos el
' método adecuado
Select Case loTipoObj.Name
Case "Empleado"
CType(loUnObjeto, Empleado).VerDatos()
Case "Administrativo" ' <-- añadimos este caso a la estructura
CType(loUnObjeto, Administrativo).VerDatos()
Console.WriteLine("El administrativo comenzó en {0}", _
CType(loUnObjeto, Administrativo).MesAlta())
Case "Factura"
CType(loUnObjeto, Factura).Emitir()
End Select
End Sub
Código fuente 284

Elementos compartidos e interfaces - II

Conociendo ya el tipo de objeto con el que tratamos, utilizaremos la función CType( ), que realiza un moldeado de la variable que contiene el objeto hacia un tipo determinado, y nos permite acceder a los elementos del objeto. 
El lector puede argumentar que no sería necesario el uso de CType(), y en efecto es así; podríamos haber utilizado directamente la variable, situando a continuación el método a ejecutar. Esta técnica, no obstante, tiene el inconveniente de que utiliza enlace tardío para acceder al objeto. 
CType( ) sin embargo, tiene la ventaja de que opera bajo enlace temprano, con lo cuál, el rendimiento en ejecución es mayor. Veamos el código cliente que accedería a estas clases en el Código fuente 282.

Module Module1
Public Sub Main()
Dim loEmple As New Empleado()
loEmple.piID = 58
loEmple.psNombre = "Elena Peral"
ManipularVarios(loEmple)
Dim loFac As New Factura()
loFac.pdtFecha = "25/2/2002"
loFac.piImporte = 475
ManipularVarios(loFac)
Console.Read()
End Sub
Public Sub ManipularVarios(ByVal loUnObjeto As Object)
' obtenemos información sobre el tipo del objeto
Dim loTipoObj As Type
loTipoObj = loUnObjeto.GetType()
' comprobamos qué tipo de objeto es,
' y en función de eso, ejecutamos el
' método adecuado
Select Case loTipoObj.Name
Case "Empleado"
CType(loUnObjeto, Empleado).VerDatos()
Case "Factura"
CType(loUnObjeto, Factura).Emitir()
End Select
End Sub
End Module
Código fuente 282

Elementos compartidos e interfaces - I

Comprobación del tipo de un objeto y moldeado (casting)

En algunas circunstancias, puede ser útil codificar un procedimiento que reciba como parámetro un objeto genérico, y dependiendo del tipo del objeto, ejecutar ciertos métodos. Si tipificamos un parámetro como un objeto genérico, es decir, de tipo Object, necesitamos implementar dentro del procedimiento un mecanismo que, en primer lugar, compruebe a qué tipo pertenece el objeto, y por otra parte, acceda adecuadamente a su lista de miembros. Como ejemplo consideremos el siguiente caso: creamos una clase Empleado y otra Factura, las cuales vemos en el Código fuente 281.

Public Class Empleado
Public piID As String
Public psNombre As String
Public Overridable Sub VerDatos()
Console.WriteLine("Código de empleado: {0}, nombre: {1}", _
Me.piID, Me.psNombre)
End Sub
End Class
Public Class Factura
Public pdtFecha As Date
Public piImporte As Integer
Public Sub Emitir()
Console.WriteLine("Se procede a generar la siguiente factura:")
Console.WriteLine("Fecha de factura: {0}", Me.pdtFecha)
Console.WriteLine("Importe de la factura: {0}", Me.piImporte)
End Sub
End Class
Código fuente 281
A continuación, necesitamos un procedimiento al que podamos pasar indistintamente un objeto Empleado o Factura. Dentro de dicho procedimiento, al que denominaremos ManipularVarios( ), y que llamaremos desde Main( ), comprobaremos el tipo de objeto llamando al método 

GetType( ), que implementan todos los objetos del entorno, debido a que es un método de la clase Object, y como ya sabemos, todas las clases heredan implícitamente de Object. GetType( ) devuelve un objeto de la clase Type. Esta clase es de una enorme utilidad, ya que nos proporciona toda la información relativa al tipo del objeto. Para nuestro problema particular, interrogaremos a la propiedad Name del objeto, que nos devolverá una cadena con el nombre de la clase a que pertenece el objeto.

Clases abstractas o no instanciables - II

Debemos tener en cuenta que los miembros abstractos sólo tienen sentido si son declarados en clases abstractas. Por tal motivo, sólo podremos crear métodos con MustOverride en clases que hayan sido definidas como MustInherit. 
En lo que respecta al polimorfismo conseguido a través de clases abstractas, podemos crear un procedimiento que reciba como parámetro tipificado como clase abstracta, de forma que en función del objeto pasado, y que debe ser de un tipo derivado de la clase abstracta, el comportamiento será diferente en cada caso. Veámoslo en el Código fuente 280.

Module Module1
Public Sub Main()
'....
End Sub
' el objeto que reciba este procedimiento será
' de cualquiera de las clases que hereden de
' Empleado y ejecutará la implementación del método
' VerDatos() que tenga dicho objeto
Public Sub MostrarInformacion(ByVal loEmple As Empleado)
loEmple.VerDatos()
End Sub
End Module
Código fuente 280

Clases abstractas o no instanciables - I

Una clase abstracta es aquella que no permite la instanciación directa de objetos a partir de ella, siendo necesario una clase derivada para acceder a sus miembros. Una clase concreta es el tipo de clase que venimos utilizando hasta el momento, desde la cuál, podemos instanciar objetos. 
Aunque en una clase abstracta podemos escribir un método constructor, sólo será accesible desde el constructor de la subclase. 
Para definir una clase abstracta utilizaremos la palabra clave MustInherit en el momento de su declaración, como muestra el Código fuente 278.

Public MustInherit Class Empleado
Código fuente 278
Dentro de una clase abstracta podemos implementar propiedades y métodos, en la forma que hemos visto hasta el momento. Adicionalmente, podemos obligar a que determinados miembros sean sobrescritos por la clase heredada; son los denominados miembros abstractos, y se declaran usando la palabra clave MustOverride, como vemos en el Código fuente 279.

Module Module1
Public Sub Main()
Dim loAdmin As New Administrativo()
loAdmin.piID = 789
loAdmin.psNombre = "Pedro Pinares"
Console.WriteLine("Nombre en mayúsculas del administrativo: {0}", _
loAdmin.NombreMay)
loAdmin.pdtFHAlta = "8/10/01"
loAdmin.MesesActivo()
Console.Read()
End Sub
End Module
' clase abstracta,
' no podemos crear objetos Empleado
Public MustInherit Class Empleado
Public piID As Integer
Public psNombre As String
Public pdtFHAlta As Date
Public ReadOnly Property NombreMay() As String
Get
Return UCase(Me.psNombre)
End Get
End Property
' método abstracto;
' este método debe ser sobrescrito
' por la clase derivada
Public MustOverride Sub MesesActivo()
Public Sub VerDatos()
Console.WriteLine("Información sobre el empleado." & _
" ID:{0} - Nombre:{1} - Fecha de alta:{2}", _
Me.piID, Me.psNombre, Me.pdtFHAlta)
Console.WriteLine()
End Sub
End Class
' desde esta clase tendremos acceso
' a los miembros de la clase Empleado
Public Class Administrativo
Inherits Empleado
' en esta clase sobrescribimos este método
' declarado como abstracto en la clase abstracta
Public Overrides Sub MesesActivo()
Console.WriteLine("Entró en el mes de {0}", _
Format(Me.pdtFHAlta, "MMMM"))
Console.WriteLine("El número de meses que lleva es: {0}", _
DateDiff(DateInterval.Month, Me.pdtFHAlta, Now))
End Sub
Public Overrides Sub VerDatos()
'....
'....
End Sub
End Class
Código fuente 279

Clases selladas o no heredables

Toda clase que declaremos en nuestro código es heredable por defecto; esto supone un elevado grado de responsabilidad, en el caso de que diseñemos una clase pensando en que pueda ser utilizada por otros programadores que hereden de ella. Si en un determinado momento, necesitamos hacer cambios en nuestra clase, dichos cambios afectarán a las clases derivadas que hayan sido creadas. 
Por dicho motivo, si no queremos que nuestra clase pueda ser heredada por otras, debemos declararla de forma que no permita herencia; a este tipo de clase se le denomina clase no heredable o sellada (sealed). Para definir una clase no heredable, debemos utilizar la palabra clave NotInheritable en el momento de su declaración. 
En la Figura 121 hemos creado la clase Fichero como no NotInheritable, por ello, cuando a continuación declaramos la clase FiTexto e intentamos que herede de Fichero, se mostrará un mensaje de error en el editor de código, indicándonos que no es posible establecer esta relación de herencia puesto que Fichero es una clase sellada.
Figura 121. No es posible heredar de una clase NotInheritable.

Herencia y métodos constructores - II

Podemos no obstante, evitar la obligación de escribir un constructor en la clase derivada, si en la clase padre creamos un constructor sin parámetros. Como ya sabemos, la sobrecarga de métodos nos permite escribir varios métodos con el mismo nombre y diferente lista de parámetros. Modifiquemos para ello la clase Empleado como muestra el Código fuente 277.

Public Class Empleado
Public piID As Integer
Public psNombre As String
Public piSalario As Integer
' constructor parametrizado
Public Sub New(ByVal lsNombre As String)
Me.psNombre = lsNombre
End Sub
' constructor sin parámetros
Public Sub New()
psNombre = "hola"
End Sub
End Class
Public Class Administrativo
Inherits Empleado
' al disponer en la clase base de
' un constructor normal, ya no hay
' necesidad de crear un constructor
' en esta clase derivada
End Class
Código fuente 277

Finalmente, debemos apuntar dos reglas que debe cumplir todo método constructor de una subclase que llame al constructor de su clase base: en primer lugar, el constructor base debe ser llamado en la primera línea del constructor derivado; en segundo lugar, el constructor base sólo puede ser llamado una vez desde el constructor derivado.

Herencia y métodos constructores - I

Podemos crear una clase base que implemente un constructor y una subclase sin él. En esta situación, cuando instanciemos un objeto de la subclase, se llamará implícitamente al constructor de la clase base para ejecutar el código de inicialización. También es posible crear el constructor sólo en la clase derivada. 
Si ambas clases disponen de un constructor, en primer lugar se ejecutará el constructor de la clase base y después el de la clase derivada. Realmente, el primer constructor ejecutado corresponde a la clase Object, y sucesivamente, se irán ejecutando todos los constructores de la jerarquía de clases hasta llegar a la clase que originó la llamada. El problema sobreviene cuando en la clase base creamos un constructor parametrizado, ya que ello obliga a sus clases derivadas a crear también un método constructor dentro del cuál se haga una llamada al constructor de la clase base. Para llamar explícitamente a un método de la clase base desde una subclase utilizaremos la palabra clave MyBase, que contiene una referencia hacia la clase padre. 
Veamos un ejemplo, en el Código fuente 276 se crea una clase padre Empleado y la subclase Administrativo. Puesto que Empleado dispone de un constructor parametrizado, en Administrativo debemos crear también un constructor, y dentro de él llamar en primer lugar al constructor base.

Public Class Empleado
Public piID As Integer
Public psNombre As String
Public piSalario As Integer
' constructor parametrizado
Public Sub New(ByVal lsNombre As String)
Me.psNombre = lsNombre
End Sub
End Class
Public Class Administrativo
Inherits Empleado
' constructor normal
Public Sub New()
' llamada al constructor
' de la clase base
MyBase.New("Juan")
Me.piSalario = 100
End Sub
End Class
Código fuente 276