Conocimientos Teóricos
§ Qué es un constructor.
§ Tipos por valor y referencia.
Conocimientos Prácticos
§ Crear un constructor de una clase.
§ Crear un constructor con parámetros.
§ Manejar errores con un constructor.
§ Crear un array de instancias de clases.
§ Usar un array de instancias de clases como fuente de datos de un
control ListBox.
§ Declarar e inicializar arrays.
§ Implementar el método ToString para una clase.
En un anterior artículo creamos una clase CHospital que incluía las propiedades, dirección, teléfono,
etc. Cada vez que necesitamos crear una nueva instancia de la clase, se
requieren varias líneas de código. Para hacer más fácil la creación de la
instancia y simplificar el programa es posible crear un
constructor de la clase que le permita
ejecutar los tres pasos a la vez: declaración, instanciación e inicialización de los campos.
Qué es un Constructor
Un constructor es código que se
ejecutará cuando se utilice la palabra clave new para crear una instancia
de una clase. Las atribuciones que
tienen se pueden establecer en:
§ Un constructor puede validar los parámetros que se le pasan cuando se crea la clase.
§ Un constructor puede tener parámetros que le permiten inicializar los campos cuando
se crea el objeto.
§ Un constructor puede llamar a otro constructor para hacer parte
de su trabajo.
§ Una clase puede tener varios constructores,
tomando cada uno un conjunto diferente de
parámetros.
parámetros.
Un constructor
sin parámetros se declara de esta manera:
Public Sub New ()
End Sub
El constructor se
declara con la palabra clave pública
Public. Se debe tener un constructor público para utilizar la palabra clave new o para crear una instancia de la clase.
Si no definimos ningún constructor en la clase, el compilador genera uno público sin parámetros, pero hay que
asegurarse de que los campos de
la clase se inicializan correctamente
cuando se declaran.
También se puede declarar un
constructor utilizando la palabra
clave private. Si definimos un único constructor en la clase,
y es privada, el código del cliente
no será capaz de crear instancias
de clases.
Tipos por valor y referencia
A continuación se describe la diferencia entre pasar un valor a
una función o procedimiento por valor o por referencia.
Tipos
Todas las variables son del
tipo de referencia o por valor.
Una
variable de tipo de valor se crea cada vez que declara una variable como
§ Un tipo
entero (por ejemplo, Integer)
§ Un tipo doble
(por ejemplo, double)
§ Un
booleano (por ejemplo, Boolean)
§ Una enumeración (por ejemplo, System)
§ Una
estructura (por ejemplo, DateTime)
Una
variable de tipo de referencia se crea cada vez que declara una variable como
Una clase por ejemplo.
Una clase por ejemplo.
§ CHospital
§ Una interfaz
§ Una
matriz (por ejemplo, Dim numeros() as Integer)
§ Un
objeto (por ejemplo, Dim x As Object)
§ Una cadena
§ Un
delegado
La
diferencia entre una declaración de una variable por valor o por referencia, es
que la declaración por valor, asigna una ubicación de memoria a la variable
completa, es decir, la ubicación contiene el valor de la variable. Mientras que
en la declaración de una variable por referencia, la ubicación de memoria
asignada contiene un puntero a la ubicación de la instancia de la clase en la
memoria. De este modo las declaraciones por referencia sólo exigen una cantidad
de memoria suficiente para apuntar a una instancia de una clase.
Dim m_hospital
As CHospital
Cuando se crea una instancia de una clase a través del operador New, se asigna memoria para los campos declarados en la clase. Estas expresiones provocan la asignación de memoria para el Teléfono, el Nombre,
En la clase CHospital. El operador New devuelve la dirección de la ubicación de los campos
la memoria.
m_hospital = New
CHospital("918483923", "Bravo Murillo 327.", 78, "De la
Princesa ")
Estas
declaraciones, entonces, crean la instancia de la clase de CHospital y
establecen en la variable Un_hospital la ubicación de la nueva instancia.
Por lo
tanto, en el primer conjunto de
estados, ¿Cuál es el valor de m_hospital antes de que sea establecido como
en los últimos conjuntos de estados?
El valor de un
tipo de referencia antes de que se
inicialice es por definición Nothing. A continuación un fragmento de código que devuelve
true:
Dim m_hospital
As CHospital
MessageBox.Show((m_hospital Is
Nothing).ToString())
Para comprobar el valor de
los tipos de referencia Visual Basic proporciona
la palabra clave is, También proporciona el método IsNothing que permite probar el valor de
la referencia. Devuelve el valor
true si la referencia es Nothing. El siguiente código equivale al anterior:
Dim m_hospital As CHospital
MessageBox.Show((IsNothing(m_hospital).ToString()))
Cuando se intenta utilizar una referencia a
una propiedad o método es Nothing, se produce un error. Dicho error se denomina System.NullReferenceException. La forma correcta de programar será
comprobar si la referencia es Null o Nothing. Hay que tener cuidado al pasar la referencia
al método como parámetro. Si es así, el código tendrá menos control sobre el
estado de la variable antes de que
sea pasado al método.
Una nueva implementación de la Clase Hospital
En el artículo anterior, se establecían las propiedades
después de que se inicializa
la clase. Esta vez, vamos a crear
dos constructores diferentes para la inicialización de los campos de la
clase. El primero, un constructor
por defecto, crea una instancia exactamente
como se creó en
el primer documento. El segundo constructor inicializa la dirección, el teléfono, el nombre y el número de salas. Además, en lugar
de crear una clase para cada teléfono,
se guardarán los teléfonos en una matriz simple. Se utilizará la matriz como la fuente de datos del control ListBox.
Creación de constructores de la clase CHospital
Se modificará
la clase CHospital con la
adición de dos constructores y un
método.
Cómo crear un constructor sin parámetros
1. Sobre
la lista de nombre de la clase, elegir New.
Se creará
el siguiente código.
Public Sub New()
m_telefono
= ""
m_direccion = ""
m_nombre = ""
m_num_salas = 0
End Sub
A continuación crearemos un constructor con parámetros. Se pueden
crear múltiples constructores en una clase, entre ellos se distinguen por la
lista de parámetros.
Añadir un constructor con parámetros
Al crear constructores con parámetros,
recordar que cualquier código de un
constructor
es ejecutado después de las inicializaciones de campos. Por lo tanto, puede anular cualquier inicialización de los campos previa.
es ejecutado después de las inicializaciones de campos. Por lo tanto, puede anular cualquier inicialización de los campos previa.
2. Agregar el código siguiente después de las declaraciones de campo para declarar un
constructor con los parámetros: Teléfono y Dirección, Número de Salas y Nombre. No modificar el constructor existente, crear uno nuevo.
Public Sub New(ByVal telefono As String, ByVal
direccion As String,
ByVal numsalas As
Integer, ByVal
nombre As String)
End Sub
3. Agregar el siguiente código al constructor, para asegurarse de que ninguno de
los campos
quedarán en blanco. Si alguno quedase en blanco saltaría una excepción en el programa, una excepción que detiene la ejecución del programa y evita que la clase cree una instancia.
quedarán en blanco. Si alguno quedase en blanco saltaría una excepción en el programa, una excepción que detiene la ejecución del programa y evita que la clase cree una instancia.
Public Sub New(ByVal telefono As String, ByVal
direccion As String,
ByVal numsalas As
Integer, ByVal
nombre As String)
If (telefono <> "")
And (direccion <> "") And (nombre
<> "") And (Not
IsNothing(telefono)) And (Not IsNothing(direccion)) And
(Not IsNothing(nombre)) Then
m_telefono
= telefono
m_direccion = direccion
m_nombre = nombre
Else
Throw
(New Exception("Los
campos Teléfono o Dirección o Nombre están vacíos."))
End If
If (numsalas <> 0) And
(Not IsNothing(numsalas)) Then
m_num_salas
= numsalas
Else
Throw
(New Exception("El
campo Num salas está vacío."))
End If
End Sub
Si se elimina el constructor
sin parámetros, el código del cliente se vería obligado
a utilizar este nuevo constructor, lo que significa que ninguna instancia se
puede crear sin Teléfono, Dirección, Número de salas o Nombre conocido.
Cómo utilizar un constructor para manejar errores
Si en los
parámetros de entrada de un método o propiedad vienen como una cadena vacía,
podemos manejar la situación de las siguientes formas:
§ Para que la sustitución
del campo por una cadena vacía sea una decisión razonable, hay que asegurarse de que los
desarrolladores son conscientes de que cualquier instancia de CHospital puede contener una cadena vacía
para el teléfono la dirección, nombre o cero para el
número de salas. Se podría reemplazar el campo con
una cadena vacía pero si se ha facilitado un constructor
para forzar a que no haya cadenas vacías en los campos de teléfono, dirección, nombre y
número de salas. No se debe recurrir a esta opción pues
para ello se ha creado un constructor que establece las
propiedades en la etapa inicial.
§ Se podría lanzar una excepción, En este caso, no se crea una nueva instancia de la clase CHospital. La ejecución del programa se detendrá en la
línea de código que contiene la
palabra clave new. Se puede
utilizar un bloque try (Prueba) para responder a un error.
Cómo usar los Constructores
A continuación se mostrará cómo reemplazar múltiples líneas de código con una llamada
al constructor. También se creará
una matriz para aprovechar las propiedades de enlace de
datos de .NET mediante el uso de la
matriz como un origen de datos.
Cómo crear una instancia de CHospital usando el constructor
En el form_Load se pone:
Dim Hospital1 As CHospital
= New CHospital("918483923",
"Bravo Murillo 327.", 78)
Tener en cuenta que cuando se escribe, new, IntelliSense indica que tiene dos constructores definidos, como se muestra en la imagen siguiente.
Cuando hay más de un constructor, se dice que los
constructores están sobrecargados.
Cómo crear
un array de instancias de una clase
Se crean dos instancias de la clase CHospital y se añaden al array de instancias.
Dim Hospital1 As CHospital = New
CHospital("918483923", "Bravo Murillo 327.", 78, "De la
Princesa ")
Dim Hospital2 As CHospital = New
CHospital("918494324", "Santa Engracia 16.", 67, "Del Norte")
Dim m_hospitales As CHospital()
= New CHospital() {Hospital1, Hospital2}
Se va a crear una matriz de teléfonos para cargar un ListBox en el formulario y se creará una instancia de la clase CHospital con el constructor que tiene cuatro parámetros.
Pero en
vez de crear manualmente las instancias del array de objetos de la clase CHospital, vamos a
leer de la base de datos y cargarlo automáticamente, para ello hacemos:
Dim m_hospitales() As CHospital
Private Sub Form1_Load(ByVal sender As
System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
'se crea un array de objetos de la clase CHospital
ReDim Preserve m_hospitales(0)
Dim dt As New DataTable
Dim Valor As String
CadConexion
= "User ID=sasorolla;Initial
Catalog=model;Data Source=SRV-SERVIDOR\INSTANCIA;Password=ae2di5rs"
Conexion.conectar(CadConexion)
Dim daDB As New SqlClient.SqlDataAdapter("SELECT
* FROM dbo.tbHospital", Conexion.cnDB)
Dim i As Integer
daDB.Fill(dt)
i =
dt.Rows.Count
'Este For carga un Array de
Objetos CHospital con los valores de la
select de tbHospital mostrada arriba
For i = 0 To dt.Rows.Count - 1
ReDim Preserve
m_hospitales(i)
m_hospitales(i) = New CHospital()
m_hospitales(i).Codigo = dt.Rows(i).Item(1) 'código
m_hospitales(i).Nombre = dt.Rows(i).Item(2) 'nombre
m_hospitales(i).Direccion = dt.Rows(i).Item(3) 'Dirección
Valor
= dt.Rows(i).Item(2) 'Nombre del hospital
ComboBox1.Items.Add(Valor) 'Se muestra en el
combobox
Next
End Sub
Cuando se ejecuta la aplicación, aparece un combox cargado
con la lista de hospitales
Queremos que al pinchar sobre uno de ellos, se cargue el resto de
datos de cada hospital, y un listbox con los teléfonos. Añadir el siguiente
código después de la declaración del array, como fuente de datos del control
ListBox llamado ListTelefonos.
ListTelefonos.DataSource
= m_hospitales
Cómo responder a selecciones en el control ListBox
Lo que se ve en el ListBox cuando se
ejecuta la aplicación, son los teléfonos
de cada hospital.
Por que la fuente de datos del listbox
es un array de instancias hospital, y cada objeto de la lista representa un
hospital. En realidad podría mostrar cualquier dato (dirección, Nº de Salas)
Pero vamos a añadir código para que
muestre los teléfonos.
Como los teléfonos no se encuentran en
el array de objetos, hay que extraer de este, el código para hacer una nueva SELECT
con el valor elegido en el combo anterior.
El Código completo se puede introducir
en el evento ComboBox1_SelectedIndexChanged
Quedando:
Private Sub
ComboBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal
e As System.EventArgs) Handles
ComboBox1.SelectedIndexChanged
Dim Valor As String
Dim i As Integer
Dim dtable As New DataTable
Dim strQuery As String
strQuery = "SELECT strTelefono FROM dbo.tbHospital WHERE strID_HOS = '" &
m_hospitales(ComboBox1.SelectedIndex).Codigo & "'"
Dim daDBTel As New SqlClient.SqlDataAdapter(strQuery, Conexion.cnDB)
ListTelefonos.Items.Clear() 'borra los datos
que hubiera anteriormente en el
list
daDBTel.Fill(dtable)
i =
dtable.Rows.Count
For i = 0 To
dtable.Rows.Count - 1
Valor
= dtable.Rows(i).Item(0) 'Telefonos del hospital
ListTelefonos.Items.Add(Valor) 'Rellena los teléfonos en el
listBox
Next
TextDireccion.Text =
m_hospitales(ComboBox1.SelectedIndex).Direccion
End Sub
El resto de campos, como la dirección del hospital, se pueden
sacar directamente del array de Objetos.
TextDireccion.Text =
m_hospitales(ComboBox1.SelectedIndex).Direccion
Finalmente
el campo número de salas no se puede obtener directamente del Array de Objetos
como.
LblNumSalas.Text =
m_hospitales(ComboBox1.SelectedIndex).NumSalas
pues el dato está en otra
tabla, y al poner el código de arriba nos muestra un 0 que es lo que carga el
constructor.
Cómo declarar e inicializar Arrays
Se puede elegir entre varias formas de declarar e inicializar
un array de instancias de clase.
La sintaxis que se utilizó al principio fue la
siguiente:
Dim m_hospitales As
CHospital() = New CHospital() {Hospital1,
Hospital2}
Aquí hay otras formas de declarar
la misma matriz:
Dim
m_hospitales() As CHospital = New CHospital() {Hospital1, Hospital2}
Y otra forma
Dim
m_hospitales() As CHospital = {New CHospital("918483923",
"Bravo Murillo 327.", 78, "De la
Princesa "), New
CHospital("918494324", "Santa Engracia 16.", 67, "Del Norte")}
Y otra más:
Dim
m_hospitales(2) As CHospital
m_hospitales(0)
= New CHospital("918483923",
"Bravo Murillo 327.", 78, "De la
Princesa ")
m_hospitales(1)
= New CHospital("918494324",
"Santa Engracia 16.", 67, "Del Norte")
Finalmente
para mostrar el Número de salas de cada hospital, añadimos este código al
evento
ComboBox1_SelectedIndexChanged.
'----------------Rellenar el número de salas--------------------------
dtable.Clear()
strQuery = "SELECT
count(*) FROM dbo.tbSalaHos where strID_HOS ='" &
m_hospitales(ComboBox1.SelectedIndex).Codigo & "'"
Dim daDBNumSal As New SqlClient.SqlDataAdapter(strQuery, Conexion.cnDB)
daDBNumSal.Fill(dtable)
LblNumSalas.Text = CStr(dtable.Rows(0).Item(1))
Quedando
así el formulario al elegir un hospital.
Agregar un método ToString
Anteriormente se utilizó el campo Teléfono como string, para mostrarlo en su caja de texto.
Ahora utilizaremos una matriz para almacenar el Teléfono. De este modo se puede utilizar una
matriz como una fuente de datos de un cuadro de lista, esto representa una ventaja respecto a la
solución anterior. A continuación definiremos un método
ToString de la
clase de objetos que se desee mostrar.
Así, un cuadro de lista puede utilizar
el método ToString para mostrar
cada uno de los objetos de la matriz.
Public Overrides Function ToString() As
String
Return m_telefono
End Function
La palabra clave Overrides, en la declaración del método indica que el código debe utilizar el método ToString que está definido en la clase CHospital, en lugar del método ToString definido en la clase System.Object del .NET. El método definido en System.Object lo único
que hace es escribir el nombre de la
clase. Hay que tener en cuenta que en .NET, todas las clases derivan de la clase System.Object
Resumen
Crear un constructor
sin parámetros.
Public Sub New()
End Sub
Crear un
constructor con parámetros. Crear un constructor sin parámetros y añadirle los
que necesite, como si fuera un método de una clase.
Crear un
array de instancias.
Dim hospitales() As CHospital
Crear e inicializar
un array de instancias
Dim hospitales() As CHospital = {New
Hospital (), New Hospital ()}
Testear
una referencia para Nothing o Null.
Dim unHospital As CHospital
If IsNothing(unHospital) Then
‘ Añadir código
aquí para tratar si viene a Nothing
End If
Un constructor complejo en C#
//****************************************
//Constructor
con un parámetro de entrada
//inicializa
todos los parámetros de entrada
//inicializa
un array de referencias que son todas nothing o null
//el
bucle while lee una línea del fichero utilizando un objeto StreamReader. Su método
ReadLine devuelve nothing o null si es final de fichero.
//la
asignacion unalinea = lector.ReadLine () dentro del while devuelve el valor de
unalinea, el cual es testeado directamente dentro del while.
//Cada línea
es testeada para averiguar el comienzo de la clase buscando las palabras
"public class", este proceso es una vía simple para encontrar la
declaración
//de una
clase pero no es exaustivo por que otros modificadores podrían preceder la
palabra clave "class", para encontrar el nombre de la clase
//el código
busca la siguiente palabra despues de "class" usándo el método split.
//la línea
nombres = unalinea.Substring(comienzoNombre).Trim().Split(separadores); es un
ejemplo de encadenamiento de llamadas a métodos. Cada método retorna una
cadena,
//de
modo que se puede llamar a un método que retorna una cadena. Cuantas veces se
realicen estas llamadas encadenadas depende de la legibilidad del código.
public Archivofuente(string RutaCompleta)
{
m_LineasDeCodigo = 0; //inicializa la variable
m_NombresClase = new string[10]; //establece
el array con una tamaño de 10 objetos
m_ContadorClases = 0;
m_RutaCompleta = RutaCompleta; //inicializa
la variable con el valor tomado de la propiedad
try
{
System.IO.StreamReader lector
= new
System.IO.StreamReader(m_RutaCompleta); //definimos
un objeto lectura (Lector) de una clase del sistema y le asignamos el valor de
la variable m_RutaCompleta
int comienzoNombre;
string unalinea;
while ((unalinea = lector.ReadLine ()) != null) //asigna
los datos del objeto del sistema a la variable unalinea
{
unalinea =
unalinea.Trim(); //quita espacios en blanco a unalinea
//no cuenta espacios en blanco ni líneas de comentarios
if((unalinea != "") && (!unalinea.StartsWith("\\")))
{
m_LineasDeCodigo++;
}
if (unalinea.StartsWith("public class"))
{
comienzoNombre =
unalinea.IndexOf("class") + 6;
char[] separadores = {' ','\t','{'};
string[] nombres =
unalinea.Substring(comienzoNombre).Trim().Split(separadores);
string nombreClase = nombres[0].Trim();
m_NombresClase[m_ContadorClases++] = nombreClase;
}
}
lector.Close();
}
catch(System.Exception ex) //aqui entra si hay error
{
throw new System.Exception("Problemas parseando
el fichero fuente:" + ex.Message);
}
}
Y aquí la definición completa de la clase en C#.
Y aquí la definición completa de la clase en C#.
namespace ProyectoCreacionPropiedadesMetodos
{
public class Archivofuente
{
private string m_RutaCompleta;
private int m_LineasDeCodigo;
private string[] m_NombresClase;
private int m_ContadorClases;
//****************************************
//Constructor
con un parámetro de entrada
//inicializa
todos los parámetros de entrada
//inicializa
un array de referencias que son todas nothing o null
//el
bucle while lee una línea del fichero utilizando un objeto StreamReader. Su método
ReadLine devuelve nothing o null si es final de fichero.
//la
asignacion unalinea = lector.ReadLine () dentro del while devuelve el valor de
unalinea, el cual es testeado directamente dentro del while.
//Cada línea
es testeada para averiguar el comienzo de la clase buscando las palabras
"public class", este proceso es una vía simple para encontrar la
declaración
//de una
clase pero no es exaustivo por que otros modificadores podrían preceder la
palabra clave "class", para encontrar el nombre de la clase
//el código
busca la siguiente palabra despues de "class" usándo el método split.
//la línea
nombres = unalinea.Substring(comienzoNombre).Trim().Split(separadores); es un
ejemplo de encadenamiento de llamadas a métodos. Cada método retorna una
cadena,
//de modo que se puede llamar a un método que retorna una
cadena. Cuantas veces se realicen estas llamadas encadenadas depende de la
legibilidad del código.
public Archivofuente(string RutaCompleta)
{
m_LineasDeCodigo = 0; //inicializa la variable
m_NombresClase = new string[10]; //establece
el array con una tamaño de 10 objetos
m_ContadorClases = 0;
m_RutaCompleta = RutaCompleta; //inicializa
la variable con el valor tomado de la propiedad
try
{
System.IO.StreamReader lector
= new
System.IO.StreamReader(m_RutaCompleta); //definimos
un objeto lectura (Lector) de una clase del sistema y le asignamos el valor de
la variable m_RutaCompleta
int comienzoNombre;
string unalinea;
while ((unalinea = lector.ReadLine ()) != null) //asigna
los datos del objeto del sistema a la variable unalinea
{
unalinea =
unalinea.Trim(); //quita espacios en blanco a unalinea
//no cuenta espacios en blanco ni líneas de comentarios
if((unalinea != "")
&& (!unalinea.StartsWith("\\")))
{
m_LineasDeCodigo++;
}
if (unalinea.StartsWith("public class"))
{
comienzoNombre =
unalinea.IndexOf("class") + 6;
char[] separadores = {' ','\t','{'};
string[] nombres =
unalinea.Substring(comienzoNombre).Trim().Split(separadores);
string nombreClase =
nombres[0].Trim();
m_NombresClase[m_ContadorClases++] = nombreClase;
}
}
lector.Close();
}
catch(System.Exception ex) //aqui entra si hay error
{
throw new System.Exception("Problemas parseando
el fichero fuente:" + ex.Message);
}
}
//*****************************************************************
//Definición
de propiedades
//propiedad
de sólo lectura
//Devuelve
la ruta completa del fichero fuente
//Esta líneas
de abajo se muestran en IntelliSense
/// <summary>
/// Esta propiedad es de sólo lectura y devuelve una cadena
con la ruta completa donde se encuentra el archivo de código fuente
/// </summary>
public string RutaCompleta
{
get { return m_RutaCompleta; }
}
///<summary>
///Esta propiedad es de sólo lectura y devuelve el Nombre del
archivo sin la ruta del archivo fuente
///</summary>
public string NombreArchivo
{
get
{
int ultimaBarra = m_RutaCompleta.LastIndexOf("\\");
return m_RutaCompleta.Substring(ultimaBarra + 1);
}
}
///<summary>
///Esta propiedad es de sólo lectura y devuelve el número total
de clases definidas en el código fuente
///</summary>
public int CuentaClases
{
get { return m_ContadorClases; }
}
///<summary>
///Esta propiedad es de sólo lectura y devuelve el número de
líneas de código del fichero fuente, excluyendo líneas vacías y de comentarios
///</summary>
///
public int LineasDeCodigo
{
get { return m_LineasDeCodigo;
}
}
//Fin
Definición de propiedades
//*****************************************************************
//Definicion
de métodos
///<summary>
///Definición del método ObtenerClase que devuelve uno de los
nombres de las clases definidas en el fichero fuente, basado en un índice
///</summary>
///<param
name="indice"> Índice basado en cero</param>
///<returns> Un nombre de clase</returns>
public string ObtenerClase(int indice)
{
if (indice < m_ContadorClases) {
return m_NombresClase[indice];
}
else
{
throw new System.IndexOutOfRangeException( m_ContadorClases + "Es
la única clase definida.");
}
}
//Fin
Definicion métodos
}
}
No hay comentarios:
Publicar un comentario