ACCESIBILIDAD Y AMBITOS EN CSHARP .NET

Accesibilidad y Ámbitos en C# .NET

Tal y como comentamos anteriormente, dependiendo de dónde y cómo estén declarados los tipos de datos y los miembros definidos en ellos, tendremos o no acceso a esos elementos.



Recordemos que el ámbito es el alcance con el que podemos acceder a un elemento y depende de dónde esté declarado; por otro lado, la accesibilidad depende de cómo declaremos cada uno de esos elementos.

Ámbito
Dependiendo de donde declaremos un miembro o un tipo, éste tendrá mayor alcance o cobertura, o lo que es lo mismo, dependiendo del ámbito en el que usemos un elemento, podremos acceder a él desde otros puntos de nuestro código.
A continuación veremos con detalle los ámbitos en los que podemos declarar los distintos elementos de Visual C# .NET.
  • Ámbito de bloque: Disponible únicamente en el bloque de código en el que se ha declarado.
    Por ejemplo, si declaramos una variable dentro de un bucle for o un if, esa variable solo estará accesible dentro de ese bloque de código.
  • Ámbito de procedimiento: Disponible únicamente dentro del procedimiento en el que se ha declarado. Cualquier variable declarada dentro de un procedimiento (método o propiedad) solo estará accesible en ese procedimiento y en cualquiera de los bloques internos a ese procedimiento.
  • Ámbito de módulo: Disponible en todo el código de la clase o la estructura donde se ha declarado. Las variables con ámbito a nivel de módulo, también estarán disponibles en los procedimientos declarados en el módulo (clase o estructura) y por extensión a cualquier bloque dentro de cada procedimiento.
  • Ámbito de espacio de nombres: Disponible en todo el código del espacio de nombres. Este es el nivel mayor de cobertura o alcance, aunque en este nivel solo podemos declarar tipos como clases, estructuras y enumeraciones, ya que los procedimientos solamente se pueden declarar dentro de un tipo.

Nota:
Por regla general, cuando declaramos una variable en un ámbito, dicha variable "ocultará" a otra que tenga el mismo nombre y esté definida en un bloque con mayor alcance, aunque veremos que en C# existen ciertas restricciones dependiendo de dónde declaremos esas variables.

En Visual C# .NET podemos definir una variable dentro de un bloque de código, en ese caso dicha variable solo será accesible dentro de ese bloque. Aunque, como veremos a continuación, en un procedimiento solamente podremos definir variables que no se oculten entre sí, estén o no dentro de un bloque de código.

Ámbito de bloque

En los siguientes ejemplos veremos cómo podemos definir variables para usar solamente en el bloque en el que están definidas.
Los bloques de código en los que podemos declarar variables son los bucles, (for,foreach, do, while), y los bloques condicionales, (if, switch), o en cualquier otro bloque formado por un par de llaves.
Por ejemplo, dentro de un procedimiento podemos tener varios de estos bloques y por tanto podemos definir variables "internas" a esos bloques:

int n = 3;
//
for(int i = 1; i <= 10; i++ )
{
    int j = 0;
    j += 1;
    if( j < n )
    {
        //...
    }
}
//
if( n < 5 )
{
    int j = n * 3;
}
//
do
{
    int j = 0;
    for(int i = 1; i <= n; i++)
    {
        j += i;
    }
    if( j > 10 ) break;
}while(true);

La variable n estará disponible en todo el procedimiento, por tanto podemos acceder a ella desde cualquiera de los bloques.
En el primer bucle for, definimos la variable i como la variable a usar de contador, esta variable solamente estará accesible dentro de este bucle for. Lo mismo ocurre con la variable j.
En el primer if definimos otra variable j, pero esa solo será accesible dentro de este bloque y por tanto no tiene ninguna relación con la definida en el bucle for anterior.
En el bucle do volvemos a definir nuevamente una variable j, a esa variable la podemos acceder solo desde el propio bucle do y cualquier otro bloque de código interno, como es el caso del bucle for, en el que nuevamente declaramos una variable llamada i, que nada tiene que ver con el resto de variables declaradas con el mismo nombre en los otros bloques.

Nota:
El código del bucle do mostrado anteriormente se convertirá en un bucle infinito, ya que la variable j siempre se pone a cero en cada repetición del bucle do/while, por tanto solo hay que tomarlo como un ejemplo para probar lo del ámbito de las variables.

Lo único que no podemos hacer en cualquiera de esos bloques, es declarar una variable llamada n, ya que, al estar declarada en el procedimiento, el compilador de C# nos indicará que no podemos ocultar una variable previamente definida fuera del bloque, tal como podemos ver en la figura 1




Figura Error al ocultar una variable definida en un procedimiento

Esta restricción solo es aplicable a las variables declaradas en el procedimiento, ya que, si declaramos una variable a nivel de módulo, no habrá ningún problema para usarla dentro de un bloque, esto es así porque en un procedimiento podemos declarar variables que se llamen de la misma forma que las declaradas a nivel de módulo, aunque éstas ocultarán a las del "nivel" superior.

Ámbito de procedimiento

Las variables declaradas en un procedimiento tendrán un ámbito o cobertura que será el procedimiento en el que está declaradas, y como hemos visto, ese ámbito incluye también cualquier bloque de código declarado dentro del procedimiento.
Estas variables ocultarán a las que se hayan declarado fuera del procedimiento, si bien, dependiendo del tipo de módulo, podremos acceder a esas variables "externas" indicando el nombre completo del módulo o bien usando la instrucción this seguida del nombre de la variable.
Pero mejor veámoslo con un ejemplo. En el siguiente código, definimos una clase en la que tenemos un campo llamado Nombre, también definimos un método en el que internamente se utiliza una variable llamada Nombre, para acceder a la variable declarada en la clase, tendremos que usar la instrucción o palabra clave this.

public class Celulares
{
    public string Marca = "MAXCEL";
    public string MostrarEspecificaciones()
    {
        string Marca= "PROTOTIPO";
        return "Externo= " + this.Marca + ", interno= " + Marca;
    }
}

Ámbito de módulo

Cuando hablamos de módulos, nos estamos refiriendo a una clase, a una estructura o a cualquier otro tipo de datos que nos permita .NET.
En estos casos, las variables declaradas dentro de un tipo de datos serán visibles desde cualquier parte de ese tipo, siempre teniendo en cuenta las restricciones mencionadas en los casos anteriores.

Ámbito de espacio de nombres

Los espacios de nombres son los contenedores de tipos de datos de mayor nivel, y sirven para contener definiciones de clases, estructuras, enumeraciones y delegados. Cualquier tipo definido a nivel de espacio de nombres estará disponible para cualquier otro elemento definido en el mismo espacio de nombres.
Al igual que ocurre en el resto de ámbitos "inferiores", si definimos un tipo en un espacio de nombres, podemos usar ese mismo nombre para nombrar a un procedimiento o a una variable, en cada caso se aplicará el ámbito correspondiente y, tal como vimos anteriormente, tendremos que usar nombres únicos para poder acceder a los nombres definidos en niveles diferentes.

Accesibilidad

La accesibilidad es la característica que podemos aplicar a cualquiera de los elementos que definamos en nuestro código. Dependiendo de la accesibilidad declarada tendremos distintos tipos de accesos a esos elementos.
Los modificadores de accesibilidad que podemos aplicar a los tipos y elementos definidos en nuestro código pueden ser cualquiera de los mostrados en la siguiente lista:
  • public: Acceso no restringido. Este es modificador de accesibilidad con mayor "cobertura", podemos acceder a cualquier miembro público desde cualquier parte de nuestro código. Aunque, como veremos, este acceso no restringido puede verse reducido dependiendo de dónde lo usemos.
  • protected: Acceso limitado a la clase contenedora o a los tipos derivados de esta clase. Este modificador solamente se usa con clases que se deriven de otras.
  • internal: Acceso limitado al proyecto actual (o ensamblado).
  • protected internal: Acceso limitado al proyecto actual o a los tipos derivados de la clase contenedora. Una mezcla de los dos modificadores anteriores.
  • private: Acceso limitado al tipo contenedor. Es el más restrictivos de todos los modificadores de accesibilidad.
Estos modificadores de accesibilidad los podemos usar tanto en clases, estructuras, interfaces, enumeraciones, delegados, eventos, métodos, propiedades y campos. Aunque no serán aplicables en espacios de nombres (Namespace) ni en los miembros de las interfaces y enumeraciones.

Accesibilidad de las variables en los procedimientos

Las variables declaradas dentro de un procedimiento solo son accesibles dentro de ese procedimiento, en este caso solo se puede aplicar el ámbito privado, aunque no podremos usar la instrucción private, sino declararlas de la forma habitual.

Las accesibilidades predeterminadas

La accesibilidad de un tipo, variable o procedimiento en la que no hemos indicado el modificador de accesibilidad dependerá del sitio en el que lo hemos declarado.
Por ejemplo, las clases y estructuras definidas a nivel de un espacio de nombres solo pueden ser declaradas como public o internal, y si no llevan un modificador de accesibilidad, por defecto serán internal, es decir serán visibles en todo el proyecto actual. Por otro lado, las interfaces y enumeraciones por defecto serán públicas.
Cuando la clase, estructura, interfaz o enumeración está definida dentro de otro tipo, la accesibilidad predeterminada será private, pero admitirán cualquiera de los modificadores, salvo las interfaces y enumeraciones que no pueden ser protected.
Por otro lado, los miembros de esos tipos también permiten diferentes niveles de accesibilidad, en el caso de las interfaces y enumeraciones, siempre serán públicos, aunque no se permite el uso de esa instrucción. Los miembros de as clases y estructuras serán privados de forma predeterminada, en el caso de las clases, en esos miembros podemos indicar cualquiera de los cinco niveles de accesibilidad, mientras que en las estructuras no podremos declarar ningún miembro como protected o protected internal, ya que una estructura no puede usarse como base de otro tipo de datos.

Anidación de tipos

Tal como hemos comentado en la sección, podemos declarar tipos dentro de otros tipos, por tanto el ámbito y accesibilidad de esos tipos dependen del ámbito y accesibilidad del tipo que los contiene. Por ejemplo, si declaramos una clase con acceso internal, cualquier tipo que esta clase contenga siempre estará supeditado al ámbito de esa clase, por tanto si declaramos otro tipo interno, aunque lo declaremos como public, nunca estará más accesible que la clase contenedora, aunque en estos casos no habrá ningún tipo de confusión, ya que para acceder a los tipos declarados dentro de otros tipos siempre tendremos que indicar la clase que los contiene.
En el siguiente código podemos ver cómo declarar dos clases "anidadas". Tal como podemos comprobar, para acceder a la clase Salario debemos indicar la clase Cliente, ya que la única forma de acceder a una clase anidada es mediante la clase contenedora.

internal class Celulares
{
    public string Marca;

    public class Pantalla
    {
        public float Medida;
    }
}

// Para usar la clase Celulares debemos declararla de esta forma:
Celulares.Marca s = new Celulares.Marca();
s.Pantalla = 6.3F;

Los tipos anidables

Las clases y estructuras pueden contener otros tipos, mientras que las interfaces y enumeraciones no pueden contener otros tipos anidados.
Los espacios de nombres pueden anidarse dentro de otros espacios de nombres y estos pueden contener definiciones de cualquiera de los tipos que C# nos permite crear.

El nombre completo de un tipo

Tal como hemos visto, al poder declarar tipos dentro de otros tipos y estos a su vez pueden estar definidos en espacios de nombres, podemos decir que el nombre "completo" de un tipo cualquiera estará formado por el/los espacios de nombres y el/los tipos que los contiene, por ejemplo si la clase Cliente definida anteriormente está a su vez dentro del espacio de nombres Ambitos, el nombre completo será: Ambitos.Cliente y el nombre completo de la clase Salario será: Ambitos.Cliente.Salario.
Aunque para acceder a la clase Cliente no es necesario indicar el espacio de nombres, al menos si la queremos usar desde cualquier otro tipo declarado dentro de ese espacio de nombres, pero si nuestra intención es usarla desde otro espacio de nombre externo a Ambitos, en ese caso si que tendremos que usar el nombre completo.
Por ejemplo, en el siguiente código tenemos dos espacios de nombres que no están anidados, cada uno de ellos declara una clase y desde una de ellas queremos acceder a la otra clase, para poder hacerlo debemos indicar el nombre completo, ya que en caso contrario, el compilador de Visual C# .NET sería incapaz de saber a que clase queremos acceder.

namespace Uno
{
    public class Clase1
    {
        public string Marca;
    }
}

namespace Dos
{
    public class Clase2
    {
        public string Marca;
        public void Prueba()
        {
            Uno.Clase1 c1 = new Uno.Clase1();
            c1.Marca = "MAXCEL";
        }
    }
}
Esto mismo lo podemos aplicar en el caso de que tengamos dos clases con el mismo nombre en espacios de nombres distintos.

Nota:
En el mismo proyecto podemos tener más de una declaración de un espacio de nombres con el mismo nombre, en estos casos el compilador lo tomará como si todas las clases definidas estuvieran dentro del mismo espacio de nombres, aunque estos estén definidos en ficheros diferentes.

Importación de espacios de nombres


Tal como hemos comentado, los espacios de nombres pueden contener otros espacios de nombres y estos a su vez también pueden contener otros espacios de nombres o clases, y como hemos visto, para poder acceder a una clase que no esté dentro del mismo espacio de nombres debemos indicar el "nombre completo".
Para evitar estar escribiendo todos los espacios de nombres en los que está la clase que nos interesa declarar, podemos usar una especie de acceso directo o para que lo entendamos mejor, podemos crear una especie de "Path", de forma que al declarar una variable, si esta no está definida en el espacio de nombres actual, el compilador busque en todos los espacios de nombres incluidos en esas rutas (paths).
Esto lo conseguimos usando la instrucción using seguida del espacio de nombres que queremos importar o incluir en el path de los espacios de nombres.
Podemos usar tantas importaciones de espacios de nombres como necesitemos y estas siempre deben aparecer al principio del fichero.
Por ejemplo, si tenemos el código anterior y hacemos la importación del espacio de nombres en el que está definida la clase Clase1:


using Uno;

podremos acceder a esa clase de cualquiera de estas dos formas:

Uno.Clase1 c1 = new Uno.Clase1();
Clase1 c1 = new Clase1();

Alias de espacios de nombres

Si hacemos demasiadas importaciones de nombres, el problema con el que nos podemos encontrar es que el IntelliSense de Visual C# .NET no sea de gran ayuda, ya que mostrará una gran cantidad de clases, y seguramente nos resultará más difícil encontrar la clase a la que queremos acceder, o también podemos encontrarnos en ocasiones en las que nos interese usar un nombre corto para acceder a las clases contenidas en un espacio de nombres, por ejemplo, si queremos indicar de forma explícita las clases de un espacio de nombres como el de System.IO, podemos hacerlo de esta forma:


using io = System.IO;


De esta forma podemos usar el "alias" io para acceder a las clases y demás tipos definidos en ese espacio de nombres. En la figura 2 podemos ver que al escribir esa palabra, el IntelliSense nos muestra un mensaje de que realmente esa palabra es el espacio de nombres System.IO.
En la figura 2 al escribir el punto, podemos ver los miembros del espacio de nombres al que hace referencia el alias io.


Figura 2 IntelliSense y los alias a los espacios de nombres


ALGUNOS EJEMPLOS SOBRE ÁMBITO DE VARIABLES:


No hay comentarios:

Publicar un comentario

DELEGADOS EN C# .NET | PUNTEROS A METODOS

Delegados Tal como hemos comentado, los eventos y los delegados están muy unidos en C#. De hecho no se pueden definir eventos si no def...