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.
Buscador
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"
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
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
• ToUpper( ), ToLower( ). Cambian la cadena a mayúsculas y minúsculas respectivamente.
Ver el Código fuente 316.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
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
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
Figura 123. Selección del interfaz implementado por una clase en el editor de código. |
Figura 124. Selección del miembro del interfaz a implementar. |
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.
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