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.
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.
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:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
No hay comentarios:
Publicar un comentario