índice del curso de VB .NET

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

Entrega número quince, (28~30/Ago/2003)
Publicada el 30/Ago/2003
(revisada el 01/Sep/03, re-revisada el 05/Dic/06)
Autor: Guillermo 'guille' Som

Pues aquí estamos de nuevo y ¡antes de que pase un mes! (pero no te ilusiones, que esto es casi un "error", je, je). Lo que pasa es que quiero aprovechar antes que te "tenga" que abandonar durante mucho tiempo; no, no me voy, pero en septiembre las cosas van a estar más "ajetreadas" para mi y... en fin... menos mal que ya estás acostumbrados a mis "desplantes"... ¡que remedio! como tampoco puedes quejarte a mis "superiores", pues eso es lo que hay...

Como te comenté en la entrega anterior, hoy veremos más cosas sobre el ámbito de los miembros de una clase... e incluso de las clases y demás código de Visual Basic .NET.

 

¿Qué es esto del ámbito?

El ámbito es la cobertura o duración de la vida de una variable, un procedimiento o una clase, es decir, hasta dónde son visibles y, por decirlo de alguna forma, durante cuanto tiempo.

Hasta ahora hemos estado usando dos instrucciones (o modificadores) que permiten indicar el ámbito de un miembro de una clase (o módulo), uno de ellos es Public y el otro es Private. Como habrás podido comprobar, (y si no lo has comprobado, al menos que te lo he dicho), al usar el modificador Public, permitimos que el miembro al que se le ha aplicado ese modificador sea visible desde cualquier sitio y por tanto estará accesible para que podamos usarlo desde la propia clase o desde fuera de ella; por otro lado, cuando usamos Private, estamos indicando que ese miembro es "privado" a la clase en la que se ha declarado y por tanto sólo podremos usarlo desde la propia clase.

Antes de entrar en detalles y para que lo comprendas mejor, (o si no para que lo comprendas mejor, al menos para que no te queden dudas, que para el caso es lo mismo), vamos a verlo con un ejemplo, el cual será parecido a uno de los usados en la entrega anterior.

Veamos el código y ahora te explico.

Public Class Prueba15
    Private elNombre As String
    Public Apellidos As String
    '
    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
    '
    ' método público para mostrar el nombre completo
    Public Sub Mostrar()
        Console.WriteLine(elNombre & " " & Apellidos)
    End Sub
End Class

En esta clase hemos definido dos campos: elNombre y Apellidos, el primero está declarado como Private, por tanto sólo será visible dentro de la clase, es decir: no podemos acceder a ese campo desde fuera de la clase, ahora lo comprobaremos. El segundo está declarado como Public, por tanto será accesible (o visible) tanto desde dentro de la clase como desde fuera de ella.

Cuando digo que será visible o accesible, me refiero a la posibilidad de poder usarlo. Como te imaginarás, para poder usar cualquier miembro de una clase "desde fuera", tendremos que declarar una variable del tipo de la clase e instanciarla por medio de New, ya que esta es la única forma de poder acceder a una clase... (¡Eh! Guille, eso no es del todo cierto) bueno, vale... ¡espabilao! ¡que eres un espabilao!, (espabilao = perspicaz, "listillo", etc.), en este caso, cuando digo clase, me refiero a un "tipo" declarado usando Class, ya que, como tendrás ocasión de comprobar, si el "tipo" lo declaramos con Module o Structure, la cosa cambia... pero no te compliques la vida todavía, que ya tendrás tiempo de complicártela.
A lo que iba, en ese código que he mostrado, (así está mejor dicho), para poder acceder a los miembros de la clase, tendremos que crear una nueva instancia; pero a los miembros a los que podemos acceder, sólo serán los que estén declarados como Public.

Además de esos dos campos, también tenemos una propiedad y un método, ambos declarados como Public.
El método Mostrar, que es un procedimiento de tipo Sub, puede acceder tanto a la variable declarada como Private como a la que hemos declarado como Public, ya que ambas variables (en este caso también campos de la clase) tienen la "cobertura" suficiente para que sean vistas (o accesibles) desde cualquier sitio "dentro" de la propia clase.
La propiedad Nombre, tiene dos bloques de código, uno será la parte Get (la que devuelve el valor de la propiedad) y la otra es el bloque Set (el que asigna el valor a la variable privada). Desde los dos bloques podemos acceder a la variable privada elNombre, ya que el ámbito de dicha variable es: toda la clase.
Pero si te fijas, en el bloque Set de la propiedad se ha declarado una variable, esa variable sólo será "visible" dentro de ese bloque y no podrá ser accedida desde ningún otro sitio, en cuanto acabe ese bloque, la variable Value "desaparece" y deja de existir... (si no te aclaras, no te preocupes por ahora, que esto lo aclararemos).

Antes de seguir con más explicaciones, veamos el código desde el que usaremos esa clase.

Module Module1
    Sub Main()
        Dim n As New Prueba15()
        '
        n.Nombre = "Guille"
        n.Apellidos = "Som"
        n.Mostrar()
    End Sub
End Module

En este módulo tenemos un procedimiento en el cual declaramos una variable del tipo Prueba15. Creamos una nueva instancia en la memoria y usamos los miembros públicos. Si intentas acceder al miembro privado, por ejemplo para hacer esta asignación: n.elNombre = "Guille", el compilador de Visual Basic .NET se quejará, ya que no "sabe" nada de un miembro llamado elNombre. Estando en el Visual Studio .NET nos mostrará la variable señalada con unos dientes de sierra y si posicionas el ratón encima te informará del error, el cual, tal como podemos ver en la figura 1, indica que la variable está declarada como privada y, por tanto, no accesible.


Figura 1

 

Antes de explicarte los diferentes modificadores de ámbito (o de niveles de visibilidad) que podemos aplicar a los distintos elementos de un proyecto de Visual Basic .NET, vamos a aclarar unos cuantos conceptos.

Cómo declarar las variables.

No, no estás volviendo al pasado, estás en la entrega 15.
Para declarar una variable podemos hacerlo de varias formas, dependiendo dónde se declaren esas variables. Usando la misma nomenclatura que en la documentación de Visual Studio .NET, existen varios niveles de ámbito, es decir, existen distintos "sitios" en los que podemos declarar una variable, (o cualquier otro elemento, pero para simplificar, vamos a centrarnos en las variables), estos niveles son:
Bloque: Un bloque puede ser, por ejemplo, un If... Then, un Do... Loop o los bloques Get y Set.
Procedimiento: Un Sub, Function o Property.
Módulo: Una clase (Class), módulo (Module) o estructura (Structure)
Espacio de nombres: Un bloque Namespace.

Nota:
Fíjate que en Visual Basic, todos los "bloques" o niveles (que siempre están encerrados entre un inicio y un final), suelen terminar con End <tipo de nivel>. Por ejemplo un bloque If... Then ocupará desde el If... Then hasta el End If, (aclaro que salvo que esté definido como si no fuese un bloque). Las excepciones son For que llegará hasta el Next y Do que lo hará hasta Loop.

Los tipos de niveles están enumerados de menor a mayor, es decir, una variable declarada en un nivel de módulo, tendrá más cobertura que otra declarada en un bloque If.
Lo cual quiere decir que si declaramos una variable a nivel de módulo, esa variable será visible desde cualquier otro "nivel" inferior. Cosa que hemos comprobado en el código de la clase anterior. La variable elNombre declarada a nivel de módulo (en este caso el módulo es uno del tipo Class) es visible en un nivel inferior, en ese ejemplo podemos acceder a ella desde los dos procedimientos que tenemos.

Como te comentaba al principio de esta sección, las variables las podemos declarar de varias formas, dependiendo del "nivel" en el que la declaremos, salvo a nivel de Namespace, ya que en un Namespace no podemos declarar una variable.

Como hemos visto desde el principio de este curso, lo habitual para declarar una variable es usando Dim. De hecho, usar Dim es la única forma de declarar una variable en los dos niveles inferiores (bloque y procedimiento). Pero en el nivel de módulo podemos usar tanto Dim como las instrucciones Private, Public (y las que ahora veremos). Cuando declaramos una variable con Dim en un nivel de módulo es lo mismo que declararla con Private.

 

Las variables declaradas en los procedimientos.

Como acabo de comentarte, las variables en un procedimiento, (Sub, Function o Property), tenemos que declararlas con Dim. Esto significa que las variables son privadas (o locales) al procedimiento, por tanto sólo se podrán usar dentro del procedimiento y desde fuera de él no sabrán de la existencia de esas variables.
Pero aún hay más, si hubiese una variable de "nivel superior" que se llamase igual que una variable "local" al procedimiento, ésta última ocultaría a la que está declarada en un nivel superior.
Por ejemplo, en la clase Prueba15 tenemos la variable elNombre, declarada a nivel de módulo, ésta variable podrá usarse dentro del procedimiento Mostrar, pero si el procedimiento Mostrar lo re-escribimos de esta forma:

Public Sub Mostrar()
    ' prueba declarando una variable "local"
    ' que se llama igual que otra declarada a nivel de módulo
    Dim elNombre As String
    '
    Console.WriteLine(elNombre & " " & Apellidos)
End Sub

La variable declarada dentro del procedimiento "ocultaría" a la declarada a nivel de módulo, por tanto, ahora no se mostraría el nombre que hubiésemos asignado a la clase, sino una cadena vacía, ya que no hemos asignado nada a esa variable.

Pero aún hay más, si el parámetro de un procedimiento recibe una variable que se llama como otra declarada a nivel superior, esa variable también ocultaría a la de nivel superior.
Por ejemplo, si tenemos este procedimiento:

Public Sub Mostrar2(ByVal elNombre As String)
    Console.WriteLine(elNombre & " " & Apellidos)
End Sub

El nombre de la variable usada como parámetro también se llama igual que la declarada a nivel de módulo, por tanto esa variable (la del parámetro) ocultará a la otra (la declarada a nivel de módulo).
Cuando digo que ocultará, me refiero a que la variable que "eclipsa" a la otra no la sustituye, ya que realmente son dos variables diferentes y cada una tendrá su propia dirección de memoria, por tanto el valor asignado a esas variables locales no afectará en nada a la variable declarada a nivel de módulo.

Cuando declaramos variables locales (al procedimiento), éstas durarán (o existirán) mientras dure el procedimiento, es decir, cuando se sale del procedimiento, el valor que tuviera asignado, se perderá. Cuando se vuelva a entrar en el procedimiento se volverán a crear esas variables y el valor que antes tenían ya no será recordado.

Nota:
Para aclarar esto, seguramente tendría que dar explicaciones de cómo funciona la pila (stack) y cómo se almacenan las variables locales en la pila, pero creo que esto es un tema que lo que podría hacer es complicarte aún más las cosas... de todas formas, sin que lo tomes como "doctrina", te diré que cada vez que se usan las variables locales a un procedimiento (incluidos los parámetros declarados con ByVal), estos valores (o referencias en el caso de que sean tipos por referencia), se almacenan en una memoria especial llamada "pila", cuando se sale del procedimiento se "limpia" la pila y por tanto esas variables se pierden.

Pero, habrá ocasiones en que no queramos que esos valores de las variables locales se pierdan, en Visual Basic podemos hacerlo declarando las variables usando la instrucción Static en lugar de Dim.
Static le indica al compilador que esa variable debe mantener el valor entre distintas llamadas al procedimiento, para que de esa forma no se pierda el valor que tuviera.
Las variables declaradas con Static siguen siendo locales al procedimiento y también "ocultarán" a variables declaradas a nivel de módulo que se llamen de igual forma. Es decir, funcionan como las declaradas con Dim, pero mantienen el valor, además de que Static sólo se puede usar para declarar variables dentro de procedimientos.
Veamos un ejemplo que lo aclare.
Si escribimos este código (el cual no tendrá nada que ver con la clase y módulo que hemos usado antes):

Module Modulo2
    Sub Main()
        Dim i As Integer
        For i = 1 To 3
            Prueba()
        Next
    End Sub
    '
    Sub Prueba()
        Dim numDim As Integer
        Static numStatic As Integer
        '
        Dim i As Integer
        For i = 1 To 10
            ' incrementamos las variables
            numDim += 1
            numStatic += 1
        Next
        Console.WriteLine("El valor de numDim = {0}, " & _
                          "el valor de numStatic = {1}", _
                          numDim, numStatic)
    End Sub
End Module

Comprobarás que el valor de la variable numStatic mantiene el valor entre distintas llamadas, (mostrando los valores 10, 20 y 30), mientras que el valor de la variable numDim siempre mostrará 10.
Fíjate también que en el procedimiento Main tenemos una variable llamada "i", esta variable al ser "local" al procedimiento no tendrá nada que ver con la variable "i" declarada en el procedimiento Prueba.

Nota para curiosos:
En C# no existen las variables Static, realmente la instrucción static tiene otro significado diferente.

Si miramos el código IL generado, el compilador lo que hace es declarar una variable a nivel de módulo con un nombre especial. Cada variable declarada como Static tendrá una variable declarada a nivel de módulo, en el caso de numStatic, el compilador la ha declarado de esta forma:
.field private static specialname int32 $STATIC$Prueba$001$numStatic
Esto es código IL, el cual puedes ver usando la utilidad ILDASM.EXE.

 

Las variables declaradas en los "bloques"

En los bloques podemos declarar variables "privadas" a esos bloques, pero en esta ocasión sólo pueden declararse con Dim.
Las variables declaradas en un bloque sólo serán visibles "dentro" de ese bloque, por tanto no podrán ser accedidas desde fuera del bloque.
Las limitaciones que tenemos en Visual Basic .NET respecto a las variables "locales" declaradas en un bloque, es que no pueden llamarse de igual forma que una declarada en el mismo "nivel" en el que se usa el bloque, normalmente en un procedimiento, ya que no podemos usar un bloque fuera de un procedimiento. Cuando digo bloque, me estoy refiriendo a un "bloque" IF, DO, FOR, CASE, etc.

Una variable declarada en un bloque si se puede llamar como otra declarada a nivel de módulo, en ese caso, la variable "local" ocultará a la declarada a nivel de módulo.

Lo que debemos tener presente es que las variables declaradas en un bloque "mantienen" el valor mientras dure la vida del procedimiento en el que se encuentran. Es como si fuesen "estáticas" mientras dure el procedimiento, aunque, cuando termina el procedimiento, ese valor se pierde.
También debemos saber que esto sólo es aplicable si no hemos asignado un valor a la variable al declararla, ya que si se asigna un valor al declararla, siempre usará ese valor.

¿No te aclaras? Pues entonces mejor será verlo con un ejemplo.

Module Modulo3
    Sub Main()
        Dim i As Integer
        For i = 1 To 2
            Console.WriteLine("Llamando a Prueba3, cuando i vale {0}", i)
            Prueba3()
        Next
    End Sub
    '
    Sub Prueba3()
        Dim i As Integer
        For i = 1 To 5
            If i > 1 Then
                Dim j As Integer
                Dim k As Integer = 2
                '
                ' incrementamos las variables
                k += 1
                j += 1
                Console.WriteLine("j = {0}, k = {1}", j, k)
            End If
        Next
    End Sub
End Module

En este ejemplo, dentro del bloque If i > 1 Then... tenemos dos variables locales al bloque. La variable j se ha declarado de forma "normal", mientras que la variable k se ha declarado usando un valor inicial. Cada vez que llamemos al procedimiento Prueba3, el bucle se repetirá 5 veces y la condición para entrar en el bloque se ejecutará 4 de esas 5 veces, y como podemos comprobar la variable j va tomando valores desde 1 hasta 4, mientras que k siempre muestra el valor 3, (dos que le asignamos al declarar y uno del incremento).

La salida de esta prueba sería la siguiente:


Llamando a Prueba3, cuando i vale 1
j = 1, k = 3
j = 2, k = 3
j = 3, k = 3
j = 4, k = 3
Llamando a Prueba3, cuando i vale 2
j = 1, k = 3
j = 2, k = 3
j = 3, k = 3
j = 4, k = 3
 

En estos casos, lo recomendable sería declarar la variable j a nivel de procedimiento, ya que, además de que no podemos tener una variable que se llame j, nos aseguramos de que no nos "confundiremos" y podamos pensar de que el valor que tendrá la variable será cero, que es lo que podría parecer.
En C#, ese problema no existe, ya que todas las variables declaradas a nivel de un bloque deben tener un valor inicial, el cual, a diferencia del Visual Basic, no es automático. Y ya que estamos con C#, (que se que a algunos os gusta que haga este tipo de comparaciones), en C# tampoco se puede tener una variable declarada en un bloque que se llame igual que otra declarada en el mismo nivel en el que se encuentra el bloque.

 

Las variables declaradas a nivel de módulo.

Para ir completando conceptos, vamos a terminar viendo las variables declaradas a nivel de módulo.
Te recuerdo que cuando hablo de módulo, me refiero tanto a un Module como a un Class o a un Structure.

En las declaraciones de las variables declaradas a nivel de módulo pueden entrar en juego otros modificadores de visibilidad, estos "modificadores", como comprobaremos, también serán aplicables a los procedimientos.

Cuando declaramos una variable a nivel de módulo, ésta será visible en todo el módulo (clase) en el que se ha declarado, además, dependiendo del modificador de nivel de visibilidad que tenga, podrá ser "vista" (o estará accesible) desde otros sitios. Recuerda que las variables declaradas en los módulos se les llama "campos" y si son accesibles desde fuera de la clase, pueden llegar a confundirse con las propiedades, al menos en la forma de usarla.

 

Los niveles de visibilidad (accesibilidad o ámbito).

Veamos un resumen de los modificadores de visibilidad que podemos usar en los programas de Visual Basic .NET y C#:

Modificador  (VB) (C#) Descripción
Dim N.A Declara una variable en un módulo, procedimiento o bloque.
Cuando se usa para declarar una variable a nivel de módulo, se puede sustituir por Private.
Private private El elemento declarado sólo es visible dentro del nivel en el que se ha declarado.
Public public El elemento es visible en cualquier parte.
Friend internal El elemento es visible dentro del propio ensamblado (proyecto).
Protected protected El elemento es visible sólo en las clases derivadas.
Protected Friend protected internal El elemento es visible en las clases derivadas y en el mismo ensamblado.

Hay que tener en cuenta que el ámbito que tendrán los elementos declarados, (por elemento entenderemos que es una variable, un procedimiento, una clase, módulo, estructura o una enumeración), dependerán del nivel en el que se encuentran. Por ejemplo, cuando declaramos un elemento con el modificador Public dentro de un nivel que ha sido declarado como Private, el elemento declarado como público no podrá "sobrepasar" el nivel privado. Por otro lado, si se declara un elemento como Private, será visible desde cualquier otro elemento que esté en el mismo nivel o en un nivel inferior.

Nota:
En la documentación de Visual Studio .NET, ámbito es el nivel de visibilidad que puede tener, ya sea a nivel de bloque, procedimiento, módulo o espacio de nombres.
Y la accesibilidad es la "visibilidad" de dicho elemento, si es público, privado, etc.
Te aclaro esto, ya que yo suelo usar indistintamente ámbito y accesibilidad para referirme a cómo podemos acceder (o desde dónde) a un elemento, ya sea una variable, procedimiento o clase. Espero no confundirte demasiado...

Otra cosa a tener en cuenta es que cuando declaramos en Visual Basic .NET una clase, una estructura, una enumeración o un procedimiento sin especificar el ámbito, (ver los ejemplos anteriores), de forma predeterminada será Public. En C# el ámbito predeterminado es private.

Que porqué te digo también lo de C#... porque hoy me ha dado por ahí... además de que algunos me lo habéis pedido y como casi me cuesta el mismo trabajo... pero no sé si esto será una tónica general en futuras entregas, todo dependerá de la "aceptación" que tenga... si no te parece bien dímelo, igualmente si te parece una buena idea, dímelo también. ¿Cómo decírmelo? Enviándome un mensaje que en el asunto diga algo como: Me parece bien que digas lo de C# en el curso de VB o como esto otro: Guille passa de decir las cosas de C# en el curso de VB ¡que me lías! (pero digas lo que digas, el Guille hará lo que le parezca... je, je...) bueno, vale, a lo mejor tengo en cuenta tu opinión... pero, por favor, opina. Gracias.

Los Namespace no pueden tener modificadores de acceso, siempre serán accesibles.

Los módulos (o clases) declaradas como Module, tampoco pueden tener modificadores de acceso, y también se consideran como Public, por tanto, siempre accesibles.
Además de que cualquier elemento declarado en un Module podrá usarse sin necesidad de indicar el nombre del módulo; dónde puedan usarse dependerá del ámbito que le hayamos dado.

Ya que estamos con esto de los Module, voy a explicártelo mejor o con más detalle.

Diferencia entre Class y Module y miembros compartidos.

En los ejemplos que te estoy mostrando, hemos visto que estoy usando tanto Class como Module, según el caso.

Por regla general, se usarán las clases de tipo Class cuando "realmente" queramos crear una clase. Para poder acceder a los miembros de una clase, tendremos que crear una nueva instancia en la memoria.
Por otro lado, usaremos las clases de tipo Module cuando queramos tener "miembros" que puedan ser usados en cualquier momento, sin necesidad de crear una nueva instancia de la clase, de hecho no podemos crear una instancia de un tipo declarado como Module. El que podamos acceder a los miembros de un Module sin necesidad de crear una nueva instancia, es por el hecho de que esos miembros son miembros compartidos.

Bueno, realmente no sólo porque son miembros compartidos, sino porque el compilador de Visual Basic .NET trata de forma especial a los "tipos de datos" declarados como Module. Los cuales, como comenté hace unas líneas, se pueden acceder sin necesidad de usar el nombre del módulo en el que están incluidos.

Pero los Module no son los únicos "poseedores" de la exclusiva de los miembros compartidos, ya que podemos declarar clases, (del tipo Class), que tengan miembros compartidos. De hecho, esta es la única forma de hacerlo en C#.
Veamos cómo podemos crear estas clases con miembros compartidos, y lo que es más importante (o casi), cómo decirle al Visual Basic .NET que un miembro de una clase es un miembro compartido.

Para que un miembro de una clase esté compartido, hay que usar el modificador Shared (static en C#).
Esta instrucción le indicará al runtime de .NET que ese miembro puede ser accedido sin necesidad de crear una nueva instancia de la clase. Para simplificar, te diré que es como si el CLR hubiese creado una instancia de esa clase y cada vez que queramos acceder a un miembro compartido, usara esa instancia para acceder a ese miembro, (seguramente no será así, pero esa es la impresión que da).
La única "pega" con los miembros compartidos es que no se pueden usar en ellos variables u otros miembros que no estén compartidos.

Nota:
A los miembros de una clase que no están compartidos se les llama miembros de instancia, ya que sólo podemos acceder a ellos desde una instancia creada en la memoria por medio de New.

Otra nota:
A los miembros compartidos también se les llama miembros estáticos, así que, si en algún sitio te dicen que este o aquel miembro es estático, ya sabes que están diciendo que son compartidos... seguramente esa es la razón de que en C# se use static en lugar de Shared. Además, en la documentación de Visual Studio .NET (versión en castellano), cuando se tratan temas de Visual Basic, se dice que son miembros compartidos, mientras que al tratar temas de C# dicen que son miembros estáticos. Así que... ¡que no te confundan! (que ya me encargo yo de confundirte... je, je).

 

Cómo declarar miembros compartidos en una clase, y cómo usarlos.

Veamos un pequeño código en el que tenemos una clase con elementos compartidos y no compartidos.

Class Prueba15_5
    ' miembros compartidos
    Public Shared elNombreShared As String
    Public Shared Sub MostrarCompartido()
        Console.WriteLine("Este procedimiento está declarado como compartido (Shared)")
        Console.WriteLine("Sólo podemos acceder a miembros compartidos: {0}", elNombreShared)
    End Sub
    '
    ' miembros de instancia
    Public elNombreInstancia As String
    Public Sub MostrarInstancia()
        Console.WriteLine("Este procedimiento es de instancia.")
        Console.WriteLine("Podemos acceder a miembros de instancia: {0}", elNombreInstancia)
        Console.WriteLine("y también a miembros compartidos: {0}", elNombreShared)
    End Sub
    '
End Class

En esta clase, hemos declarado un "campo" y un procedimiento compartido (usando Shared) y otro campo y otro procedimiento no compartido (de instancia).
Desde el procedimiento compartido sólo podemos acceder al campo compartido.
Pero desde el procedimiento de instancia podemos acceder tanto al miembro compartido como al de instancia.
Recuerda que una instancia es un nuevo objeto creado en la memoria. Por tanto cada instancia (u objeto en la memoria) tendrá su propia copia de los miembros de instancia; mientras que de los miembros compartidos sólo existe una copia en memoria que será común para todos y cada uno de los objetos instanciados.

Esto lo podemos comprobar en el siguiente código.

Module Modulo5
    Sub Main()
        ' para acceder a miembros compartidos
        ' usaremos el nombre de la clase:
        Prueba15_5.elNombreShared = "Shared"
        Prueba15_5.MostrarCompartido()
        '
        Console.WriteLine()
        '
        ' creamos una instancia de la clase
        Dim o As New Prueba15_5()
        '
        o.elNombreInstancia = "Instancia"
        o.MostrarCompartido()
        o.elNombreShared = "Shared cambiado"
        o.MostrarInstancia()
        '
        Console.WriteLine()
        '
        ' creamos otro objeto del mismo tipo (otra instancia)
        Dim o2 As New Prueba15_5()
        '
        o2.elNombreInstancia = "Instancia 2"
        o2.MostrarInstancia()
        'o2.MostrarCompartido()
        Prueba15_5.MostrarCompartido()
    End Sub
End Module

La salida de este código será la siguiente:


Este procedimiento está declarado como compartido (Shared)
Sólo podemos acceder a miembros compartidos: Shared

Este procedimiento está declarado como compartido (Shared)
Sólo podemos acceder a miembros compartidos: Shared
Este procedimiento es de instancia.
Podemos acceder a miembros de instancia: Instancia
y también a miembros compartidos: Shared cambiado

Este procedimiento es de instancia.
Podemos acceder a miembros de instancia: Instancia 2
y también a miembros compartidos: Shared cambiado
Este procedimiento está declarado como compartido (Shared)
Sólo podemos acceder a miembros compartidos: Shared cambiado
 

Como puedes comprobar, para acceder a los miembros compartidos, hemos usado el nombre de la clase.
De esta forma sólo podemos acceder a los miembros compartidos.
Por otro lado, al crear una instancia de esa clase, podemos acceder tanto a los miembros compartidos como a los de instancia, en este ejemplo, se asigna un valor al campo de instancia, se llama al procedimiento compartido y después cambiamos el contenido del campo compartido y llamamos al procedimiento de instancia.
En la segunda clase, al mostrar el contenido del campo compartido, mostrará el valor que el objeto (de instancia) le asignó, ya que ese campo está compartido por todas las instancias.
En la última línea de código he usado el método compartido usando la clase, pero el resultado sería el mismo si se usara el objeto para llamar al método compartido.

Nota sobre C#:
En C# no existe el tipo Module, siempre habrá que declararlo como class, e indicar mediante static los miembros que queramos que estén compartidos.

Decirte que en el caso de los campos compartidos, (realmente son variables), si no indicamos el nivel de visibilidad, éstos serán Private y que se pueden declarar con cualquiera de los "niveles" de visibilidad.
Por otro lado, los procedimientos compartidos, si no se indica el nivel de visibilidad, serán Public, (recuerda que todos los procedimientos, ya sean de instancia o compartidos, siempre tienen el nivel predeterminado de Public), y también podemos usar cualquiera de los niveles de visibilidad que queramos.
En ambos casos, si indicamos Protected, sólo podremos acceder a ellos a partir de una clase derivada.

 

 

Sobre el código y los datos de una clase.

Para terminar esta entrega, te voy a contar algo, que si bien no es necesario saber para poder trabajar con las clases, puede que alguna vez te surja la curiosidad de saber cómo maneja el Visual Basic .NET y en general, creo, que todos los lenguajes de programación orientados a objeto o al menos los que utilizan las clases, incluido el Visual Basic clásico.

A lo que me refiero es a si existe "sobrecarga" (en este caso me refiero a que el programa se sobrecargue en el sentido de que tenga más consumo de memoria/trabajo) cuando se utilizan varios objetos en la memoria.

Como sabemos cada vez que creamos (instanciamos) un objeto en la memoria, estamos reservando espacio para ese objeto, la pregunta es:
¿Se reserva espacio tanto para los datos como para el código de los procedimientos?
La respuesta es que sí... Si se reserva espacio tanto para el código como para los datos, pero... el código siempre es el mismo para todos los objetos creados en la memoria, es decir, sólo existe una copia en la memoria del código, no se "copia" ese código una vez por cada objeto que hemos creado. Por otro lado, los datos si que ocupan espacios de memoria diferentes, uno para cada instancia.
Al menos eso es lo que tengo entendido, no de ahora con el .NET, sino de antes... con el VB5, incluso me atrevería a decir que con el VB4.
Cuando se usa un procedimiento de una clase y en ese procedimiento se usan valores de instancia, (los que existen de forma independiente para cada objeto creado), el runtime realmente hace una llamada al "código" indicándole dónde están los datos que tiene que manipular, es decir, le pasa al código la dirección de memoria en la que se encuentran los datos a manipular, de esta forma, sólo existe una copia del código (igual que ocurre con los datos compartidos) y varias direcciones de memoria para los datos no compartidos, (según el número de instancias que se hayan creado).

Así que, no te preocupes demasiado si el código contenido en un procedimiento es demasiado largo, ya que sólo existirá una vez en la memoria y no una vez por cada objeto creado.

 

 

Bueno, creo que ya es bastante por hoy.
Sí, ya se que nunca es suficiente... ¡devoradores de entregas, eso es lo que sois!
 

Estos son los links relacionados con el tema tratado en esta entrega, recuerda que los tres puntos suspensivos deberás cambiarlos por lo que en tu instalación indique. En la entrega número 13 te explico de que va todo esto de los enlaces a la ayuda y hasta de cómo usarlos desde el sitio de Microsoft, aunque la documentación esté en inglés.

Tema Link en la ayuda de Visual Studio .NET
Accesibilidad
Tipos de acceso
ms-help://.../vbcn7/html/vbconAccessibility.htm
ms-help://.../vbls7/html/vblrfVBSpec4_5.htm
Ámbito ms-help://.../vbcn7/html/vbconscope.htm
ms-help://.../vbls7/html/vblrfvbspec4_6.htm
Niveles de ámbito ms-help://.../vbcn7/html/vbconscopelevels.htm
Miembros compartidos ms-help://.../vbcn7/html/vaconSharedMembers.htm
Clases vs Module ms-help://.../vbcn7/html/vbconClassModulesVsStandardModules.htm
Module (instrucción) ms-help://.../vblr7/html/vakeyModule.htm
Class (instrucción) ms-help://.../vblr7/html/vastmClass.htm
Procedimientos Sub ms-help://.../vbcn7/html/vbconsubprocedures.htm
Procedimientos Function ms-help://.../vbcn7/html/vbconfunctionprocedures.htm
Procedimientos Property ms-help://.../vbcn7/html/vaconwritingproperties.htm

 

En la próxima entrega veremos cómo acceder a diferentes miembros de diferentes espacios de nombres y de paso hablaremos de cómo usar los espacios de nombres. También seguiremos tratando sobre más cosas en relación a la diferencia entre los módulos y las clases, sobre todo basándonos en que estén en distintos espacios de nombres y esas cosillas. Y si el tiempo, (y la ocasión), lo permite, seguramente veremos cosas sobre los constructores de las clases y cómo declarar y usar las estructuras... ya veremos...
Me apuntaré por aquí que tengo que contarte también más cosillas sobre la sobrecarga de procedimientos, pero eso no será en la próxima entrega, simplemente "tomo nota" para que no se me olvide... si, ya se que podría usar una agenda, pero... ¿para qué quiero una agenda si después no la uso? (zeñó, zeñó, este Guille no tiene remedio...)

Lo que es seguro, es que el contenido de la siguiente entrega se verá a cuando llegue el momento...
¿Cuando será ese momento?
Ah, my friend... eso, como de costumbre, es un misterio... y ya no te digo que antes de un mes, porque... no lo sé; confío en que sí, pero...


Nos vemos.
Guillermo
Nerja, 28 al 30 de agosto de 2003

 

Nota del 05/Dic/2006:

Sobre los miembros compartidos de las clases y advertencia de Visual Basic 2005 al usarlos desde un objeto de instancia

Pues eso, que sobre este tema del acceso a los miembros compartidos desde un objeto de instancia de una clase, decirte que en Visual Basic 2005 te mostrará una advertencia indicándote que es posible que ese código no se evalúe correctamente, en realidad la advertencia dice que NO se evaluará, pero si se evalúa... o ese creo, je, je, en cualquier caso, siempre es recomendable que se indique el nombre de la clase para acceder a los miembros compartidos (Shared) incluso si estás usando una versión anterior a la 2005 de Visual Basic.

De la misma forma, yo también recomiendo siempre (o casi siempre, salvo cuando se me olvida, que a uno también se le olvidan las cosas... sí, seguramente será por la edad o porque solo tengo una neurona que está sobrecargada de trabajo, je, je), a lo que iba... que incluso cuando accedas a miembros definidos en los módulos (Module) deberías usar el nombre de dicho módulo para accederlos, así te resultará más fácil de depurar e incluso de entender mejor el código, sobre todo cuando lo vuelves a mirar pasado un tiempo... porque cuando las cosas están recién hechas, nos acordamos de todo, pero deja que pase unos meses y ya me dirás si te acuerdas de todo...

Aclarar que en C# siempre hay que usar el nombre de la clase para acceder a los miembros compartidos (perdón estáticos).

Gracias a Juan Ramirez por "recordarme" que en esta entrega (muy pre-vb2005 debo decir en mi defensa, je, je) no estaba aclarado esto del acceso a los miembros estáticos (ya estamos..., sí..., vale..., compartidos...) de las clases desde VB2005.


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...