índice del curso de VB .NET

Curso de iniciación a la programación
con Visual Basic .NET

Entrega número catorce, (10/Ago/2003)
Publicada el 11/Ago/2003


Seguramente te preguntarás que cómo es posible que con estas calores que hace en el mes de Agosto (al menos por estas latitudes) el Guille se ponga a escribir una nueva entrega... pues fíjate tú... aquí, sudando a chorro voy a intentar explicarte algo de lo que te dije que te iba  a explicar, aunque no sea dentro del plazo de un mes que "me prometí"...

En esta decimocuarta entrega del curso de iniciación a la programación con Visual Basic .NET vamos a ver, al menos esa es mi intención, algunas de las cosillas que te dije en las entregas anteriores que íbamos a tratar... Entre esas cosas intentaré explicarte qué son los campos, propiedades, campos de sólo lectura y la diferencia entre campos y propiedades, y si da tiempo, veremos también cómo crear propiedades por defecto (ahora llamadas indizadores, después verás porqué) y, si el calor lo permite, intentaré explicarte algo sobre la sobrecarga de procedimientos y todo esto aderezado con las instrucciones o modificadores de cobertura o ámbito que podemos usar en los miembros de una clase.

Empecemos por saber qué son

Los campos y las propiedades.

Los campos, tal como te comenté en la entrega doce, (je, je, ¿creías que no iba a haber ningún link a entregas anteriores?), son variables usadas a nivel de una clase. Los campos representan los datos de la clase. Como sabrás, (creo que lo he comentado en otras ocasiones), la razón de ser de una clase es poder manipular cierta información, los campos y propiedades representan los datos manipulados por la clase, mientras que los métodos manipulan (o permiten manipular) esos datos.

Los campos representan la información que "internamente" la clase manipulará. Dependiendo de que esos campos sean accesibles sólo dentro de la clase o también puedan ser accedidos desde cualquier instancia, (creada en la memoria), de la clase, estarán declarados de una forma o de otra. Para simplificar, después entraremos en detalle, podemos declarar cualquier miembro de una clase de dos formas, según el nivel de visibilidad o ámbito que queramos que tenga.
Si lo declaramos con el modificador de acceso Private, ese miembro sólo será accesible desde "dentro" de la clase, es decir, en cualquier sitio de la clase podremos usar ese miembro, pero no será accesible desde "fuera" de la clase, por ejemplo, en una nueva instancia creada.
Por otro lado, si declaramos un miembro de la clase como Public, ese miembro será accesible tanto desde dentro de la clase como desde fuera de la misma. Un miembro público de una clase siempre será accesible.

Nota:
Cuando digo miembro de una clase, me refiero a cualquier campo, propiedad, enumeración, método o evento.

Te aclaro que además del modificador Private y Public hay otros, los cuales veremos en esta o en otra entrega posterior.

Cuando declaramos un campo con el modificador Public, estamos haciendo que ese campo (o variable) sea accesible desde cualquier sitio, por otro lado, si lo declaramos como Private, sólo estará accesible en la propia clase.

Para clarificarlo, vamos a verlo con un ejemplo.
En el siguiente código vamos a declarar una clase que tendrá tres miembros públicos y uno privado.
De estos tres miembros públicos, dos de ellos serán campos y el tercero será un método que nos permitirá mostrar por la consola el contenido de esos campos. El campo privado simplemente lo usaremos dentro del método, en otro ejemplo le daremos una utilidad más práctica, ya que en este ejemplo no sería necesario el uso de ese campo privado, pero al menos nos servirá para saber que "realmente" es privado y no accesible desde fuera de la clase.

Public Class Prueba14
    ' campo privado
    Private elNombreCompleto As String
    ' campos públicos
    Public Nombre As String
    Public Apellidos As String
    '
    ' método público
    Public Sub Mostrar()
        elNombreCompleto = Nombre & " " & Apellidos
        Console.WriteLine(elNombreCompleto)
    End Sub
End Class

Module Module1
    Sub Main()
        ' creamos una nueva instancia de la clase
        Dim p As New Prueba14()
        '
        ' asigmos los valores a los campos públicos
        p.Nombre = "Guillermo"
        p.Apellidos = "Som"
        ' usamos el método para mostrar la información en la consola
        p.Mostrar()
        '
        ' esto dará error
        'Console.WriteLine(p.elNombreCompleto)
        '
        Console.WriteLine("Pulsa Intro")
        Console.ReadLine()
    End Sub
End Module

En la clase Prueba14 (que está declarada como Public) tenemos declarado Nombre y Apellidos con el modificador Public, por tanto podemos acceder a estos dos campos desde una nueva instancia de la clase, así como desde cualquier sitio de la clase.
Lo mismo es aplicable al método Mostrar, al ser público se puede usar desde la variable declarada en el procedimiento Main.
Por otro lado, el campo elNombreCompleto, está declarado como Private, por tanto sólo será accesible desde la propia clase, (esto se demuestra, porque se usa en el método Mostrar), y no desde fuera de ella, es decir, no podremos usar ese campo desde la instancia creada en Main por la sencilla razón de que es "privada" y por tanto no visible ni accesible desde fuera de la propia clase.
Si quitas el comentario que muestra por la consola el contenido del campo privado, comprobarás que dará un error en tiempo de compilación, ya que elNombreCompleto no es accesible por estar declarado como privado.

Si ya has trabajado antes con Visual Basic, a estos campos públicos también se les podían llamar propiedades, en Visual Basic .NET esto no ha cambiado y se pueden considerar a los campos públicos como si fuesen propiedades de una clase.

Pero, realmente, las propiedades son otra cosa diferente, al menos así deberíamos planteárnoslo y, tanto en Visual Basic .NET como en las versiones anteriores, además de declarar una propiedad usando la declaración de un campo público, también podemos usar la instrucción Property para que quede más claro cual es nuestra intención.
Como veremos a continuación, el uso de Property nos dará algo de más trabajo, pero también nos proporcionará mayor control sobre lo que se asigne a esa propiedad.
Por ejemplo, imagínate que no se debería permitir que se asigne una cadena vacía al al Nombre o al Apellido, en el ejemplo anterior, esto no sería posible de "controlar", ya que al ser "variables" públicas no tenemos ningún control sobre cómo ni cuando se asigna el valor, por tanto, salvo que creemos unos métodos específicos para asignar y recuperar los valores de esas dos "propiedades", no tendríamos control sobre la asignación de una cadena vacía.

Vamos a ver cómo se podría "controlar" esto que acabo de comentar, para simplificar, sólo vamos a comprobar si al nombre se le asigna una cadena vacía, para ello, vamos a crear un procedimiento que se encargue de asignar el valor al nombre y otro que se encargue de recuperar el contenido de esa "propiedad".
Estos métodos se llamarán AsignaNombre y RecuperaNombre.
El primero nos permitirá asignar un nuevo nombre y el segundo nos permitirá recuperar el contenido de esa "propiedad".

Public Class Prueba14_2
    ' variable (campo) privado para guardar el nombre
    Private elNombre As String
    '
    ' campo público para los apellidos
    Public Apellidos As String
    '
    ' método que permite asignar el nuevo nombre
    Public Sub AsignarNombre(ByVal nuevoNombre As String)
        ' comprobamos si no es una cadena vacía
        If nuevoNombre <> "" Then
            elNombre = nuevoNombre
        End If
    End Sub
    ' método que recupera el nombre asignado
    Public Function RecuperarNombre() As String
        Return elNombre
    End Function
    '
    ' método público para mostrar el nombre completo
    Public Sub Mostrar()
        Console.WriteLine(elNombre & " " & Apellidos)
    End Sub
End Class

Module Module1
    Sub Main()
        ' creamos una nueva instancia de la clase
        Dim p As New Prueba14_2()
        '
        ' asigmos los valores a los campos públicos
        p.AsignarNombre("Guillermo")
        p.Apellidos = "Som"
        ' usamos el método para mostrar la información en la consola
        p.Mostrar()
        '
        Console.WriteLine("El nombre es: " & p.RecuperarNombre)
        '
        Console.WriteLine()
        Console.WriteLine("Pulsa Intro")
        Console.ReadLine()
    End Sub
End Module

Aunque pueda parecer una "barbaridad" (casi lo es), analicemos el contenido de la clase Prueba14_2 para ver si nos enteramos de las cosas...

Declaramos una variable privada (un campo privado) llamado elNombre, este campo nos permitirá almacenar el nombre que se asigne usando el método AsignarNombre, el cual recibirá un parámetro, antes de asignar el contenido indicado en ese parámetro, comprobamos si es una cadena vacía, de ser así, no hacemos nada, ya que sólo se asigna el nuevo valor si el contenido del parámetro es distinto de una cadena vacía, con esto conseguimos lo que nos proponíamos: no asignar al nombre una cadena vacía. 
Por otro lado, si queremos acceder al contenido del "nombre", tendremos que usar el método RecuperarNombre, esta función simplemente devolverá el contenido del campo privado elNombre.
En cuanto al campo Apellidos, (el cual actuará como una propiedad, es decir, representa a un dato de la clase), éste permanece sin cambios, por tanto no se hace ninguna comprobación y podemos asignarle lo que queramos, incluso una cadena vacía.

Esto está muy bien, pero no es "intuitivo", lo lógico sería poder usar la propiedad Nombre tal como la usamos en el primer ejemplo, pero de forma que no permita asignarle una cadena vacía.
Esto lo podemos conseguir si declaramos Nombre como un procedimiento de tipo Property.
La forma de usarlo sería como en el segundo ejemplo, pero en lugar de usar AsignarNombre o RecuperarNombre, usaremos Nombre, que es más intuitivo, aunque, como comprobarás, "internamente" es como si usáramos los dos procedimientos que acabamos de ver.

 

¿Cómo declarar una propiedad como un procedimiento Property?

Para estos casos, el compilador de Visual Basic .NET pone a nuestra disposición una instrucción, que al igual que Sub o Function, nos permiten declarar un procedimiento que tiene un trato especial, este es el caso de Property.
La forma de usar Property es muy parecido a como se declara una función, pero con un tratamiento especial, ya que dentro de esa declaración hay que especificar por un lado lo que se debe hacer cuando se quiera recuperar el valor de la propiedad y por otro lo que hay que hacer cuando se quiere asignar un nuevo valor.
Cuando queremos recuperar el valor de una propiedad, por ejemplo para usarlo en la parte derecha de una asignación o para usarlo en una expresión, tal es el caso de que queramos hacer algo como esto: Dim s As String = p.Nombre
O como esto otro: Console.WriteLine(p.Nombre)
En estos dos casos, lo que queremos es recuperar el contenido de la propiedad.
Pero si lo que queremos es asignar un nuevo valor, esa propiedad normalmente estará a la izquierda de una asignación, como sería el caso de hacer esto: p.Nombre = "Guillermo". En este caso estaríamos asignando un nuevo valor a la propiedad Nombre.

Vale, esto está muy bien, pero... ¿cómo se declara un procedimiento Property?
Tranqui... que ya llegamos a eso...

Si queremos que Nombre sea realmente una propiedad (un procedimiento del tipo Property) para que podamos hacer ciertas comprobaciones tanto al asignar un nuevo valor como al recuperar el que ya tiene asignado, tendremos que crear un procedimiento como el que te muestro a continuación:

Public Property Nombre() As String
    ' la parte Get es la que devuelve el valor de la propiedad
    Get
        Return elNombre
    End Get
    ' la parte Set es la que se usa al asignar el nuevo valor
    Set(ByVal Value As String)
        If Value <> "" Then
            elNombre = Value
        End If
    End Set
End Property

Es decir, declaramos un procedimiento del tipo Property, el cual tiene dos bloques internos:
El primero es el bloque Get, que será el código que se utilice cuando queramos recuperar el valor de la propiedad, por ejemplo para usarlo en la parte derecha de una asignación o en una expresión.
El segundo es el bloque Set, que será el código que se utilice cuando queramos asignar un nuevo valor a la propiedad, tal sería el caso de que esa propiedad estuviera en la parte izquierda de una asignación.
Como puedes comprobar, el bloque Set recibe un parámetro llamado Value que es del mismo tipo que la propiedad, en este caso de tipo String. Value representa el valor que queremos asignar a la propiedad y representará lo que esté a la derecha del signo igual de la asignación. Por ejemplo, si tenemos esto: p.Nombre = "Guillermo", "Guillermo" será lo que Value contenga.

Fíjate que al declarar la propiedad, no se indica ningún parámetro, esto lo veremos en otra ocasión, pero lo que ahora nos interesa saber es que lo que se asigna a la propiedad está indicado por el parámetro Value del bloque Set.

Nota:
En el caso de Visual Basic .NET, el parámetro indicado en el bloque Set se puede llamar como queramos. En C#, siempre se llamará value, además de que no se indica el parámetro en el bloque set.
Por esa razón, se recomienda dejar el nombre Value, que es el que el VB .NET utiliza automáticamente cuando declaramos un procedimiento de tipo Property.

Fíjate que cuando creamos un procedimiento Property siempre será necesario tener un campo (o variable) privado que sea el que contenga el valor de la propiedad. Ese campo privado lo usaremos para devolver en el bloque Get el valor de nuestra propiedad y es el que usaremos en el bloque Set para conservar el nuevo valor asignado.
Ni que decir tiene que el tipo de datos del campo privado debe ser del mismo tipo que el de la propiedad.

La ventaja de usar propiedades declaradas como Property en lugar de usar variables (o campos) públicos es que podemos hacer comprobaciones u otras cosas dentro de cada bloque Get o Set, tal como hemos hecho en el ejemplo de la propiedad Nombre para que no se asigne una cadena vacía al Nombre.

Nota:
De todas formas, no es recomendable hacer mucho "trabajo" dentro de una propiedad, sólo lo justo y necesario.
Si nuestra intención es que dentro de una propiedad se ejecute un código que pueda consumir mucho tiempo o recursos, deberíamos plantearnos crear un método, ya que las propiedades deberían asignar o devolver los valores de forma rápida.

Debido a que en Visual Basic .NET los campos públicos son tratados como propiedades, no habría demasiada diferencia en crear una propiedad declarando una variable pública o usando un procedimiento Property, pero deberíamos acostumbrarnos a crear procedimientos del tipo Property si nuestra intención es crear una propiedad, además de que el uso de procedimientos Property nos da más juego que simplemente declarando una variable pública.

Nota:
En las versiones de Visual Basic anteriores a .NET la forma de declarar una propiedad es mediante tres tipos de procedimientos Property:
Property Get para devolver el valor de la propiedad,
Property Let para asignar el nuevo valor a la propiedad y
Property Set para asignar un objeto a esa propiedad.
En Visual Basic .NET sólo existen dos bloques: Get y Set, el segundo sería el equivalente a los dos últimos de Visual Basic 6.0

 

Propiedades de sólo lectura.

Una de las ventajas de usar un procedimiento Property es que podemos crear propiedades de sólo lectura, es decir, propiedades a las que no se pueden asignar valores nuevos, simplemente podemos acceder al valor que contiene.

Para poder conseguir que una propiedad sea de sólo lectura, tendremos que indicárselo al Visual Basic .NET de la siguiente forma:

Private valorFijo As Integer = 10
'
Public ReadOnly Property Valor() As Integer
    Get
        Return valorFijo
    End Get
End Property

Es decir, usamos la palabra clave (o modificador) ReadOnly al declarar la propiedad y tan sólo especificamos el bloque Get.
Si declaramos un procedimiento ReadOnly Property no podemos indicar el bloque Set, eso dará error.

 

Propiedades de sólo escritura.

De la misma forma que podemos definir una propiedad de sólo lectura, también podemos crear una propiedad de sólo escritura, es decir, una propiedad que sólo aceptará que se asignen nuevos valores, pero que no permitan obtener el valor que tienen... la verdad es que este tipo de propiedades no son muy habituales, pero podemos hacerlo.
Veamos cómo tendríamos que declarar una propiedad de sólo escritura.

Private valorEscritura As Boolean
'
Public WriteOnly Property Escribir() As Boolean
    Set(ByVal Value As Boolean)
        valorEscritura = Value
    End Set
End Property

Es decir, usamos el modificador WriteOnly al declarar la propiedad y sólo debemos especificar el bloque Set.
Si declaramos un procedimiento WriteOnly Property no podemos indicar el bloque Get, ya que eso dará error.

Cuando declaramos una propiedad de sólo lectura no podemos declarar otra propiedad con el mismo nombre que sólo sea de escritura. Si nuestra intención es crear una propiedad de lectura/escritura, simplemente con no complicarnos la existencia es suficiente, es decir, declaramos la propiedad sin indicar ni ReadOnly ni WriteOnly.

Nota:
En las versiones anteriores de Visual Basic si sólo queríamos una propiedad de sólo lectura, simplemente se declaraba el procedimiento Property Get y no era necesario indicar ReadOnly. El mero hecho de no declarar el procedimiento Property Let indica que es de sólo lectura. Esto mismo también es aplicable a C#, en ese lenguaje no es necesario indicar explícitamente si es o no de sólo lectura. Por tanto, si ya has programado con C# y quieres hacer algo en VB .NET tendrás que tener esto presente.

 

Campos de sólo lectura.

Lo mismo que existen propiedades de sólo lectura, podemos crear campos de los que sólo podamos leer el valor que contiene y no asignar ninguno nuevo.
Aunque en principio podrías pensar que con una constante se podría conseguir lo mismo que con un campo de solo lectura, como verás puede haber una pequeña diferencia de la que nos podemos aprovechar.
Para aclarar esto último, te diré, aunque ya lo sabes, que una constante es como una variable que tiene un valor fijo, es decir, una vez que se ha asignado, no se puede cambiar.
Los campos de sólo lectura a diferencia de las constantes, se pueden cambiar de valor, pero sólo en la definición, lo cual no se diferenciaría de la forma de declarar una constante, o dentro del constructor de la clase.
Esto último es algo que no se puede hacer con una constante, ya que las constantes siempre tienen el mismo valor, el cual se asigna al declararla.

Para aclarar todo este "lío" vamos a ver un par de ejemplos.
En el siguiente código vamos a declarar una constante y también un campo (o variable) de sólo lectura.

Public Const PI As Double = 3.14159
Public ReadOnly LongitudNombre As Integer = 50

En este código tenemos declarada una constante llamada PI que tiene un valor fijo. Las constantes siempre deben declararse con el valor que contendrán.
Por otro lado, tenemos un campo de sólo lectura llamado LongitudNombre, que es del tipo Integer y tiene un valor de 50.
Como podrás comprobar, en este caso no hay diferencia entre una constante y un campo de sólo lectura. La verdad es que si lo dejamos así, no habría ninguna diferencia entre una declaración y otra.
Bueno, si hay diferencia. Cuando declaramos una constante pública, ésta estará accesible en las nuevas instancias de la clase además de ser accesible "globalmente", es decir, no tendremos que crear una nueva instancia de la clase para poder acceder a la constante. Por tanto podríamos decir que las constantes declaradas en una clase son "variables" compartidas por todas las instancias de la clase. Es como si declarásemos la constante usando Shared o como si estuviese declarada en una clase de tipo Module, como ya te comenté anteriormente, la diferencia entre una clase de tipo Module y una de tipo Class es que en la primera, todos los miembros están compartidos (Shared), mientras que en la segunda, salvo que se indique explícitamente, cada miembro pertenecerá a la instancia de la clase, es decir, de cada objeto creado con New. De esto hablaremos más en profundidad en otra ocasión.

A lo que vamos, que siempre me enredo...
Y para que no sea yo el único que se lía... te voy a liar a ti un poquito... je, je...

Suponte que cambiamos la declaración de LongitudNombre de la siguiente forma:

Public Shared ReadOnly LongitudNombre As Integer = 50

En este caso, no habría diferencia con una constante.

Pero, esta no sería la forma habitual de declarar un campo de sólo lectura. Lo habitual es declararlo sin un valor inicial, aunque haciéndolo así nos aseguramos que tenga un valor predeterminado, en caso de que no se asigne ninguno nuevo.

Como te comenté hace unas líneas, la forma de asignar el valor que tendrá un campo de sólo lectura, sería asignándolo en el constructor de la clase. Por tanto, podríamos tener un constructor (Sub New) que reciba como parámetro el valor que tendrá ese campo de sólo lectura.
En el siguiente código vamos a declarar una clase que tendrá un campo de sólo lectura, el cual se asigna al crear una nueva instancia de la clase.

Public Class Prueba14_6
    Public ReadOnly LongitudNombre As Integer = 50
    '
    Public Sub New()
        '
    End Sub
    Public Sub New(ByVal nuevaLongitud As Integer)
        LongitudNombre = nuevaLongitud
    End Sub
    '
    ' Este será el punto de entrada del ejecutable
    Public Shared Sub Main()
        '
        ' si creamos la clase sin indicar la nueva longitud...
        Dim p As New Prueba14_6()
        ' el valor será el predeterminado: 50
        Console.WriteLine("p.LongitudNombre = {0}", p.LongitudNombre)
        '
        ' si creamos la clase sin indicar la nueva longitud...
        Dim p1 As New Prueba14_6(25)
        ' el valor será el indicado al crear la instancia
        Console.WriteLine("p1.LongitudNombre = {0}", p1.LongitudNombre)
        '
        Console.WriteLine()
        Console.WriteLine("Pulsa Intro")
        Console.ReadLine()
    End Sub
End Class

Esta clase tiene definidos dos constructores: uno sin parámetros y otro que recibe un valor de tipo Integer, ese valor será el que se use para el campo de sólo lectura.
En el Sub Main, el cual está declarado como Shared para que se pueda usar como punto de entrada del ejecutable, declaramos dos objetos del tipo de la clase, el primero se instancia usando New sin ningún parámetro, mientras que el segundo se crea la nueva instancia indicando un valor en el constructor, ese valor será el que se utilice para darle valor al campo de sólo lectura, cosa que se demuestra en la salida del programa:

p.LongitudNombre = 50
p1.LongitudNombre = 25

En los comentarios está aclarado porqué el objeto p toma el valor 50 y porqué usando p1 el valor es 25.

Y para que queden las cosas claras, decirte que una vez que hemos asignado el valor al campo de sólo lectura, ya no podemos modificar dicho valor, salvo que esa modificación se haga en el constructor. Por tanto, sólo podemos asignar un nuevo valor a un campo de sólo lectura en el constructor de la clase.
Dicho esto, si nuestras mentes fuesen tan retorcidas como para hacer esto, sería factible y no produciría ningún error:

Public Sub New(ByVal nuevaLongitud As Integer)
    LongitudNombre = nuevaLongitud
    LongitudNombre = LongitudNombre + 10
End Sub

Por la sencilla razón de que "dentro" del constructor podemos asignar un nuevo valor al campo de sólo lectura.

 

Bueno, esto ha sido todo por hoy.
Sí, ya se que me he dejado algunas cosas por explicar, pero eso será tema de otra (u otras) entregas.

Aquí te dejo algunos links de la ayuda de Visual Studio .NET que te ayudarán a comprender mejor lo que hemos tratado en esta entrega:

Tema Link en la ayuda de Visual Studio .NET
Instrucciones de uso de campos ms-help://.../cpgenref/html/cpconfieldusageguidelines.htm
Comparación de procedimientos de propiedades y campos ms-help://.../vbcn7/html/vbconPropertyProceduresVsPublicVariables.htm
Agregar campos y propiedades a una clase ms-help://.../vbcn7/html/vbconProperties.htm
Propiedades y procedimientos de propiedad ms-help://.../vbcn7/html/vaconpropertyprocedures.htm
Property (Instrucción) ms-help://.../vblr7/html/vastmproperty.htm
Get (Instrucción) ms-help://.../vblr7/html/vastmget.htm
Set (Instrucción) ms-help://.../vblr7/html/vastmset.htm

 

En la próxima entrega, si no cambio de idea, veremos algo más del ámbito de los miembros de una clase, es decir, más cosas sobre Public, Private y otros modificadores de ámbito. También intentaré explicarte algo más sobre la sobrecarga de procedimientos... pero eso será dentro de un tiempo... ¿un mes? esperemos que como mucho sea dentro de un mes...


Nos vemos.
Guillermo
Nerja, 10 y 11 de agosto de 2003


ir a la entrega anterior ir al índice del curso vb.NET
 
ir al Glosario .NET
ir a la siguiente entrega (o al índice si esta es la última)

la Luna del Guille o... el Guille que está en la Luna... tanto monta...